Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(201)

Side by Side Diff: chrome/browser/resources/file_manager/js/photo/ribbon.js

Issue 39123003: [Files.app] Split the JavaScript files into subdirectories: common, background, and foreground (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed test failure. Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 'use strict';
6
7 /**
8 * Scrollable thumbnail ribbon at the bottom of the Gallery in the Slide mode.
9 *
10 * @param {Document} document Document.
11 * @param {MetadataCache} metadataCache MetadataCache instance.
12 * @param {cr.ui.ArrayDataModel} dataModel Data model.
13 * @param {cr.ui.ListSelectionModel} selectionModel Selection model.
14 * @return {Element} Ribbon element.
15 * @constructor
16 */
17 function Ribbon(document, metadataCache, dataModel, selectionModel) {
18 var self = document.createElement('div');
19 Ribbon.decorate(self, metadataCache, dataModel, selectionModel);
20 return self;
21 }
22
23 /**
24 * Inherit from HTMLDivElement.
25 */
26 Ribbon.prototype.__proto__ = HTMLDivElement.prototype;
27
28 /**
29 * Decorate a Ribbon instance.
30 *
31 * @param {Ribbon} self Self pointer.
32 * @param {MetadataCache} metadataCache MetadataCache instance.
33 * @param {cr.ui.ArrayDataModel} dataModel Data model.
34 * @param {cr.ui.ListSelectionModel} selectionModel Selection model.
35 */
36 Ribbon.decorate = function(self, metadataCache, dataModel, selectionModel) {
37 self.__proto__ = Ribbon.prototype;
38 self.metadataCache_ = metadataCache;
39 self.dataModel_ = dataModel;
40 self.selectionModel_ = selectionModel;
41
42 self.className = 'ribbon';
43 };
44
45 /**
46 * Max number of thumbnails in the ribbon.
47 * @type {number}
48 */
49 Ribbon.ITEMS_COUNT = 5;
50
51 /**
52 * Force redraw the ribbon.
53 */
54 Ribbon.prototype.redraw = function() {
55 this.onSelection_();
56 };
57
58 /**
59 * Clear all cached data to force full redraw on the next selection change.
60 */
61 Ribbon.prototype.reset = function() {
62 this.renderCache_ = {};
63 this.firstVisibleIndex_ = 0;
64 this.lastVisibleIndex_ = -1; // Zero thumbnails
65 };
66
67 /**
68 * Enable the ribbon.
69 */
70 Ribbon.prototype.enable = function() {
71 this.onContentBound_ = this.onContentChange_.bind(this);
72 this.dataModel_.addEventListener('content', this.onContentBound_);
73
74 this.onSpliceBound_ = this.onSplice_.bind(this);
75 this.dataModel_.addEventListener('splice', this.onSpliceBound_);
76
77 this.onSelectionBound_ = this.onSelection_.bind(this);
78 this.selectionModel_.addEventListener('change', this.onSelectionBound_);
79
80 this.reset();
81 this.redraw();
82 };
83
84 /**
85 * Disable ribbon.
86 */
87 Ribbon.prototype.disable = function() {
88 this.dataModel_.removeEventListener('content', this.onContentBound_);
89 this.dataModel_.removeEventListener('splice', this.onSpliceBound_);
90 this.selectionModel_.removeEventListener('change', this.onSelectionBound_);
91
92 this.removeVanishing_();
93 this.textContent = '';
94 };
95
96 /**
97 * Data model splice handler.
98 * @param {Event} event Event.
99 * @private
100 */
101 Ribbon.prototype.onSplice_ = function(event) {
102 if (event.removed.length == 0)
103 return;
104
105 if (event.removed.length > 1) {
106 console.error('Cannot remove multiple items');
107 return;
108 }
109
110 var removed = this.renderCache_[event.removed[0].getUrl()];
111 if (!removed || !removed.parentNode || !removed.hasAttribute('selected')) {
112 console.error('Can only remove the selected item');
113 return;
114 }
115
116 var persistentNodes = this.querySelectorAll('.ribbon-image:not([vanishing])');
117 if (this.lastVisibleIndex_ < this.dataModel_.length) { // Not at the end.
118 var lastNode = persistentNodes[persistentNodes.length - 1];
119 if (lastNode.nextSibling) {
120 // Pull back a vanishing node from the right.
121 lastNode.nextSibling.removeAttribute('vanishing');
122 } else {
123 // Push a new item at the right end.
124 this.appendChild(this.renderThumbnail_(this.lastVisibleIndex_));
125 }
126 } else {
127 // No items to the right, move the window to the left.
128 this.lastVisibleIndex_--;
129 if (this.firstVisibleIndex_) {
130 this.firstVisibleIndex_--;
131 var firstNode = persistentNodes[0];
132 if (firstNode.previousSibling) {
133 // Pull back a vanishing node from the left.
134 firstNode.previousSibling.removeAttribute('vanishing');
135 } else {
136 // Push a new item at the left end.
137 var newThumbnail = this.renderThumbnail_(this.firstVisibleIndex_);
138 newThumbnail.style.marginLeft = -(this.clientHeight - 2) + 'px';
139 this.insertBefore(newThumbnail, this.firstChild);
140 setTimeout(function() {
141 newThumbnail.style.marginLeft = '0';
142 }, 0);
143 }
144 }
145 }
146
147 removed.removeAttribute('selected');
148 removed.setAttribute('vanishing', 'smooth');
149 this.scheduleRemove_();
150 };
151
152 /**
153 * Selection change handler.
154 * @private
155 */
156 Ribbon.prototype.onSelection_ = function() {
157 var indexes = this.selectionModel_.selectedIndexes;
158 if (indexes.length == 0)
159 return; // Ignore temporary empty selection.
160 var selectedIndex = indexes[0];
161
162 var length = this.dataModel_.length;
163
164 // TODO(dgozman): use margin instead of 2 here.
165 var itemWidth = this.clientHeight - 2;
166 var fullItems = Ribbon.ITEMS_COUNT;
167 fullItems = Math.min(fullItems, length);
168 var right = Math.floor((fullItems - 1) / 2);
169
170 var fullWidth = fullItems * itemWidth;
171 this.style.width = fullWidth + 'px';
172
173 var lastIndex = selectedIndex + right;
174 lastIndex = Math.max(lastIndex, fullItems - 1);
175 lastIndex = Math.min(lastIndex, length - 1);
176 var firstIndex = lastIndex - fullItems + 1;
177
178 if (this.firstVisibleIndex_ != firstIndex ||
179 this.lastVisibleIndex_ != lastIndex) {
180
181 if (this.lastVisibleIndex_ == -1) {
182 this.firstVisibleIndex_ = firstIndex;
183 this.lastVisibleIndex_ = lastIndex;
184 }
185
186 this.removeVanishing_();
187
188 this.textContent = '';
189 var startIndex = Math.min(firstIndex, this.firstVisibleIndex_);
190 // All the items except the first one treated equally.
191 for (var index = startIndex + 1;
192 index <= Math.max(lastIndex, this.lastVisibleIndex_);
193 ++index) {
194 // Only add items that are in either old or the new viewport.
195 if (this.lastVisibleIndex_ < index && index < firstIndex ||
196 lastIndex < index && index < this.firstVisibleIndex_)
197 continue;
198 var box = this.renderThumbnail_(index);
199 box.style.marginLeft = '0';
200 this.appendChild(box);
201 if (index < firstIndex || index > lastIndex) {
202 // If the node is not in the new viewport we only need it while
203 // the animation is playing out.
204 box.setAttribute('vanishing', 'slide');
205 }
206 }
207
208 var slideCount = this.childNodes.length + 1 - Ribbon.ITEMS_COUNT;
209 var margin = itemWidth * slideCount;
210 var startBox = this.renderThumbnail_(startIndex);
211 if (startIndex == firstIndex) {
212 // Sliding to the right.
213 startBox.style.marginLeft = -margin + 'px';
214 if (this.firstChild)
215 this.insertBefore(startBox, this.firstChild);
216 else
217 this.appendChild(startBox);
218 setTimeout(function() {
219 startBox.style.marginLeft = '0';
220 }, 0);
221 } else {
222 // Sliding to the left. Start item will become invisible and should be
223 // removed afterwards.
224 startBox.setAttribute('vanishing', 'slide');
225 startBox.style.marginLeft = '0';
226 if (this.firstChild)
227 this.insertBefore(startBox, this.firstChild);
228 else
229 this.appendChild(startBox);
230 setTimeout(function() {
231 startBox.style.marginLeft = -margin + 'px';
232 }, 0);
233 }
234
235 ImageUtil.setClass(this, 'fade-left',
236 firstIndex > 0 && selectedIndex != firstIndex);
237
238 ImageUtil.setClass(this, 'fade-right',
239 lastIndex < length - 1 && selectedIndex != lastIndex);
240
241 this.firstVisibleIndex_ = firstIndex;
242 this.lastVisibleIndex_ = lastIndex;
243
244 this.scheduleRemove_();
245 }
246
247 var oldSelected = this.querySelector('[selected]');
248 if (oldSelected) oldSelected.removeAttribute('selected');
249
250 var newSelected =
251 this.renderCache_[this.dataModel_.item(selectedIndex).getUrl()];
252 if (newSelected) newSelected.setAttribute('selected', true);
253 };
254
255 /**
256 * Schedule the removal of thumbnails marked as vanishing.
257 * @private
258 */
259 Ribbon.prototype.scheduleRemove_ = function() {
260 if (this.removeTimeout_)
261 clearTimeout(this.removeTimeout_);
262
263 this.removeTimeout_ = setTimeout(function() {
264 this.removeTimeout_ = null;
265 this.removeVanishing_();
266 }.bind(this), 200);
267 };
268
269 /**
270 * Remove all thumbnails marked as vanishing.
271 * @private
272 */
273 Ribbon.prototype.removeVanishing_ = function() {
274 if (this.removeTimeout_) {
275 clearTimeout(this.removeTimeout_);
276 this.removeTimeout_ = 0;
277 }
278 var vanishingNodes = this.querySelectorAll('[vanishing]');
279 for (var i = 0; i != vanishingNodes.length; i++) {
280 vanishingNodes[i].removeAttribute('vanishing');
281 this.removeChild(vanishingNodes[i]);
282 }
283 };
284
285 /**
286 * Create a DOM element for a thumbnail.
287 *
288 * @param {number} index Item index.
289 * @return {Element} Newly created element.
290 * @private
291 */
292 Ribbon.prototype.renderThumbnail_ = function(index) {
293 var item = this.dataModel_.item(index);
294 var url = item.getUrl();
295
296 var cached = this.renderCache_[url];
297 if (cached) {
298 var img = cached.querySelector('img');
299 if (img)
300 img.classList.add('cached');
301 return cached;
302 }
303
304 var thumbnail = this.ownerDocument.createElement('div');
305 thumbnail.className = 'ribbon-image';
306 thumbnail.addEventListener('click', function() {
307 var index = this.dataModel_.indexOf(item);
308 this.selectionModel_.unselectAll();
309 this.selectionModel_.setIndexSelected(index, true);
310 }.bind(this));
311
312 util.createChild(thumbnail, 'image-wrapper');
313
314 this.metadataCache_.get(url, Gallery.METADATA_TYPE,
315 this.setThumbnailImage_.bind(this, thumbnail, url));
316
317 // TODO: Implement LRU eviction.
318 // Never evict the thumbnails that are currently in the DOM because we rely
319 // on this cache to find them by URL.
320 this.renderCache_[url] = thumbnail;
321 return thumbnail;
322 };
323
324 /**
325 * Set the thumbnail image.
326 *
327 * @param {Element} thumbnail Thumbnail element.
328 * @param {string} url Image url.
329 * @param {Object} metadata Metadata.
330 * @private
331 */
332 Ribbon.prototype.setThumbnailImage_ = function(thumbnail, url, metadata) {
333 new ThumbnailLoader(url, ThumbnailLoader.LoaderType.IMAGE, metadata).load(
334 thumbnail.querySelector('.image-wrapper'),
335 ThumbnailLoader.FillMode.FILL /* fill */,
336 ThumbnailLoader.OptimizationMode.NEVER_DISCARD);
337 };
338
339 /**
340 * Content change handler.
341 *
342 * @param {Event} event Event.
343 * @private
344 */
345 Ribbon.prototype.onContentChange_ = function(event) {
346 var url = event.item.getUrl();
347 this.remapCache_(event.oldUrl, url);
348
349 var thumbnail = this.renderCache_[url];
350 if (thumbnail && event.metadata)
351 this.setThumbnailImage_(thumbnail, url, event.metadata);
352 };
353
354 /**
355 * Update the thumbnail element cache.
356 *
357 * @param {string} oldUrl Old url.
358 * @param {string} newUrl New url.
359 * @private
360 */
361 Ribbon.prototype.remapCache_ = function(oldUrl, newUrl) {
362 if (oldUrl != newUrl && (oldUrl in this.renderCache_)) {
363 this.renderCache_[newUrl] = this.renderCache_[oldUrl];
364 delete this.renderCache_[oldUrl];
365 }
366 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698