Chromium Code Reviews| 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 /** | 6 /** |
| 7 * Class to own and manage download items. | 7 * Class to own and manage download items. |
| 8 * @constructor | 8 * @constructor |
| 9 * @implements {downloads.ActionService} | |
| 9 */ | 10 */ |
| 10 function Manager() {} | 11 function Manager() {} |
| 11 | 12 |
| 12 cr.addSingletonGetter(Manager); | 13 cr.addSingletonGetter(Manager); |
| 13 | 14 |
| 15 /** | |
| 16 * @param {string} chromeSendName | |
| 17 * @return {function(string):void} A chrome.send() callback with curried name. | |
| 18 */ | |
| 19 function chromeSendWithId(chromeSendName) { | |
| 20 return function(id) { chrome.send(chromeSendName, [id]); }; | |
| 21 } | |
| 22 | |
| 14 Manager.prototype = { | 23 Manager.prototype = { |
| 15 /** @private {string} */ | 24 /** @override */ |
| 16 searchText_: '', | 25 cancel: chromeSendWithId('cancel'), |
| 17 | 26 |
| 18 /** | 27 /** @override */ |
| 19 * Sets the search text, updates related UIs, and tells the browser. | 28 clearAll: function() { |
| 20 * @param {string} searchText Text we're searching for. | 29 if (loadTimeData.getBoolean('allowDeletingHistory')) { |
| 21 * @private | 30 chrome.send('clearAll'); |
| 22 */ | 31 this.search(''); |
| 23 setSearchText_: function(searchText) { | 32 } |
| 24 this.searchText_ = searchText; | 33 }, |
| 25 | 34 |
| 26 $('downloads-summary-text').textContent = this.searchText_ ? | 35 /** @override */ |
| 27 loadTimeData.getStringF('searchResultsFor', this.searchText_) : ''; | 36 discardDangerous: chromeSendWithId('discardDangerous'), |
| 28 | 37 |
| 38 /** @override */ | |
| 39 drag: chromeSendWithId('drag'), | |
| 40 | |
| 41 /** @override */ | |
| 42 openDownloadsFolder: function() { | |
| 43 chrome.send('openDownloadsFolder'); | |
| 44 }, | |
| 45 | |
| 46 /** @override */ | |
| 47 openFile: chromeSendWithId('openFile'), | |
| 48 | |
| 49 /** @override */ | |
| 50 pause: chromeSendWithId('pause'), | |
| 51 | |
| 52 /** @override */ | |
| 53 remove: chromeSendWithId('remove'), | |
| 54 | |
| 55 /** @override */ | |
| 56 resume: chromeSendWithId('resume'), | |
| 57 | |
| 58 /** @override */ | |
| 59 saveDangerous: chromeSendWithId('saveDangerous'), | |
| 60 | |
| 61 /** @override */ | |
| 62 search: function(searchText) { | |
| 29 // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']). | 63 // Split quoted terms (e.g., 'The "lazy" dog' => ['The', 'lazy', 'dog']). |
| 30 function trim(s) { return s.trim(); } | 64 function trim(s) { return s.trim(); } |
| 31 chrome.send('getDownloads', searchText.split(/"([^"]*)"/).map(trim)); | 65 chrome.send('getDownloads', searchText.split(/"([^"]*)"/).map(trim)); |
| 32 }, | 66 }, |
| 33 | 67 |
| 68 /** @override */ | |
| 69 show: chromeSendWithId('show'), | |
| 70 | |
| 71 /** | |
| 72 * @return {boolean} Whether all can be cleared. | |
| 73 * @private | |
| 74 */ | |
| 75 canClearAll_: function() { | |
| 76 return loadTimeData.getBoolean('allowDeletingHistory') && | |
| 77 this.size_() > 0; | |
| 78 }, | |
| 79 | |
| 34 /** | 80 /** |
| 35 * @return {number} A guess at how many items could be visible at once. | 81 * @return {number} A guess at how many items could be visible at once. |
| 36 * @private | 82 * @private |
| 37 */ | 83 */ |
| 38 guesstimateNumberOfVisibleItems_: function() { | 84 guesstimateNumberOfVisibleItems_: function() { |
| 39 var toolbarHeight = $('downloads-toolbar').offsetHeight; | 85 var toolbarHeight = this.toolbar_.offsetHeight; |
| 40 var summaryHeight = $('downloads-summary').offsetHeight; | 86 return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1; |
| 41 var nonItemSpace = toolbarHeight + summaryHeight; | |
| 42 return Math.floor((window.innerHeight - nonItemSpace) / 46) + 1; | |
| 43 }, | 87 }, |
| 44 | 88 |
| 45 /** | 89 /** |
| 46 * Called when all items need to be updated. | 90 * Called when all items need to be updated. |
| 47 * @param {!Array<!downloads.Data>} list A list of new download data. | 91 * @param {!Array<!downloads.Data>} list A list of new download data. |
| 48 * @private | 92 * @private |
| 49 */ | 93 */ |
| 50 updateAll_: function(list) { | 94 updateAll_: function(list) { |
| 51 var oldIdMap = this.idMap_ || {}; | 95 var oldIdMap = this.idMap_ || {}; |
| 52 | 96 |
| 53 /** @private {!Object<!downloads.ItemView>} */ | 97 /** @private {!Object<!downloads.ItemView>} */ |
| 54 this.idMap_ = {}; | 98 this.idMap_ = {}; |
| 55 | 99 |
| 56 /** @private {!Array<!downloads.ItemView>} */ | 100 /** @private {!Array<!downloads.ItemView>} */ |
| 57 this.items_ = []; | 101 this.items_ = []; |
| 58 | 102 |
| 59 if (!this.iconLoader_) { | 103 if (!this.iconLoader_) { |
| 60 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1); | 104 var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1); |
| 61 /** @private {downloads.ThrottledIconLoader} */ | 105 /** @private {downloads.ThrottledIconLoader} */ |
| 62 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate); | 106 this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate); |
| 63 } | 107 } |
| 64 | 108 |
| 65 for (var i = 0; i < list.length; ++i) { | 109 for (var i = 0; i < list.length; ++i) { |
| 66 var data = list[i]; | 110 var data = list[i]; |
| 67 var id = data.id; | 111 var id = data.id; |
| 68 | 112 |
| 69 // Re-use old items when possible (saves work, preserves focus). | 113 // Re-use old items when possible (saves work, preserves focus). |
| 70 var item = oldIdMap[id] || new downloads.ItemView(this.iconLoader_); | 114 var item = |
| 115 oldIdMap[id] || new downloads.ItemView(this.iconLoader_, this); | |
| 71 | 116 |
| 72 this.idMap_[id] = item; // Associated by ID for fast lookup. | 117 this.idMap_[id] = item; // Associated by ID for fast lookup. |
| 73 this.items_.push(item); // Add to sorted list for order. | 118 this.items_.push(item); // Add to sorted list for order. |
| 74 | 119 |
| 75 // Render |item| but don't actually add to the DOM yet. |this.items_| | 120 // 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. | 121 // must be fully created to be able to find the right spot to insert. |
| 77 item.update(data); | 122 item.update(data); |
| 78 | 123 |
| 79 // Collapse redundant dates. | 124 // Collapse redundant dates. |
| 80 var prev = list[i - 1]; | 125 var prev = list[i - 1]; |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 95 if (item.parentNode) // Already in the DOM; skip. | 140 if (item.parentNode) // Already in the DOM; skip. |
| 96 continue; | 141 continue; |
| 97 | 142 |
| 98 var before = null; | 143 var before = null; |
| 99 // Find the next rendered item after this one, and insert before it. | 144 // Find the next rendered item after this one, and insert before it. |
| 100 for (var j = i + 1; !before && j < this.items_.length; ++j) { | 145 for (var j = i + 1; !before && j < this.items_.length; ++j) { |
| 101 if (this.items_[j].parentNode) | 146 if (this.items_[j].parentNode) |
| 102 before = this.items_[j]; | 147 before = this.items_[j]; |
| 103 } | 148 } |
| 104 // If |before| is null, |item| will just get added at the end. | 149 // If |before| is null, |item| will just get added at the end. |
| 105 this.node_.insertBefore(item, before); | 150 this.list_.insertBefore(item, before); |
| 106 } | 151 } |
| 107 | 152 |
| 108 var noDownloadsOrResults = $('no-downloads-or-results'); | |
| 109 noDownloadsOrResults.textContent = loadTimeData.getString( | |
| 110 this.searchText_ ? 'noSearchResults' : 'noDownloads'); | |
| 111 | |
| 112 var hasDownloads = this.size_() > 0; | 153 var hasDownloads = this.size_() > 0; |
| 113 this.node_.hidden = !hasDownloads; | 154 this.list_.hidden = !hasDownloads; |
| 114 noDownloadsOrResults.hidden = hasDownloads; | 155 $('no-downloads').hidden = hasDownloads; |
| 115 | 156 |
| 116 if (loadTimeData.getBoolean('allowDeletingHistory')) | 157 if (loadTimeData.getBoolean('allowDeletingHistory')) |
| 117 $('clear-all').hidden = !hasDownloads || this.searchText_.length > 0; | 158 this.toolbar_.canClearAll = hasDownloads; |
| 118 }, | 159 }, |
| 119 | 160 |
| 120 /** | 161 /** |
| 121 * @param {!downloads.Data} data | 162 * @param {!downloads.Data} data |
| 122 * @private | 163 * @private |
| 123 */ | 164 */ |
| 124 updateItem_: function(data) { | 165 updateItem_: function(data) { |
| 125 this.idMap_[data.id].update(data); | 166 this.idMap_[data.id].update(data); |
| 126 }, | 167 }, |
| 127 | 168 |
| 128 /** | 169 /** |
| 129 * @return {number} The number of downloads shown on the page. | 170 * @return {number} The number of downloads shown on the page. |
| 130 * @private | 171 * @private |
| 131 */ | 172 */ |
| 132 size_: function() { | 173 size_: function() { |
| 133 return this.items_.length; | 174 return this.items_.length; |
| 134 }, | 175 }, |
| 135 | 176 |
| 136 /** @private */ | |
| 137 clearAll_: function() { | |
| 138 if (loadTimeData.getBoolean('allowDeletingHistory')) { | |
| 139 chrome.send('clearAll'); | |
| 140 this.setSearchText_(''); | |
| 141 } | |
| 142 }, | |
| 143 | |
| 144 /** @private */ | |
| 145 onLoad_: function() { | |
| 146 this.node_ = $('downloads-display'); | |
| 147 | |
| 148 $('clear-all').onclick = function() { | |
| 149 this.clearAll_(); | |
| 150 }.bind(this); | |
| 151 | |
| 152 $('open-downloads-folder').onclick = function() { | |
| 153 chrome.send('openDownloadsFolder'); | |
| 154 }; | |
| 155 | |
| 156 $('search-button').onclick = function() { | |
| 157 if (!$('search-term').hidden) | |
| 158 return; | |
| 159 $('clear-search').hidden = false; | |
| 160 $('search-term').hidden = false; | |
| 161 }; | |
| 162 | |
| 163 $('clear-search').onclick = function() { | |
| 164 $('clear-search').hidden = true; | |
| 165 $('search-term').hidden = true; | |
| 166 $('search-term').value = ''; | |
| 167 this.setSearchText_(''); | |
| 168 }.bind(this); | |
| 169 | |
| 170 // TODO(dbeam): this previously used onsearch, which batches keystrokes | |
| 171 // together. This should probably be re-instated eventually. | |
| 172 $('search-term').oninput = function(e) { | |
| 173 this.setSearchText_($('search-term').value); | |
| 174 }.bind(this); | |
| 175 | |
| 176 cr.ui.decorate('command', cr.ui.Command); | |
| 177 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); | |
| 178 document.addEventListener('command', this.onCommand_.bind(this)); | |
| 179 | |
| 180 this.setSearchText_(''); | |
| 181 }, | |
| 182 | |
| 183 /** | 177 /** |
| 184 * @param {Event} e | 178 * @param {Event} e |
| 185 * @private | 179 * @private |
| 186 */ | 180 */ |
| 187 onCanExecute_: function(e) { | 181 onCanExecute_: function(e) { |
| 188 e = /** @type {cr.ui.CanExecuteEvent} */(e); | 182 e = /** @type {cr.ui.CanExecuteEvent} */(e); |
| 189 switch (e.command.id) { | 183 switch (e.command.id) { |
| 190 case 'undo-command': | 184 case 'undo-command': |
| 191 e.canExecute = !$('search-term').contains(document.activeElement); | 185 var activeElement = document.activeElement; |
| 186 var searchTerm = this.toolbar_ && this.toolbar_.searchTerm; | |
| 187 e.canExecute = searchTerm && !searchTerm.contains(activeElement); | |
|
Jeremy Klein
2015/07/16 18:54:53
This is a cool trick for determining focus. I've n
Dan Beam
2015/07/18 01:02:20
Acknowledged.
| |
| 192 break; | 188 break; |
| 193 case 'clear-all-command': | 189 case 'clear-all-command': |
| 194 e.canExecute = true; | 190 e.canExecute = true; |
| 195 break; | 191 break; |
| 196 } | 192 } |
| 197 }, | 193 }, |
| 198 | 194 |
| 199 /** | 195 /** |
| 200 * @param {Event} e | 196 * @param {Event} e |
| 201 * @private | 197 * @private |
| 202 */ | 198 */ |
| 203 onCommand_: function(e) { | 199 onCommand_: function(e) { |
| 204 if (e.command.id == 'undo-command') | 200 if (e.command.id == 'undo-command') |
| 205 chrome.send('undo'); | 201 chrome.send('undo'); |
| 206 else if (e.command.id == 'clear-all-command') | 202 else if (e.command.id == 'clear-all-command') |
| 207 this.clearAll_(); | 203 this.clearAll(); |
| 204 }, | |
| 205 | |
| 206 /** @private */ | |
| 207 onLoad_: function() { | |
| 208 this.list_ = $('downloads-list'); | |
| 209 | |
| 210 var toolbar = document.querySelector('downloads-toolbar'); | |
| 211 /** @private {!downloads.Toolbar} */ | |
| 212 this.toolbar_ = assertInstanceof(toolbar, downloads.Toolbar); | |
| 213 this.toolbar_.setActionService(this); | |
|
Jeremy Klein
2015/07/16 18:54:53
This creates a circular dependency that makes thes
Dan Beam
2015/07/18 01:02:20
Done.
| |
| 214 | |
| 215 cr.ui.decorate('command', cr.ui.Command); | |
| 216 document.addEventListener('canExecute', this.onCanExecute_.bind(this)); | |
| 217 document.addEventListener('command', this.onCommand_.bind(this)); | |
| 218 | |
| 219 // Shows all downloads. | |
| 220 this.search(''); | |
| 208 }, | 221 }, |
| 209 }; | 222 }; |
| 210 | 223 |
| 211 Manager.updateAll = function(list) { | |
| 212 Manager.getInstance().updateAll_(list); | |
| 213 }; | |
| 214 | |
| 215 Manager.updateItem = function(item) { | |
| 216 Manager.getInstance().updateItem_(item); | |
| 217 }; | |
| 218 | |
| 219 Manager.setSearchText = function(searchText) { | |
| 220 Manager.getInstance().setSearchText_(searchText); | |
| 221 }; | |
| 222 | |
| 223 Manager.onLoad = function() { | 224 Manager.onLoad = function() { |
| 224 Manager.getInstance().onLoad_(); | 225 Manager.getInstance().onLoad_(); |
| 225 }; | 226 }; |
| 226 | 227 |
| 227 Manager.size = function() { | 228 Manager.size = function() { |
| 228 return Manager.getInstance().size_(); | 229 return Manager.getInstance().size_(); |
| 229 }; | 230 }; |
| 230 | 231 |
| 232 Manager.updateAll = function(list) { | |
| 233 Manager.getInstance().updateAll_(list); | |
| 234 }; | |
| 235 | |
| 236 Manager.updateItem = function(item) { | |
| 237 Manager.getInstance().updateItem_(item); | |
| 238 }; | |
| 239 | |
| 231 return {Manager: Manager}; | 240 return {Manager: Manager}; |
| 232 }); | 241 }); |
| 233 | 242 |
| 234 window.addEventListener('load', downloads.Manager.onLoad); | 243 window.addEventListener('load', downloads.Manager.onLoad); |
| OLD | NEW |