| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 cr.define('downloads', function() { | |
| 6 /** | |
| 7 * Class to own and manage download items. | |
| 8 * @constructor | |
| 9 */ | |
| 10 function Manager() {} | |
| 11 | |
| 12 cr.addSingletonGetter(Manager); | |
| 13 | |
| 14 Manager.prototype = { | |
| 15 /** @private {string} */ | |
| 16 searchText_: '', | |
| 17 | |
| 18 /** | |
| 19 * Sets the search text, updates related UIs, and tells the browser. | |
| 20 * @param {string} searchText Text we're searching for. | |
| 21 * @private | |
| 22 */ | |
| 23 setSearchText_: function(searchText) { | |
| 24 this.searchText_ = searchText; | |
| 25 | |
| 26 $('downloads-summary-text').textContent = this.searchText_ ? | |
| 27 loadTimeData.getStringF('searchResultsFor', this.searchText_) : ''; | |
| 28 | |
| 29 // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']). | |
| 30 function trim(s) { return s.trim(); } | |
| 31 chrome.send('getDownloads', searchText.split(/"([^"]*)"/).map(trim)); | |
| 32 }, | |
| 33 | |
| 34 /** | |
| 35 * @return {number} A guess at how many items could be visible at once. | |
| 36 * @private | |
| 37 */ | |
| 38 guesstimateNumberOfVisibleItems_: function() { | |
| 39 var headerHeight = document.querySelector('header').offsetHeight; | |
| 40 var summaryHeight = $('downloads-summary').offsetHeight; | |
| 41 var nonItemSpace = headerHeight + summaryHeight; | |
| 42 return Math.floor((window.innerHeight - nonItemSpace) / 46) + 1; | |
| 43 }, | |
| 44 | |
| 45 /** | |
| 46 * Called when all items need to be updated. | |
| 47 * @param {!Array<!downloads.Data>} list A list of new download data. | |
| 48 * @private | |
| 49 */ | |
| 50 updateAll_: function(list) { | |
| 51 var oldIdMap = this.idMap_ || {}; | |
| 52 | |
| 53 /** @private {!Object<!downloads.ItemView>} */ | |
| 54 this.idMap_ = {}; | |
| 55 | |
| 56 /** @private {!Array<!downloads.ItemView>} */ | |
| 57 this.items_ = []; | |
| 58 | |
| 59 if (!this.iconLoader_) { | |
| 60 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1); | |
| 61 /** @private {downloads.ThrottledIconLoader} */ | |
| 62 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate); | |
| 63 } | |
| 64 | |
| 65 for (var i = 0; i < list.length; ++i) { | |
| 66 var data = list[i]; | |
| 67 var id = data.id; | |
| 68 | |
| 69 // Re-use old items when possible (saves work, preserves focus). | |
| 70 var item = oldIdMap[id] || new downloads.ItemView(this.iconLoader_); | |
| 71 | |
| 72 this.idMap_[id] = item; // Associated by ID for fast lookup. | |
| 73 this.items_.push(item); // Add to sorted list for order. | |
| 74 | |
| 75 // Render |item| but don't actually add to the DOM yet. |this.items_| | |
| 76 // must be fully created to be able to find the right spot to insert. | |
| 77 item.update(data); | |
| 78 | |
| 79 // Collapse redundant dates. | |
| 80 var prev = list[i - 1]; | |
| 81 item.dateContainer.hidden = | |
| 82 prev && prev.date_string == data.date_string; | |
| 83 | |
| 84 delete oldIdMap[id]; | |
| 85 } | |
| 86 | |
| 87 // Remove stale, previously rendered items from the DOM. | |
| 88 for (var id in oldIdMap) { | |
| 89 var oldNode = oldIdMap[id].node; | |
| 90 if (oldNode.parentNode) | |
| 91 oldNode.parentNode.removeChild(oldNode); | |
| 92 delete oldIdMap[id]; | |
| 93 } | |
| 94 | |
| 95 for (var i = 0; i < this.items_.length; ++i) { | |
| 96 var item = this.items_[i]; | |
| 97 if (item.node.parentNode) // Already in the DOM; skip. | |
| 98 continue; | |
| 99 | |
| 100 var before = null; | |
| 101 // Find the next rendered item after this one, and insert before it. | |
| 102 for (var j = i + 1; !before && j < this.items_.length; ++j) { | |
| 103 if (this.items_[j].node.parentNode) | |
| 104 before = this.items_[j].node; | |
| 105 } | |
| 106 // If |before| is null, |item| will just get added at the end. | |
| 107 this.node_.insertBefore(item.node, before); | |
| 108 } | |
| 109 | |
| 110 var noDownloadsOrResults = $('no-downloads-or-results'); | |
| 111 noDownloadsOrResults.textContent = loadTimeData.getString( | |
| 112 this.searchText_ ? 'noSearchResults' : 'noDownloads'); | |
| 113 | |
| 114 var hasDownloads = this.size_() > 0; | |
| 115 this.node_.hidden = !hasDownloads; | |
| 116 noDownloadsOrResults.hidden = hasDownloads; | |
| 117 | |
| 118 if (loadTimeData.getBoolean('allowDeletingHistory')) | |
| 119 $('clear-all').hidden = !hasDownloads || this.searchText_.length > 0; | |
| 120 | |
| 121 this.rebuildFocusGrid_(); | |
| 122 }, | |
| 123 | |
| 124 /** | |
| 125 * @param {!downloads.Data} data Info about the item to update. | |
| 126 * @private | |
| 127 */ | |
| 128 updateItem_: function(data) { | |
| 129 var activeElement = document.activeElement; | |
| 130 | |
| 131 var item = this.idMap_[data.id]; | |
| 132 item.update(data); | |
| 133 | |
| 134 if (item.node.contains(activeElement) && | |
| 135 !cr.ui.FocusRow.isFocusable(activeElement)) { | |
| 136 var focusRow = this.focusGrid_.getRowForRoot(item.node); | |
| 137 focusRow.getEquivalentElement(activeElement).focus(); | |
| 138 } | |
| 139 }, | |
| 140 | |
| 141 /** | |
| 142 * Rebuild the focusGrid_ using the elements that each download will have. | |
| 143 * @private | |
| 144 */ | |
| 145 rebuildFocusGrid_: function() { | |
| 146 var activeElement = document.activeElement; | |
| 147 | |
| 148 /** @private {!cr.ui.FocusGrid} */ | |
| 149 this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid(); | |
| 150 this.focusGrid_.destroy(); | |
| 151 | |
| 152 this.items_.forEach(function(item) { | |
| 153 var focusRow = new downloads.FocusRow(item.node, this.node_); | |
| 154 | |
| 155 this.focusGrid_.addRow(focusRow); | |
| 156 | |
| 157 if (item.node.contains(activeElement) && | |
| 158 !cr.ui.FocusRow.isFocusable(activeElement)) { | |
| 159 focusRow.getEquivalentElement(activeElement).focus(); | |
| 160 } | |
| 161 }, this); | |
| 162 | |
| 163 this.focusGrid_.ensureRowActive(); | |
| 164 }, | |
| 165 | |
| 166 /** | |
| 167 * @return {number} The number of downloads shown on the page. | |
| 168 * @private | |
| 169 */ | |
| 170 size_: function() { | |
| 171 return this.items_.length; | |
| 172 }, | |
| 173 | |
| 174 /** @private */ | |
| 175 clearAll_: function() { | |
| 176 if (loadTimeData.getBoolean('allowDeletingHistory')) { | |
| 177 chrome.send('clearAll'); | |
| 178 this.setSearchText_(''); | |
| 179 } | |
| 180 }, | |
| 181 | |
| 182 /** @private */ | |
| 183 onLoad_: function() { | |
| 184 this.node_ = $('downloads-display'); | |
| 185 | |
| 186 $('clear-all').onclick = function() { | |
| 187 this.clearAll_(); | |
| 188 }.bind(this); | |
| 189 | |
| 190 $('open-downloads-folder').onclick = function() { | |
| 191 chrome.send('openDownloadsFolder'); | |
| 192 }; | |
| 193 | |
| 194 $('term').onsearch = function(e) { | |
| 195 this.setSearchText_($('term').value); | |
| 196 }.bind(this); | |
| 197 | |
| 198 cr.ui.decorate('command', cr.ui.Command); | |
| 199 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); | |
| 200 document.addEventListener('command', this.onCommand_.bind(this)); | |
| 201 | |
| 202 this.setSearchText_(''); | |
| 203 }, | |
| 204 | |
| 205 /** | |
| 206 * @param {Event} e | |
| 207 * @private | |
| 208 */ | |
| 209 onCanExecute_: function(e) { | |
| 210 e = /** @type {cr.ui.CanExecuteEvent} */(e); | |
| 211 switch (e.command.id) { | |
| 212 case 'undo-command': | |
| 213 e.canExecute = document.activeElement != $('term'); | |
| 214 break; | |
| 215 case 'clear-all-command': | |
| 216 e.canExecute = true; | |
| 217 break; | |
| 218 } | |
| 219 }, | |
| 220 | |
| 221 /** | |
| 222 * @param {Event} e | |
| 223 * @private | |
| 224 */ | |
| 225 onCommand_: function(e) { | |
| 226 if (e.command.id == 'undo-command') | |
| 227 chrome.send('undo'); | |
| 228 else if (e.command.id == 'clear-all-command') | |
| 229 this.clearAll_(); | |
| 230 }, | |
| 231 }; | |
| 232 | |
| 233 Manager.updateAll = function(list) { | |
| 234 Manager.getInstance().updateAll_(list); | |
| 235 }; | |
| 236 | |
| 237 Manager.updateItem = function(item) { | |
| 238 Manager.getInstance().updateItem_(item); | |
| 239 }; | |
| 240 | |
| 241 Manager.setSearchText = function(searchText) { | |
| 242 Manager.getInstance().setSearchText_(searchText); | |
| 243 }; | |
| 244 | |
| 245 Manager.onLoad = function() { | |
| 246 Manager.getInstance().onLoad_(); | |
| 247 }; | |
| 248 | |
| 249 Manager.size = function() { | |
| 250 return Manager.getInstance().size_(); | |
| 251 }; | |
| 252 | |
| 253 return {Manager: Manager}; | |
| 254 }); | |
| 255 | |
| 256 window.addEventListener('DOMContentLoaded', downloads.Manager.onLoad); | |
| OLD | NEW |