1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 cr
.define('downloads', function() {
6 var Manager
= Polymer({
7 is
: 'downloads-manager',
10 /** @private {!downloads.ActionService} */
11 this.actionService_
= new downloads
.ActionService
;
22 window
.addEventListener('resize', this.onResize_
.bind(this));
23 // onResize_() doesn't need to be called immediately here because it's
24 // guaranteed to be called again shortly when items are received.
28 * @return {number} A guess at how many items could be visible at once.
31 guesstimateNumberOfVisibleItems_: function() {
32 var toolbarHeight
= this.$.toolbar
.offsetHeight
;
33 return Math
.floor((window
.innerHeight
- toolbarHeight
) / 46) + 1;
40 onCanExecute_: function(e
) {
41 e
= /** @type {cr.ui.CanExecuteEvent} */(e
);
42 switch (e
.command
.id
) {
44 e
.canExecute
= this.$.toolbar
.canUndo();
46 case 'clear-all-command':
47 e
.canExecute
= this.$.toolbar
.canClearAll();
56 onCommand_: function(e
) {
57 if (e
.command
.id
== 'clear-all-command')
58 this.actionService_
.clearAll();
59 else if (e
.command
.id
== 'undo-command')
60 this.actionService_
.undo();
65 this.$.toolbar
.setActionService(this.actionService_
);
67 cr
.ui
.decorate('command', cr
.ui
.Command
);
68 document
.addEventListener('canExecute', this.onCanExecute_
.bind(this));
69 document
.addEventListener('command', this.onCommand_
.bind(this));
71 // Shows all downloads.
72 this.actionService_
.search('');
76 onResize_: function() {
77 // TODO(dbeam): expose <paper-header-panel>'s #mainContainer in Polymer.
78 var container
= this.$.panel
.$.mainContainer
;
79 var scrollbarWidth
= container
.offsetWidth
- container
.clientWidth
;
80 this.items_
.forEach(function(item
) {
81 item
.scrollbarWidth
= scrollbarWidth
;
86 rebuildFocusGrid_: function() {
87 var activeElement
= this.shadowRoot
.activeElement
;
90 if (activeElement
&& activeElement
.tagName
== 'download-item')
91 activeItem
= activeElement
;
93 var activeControl
= activeItem
&& activeItem
.shadowRoot
.activeElement
;
95 /** @private {!cr.ui.FocusGrid} */
96 this.focusGrid_
= this.focusGrid_
|| new cr
.ui
.FocusGrid
;
97 this.focusGrid_
.destroy();
99 var boundary
= this.$['downloads-list'];
101 this.items_
.forEach(function(item
) {
102 var focusRow
= new downloads
.FocusRow(item
.content
, boundary
);
103 this.focusGrid_
.addRow(focusRow
);
105 if (item
== activeItem
&& !cr
.ui
.FocusRow
.isFocusable(activeControl
))
106 focusRow
.getEquivalentElement(activeControl
).focus();
109 this.focusGrid_
.ensureRowActive();
113 * @return {number} The number of downloads shown on the page.
117 return this.items_
.length
;
121 * Called when all items need to be updated.
122 * @param {!Array<!downloads.Data>} list A list of new download data.
125 updateAll_: function(list
) {
126 var oldIdMap
= this.idMap_
|| {};
128 /** @private {!Object<!downloads.Item>} */
131 /** @private {!Array<!downloads.Item>} */
134 if (!this.iconLoader_
) {
135 var guesstimate
= Math
.max(this.guesstimateNumberOfVisibleItems_(), 1);
136 /** @private {downloads.ThrottledIconLoader} */
137 this.iconLoader_
= new downloads
.ThrottledIconLoader(guesstimate
);
140 for (var i
= 0; i
< list
.length
; ++i
) {
144 // Re-use old items when possible (saves work, preserves focus).
145 var item
= oldIdMap
[id
] ||
146 new downloads
.Item(this.iconLoader_
, this.actionService_
);
148 this.idMap_
[id
] = item
; // Associated by ID for fast lookup.
149 this.items_
.push(item
); // Add to sorted list for order.
151 // Render |item| but don't actually add to the DOM yet. |this.items_|
152 // must be fully created to be able to find the right spot to insert.
155 // Collapse redundant dates.
156 var prev
= list
[i
- 1];
157 item
.hideDate
= !!prev
&& prev
.date_string
== data
.date_string
;
162 // Remove stale, previously rendered items from the DOM.
163 for (var id
in oldIdMap
) {
164 if (oldIdMap
[id
].parentNode
)
165 oldIdMap
[id
].parentNode
.removeChild(oldIdMap
[id
]);
169 for (var i
= 0; i
< this.items_
.length
; ++i
) {
170 var item
= this.items_
[i
];
171 if (item
.parentNode
) // Already in the DOM; skip.
175 // Find the next rendered item after this one, and insert before it.
176 for (var j
= i
+ 1; !before
&& j
< this.items_
.length
; ++j
) {
177 if (this.items_
[j
].parentNode
)
178 before
= this.items_
[j
];
180 // If |before| is null, |item| will just get added at the end.
181 this.$['downloads-list'].insertBefore(item
, before
);
184 this.hasDownloads_
= this.size_() > 0;
186 if (loadTimeData
.getBoolean('allowDeletingHistory'))
187 this.$.toolbar
.downloadsShowing
= this.hasDownloads_
;
190 this.$.panel
.classList
.remove('loading');
192 var allReady
= this.items_
.map(function(i
) { return i
.readyPromise
; });
193 Promise
.all(allReady
).then(this.rebuildFocusGrid_
.bind(this));
197 * @param {!downloads.Data} data
200 updateItem_: function(data
) {
201 var item
= this.idMap_
[data
.id
];
203 var activeControl
= this.shadowRoot
.activeElement
== item
?
204 item
.shadowRoot
.activeElement
: null;
208 if (activeControl
&& !cr
.ui
.FocusRow
.isFocusable(activeControl
)) {
209 var focusRow
= this.focusGrid_
.getRowForRoot(item
.content
);
210 focusRow
.getEquivalentElement(activeControl
).focus();
217 Manager
.size = function() {
218 return document
.querySelector('downloads-manager').size_();
221 Manager
.updateAll = function(list
) {
222 document
.querySelector('downloads-manager').updateAll_(list
);
225 Manager
.updateItem = function(item
) {
226 document
.querySelector('downloads-manager').updateItem_(item
);
229 Manager
.onLoad = function() {
230 document
.querySelector('downloads-manager').onLoad_();
233 return {Manager
: Manager
};
236 window
.addEventListener('load', downloads
.Manager
.onLoad
);