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() {
7 * Class to own and manage download items.
12 cr
.addSingletonGetter(Manager
);
15 /** @private {string} */
19 * Sets the search text, updates related UIs, and tells the browser.
20 * @param {string} searchText Text we're searching for.
23 setSearchText_: function(searchText
) {
24 this.searchText_
= searchText
;
26 $('downloads-summary-text').textContent
= this.searchText_
?
27 loadTimeData
.getStringF('searchResultsFor', this.searchText_
) : '';
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
));
35 * @return {number} A guess at how many items could be visible at once.
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;
46 * Called when all items need to be updated.
47 * @param {!Array<!downloads.Data>} list A list of new download data.
50 updateAll_: function(list
) {
51 var oldIdMap
= this.idMap_
|| {};
53 /** @private {!Object<!downloads.ItemView>} */
56 /** @private {!Array<!downloads.ItemView>} */
59 if (!this.iconLoader_
) {
60 var guesstimate
= Math
.max(this.guesstimateNumberOfVisibleItems_(), 1);
61 /** @private {downloads.ThrottledIconLoader} */
62 this.iconLoader_
= new downloads
.ThrottledIconLoader(guesstimate
);
65 for (var i
= 0; i
< list
.length
; ++i
) {
69 // Re-use old items when possible (saves work, preserves focus).
70 var item
= oldIdMap
[id
] || new downloads
.ItemView(this.iconLoader_
);
72 this.idMap_
[id
] = item
; // Associated by ID for fast lookup.
73 this.items_
.push(item
); // Add to sorted list for order.
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.
79 // Collapse redundant dates.
80 var prev
= list
[i
- 1];
81 item
.dateContainer
.hidden
=
82 prev
&& prev
.date_string
== data
.date_string
;
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
);
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.
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
;
106 // If |before| is null, |item| will just get added at the end.
107 this.node_
.insertBefore(item
.node
, before
);
110 var noDownloadsOrResults
= $('no-downloads-or-results');
111 noDownloadsOrResults
.textContent
= loadTimeData
.getString(
112 this.searchText_
? 'noSearchResults' : 'noDownloads');
114 var hasDownloads
= this.size_() > 0;
115 this.node_
.hidden
= !hasDownloads
;
116 noDownloadsOrResults
.hidden
= hasDownloads
;
118 if (loadTimeData
.getBoolean('allowDeletingHistory'))
119 $('clear-all').hidden
= !hasDownloads
|| this.searchText_
.length
> 0;
121 this.rebuildFocusGrid_();
125 * @param {!downloads.Data} data Info about the item to update.
128 updateItem_: function(data
) {
129 var activeElement
= document
.activeElement
;
131 var item
= this.idMap_
[data
.id
];
133 var focusRow
= this.decorateItem_(item
);
135 if (focusRow
.contains(activeElement
) &&
136 !downloads
.FocusRow
.shouldFocus(activeElement
)) {
137 focusRow
.getEquivalentElement(activeElement
).focus();
142 * Rebuild the focusGrid_ using the elements that each download will have.
145 rebuildFocusGrid_: function() {
146 var activeElement
= document
.activeElement
;
148 /** @private {!cr.ui.FocusGrid} */
149 this.focusGrid_
= this.focusGrid_
|| new cr
.ui
.FocusGrid();
150 this.focusGrid_
.destroy();
152 this.items_
.forEach(function(item
) {
153 var focusRow
= this.decorateItem_(item
);
154 this.focusGrid_
.addRow(focusRow
);
156 if (focusRow
.contains(activeElement
) &&
157 !downloads
.FocusRow
.shouldFocus(activeElement
)) {
158 focusRow
.getEquivalentElement(activeElement
).focus();
161 this.focusGrid_
.ensureRowActive();
165 * @param {!downloads.ItemView} item An item to decorate as a FocusRow.
166 * @return {!downloads.FocusRow} |item| decorated as a FocusRow.
169 decorateItem_: function(item
) {
170 downloads
.FocusRow
.decorate(item
.node
, item
, this.node_
);
171 return assertInstanceof(item
.node
, downloads
.FocusRow
);
175 * @return {number} The number of downloads shown on the page.
179 return this.items_
.length
;
183 clearAll_: function() {
184 if (loadTimeData
.getBoolean('allowDeletingHistory')) {
185 chrome
.send('clearAll');
186 this.setSearchText_('');
191 onLoad_: function() {
192 this.node_
= $('downloads-display');
194 $('clear-all').onclick = function() {
198 $('open-downloads-folder').onclick = function() {
199 chrome
.send('openDownloadsFolder');
202 $('term').onsearch = function(e
) {
203 this.setSearchText_($('term').value
);
206 cr
.ui
.decorate('command', cr
.ui
.Command
);
207 document
.addEventListener('canExecute', this.onCanExecute_
.bind(this));
208 document
.addEventListener('command', this.onCommand_
.bind(this));
210 this.setSearchText_('');
217 onCanExecute_: function(e
) {
218 e
= /** @type {cr.ui.CanExecuteEvent} */(e
);
219 switch (e
.command
.id
) {
221 e
.canExecute
= document
.activeElement
!= $('term');
223 case 'clear-all-command':
233 onCommand_: function(e
) {
234 if (e
.command
.id
== 'undo-command')
236 else if (e
.command
.id
== 'clear-all-command')
241 Manager
.updateAll = function(list
) {
242 Manager
.getInstance().updateAll_(list
);
245 Manager
.updateItem = function(item
) {
246 Manager
.getInstance().updateItem_(item
);
249 Manager
.setSearchText = function(searchText
) {
250 Manager
.getInstance().setSearchText_(searchText
);
253 Manager
.onLoad = function() {
254 Manager
.getInstance().onLoad_();
257 Manager
.size = function() {
258 return Manager
.getInstance().size_();
261 return {Manager
: Manager
};
264 window
.addEventListener('DOMContentLoaded', downloads
.Manager
.onLoad
);