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

Side by Side Diff: chrome/browser/resources/file_manager/js/photo/slide_mode.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 * Slide mode displays a single image and has a set of controls to navigate 6 * Slide mode displays a single image and has a set of controls to navigate
7 * between the images and to edit an image. 7 * between the images and to edit an image.
8 * 8 *
9 * @param {Element} container Container element. 9 * TODO(kaznacheev): Introduce a parameter object.
10 *
11 * @param {Element} container Main container element.
12 * @param {Element} content Content container element.
10 * @param {Element} toolbar Toolbar element. 13 * @param {Element} toolbar Toolbar element.
11 * @param {ImageEditor.Prompt} prompt Prompt. 14 * @param {ImageEditor.Prompt} prompt Prompt.
15 * @param {cr.ui.ArrayDataModel} dataModel Data model.
16 * @param {cr.ui.ListSelectionModel} selectionModel Selection model.
12 * @param {Object} context Context. 17 * @param {Object} context Context.
13 * @param {function(string):string} displayStringFunction String formatting 18 * @param {function(string):string} displayStringFunction String formatting
14 * function. 19 * function.
15 * @constructor 20 * @constructor
16 */ 21 */
17 function SlideMode(container, toolbar, prompt, context, displayStringFunction) { 22 function SlideMode(container, content, toolbar, prompt,
23 dataModel, selectionModel,
24 context, displayStringFunction) {
18 this.container_ = container; 25 this.container_ = container;
26 this.document_ = container.ownerDocument;
27 this.content = content;
19 this.toolbar_ = toolbar; 28 this.toolbar_ = toolbar;
20 this.document_ = container.ownerDocument;
21 this.prompt_ = prompt; 29 this.prompt_ = prompt;
30 this.dataModel_ = dataModel;
31 this.selectionModel_ = selectionModel;
22 this.context_ = context; 32 this.context_ = context;
23 this.metadataCache_ = context.metadataCache; 33 this.metadataCache_ = context.metadataCache;
24 this.displayStringFunction_ = displayStringFunction; 34 this.displayStringFunction_ = displayStringFunction;
25 35
36 this.onSelectionBound_ = this.onSelection_.bind(this);
37 this.onSpliceBound_ = this.onSplice_.bind(this);
38
26 this.initListeners_(); 39 this.initListeners_();
27 this.initDom_(); 40 this.initDom_();
28 } 41 }
29 42
30 /** 43 /**
31 * SlideMode extends cr.EventTarget. 44 * SlideMode extends cr.EventTarget.
32 */ 45 */
33 SlideMode.prototype.__proto__ = cr.EventTarget.prototype; 46 SlideMode.prototype.__proto__ = cr.EventTarget.prototype;
34 47
35 /** 48 /**
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
129 142
130 util.createChild(this.arrowBox_, 'arrow-spacer'); 143 util.createChild(this.arrowBox_, 'arrow-spacer');
131 144
132 this.arrowRight_ = 145 this.arrowRight_ =
133 util.createChild(this.arrowBox_, 'arrow right tool dimmable'); 146 util.createChild(this.arrowBox_, 'arrow right tool dimmable');
134 this.arrowRight_.addEventListener('click', 147 this.arrowRight_.addEventListener('click',
135 this.selectNext.bind(this, 1, null)); 148 this.selectNext.bind(this, 1, null));
136 util.createChild(this.arrowRight_); 149 util.createChild(this.arrowRight_);
137 150
138 this.ribbonSpacer_ = util.createChild(this.toolbar_, 'ribbon-spacer'); 151 this.ribbonSpacer_ = util.createChild(this.toolbar_, 'ribbon-spacer');
152 this.ribbon_ = new Ribbon(this.document_,
153 this.metadataCache_, this.dataModel_, this.selectionModel_);
154 this.ribbonSpacer_.appendChild(this.ribbon_);
139 155
140 // Error indicator. 156 // Error indicator.
141 var errorWrapper = util.createChild(this.container_, 'prompt-wrapper'); 157 var errorWrapper = util.createChild(this.container_, 'prompt-wrapper');
142 errorWrapper.setAttribute('pos', 'center'); 158 errorWrapper.setAttribute('pos', 'center');
143 159
144 this.errorBanner_ = util.createChild(errorWrapper, 'error-banner'); 160 this.errorBanner_ = util.createChild(errorWrapper, 'error-banner');
145 161
146 util.createChild(this.container_, 'spinner'); 162 util.createChild(this.container_, 'spinner');
147 163
164 this.slideShowButton_ = util.createChild(this.toolbar_, 'button slideshow');
165 this.slideShowButton_.title = this.displayStringFunction_('slideshow');
166 this.slideShowButton_.addEventListener('click',
167 this.toggleSlideshow_.bind(this, SlideMode.SLIDESHOW_INTERVAL_FIRST));
168
169 // Editor.
148 170
149 this.editButton_ = util.createChild(this.toolbar_, 'button edit'); 171 this.editButton_ = util.createChild(this.toolbar_, 'button edit');
150 this.editButton_.textContent = this.displayStringFunction_('edit'); 172 this.editButton_.title = this.displayStringFunction_('edit');
151 this.editButton_.addEventListener('click', this.onEdit_.bind(this)); 173 this.editButton_.addEventListener('click', this.toggleEditor_.bind(this));
152
153 // Editor toolbar.
154 174
155 this.editBar_ = util.createChild(this.toolbar_, 'edit-bar'); 175 this.editBar_ = util.createChild(this.toolbar_, 'edit-bar');
156 this.editBarMain_ = util.createChild(this.editBar_, 'edit-main'); 176 this.editBarMain_ = util.createChild(this.editBar_, 'edit-main');
157 177
158 this.editBarMode_ = util.createChild(this.container_, 'edit-modal'); 178 this.editBarMode_ = util.createChild(this.container_, 'edit-modal');
159 this.editBarModeWrapper_ = util.createChild( 179 this.editBarModeWrapper_ = util.createChild(
160 this.editBarMode_, 'edit-modal-wrapper'); 180 this.editBarMode_, 'edit-modal-wrapper');
161 this.editBarModeWrapper_.hidden = true; 181 this.editBarModeWrapper_.hidden = true;
162 182
163 // Objects supporting image display and editing. 183 // Objects supporting image display and editing.
164 this.viewport_ = new Viewport(); 184 this.viewport_ = new Viewport();
165 185
166 this.imageView_ = new ImageView( 186 this.imageView_ = new ImageView(
167 this.imageContainer_, 187 this.imageContainer_,
168 this.viewport_, 188 this.viewport_,
169 this.metadataCache_); 189 this.metadataCache_);
170 190
171 this.imageView_.addContentCallback(this.onImageContentChanged_.bind(this));
172
173 this.editor_ = new ImageEditor( 191 this.editor_ = new ImageEditor(
174 this.viewport_, 192 this.viewport_,
175 this.imageView_, 193 this.imageView_,
176 this.prompt_, 194 this.prompt_,
177 { 195 {
178 root: this.container_, 196 root: this.container_,
179 image: this.imageContainer_, 197 image: this.imageContainer_,
180 toolbar: this.editBarMain_, 198 toolbar: this.editBarMain_,
181 mode: this.editBarModeWrapper_ 199 mode: this.editBarModeWrapper_
182 }, 200 },
183 SlideMode.editorModes, 201 SlideMode.editorModes,
184 this.displayStringFunction_); 202 this.displayStringFunction_);
185 203
186 this.editor_.getBuffer().addOverlay( 204 this.editor_.getBuffer().addOverlay(
187 new SwipeOverlay(this.selectNext.bind(this))); 205 new SwipeOverlay(this.selectNext.bind(this)));
188 }; 206 };
189 207
190 /** 208 /**
191 * Load items, display the selected item. 209 * Load items, display the selected item.
192 * 210 *
193 * @param {Array.<Gallery.Item>} items Array of items. 211 * @param {function} opt_callback Callback.
194 * @param {number} selectedIndex Selected index.
195 * @param {function} callback Callback.
196 */ 212 */
197 SlideMode.prototype.load = function(items, selectedIndex, callback) { 213 SlideMode.prototype.enter = function(opt_callback) {
198 var selectedItem = items[selectedIndex]; 214 this.container_.setAttribute('mode', 'slide');
199 if (!selectedItem) { 215
200 this.showErrorBanner_('IMAGE_ERROR'); 216 this.sequenceDirection_ = 0;
201 return; 217 this.sequenceLength_ = 0;
202 }
203 218
204 var loadDone = function() { 219 var loadDone = function() {
205 this.items_ = items; 220 this.active_ = true;
206 this.setSelectedIndex_(selectedIndex); 221
207 this.setupNavigation_(); 222 this.selectionModel_.addEventListener('change', this.onSelectionBound_);
208 setTimeout(this.requestPrefetch.bind(this, 1 /* Next item */), 1000); 223 this.dataModel_.addEventListener('splice', this.onSpliceBound_);
209 callback(); 224
225 this.ribbon_.enable();
226
227 this.prefetchTimer_ = setTimeout(function() {
228 this.prefetchTimer_ = null;
229 this.requestPrefetch(1); // Prefetch the next image.
230 }.bind(this), 1000);
231 if (opt_callback) opt_callback();
210 }.bind(this); 232 }.bind(this);
211 233
212 var selectedUrl = selectedItem.getUrl(); 234 if (this.getItemCount_() == 0) {
213 // Show the selected item ASAP, then complete the initialization 235 this.displayedIndex_ = -1;
214 // (loading the ribbon thumbnails can take some time). 236 //TODO(kaznacheev) Show this message in the grid mode too.
215 this.metadataCache_.get(selectedUrl, Gallery.METADATA_TYPE, 237 this.showErrorBanner_('NO_IMAGES');
216 function(metadata) { 238 loadDone();
217 this.loadItem_(selectedUrl, metadata, 0, loadDone); 239 } else {
218 }.bind(this)); 240 // Ensure valid single selection.
241 // Note that the SlideMode object is not listening to selection change yet.
242 this.select(Math.max(0, this.getSelectedIndex()));
243 cr.dispatchSimpleEvent(this, 'namechange'); // Update name in the UI.
244 this.displayedIndex_ = this.getSelectedIndex();
245
246 var selectedItem = this.getSelectedItem();
247 var selectedUrl = selectedItem.getUrl();
248 // Show the selected item ASAP, then complete the initialization
249 // (loading the ribbon thumbnails can take some time).
250 this.metadataCache_.get(selectedUrl, Gallery.METADATA_TYPE,
251 function(metadata) {
252 this.loadItem_(selectedUrl, metadata, 0, loadDone);
253 }.bind(this));
254
255 }
219 }; 256 };
220 257
221 /** 258 /**
222 * Setup navigation controls. 259 * Leave the mode.
260 * @param {function} opt_callback Callback.
261 */
262 SlideMode.prototype.leave = function(opt_callback) {
263 if (this.prefetchTimer_) {
264 clearTimeout(this.prefetchTimer_);
265 this.prefetchTimer_ = null;
266 }
267
268 var commitDone = function() {
269 this.stopEditing_();
270 this.stopSlideshow_();
271 this.unloadImage_();
272 this.selectionModel_.removeEventListener(
273 'change', this.onSelectionBound_);
274 this.dataModel_.removeEventListener('splice', this.onSpliceBound_);
275 this.ribbon_.disable();
276 this.active_ = false;
277 if (opt_callback) opt_callback();
278 }.bind(this);
279
280 if (this.getItemCount_() == 0) {
281 this.showErrorBanner_(false);
282 commitDone();
283 } else {
284 this.commitItem_(commitDone);
285 }
286 };
287
288 /**
289 * @return {boolean} True if the mode has active tools (that should not fade).
290 */
291 SlideMode.prototype.hasActiveTool = function() {
292 return this.isEditing();
293 };
294
295 /**
296 * @return {number} Item count.
223 * @private 297 * @private
224 */ 298 */
225 SlideMode.prototype.setupNavigation_ = function() { 299 SlideMode.prototype.getItemCount_ = function() {
226 this.sequenceDirection_ = 0; 300 return this.dataModel_.length;
227 this.sequenceLength_ = 0; 301 };
228 302
229 ImageUtil.setAttribute(this.arrowLeft_, 'active', this.items_.length > 1); 303 /**
230 ImageUtil.setAttribute(this.arrowRight_, 'active', this.items_.length > 1); 304 * @param {number} index Index.
305 * @return {Gallery.Item} Item.
306 */
307 SlideMode.prototype.getItem = function(index) {
308 return this.dataModel_.item(index);
309 };
231 310
232 this.ribbon_ = new Ribbon( 311 /**
233 this.document_, this.metadataCache_, this.select.bind(this)); 312 * @return {Gallery.Item} Selected index.
234 this.ribbonSpacer_.appendChild(this.ribbon_); 313 */
235 this.ribbon_.update(this.items_, this.selectedIndex_); 314 SlideMode.prototype.getSelectedIndex = function() {
315 return this.selectionModel_.selectedIndexes.length ?
316 this.selectionModel_.selectedIndexes[0] :
317 -1;
236 }; 318 };
237 319
238 /** 320 /**
239 * @return {Gallery.Item} Selected item 321 * @return {Gallery.Item} Selected item
240 */ 322 */
241 SlideMode.prototype.getSelectedItem = function() { 323 SlideMode.prototype.getSelectedItem = function() {
242 return this.items_ && this.items_[this.selectedIndex_]; 324 return this.getItem(this.getSelectedIndex());
325 };
326
327 /**
328 * Selection change handler.
329 *
330 * Commits the current image and displays the newly selected image.
331 * @private
332 */
333 SlideMode.prototype.onSelection_ = function() {
334 if (this.selectionModel_.selectedIndexes.length == 0)
335 return; // Temporary empty selection.
336
337 if (this.getSelectedIndex() == this.displayedIndex_)
338 return; // Do not reselect.
339
340 this.commitItem_(this.loadSelectedItem_.bind(this));
243 }; 341 };
244 342
245 /** 343 /**
246 * Change the selection. 344 * Change the selection.
247 * 345 *
248 * Commits the current image and changes the selection.
249 *
250 * @param {number} index New selected index. 346 * @param {number} index New selected index.
251 * @param {number} opt_slideDir Slide animation direction (-1|0|1). 347 * @param {number} opt_slideHint Slide animation direction (-1|1).
252 * Derived from the new and the old selected indices if omitted.
253 * @param {function} opt_callback Callback.
254 */ 348 */
255 SlideMode.prototype.select = function(index, opt_slideDir, opt_callback) { 349 SlideMode.prototype.select = function(index, opt_slideHint) {
256 if (!this.items_) 350 this.slideHint_ = opt_slideHint;
257 return; // Not fully initialized, still loading the first image. 351 this.selectionModel_.unselectAll();
258 352 this.selectionModel_.setIndexSelected(index, true);
259 if (index == this.selectedIndex_)
260 return; // Do not reselect.
261
262 this.commitItem_(
263 this.doSelect_.bind(this, index, opt_slideDir, opt_callback));
264 }; 353 };
265 354
266 /** 355 /**
267 * Set the new selected index value. 356 * Load the selected item.
268 * @param {number} index New selected index. 357 *
269 * @private 358 * @private
270 */ 359 */
271 SlideMode.prototype.setSelectedIndex_ = function(index) { 360 SlideMode.prototype.loadSelectedItem_ = function() {
272 this.selectedIndex_ = index; 361 var slideHint = this.slideHint_;
273 cr.dispatchSimpleEvent(this, 'selection'); 362 this.slideHint_ = undefined;
274 363
275 // For once edited image, disallow the 'overwrite' setting change. 364 var index = this.getSelectedIndex();
276 ImageUtil.setAttribute(this.options_, 'saved', 365 if (index == this.displayedIndex_)
277 !this.getSelectedItem().isOriginal()); 366 return; // Do not reselect.
278 };
279 367
280 /** 368 var step = slideHint || (index - this.displayedIndex_);
281 * Perform the actual selection change.
282 *
283 * @param {number} index New selected index.
284 * @param {number} opt_slideDir Slide animation direction (-1|0|1).
285 * Derived from the new and the old selected indices if omitted.
286 * @param {function} opt_callback Callback.
287 * @private
288 */
289 SlideMode.prototype.doSelect_ = function(index, opt_slideDir, opt_callback) {
290 if (index == this.selectedIndex_)
291 return; // Do not reselect
292
293 var step = opt_slideDir != undefined ?
294 opt_slideDir :
295 (index - this.selectedIndex_);
296 369
297 if (Math.abs(step) != 1) { 370 if (Math.abs(step) != 1) {
298 // Long leap, the sequence is broken, we have no good prefetch candidate. 371 // Long leap, the sequence is broken, we have no good prefetch candidate.
299 this.sequenceDirection_ = 0; 372 this.sequenceDirection_ = 0;
300 this.sequenceLength_ = 0; 373 this.sequenceLength_ = 0;
301 } else if (this.sequenceDirection_ == step) { 374 } else if (this.sequenceDirection_ == step) {
302 // Keeping going in sequence. 375 // Keeping going in sequence.
303 this.sequenceLength_++; 376 this.sequenceLength_++;
304 } else { 377 } else {
305 // Reversed the direction. Reset the counter. 378 // Reversed the direction. Reset the counter.
306 this.sequenceDirection_ = step; 379 this.sequenceDirection_ = step;
307 this.sequenceLength_ = 1; 380 this.sequenceLength_ = 1;
308 } 381 }
309 382
310 if (this.sequenceLength_ <= 1) { 383 if (this.sequenceLength_ <= 1) {
311 // We have just broke the sequence. Touch the current image so that it stays 384 // We have just broke the sequence. Touch the current image so that it stays
312 // in the cache longer. 385 // in the cache longer.
313 this.editor_.prefetchImage(this.getSelectedItem().getUrl()); 386 this.imageView_.prefetch(this.imageView_.contentID_);
314 } 387 }
315 388
316 this.setSelectedIndex_(index); 389 this.displayedIndex_ = index;
317
318 if (this.ribbon_)
319 this.ribbon_.update(this.items_, this.selectedIndex_);
320 390
321 function shouldPrefetch(loadType, step, sequenceLength) { 391 function shouldPrefetch(loadType, step, sequenceLength) {
322 // Never prefetch when selecting out of sequence. 392 // Never prefetch when selecting out of sequence.
323 if (Math.abs(step) != 1) 393 if (Math.abs(step) != 1)
324 return false; 394 return false;
325 395
326 // Never prefetch after a video load (decoding the next image can freeze 396 // Never prefetch after a video load (decoding the next image can freeze
327 // the UI for a second or two). 397 // the UI for a second or two).
328 if (loadType == ImageView.LOAD_TYPE_VIDEO_FILE) 398 if (loadType == ImageView.LOAD_TYPE_VIDEO_FILE)
329 return false; 399 return false;
330 400
331 // Always prefetch if the previous load was from cache. 401 // Always prefetch if the previous load was from cache.
332 if (loadType == ImageView.LOAD_TYPE_CACHED_FULL) 402 if (loadType == ImageView.LOAD_TYPE_CACHED_FULL)
333 return true; 403 return true;
334 404
335 // Prefetch if we have been going in the same direction for long enough. 405 // Prefetch if we have been going in the same direction for long enough.
336 return sequenceLength >= 3; 406 return sequenceLength >= 3;
337 } 407 }
338 408
339 var selectedItem = this.getSelectedItem(); 409 var selectedItem = this.getSelectedItem();
340 var onMetadata = function(metadata) { 410 var onMetadata = function(metadata) {
341 if (selectedItem != this.getSelectedItem()) return; 411 if (selectedItem != this.getSelectedItem()) return;
342 this.loadItem_(selectedItem.getUrl(), metadata, step, 412 this.loadItem_(selectedItem.getUrl(), metadata, step,
343 function(loadType) { 413 function(loadType) {
344 if (selectedItem != this.getSelectedItem()) return; 414 if (selectedItem != this.getSelectedItem()) return;
345 if (shouldPrefetch(loadType, step, this.sequenceLength_)) { 415 if (shouldPrefetch(loadType, step, this.sequenceLength_)) {
346 this.requestPrefetch(step); 416 this.requestPrefetch(step);
347 } 417 }
348 if (opt_callback) opt_callback(); 418 if (this.isSlideshowOn_())
419 this.scheduleNextSlide_();
349 }.bind(this)); 420 }.bind(this));
350 }.bind(this); 421 }.bind(this);
351 this.metadataCache_.get( 422 this.metadataCache_.get(
352 selectedItem.getUrl(), Gallery.METADATA_TYPE, onMetadata); 423 selectedItem.getUrl(), Gallery.METADATA_TYPE, onMetadata);
353 }; 424 };
354 425
355 /** 426 /**
427 * Unload the current image.
428 * @private
429 */
430 SlideMode.prototype.unloadImage_ = function() {
431 this.imageView_.unload();
432 this.container_.removeAttribute('video');
433 };
434
435 /**
436 * Data model 'splice' event handler.
437 * @param {Event} event Event.
438 * @private
439 */
440 SlideMode.prototype.onSplice_ = function(event) {
441 ImageUtil.setAttribute(this.arrowLeft_, 'active', this.getItemCount_() > 1);
442 ImageUtil.setAttribute(this.arrowRight_, 'active', this.getItemCount_() > 1);
443
444 if (event.removed.length != 1)
445 return;
446
447 // Delay the selection to let the ribbon splice handler work first.
448 setTimeout(function() {
449 if (event.index < this.dataModel_.length) {
450 // There is the next item, select it.
451 // The next item is now at the same index as the removed one, so we need
452 // to correct displayIndex_.
dgozman 2012/08/27 15:07:43 I think, this is done for nice animation? Needs a
453 this.displayedIndex_ = event.index - 1;
454 this.select(event.index);
455 } else if (this.dataModel_.length) {
456 // Removed item is the rightmost, but there are more items.
457 this.select(event.index - 1); // Select the new last index.
458 } else {
459 // No items left. Unload the image and show the banner.
460 this.commitItem_(function() {
461 this.unloadImage_();
462 this.showErrorBanner_('NO_IMAGES');
463 }.bind(this));
464 }
465 }.bind(this), 0);
466 };
467
468 /**
356 * @param {number} direction -1 for left, 1 for right. 469 * @param {number} direction -1 for left, 1 for right.
357 * @return {number} Next index in the gived direction, with wrapping. 470 * @return {number} Next index in the gived direction, with wrapping.
358 * @private 471 * @private
359 */ 472 */
360 SlideMode.prototype.getNextSelectedIndex_ = function(direction) { 473 SlideMode.prototype.getNextSelectedIndex_ = function(direction) {
361 var index = this.selectedIndex_ + (direction > 0 ? 1 : -1); 474 var index = this.getSelectedIndex() + (direction > 0 ? 1 : -1);
362 if (index == -1) return this.items_.length - 1; 475 if (index == -1) return this.getItemCount_() - 1;
363 if (index == this.items_.length) return 0; 476 if (index == this.getItemCount_()) return 0;
364 return index; 477 return index;
365 }; 478 };
366 479
367 /** 480 /**
368 * Select the next item. 481 * Select the next item.
369 * @param {number} direction -1 for left, 1 for right. 482 * @param {number} direction -1 for left, 1 for right.
370 * @param {function} opt_callback Callback.
371 */ 483 */
372 SlideMode.prototype.selectNext = function(direction, opt_callback) { 484 SlideMode.prototype.selectNext = function(direction) {
373 this.select(this.getNextSelectedIndex_(direction), direction, opt_callback); 485 this.select(this.getNextSelectedIndex_(direction), direction);
374 }; 486 };
375 487
376 /** 488 /**
377 * Select the first item. 489 * Select the first item.
378 */ 490 */
379 SlideMode.prototype.selectFirst = function() { 491 SlideMode.prototype.selectFirst = function() {
380 this.select(0); 492 this.select(0);
381 }; 493 };
382 494
383 /** 495 /**
384 * Select the last item. 496 * Select the last item.
385 */ 497 */
386 SlideMode.prototype.selectLast = function() { 498 SlideMode.prototype.selectLast = function() {
387 this.select(this.items_.length - 1); 499 this.select(this.getItemCount_() - 1);
388 }; 500 };
389 501
390 // Loading/unloading 502 // Loading/unloading
391 503
392 /** 504 /**
393 * Load and display an item. 505 * Load and display an item.
394 * 506 *
395 * @param {string} url Item url. 507 * @param {string} url Item url.
396 * @param {Object} metadata Item metadata. 508 * @param {Object} metadata Item metadata.
397 * @param {number} slide Slide animation direction (-1|0|1). 509 * @param {number} slide Slide animation direction (-1|0|1).
(...skipping 10 matching lines...) Expand all
408 ImageUtil.setAttribute(this.container_, 'video', video); 520 ImageUtil.setAttribute(this.container_, 'video', video);
409 521
410 this.showSpinner_(false); 522 this.showSpinner_(false);
411 if (loadType == ImageView.LOAD_TYPE_ERROR) { 523 if (loadType == ImageView.LOAD_TYPE_ERROR) {
412 this.showErrorBanner_(video ? 'VIDEO_ERROR' : 'IMAGE_ERROR'); 524 this.showErrorBanner_(video ? 'VIDEO_ERROR' : 'IMAGE_ERROR');
413 } else if (loadType == ImageView.LOAD_TYPE_OFFLINE) { 525 } else if (loadType == ImageView.LOAD_TYPE_OFFLINE) {
414 this.showErrorBanner_(video ? 'VIDEO_OFFLINE' : 'IMAGE_OFFLINE'); 526 this.showErrorBanner_(video ? 'VIDEO_OFFLINE' : 'IMAGE_OFFLINE');
415 } 527 }
416 528
417 if (video) { 529 if (video) {
418 if (this.isEditing()) { 530 // The editor toolbar does not make sense for video, hide it.
419 // The editor toolbar does not make sense for video, hide it. 531 this.stopEditing_();
420 this.onEdit_();
421 }
422 this.mediaControls_.attachMedia(this.imageView_.getVideo()); 532 this.mediaControls_.attachMedia(this.imageView_.getVideo());
423 //TODO(kaznacheev): Add metrics for video playback. 533 //TODO(kaznacheev): Add metrics for video playback.
424 } else { 534 } else {
425 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('View')); 535 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('View'));
426 536
427 function toMillions(number) { return Math.round(number / (1000 * 1000)) } 537 function toMillions(number) { return Math.round(number / (1000 * 1000)) }
428 538
429 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MB'), 539 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MB'),
430 toMillions(metadata.filesystem.size)); 540 toMillions(metadata.filesystem.size));
431 541
432 var canvas = this.imageView_.getCanvas(); 542 var canvas = this.imageView_.getCanvas();
433 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MPix'), 543 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MPix'),
434 toMillions(canvas.width * canvas.height)); 544 toMillions(canvas.width * canvas.height));
435 545
436 var extIndex = url.lastIndexOf('.'); 546 var extIndex = url.lastIndexOf('.');
437 var ext = extIndex < 0 ? '' : url.substr(extIndex + 1).toLowerCase(); 547 var ext = extIndex < 0 ? '' : url.substr(extIndex + 1).toLowerCase();
438 if (ext == 'jpeg') ext = 'jpg'; 548 if (ext == 'jpeg') ext = 'jpg';
439 ImageUtil.metrics.recordEnum( 549 ImageUtil.metrics.recordEnum(
440 ImageUtil.getMetricName('FileType'), ext, ImageUtil.FILE_TYPES); 550 ImageUtil.getMetricName('FileType'), ext, ImageUtil.FILE_TYPES);
441 } 551 }
442 552
553 // For once edited image, disallow the 'overwrite' setting change.
554 ImageUtil.setAttribute(this.options_, 'saved',
555 !this.getSelectedItem().isOriginal());
556
557 var times = SlideMode.OVERWRITE_BUBBLE_KEY in localStorage ?
558 parseInt(localStorage[SlideMode.OVERWRITE_BUBBLE_KEY], 10) : 0;
559 if (times < SlideMode.OVERWRITE_BUBBLE_MAX_TIMES) {
560 this.bubble_.hidden = false;
561 if (this.isEditing()) {
562 localStorage[SlideMode.OVERWRITE_BUBBLE_KEY] = times + 1;
563 }
564 }
565
443 callback(loadType); 566 callback(loadType);
444 }.bind(this); 567 }.bind(this);
445 568
446 this.editor_.openSession( 569 this.editor_.openSession(
447 url, metadata, slide, this.saveCurrentImage_.bind(this), loadDone); 570 url, metadata, slide, this.saveCurrentImage_.bind(this), loadDone);
448 }; 571 };
449 572
450 /** 573 /**
451 * Commit changes to the current item and reset all messages/indicators. 574 * Commit changes to the current item and reset all messages/indicators.
452 * 575 *
(...skipping 10 matching lines...) Expand all
463 } 586 }
464 this.editor_.closeSession(callback); 587 this.editor_.closeSession(callback);
465 }; 588 };
466 589
467 /** 590 /**
468 * Request a prefetch for the next image. 591 * Request a prefetch for the next image.
469 * 592 *
470 * @param {number} direction -1 or 1. 593 * @param {number} direction -1 or 1.
471 */ 594 */
472 SlideMode.prototype.requestPrefetch = function(direction) { 595 SlideMode.prototype.requestPrefetch = function(direction) {
473 if (this.items_.length <= 1) return; 596 if (this.getItemCount_() <= 1) return;
474 597
475 var index = this.getNextSelectedIndex_(direction); 598 var index = this.getNextSelectedIndex_(direction);
476 var nextItemUrl = this.items_[index].getUrl(); 599 var nextItemUrl = this.getItem(index).getUrl();
477 600
478 var selectedItem = this.getSelectedItem(); 601 var selectedItem = this.getSelectedItem();
479 this.metadataCache_.get(nextItemUrl, Gallery.METADATA_TYPE, 602 this.metadataCache_.get(nextItemUrl, Gallery.METADATA_TYPE,
480 function(metadata) { 603 function(metadata) {
481 if (selectedItem != this.getSelectedItem()) return; 604 if (selectedItem != this.getSelectedItem()) return;
482 this.editor_.prefetchImage(nextItemUrl); 605 this.editor_.prefetchImage(nextItemUrl);
483 }.bind(this)); 606 }.bind(this));
484 }; 607 };
485 608
486 // Event handlers. 609 // Event handlers.
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
534 return true; 657 return true;
535 658
536 switch (util.getKeyModifiers(event) + event.keyIdentifier) { 659 switch (util.getKeyModifiers(event) + event.keyIdentifier) {
537 case 'U+0020': // Space toggles the video playback. 660 case 'U+0020': // Space toggles the video playback.
538 if (this.isShowingVideo_()) { 661 if (this.isShowingVideo_()) {
539 this.mediaControls_.togglePlayStateWithFeedback(); 662 this.mediaControls_.togglePlayStateWithFeedback();
540 } 663 }
541 break; 664 break;
542 665
543 case 'U+0045': // 'e' toggles the editor 666 case 'U+0045': // 'e' toggles the editor
544 this.onEdit_(); 667 this.toggleEditor_();
545 break; 668 break;
546 669
547 case 'U+001B': // Escape 670 case 'U+001B': // Escape
548 if (!this.isEditing()) 671 if (!this.isEditing())
549 return false; // Not handled. 672 return false; // Not handled.
550 this.onEdit_(); 673 this.toggleEditor_();
551 break; 674 break;
552 675
553 case 'Ctrl-U+00DD': // Ctrl+] (cryptic on purpose). 676 case 'Ctrl-U+00DD': // Ctrl+]. TODO(kaznacheev): Find a non-cryptic key.
554 this.toggleSlideshow_(); 677 this.toggleSlideshow_(SlideMode.SLIDESHOW_INTERVAL_FIRST);
555 break; 678 break;
556 679
557 case 'Home': 680 case 'Home':
558 this.selectFirst(); 681 this.selectFirst();
559 break; 682 break;
560 case 'End': 683 case 'End':
561 this.selectLast(); 684 this.selectLast();
562 break; 685 break;
563 case 'Left': 686 case 'Left':
564 this.selectNext(-1); 687 this.selectNext(-1);
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
607 this.shouldOverwriteOriginal_(), 730 this.shouldOverwriteOriginal_(),
608 canvas, 731 canvas,
609 metadataEncoder, 732 metadataEncoder,
610 function(success) { 733 function(success) {
611 // TODO(kaznacheev): Implement write error handling. 734 // TODO(kaznacheev): Implement write error handling.
612 // Until then pretend that the save succeeded. 735 // Until then pretend that the save succeeded.
613 this.showSpinner_(false); 736 this.showSpinner_(false);
614 this.flashSavedLabel_(); 737 this.flashSavedLabel_();
615 var newUrl = item.getUrl(); 738 var newUrl = item.getUrl();
616 this.updateSelectedUrl_(oldUrl, newUrl); 739 this.updateSelectedUrl_(oldUrl, newUrl);
617 this.ribbon_.updateThumbnail( 740 this.ribbon_.updateThumbnail(newUrl, this.selectedImageMetadata_);
618 this.selectedIndex_, newUrl, this.selectedImageMetadata_); 741 cr.dispatchSimpleEvent(this, 'content');
742
743 if (this.imageView_.getContentRevision() == 1) { // First edit.
744 // Lock the 'Overwrite original' checkbox for this item.
745 ImageUtil.setAttribute(this.options_, 'saved', true);
746 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('Edit'));
747 }
748
749 if (oldUrl != newUrl) {
750 this.displayedIndex_++;
751 // This splice call will change the selection change event. SlideMode
752 // will ignore it as the selected item is the same.
753 // The ribbon will redraw while being obscured by the Editor toolbar,
754 // so there is no need for nice animation here.
755 this.dataModel_.splice(
756 this.getSelectedIndex(), 0, new Gallery.Item(oldUrl));
757 }
619 callback(); 758 callback();
620 }.bind(this)); 759 }.bind(this));
621 }; 760 };
622 761
623 /** 762 /**
624 * Update caches when the selected item url has changed. 763 * Update caches when the selected item url has changed.
625 * 764 *
626 * @param {string} oldUrl Old url. 765 * @param {string} oldUrl Old url.
627 * @param {string} newUrl New url. 766 * @param {string} newUrl New url.
628 * @private 767 * @private
629 */ 768 */
630 SlideMode.prototype.updateSelectedUrl_ = function(oldUrl, newUrl) { 769 SlideMode.prototype.updateSelectedUrl_ = function(oldUrl, newUrl) {
631 this.metadataCache_.clear(oldUrl, Gallery.METADATA_TYPE); 770 this.metadataCache_.clear(oldUrl, Gallery.METADATA_TYPE);
632 771
633 if (oldUrl == newUrl) 772 if (oldUrl == newUrl)
634 return; 773 return;
635 774
636 this.imageView_.changeUrl(newUrl); 775 this.imageView_.changeUrl(newUrl);
637 this.ribbon_.remapCache(oldUrl, newUrl); 776 this.ribbon_.remapCache(oldUrl, newUrl);
638
639 // Let the gallery know that the selected item url has changed.
640 cr.dispatchSimpleEvent(this, 'selection');
641 }; 777 };
642 778
643 /** 779 /**
644 * Flash 'Saved' label briefly to indicate that the image has been saved. 780 * Flash 'Saved' label briefly to indicate that the image has been saved.
645 * @private 781 * @private
646 */ 782 */
647 SlideMode.prototype.flashSavedLabel_ = function() { 783 SlideMode.prototype.flashSavedLabel_ = function() {
648 var setLabelHighlighted = 784 var setLabelHighlighted =
649 ImageUtil.setAttribute.bind(null, this.savedLabel_, 'highlighted'); 785 ImageUtil.setAttribute.bind(null, this.savedLabel_, 'highlighted');
650 setTimeout(setLabelHighlighted.bind(null, true), 0); 786 setTimeout(setLabelHighlighted.bind(null, true), 0);
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
691 /** 827 /**
692 * Overwrite info bubble close handler. 828 * Overwrite info bubble close handler.
693 * @private 829 * @private
694 */ 830 */
695 SlideMode.prototype.onCloseBubble_ = function() { 831 SlideMode.prototype.onCloseBubble_ = function() {
696 this.bubble_.hidden = true; 832 this.bubble_.hidden = true;
697 localStorage[SlideMode.OVERWRITE_BUBBLE_KEY] = 833 localStorage[SlideMode.OVERWRITE_BUBBLE_KEY] =
698 SlideMode.OVERWRITE_BUBBLE_MAX_TIMES; 834 SlideMode.OVERWRITE_BUBBLE_MAX_TIMES;
699 }; 835 };
700 836
837 // Slideshow
838
701 /** 839 /**
702 * Callback called when the image is edited. 840 * Slideshow interval in ms.
841 */
842 SlideMode.SLIDESHOW_INTERVAL = 5000;
843
844 /**
845 * First slideshow interval in ms. It should be shorter so that the user
846 * is not guessing whether the button worked.
847 */
848 SlideMode.SLIDESHOW_INTERVAL_FIRST = 1000;
849
850 /**
851 * @return {boolean} True if the slideshow is on.
703 * @private 852 * @private
704 */ 853 */
705 SlideMode.prototype.onImageContentChanged_ = function() { 854 SlideMode.prototype.isSlideshowOn_ = function() {
706 var revision = this.imageView_.getContentRevision(); 855 return this.container_.hasAttribute('slideshow');
707 if (revision == 0) { 856 };
708 // Just loaded. 857
709 var times = SlideMode.OVERWRITE_BUBBLE_KEY in localStorage ? 858 /**
710 parseInt(localStorage[SlideMode.OVERWRITE_BUBBLE_KEY], 10) : 0; 859 * Stop the slideshow if it is on.
711 if (times < SlideMode.OVERWRITE_BUBBLE_MAX_TIMES) { 860 * @private
712 this.bubble_.hidden = false; 861 */
713 if (this.isEditing()) { 862 SlideMode.prototype.stopSlideshow_ = function() {
714 localStorage[SlideMode.OVERWRITE_BUBBLE_KEY] = times + 1; 863 if (this.isSlideshowOn_())
715 } 864 this.toggleSlideshow_();
716 } 865 };
866
867 /**
868 * Start/stop the slideshow.
869 *
870 * @param {number} opt_interval First interval in ms.
871 * @param {Event} opt_event Event.
872 * @private
873 */
874
875 SlideMode.prototype.toggleSlideshow_ = function(opt_interval, opt_event) {
876 if (opt_event) // Caused by user action, notify the Gallery.
877 cr.dispatchSimpleEvent(this, 'useraction');
878
879 if (!this.active_) {
880 // Enter the slide mode. Show the first image for the full interval.
881 this.enter(this.toggleSlideshow_.bind(this, SlideMode.SLIDESHOW_INTERVAL));
882 return;
717 } 883 }
718 884
719 if (revision == 1) { 885 this.stopEditing_();
720 // First edit. 886 ImageUtil.setAttribute(this.container_, 'slideshow', !this.isSlideshowOn_());
721 ImageUtil.setAttribute(this.options_, 'saved', true); 887 ImageUtil.setAttribute(
722 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('Edit')); 888 this.slideShowButton_, 'pressed', this.isSlideshowOn_());
889
890 if (this.isSlideshowOn_()) {
891 this.scheduleNextSlide_(opt_interval);
892 } else {
893 if (this.slideShowTimeout_) {
894 clearInterval(this.slideShowTimeout_);
895 this.slideShowTimeout_ = null;
896 }
723 } 897 }
724 }; 898 };
725 899
726 // Misc
727
728 /** 900 /**
729 * Start/stop the slide show. 901 * @param {number} opt_interval Slideshow interval in ms.
730 * @private 902 * @private
731 */ 903 */
732 SlideMode.prototype.toggleSlideshow_ = function() { 904 SlideMode.prototype.scheduleNextSlide_ = function(opt_interval) {
733 if (this.slideShowTimeout_) { 905 if (this.slideShowTimeout_)
734 clearInterval(this.slideShowTimeout_); 906 clearTimeout(this.slideShowTimeout_);
735 this.slideShowTimeout_ = null; 907
736 } else { 908 this.slideShowTimeout_ = setTimeout(function() {
737 var self = this; 909 this.slideShowTimeout_ = null;
738 function nextSlide() { 910 this.selectNext(1);
739 self.selectNext(1, 911 }.bind(this),
740 function() { self.slideShowTimeout_ = setTimeout(nextSlide, 5000) }); 912 opt_interval || SlideMode.SLIDESHOW_INTERVAL);
741 }
742 nextSlide();
743 }
744 }; 913 };
745 914
746 /** 915 /**
747 * @return {boolean} True if the editor is active. 916 * @return {boolean} True if the editor is active.
748 */ 917 */
749 SlideMode.prototype.isEditing = function() { 918 SlideMode.prototype.isEditing = function() {
750 return this.container_.hasAttribute('editing'); 919 return this.container_.hasAttribute('editing');
751 }; 920 };
752 921
753 /** 922 /**
923 * Stop editing.
924 * @private
925 */
926 SlideMode.prototype.stopEditing_ = function() {
927 if (this.isEditing())
928 this.toggleEditor_();
929 };
930
931 /**
754 * Activate/deactivate editor. 932 * Activate/deactivate editor.
933 * @param {Event} opt_event Event.
755 * @private 934 * @private
756 */ 935 */
757 SlideMode.prototype.onEdit_ = function() { 936 SlideMode.prototype.toggleEditor_ = function(opt_event) {
937 if (opt_event) // Caused by user action, notify the Gallery.
938 cr.dispatchSimpleEvent(this, 'useraction');
939
940 if (!this.active_) {
941 this.enter(this.toggleEditor_.bind(this));
942 return;
943 }
944
945 this.stopSlideshow_();
758 if (!this.isEditing() && this.isShowingVideo_()) 946 if (!this.isEditing() && this.isShowingVideo_())
759 return; // No editing for videos. 947 return; // No editing for videos.
760 948
761 ImageUtil.setAttribute(this.container_, 'editing', !this.isEditing()); 949 ImageUtil.setAttribute(this.container_, 'editing', !this.isEditing());
762 950
763 if (this.isEditing()) { // isEditing_ has just been flipped to a new value. 951 if (this.isEditing()) { // isEditing_ has just been flipped to a new value.
764 if (this.context_.readonlyDirName) { 952 if (this.context_.readonlyDirName) {
765 this.editor_.getPrompt().showAt( 953 this.editor_.getPrompt().showAt(
766 'top', 'readonly_warning', 0, this.context_.readonlyDirName); 954 'top', 'readonly_warning', 0, this.context_.readonlyDirName);
767 } 955 }
768 } else { 956 } else {
769 this.editor_.getPrompt().hide(); 957 this.editor_.getPrompt().hide();
770 } 958 }
771 959
772 ImageUtil.setAttribute(this.editButton_, 'pressed', this.isEditing()); 960 ImageUtil.setAttribute(this.editButton_, 'pressed', this.isEditing());
773
774 cr.dispatchSimpleEvent(this, 'edit');
775 }; 961 };
776 962
777 /** 963 /**
778 * Display the error banner. 964 * Display the error banner.
779 * @param {string} message Message. 965 * @param {string} message Message.
780 * @private 966 * @private
781 */ 967 */
782 SlideMode.prototype.showErrorBanner_ = function(message) { 968 SlideMode.prototype.showErrorBanner_ = function(message) {
783 if (message) { 969 if (message) {
784 this.errorBanner_.textContent = this.displayStringFunction_(message); 970 this.errorBanner_.textContent = this.displayStringFunction_(message);
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
862 done = true; 1048 done = true;
863 } 1049 }
864 }.bind(this); 1050 }.bind(this);
865 }; 1051 };
866 1052
867 /** 1053 /**
868 * If the user touched the image and moved the finger more than SWIPE_THRESHOLD 1054 * If the user touched the image and moved the finger more than SWIPE_THRESHOLD
869 * horizontally it's considered as a swipe gesture (change the current image). 1055 * horizontally it's considered as a swipe gesture (change the current image).
870 */ 1056 */
871 SwipeOverlay.SWIPE_THRESHOLD = 100; 1057 SwipeOverlay.SWIPE_THRESHOLD = 100;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698