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 * @return {number} A guess at how many items could be visible at once.
25 guesstimateNumberOfVisibleItems_: function() {
26 var toolbarHeight = this.$.toolbar.offsetHeight;
27 return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1;
34 onCanExecute_: function(e) {
35 e = /** @type {cr.ui.CanExecuteEvent} */(e);
36 switch (e.command.id) {
38 e.canExecute = this.$.toolbar.canUndo();
40 case 'clear-all-command':
41 e.canExecute = this.$.toolbar.canClearAll();
50 onCommand_: function(e) {
51 if (e.command.id == 'clear-all-command')
52 this.actionService_.clearAll();
53 else if (e.command.id == 'undo-command')
54 this.actionService_.undo();
59 this.$.toolbar.setActionService(this.actionService_);
61 cr.ui.decorate('command', cr.ui.Command);
62 document.addEventListener('canExecute', this.onCanExecute_.bind(this));
63 document.addEventListener('command', this.onCommand_.bind(this));
65 // Shows all downloads.
66 this.actionService_.search('');
70 rebuildFocusGrid_: function() {
71 var activeElement = this.shadowRoot.activeElement;
74 if (activeElement && activeElement.tagName == 'downloads-item')
75 activeItem = activeElement;
77 var activeControl = activeItem && activeItem.shadowRoot.activeElement;
79 /** @private {!cr.ui.FocusGrid} */
80 this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid;
81 this.focusGrid_.destroy();
83 var boundary = this.$['downloads-list'];
85 this.items_.forEach(function(item) {
86 var focusRow = new downloads.FocusRow(item.content, boundary);
87 this.focusGrid_.addRow(focusRow);
89 if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl))
90 focusRow.getEquivalentElement(activeControl).focus();
93 this.focusGrid_.ensureRowActive();
97 * @return {number} The number of downloads shown on the page.
101 return this.items_.length;
105 * Called when all items need to be updated.
106 * @param {!Array<!downloads.Data>} list A list of new download data.
109 updateAll_: function(list) {
110 var oldIdMap = this.idMap_ || {};
112 /** @private {!Object<!downloads.Item>} */
115 /** @private {!Array<!downloads.Item>} */
118 if (!this.iconLoader_) {
119 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1);
120 /** @private {downloads.ThrottledIconLoader} */
121 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate);
124 for (var i = 0; i < list.length; ++i) {
128 // Re-use old items when possible (saves work, preserves focus).
129 var item = oldIdMap[id] ||
130 new downloads.Item(this.iconLoader_, this.actionService_);
132 this.idMap_[id] = item; // Associated by ID for fast lookup.
133 this.items_.push(item); // Add to sorted list for order.
135 // Render |item| but don't actually add to the DOM yet. |this.items_|
136 // must be fully created to be able to find the right spot to insert.
139 // Collapse redundant dates.
140 var prev = list[i - 1];
141 item.hideDate = !!prev && prev.date_string == data.date_string;
146 // Remove stale, previously rendered items from the DOM.
147 for (var id in oldIdMap) {
148 if (oldIdMap[id].parentNode)
149 oldIdMap[id].parentNode.removeChild(oldIdMap[id]);
153 for (var i = 0; i < this.items_.length; ++i) {
154 var item = this.items_[i];
155 if (item.parentNode) // Already in the DOM; skip.
159 // Find the next rendered item after this one, and insert before it.
160 for (var j = i + 1; !before && j < this.items_.length; ++j) {
161 if (this.items_[j].parentNode)
162 before = this.items_[j];
164 // If |before| is null, |item| will just get added at the end.
165 this.$['downloads-list'].insertBefore(item, before);
168 var hasDownloads = this.size_() > 0;
170 var isSearching = this.actionService_.isSearching();
171 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads';
172 this.$['no-downloads'].querySelector('span').textContent =
173 loadTimeData.getString(messageToShow);
175 this.hasDownloads_ = hasDownloads;
177 if (loadTimeData.getBoolean('allowDeletingHistory'))
178 this.$.toolbar.downloadsShowing = this.hasDownloads_;
180 this.$.panel.classList.remove('loading');
182 var allReady = this.items_.map(function(i) { return i.readyPromise; });
183 Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this));
187 * @param {!downloads.Data} data
190 updateItem_: function(data) {
191 var item = this.idMap_[data.id];
193 var activeControl = this.shadowRoot.activeElement == item ?
194 item.shadowRoot.activeElement : null;
198 this.async(function() {
199 if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) {
200 var focusRow = this.focusGrid_.getRowForRoot(item.content);
201 focusRow.getEquivalentElement(activeControl).focus();
207 Manager.size = function() {
208 return document.querySelector('downloads-manager').size_();
211 Manager.updateAll = function(list) {
212 document.querySelector('downloads-manager').updateAll_(list);
215 Manager.updateItem = function(item) {
216 document.querySelector('downloads-manager').updateItem_(item);
219 Manager.onLoad = function() {
220 document.querySelector('downloads-manager').onLoad_();
223 return {Manager: Manager};
226 window.addEventListener('load', downloads.Manager.onLoad);