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 |