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);