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

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

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

Powered by Google App Engine
This is Rietveld 408576698