| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 cr.define('downloads', function() { | 5 cr.define('downloads', function() { |
| 6 var Manager = Polymer({ | 6 var Manager = Polymer({ |
| 7 is: 'downloads-manager', | 7 is: 'downloads-manager', |
| 8 | 8 |
| 9 properties: { | 9 properties: { |
| 10 hasDownloads_: { | 10 hasDownloads_: { |
| 11 type: Boolean, | 11 type: Boolean, |
| 12 value: false, | 12 value: false, |
| 13 }, | 13 }, |
| 14 |
| 15 items_: { |
| 16 type: Array, |
| 17 }, |
| 14 }, | 18 }, |
| 15 | 19 |
| 16 /** | 20 /** |
| 17 * @return {number} A guess at how many items could be visible at once. | |
| 18 * @private | |
| 19 */ | |
| 20 guesstimateNumberOfVisibleItems_: function() { | |
| 21 var toolbarHeight = this.$.toolbar.offsetHeight; | |
| 22 return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1; | |
| 23 }, | |
| 24 | |
| 25 /** | |
| 26 * @param {Event} e | 21 * @param {Event} e |
| 27 * @private | 22 * @private |
| 28 */ | 23 */ |
| 29 onCanExecute_: function(e) { | 24 onCanExecute_: function(e) { |
| 30 e = /** @type {cr.ui.CanExecuteEvent} */(e); | 25 e = /** @type {cr.ui.CanExecuteEvent} */(e); |
| 31 switch (e.command.id) { | 26 switch (e.command.id) { |
| 32 case 'undo-command': | 27 case 'undo-command': |
| 33 e.canExecute = this.$.toolbar.canUndo(); | 28 e.canExecute = this.$.toolbar.canUndo(); |
| 34 break; | 29 break; |
| 35 case 'clear-all-command': | 30 case 'clear-all-command': |
| (...skipping 16 matching lines...) Expand all Loading... |
| 52 /** @private */ | 47 /** @private */ |
| 53 onLoad_: function() { | 48 onLoad_: function() { |
| 54 cr.ui.decorate('command', cr.ui.Command); | 49 cr.ui.decorate('command', cr.ui.Command); |
| 55 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); | 50 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); |
| 56 document.addEventListener('command', this.onCommand_.bind(this)); | 51 document.addEventListener('command', this.onCommand_.bind(this)); |
| 57 | 52 |
| 58 // Shows all downloads. | 53 // Shows all downloads. |
| 59 downloads.ActionService.getInstance().search(''); | 54 downloads.ActionService.getInstance().search(''); |
| 60 }, | 55 }, |
| 61 | 56 |
| 62 /** @private */ | |
| 63 rebuildFocusGrid_: function() { | |
| 64 var activeElement = this.shadowRoot.activeElement; | |
| 65 | |
| 66 var activeItem; | |
| 67 if (activeElement && activeElement.tagName == 'downloads-item') | |
| 68 activeItem = activeElement; | |
| 69 | |
| 70 var activeControl = activeItem && activeItem.shadowRoot.activeElement; | |
| 71 | |
| 72 /** @private {!cr.ui.FocusGrid} */ | |
| 73 this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid; | |
| 74 this.focusGrid_.destroy(); | |
| 75 | |
| 76 var boundary = this.$['downloads-list']; | |
| 77 | |
| 78 this.items_.forEach(function(item) { | |
| 79 var focusRow = new downloads.FocusRow(item.content, boundary); | |
| 80 this.focusGrid_.addRow(focusRow); | |
| 81 | |
| 82 if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl)) | |
| 83 focusRow.getEquivalentElement(activeControl).focus(); | |
| 84 }, this); | |
| 85 | |
| 86 this.focusGrid_.ensureRowActive(); | |
| 87 }, | |
| 88 | |
| 89 /** | 57 /** |
| 90 * @return {number} The number of downloads shown on the page. | 58 * @return {number} The number of downloads shown on the page. |
| 91 * @private | 59 * @private |
| 92 */ | 60 */ |
| 93 size_: function() { | 61 size_: function() { |
| 94 return this.items_.length; | 62 return this.items_.length; |
| 95 }, | 63 }, |
| 96 | 64 |
| 97 /** | 65 /** |
| 98 * Called when all items need to be updated. | 66 * Called when all items need to be updated. |
| 99 * @param {!Array<!downloads.Data>} list A list of new download data. | 67 * @param {!Array<!downloads.Data>} list A list of new download data. |
| 100 * @private | 68 * @private |
| 101 */ | 69 */ |
| 102 updateAll_: function(list) { | 70 updateAll_: function(list) { |
| 103 var oldIdMap = this.idMap_ || {}; | 71 /** @private {!Object<number>} */ |
| 104 | 72 this.idToIndex_ = {}; |
| 105 /** @private {!Object<!downloads.Item>} */ | |
| 106 this.idMap_ = {}; | |
| 107 | |
| 108 /** @private {!Array<!downloads.Item>} */ | |
| 109 this.items_ = []; | |
| 110 | |
| 111 if (!this.iconLoader_) { | |
| 112 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1); | |
| 113 /** @private {downloads.ThrottledIconLoader} */ | |
| 114 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate); | |
| 115 } | |
| 116 | 73 |
| 117 for (var i = 0; i < list.length; ++i) { | 74 for (var i = 0; i < list.length; ++i) { |
| 118 var data = list[i]; | 75 var data = list[i]; |
| 119 var id = data.id; | |
| 120 | 76 |
| 121 // Re-use old items when possible (saves work, preserves focus). | 77 this.idToIndex_[data.id] = data.index = i; |
| 122 var item = oldIdMap[id] || new downloads.Item(this.iconLoader_); | |
| 123 | 78 |
| 124 this.idMap_[id] = item; // Associated by ID for fast lookup. | |
| 125 this.items_.push(item); // Add to sorted list for order. | |
| 126 | |
| 127 // Render |item| but don't actually add to the DOM yet. |this.items_| | |
| 128 // must be fully created to be able to find the right spot to insert. | |
| 129 item.update(data); | |
| 130 | |
| 131 // Collapse redundant dates. | |
| 132 var prev = list[i - 1]; | 79 var prev = list[i - 1]; |
| 133 item.hideDate = !!prev && prev.date_string == data.date_string; | 80 data.hideDate = !!prev && prev.date_string == data.date_string; |
| 134 | |
| 135 delete oldIdMap[id]; | |
| 136 } | 81 } |
| 137 | 82 |
| 138 // Remove stale, previously rendered items from the DOM. | 83 // TODO(dbeam): this resets the scroll position, which is a huge bummer. |
| 139 for (var id in oldIdMap) { | 84 // Removing something from the bottom of the list should not scroll you |
| 140 if (oldIdMap[id].parentNode) | 85 // back to the top. The grand plan is to restructure how the C++ sends the |
| 141 oldIdMap[id].parentNode.removeChild(oldIdMap[id]); | 86 // JS data so that it only gets updates (rather than the most recent set |
| 142 delete oldIdMap[id]; | 87 // of items). TL;DR - we can't ship with this bug. |
| 143 } | 88 this.items_ = list; |
| 144 | |
| 145 for (var i = 0; i < this.items_.length; ++i) { | |
| 146 var item = this.items_[i]; | |
| 147 if (item.parentNode) // Already in the DOM; skip. | |
| 148 continue; | |
| 149 | |
| 150 var before = null; | |
| 151 // Find the next rendered item after this one, and insert before it. | |
| 152 for (var j = i + 1; !before && j < this.items_.length; ++j) { | |
| 153 if (this.items_[j].parentNode) | |
| 154 before = this.items_[j]; | |
| 155 } | |
| 156 // If |before| is null, |item| will just get added at the end. | |
| 157 this.$['downloads-list'].insertBefore(item, before); | |
| 158 } | |
| 159 | 89 |
| 160 var hasDownloads = this.size_() > 0; | 90 var hasDownloads = this.size_() > 0; |
| 161 if (!hasDownloads) { | 91 if (!hasDownloads) { |
| 162 var isSearching = downloads.ActionService.getInstance().isSearching(); | 92 var isSearching = downloads.ActionService.getInstance().isSearching(); |
| 163 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; | 93 var messageToShow = isSearching ? 'noSearchResults' : 'noDownloads'; |
| 164 this.$['no-downloads'].querySelector('span').textContent = | 94 this.$['no-downloads'].querySelector('span').textContent = |
| 165 loadTimeData.getString(messageToShow); | 95 loadTimeData.getString(messageToShow); |
| 166 } | 96 } |
| 167 this.hasDownloads_ = hasDownloads; | 97 this.hasDownloads_ = hasDownloads; |
| 168 | 98 |
| 169 if (loadTimeData.getBoolean('allowDeletingHistory')) | 99 if (loadTimeData.getBoolean('allowDeletingHistory')) |
| 170 this.$.toolbar.downloadsShowing = this.hasDownloads_; | 100 this.$.toolbar.downloadsShowing = this.hasDownloads_; |
| 171 | 101 |
| 172 this.$.panel.classList.remove('loading'); | 102 this.$.panel.classList.remove('loading'); |
| 173 | |
| 174 var allReady = this.items_.map(function(i) { return i.readyPromise; }); | |
| 175 Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this)); | |
| 176 }, | 103 }, |
| 177 | 104 |
| 178 /** | 105 /** |
| 179 * @param {!downloads.Data} data | 106 * @param {!downloads.Data} data |
| 180 * @private | 107 * @private |
| 181 */ | 108 */ |
| 182 updateItem_: function(data) { | 109 updateItem_: function(data) { |
| 183 var item = this.idMap_[data.id]; | 110 var index = this.idToIndex_[data.id]; |
| 184 | 111 this.set('items_.' + index, data); |
| 185 var activeControl = this.shadowRoot.activeElement == item ? | 112 this.$['downloads-list'].updateSizeForItem(index); |
| 186 item.shadowRoot.activeElement : null; | |
| 187 | |
| 188 item.update(data); | |
| 189 | |
| 190 this.async(function() { | |
| 191 if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) { | |
| 192 var focusRow = this.focusGrid_.getRowForRoot(item.content); | |
| 193 focusRow.getEquivalentElement(activeControl).focus(); | |
| 194 } | |
| 195 }.bind(this)); | |
| 196 }, | 113 }, |
| 197 }); | 114 }); |
| 198 | 115 |
| 199 Manager.size = function() { | 116 Manager.size = function() { |
| 200 return document.querySelector('downloads-manager').size_(); | 117 return document.querySelector('downloads-manager').size_(); |
| 201 }; | 118 }; |
| 202 | 119 |
| 203 Manager.updateAll = function(list) { | 120 Manager.updateAll = function(list) { |
| 204 document.querySelector('downloads-manager').updateAll_(list); | 121 document.querySelector('downloads-manager').updateAll_(list); |
| 205 }; | 122 }; |
| 206 | 123 |
| 207 Manager.updateItem = function(item) { | 124 Manager.updateItem = function(item) { |
| 208 document.querySelector('downloads-manager').updateItem_(item); | 125 document.querySelector('downloads-manager').updateItem_(item); |
| 209 }; | 126 }; |
| 210 | 127 |
| 211 Manager.onLoad = function() { | 128 Manager.onLoad = function() { |
| 212 document.querySelector('downloads-manager').onLoad_(); | 129 document.querySelector('downloads-manager').onLoad_(); |
| 213 }; | 130 }; |
| 214 | 131 |
| 215 return {Manager: Manager}; | 132 return {Manager: Manager}; |
| 216 }); | 133 }); |
| 217 | 134 |
| 218 window.addEventListener('load', downloads.Manager.onLoad); | 135 window.addEventListener('load', downloads.Manager.onLoad); |
| OLD | NEW |