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

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

Issue 10834354: Refactor the Photo Editor to enable new feature work (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase Created 8 years, 4 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
(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 /**
6 * Slide mode displays a single image and has a set of controls to navigate
7 * between the images and to edit an image.
8 *
9 * @param {Element} container Container element.
10 * @param {Element} toolbar Toolbar element.
11 * @param {ImageEditor.Prompt} prompt Prompt.
12 * @param {Object} context Context.
13 * @param {function(string):string} displayStringFunction String formatting
14 * function.
15 * @constructor
16 */
17 function SlideMode(container, toolbar, prompt, context, displayStringFunction) {
18 this.container_ = container;
19 this.toolbar_ = toolbar;
20 this.document_ = container.ownerDocument;
21 this.prompt_ = prompt;
22 this.context_ = context;
23 this.metadataCache_ = context.metadataCache;
24 this.displayStringFunction_ = displayStringFunction;
25
26 this.initListeners_();
27 this.initDom_();
28 }
29
30 /**
31 * SlideMode extends cr.EventTarget.
32 */
33 SlideMode.prototype.__proto__ = cr.EventTarget.prototype;
34
35 /**
36 * List of available editor modes.
37 * @type {Array.<ImageEditor.Mode>}
38 */
39 SlideMode.editorModes = [
40 new ImageEditor.Mode.InstantAutofix(),
41 new ImageEditor.Mode.Crop(),
42 new ImageEditor.Mode.Exposure(),
43 new ImageEditor.Mode.OneClick('rotate_left', new Command.Rotate(-1)),
44 new ImageEditor.Mode.OneClick('rotate_right', new Command.Rotate(1))
45 ];
46
47 /**
48 * Initialize the listeners.
49 * @private
50 */
51 SlideMode.prototype.initListeners_ = function() {
52 var win = this.document_.defaultView;
53
54 this.onBeforeUnloadBound_ = this.onBeforeUnload_.bind(this);
55 win.top.addEventListener('beforeunload', this.onBeforeUnloadBound_);
56
57 win.addEventListener('unload', this.onUnload_.bind(this));
58
59 // We need to listen to the top window 'unload' and 'beforeunload' because
60 // the Gallery iframe does not get notified if the tab is closed.
61 this.onTopUnloadBound_ = this.onTopUnload_.bind(this);
62 win.top.addEventListener('unload', this.onTopUnloadBound_);
63
64 win.addEventListener('resize', this.onResize_.bind(this), false);
65 };
66
67 /**
68 * Initialize the UI.
69 * @private
70 */
71 SlideMode.prototype.initDom_ = function() {
72 // Container for displayed image or video.
73 this.imageContainer_ = util.createChild(
74 this.document_.querySelector('.content'), 'image-container');
75 this.imageContainer_.addEventListener('click', this.onClick_.bind(this));
76
77 // Overwrite options and info bubble.
78 this.options_ = util.createChild(
79 this.toolbar_.querySelector('.filename-spacer'), 'options');
80
81 this.savedLabel_ = util.createChild(this.options_, 'saved');
82 this.savedLabel_.textContent = this.displayStringFunction_('saved');
83
84 var overwriteOriginalBox =
85 util.createChild(this.options_, 'overwrite-original');
86
87 this.overwriteOriginal_ = util.createChild(
88 overwriteOriginalBox, 'common white', 'input');
89 this.overwriteOriginal_.type = 'checkbox';
90 this.overwriteOriginal_.id = 'overwrite-checkbox';
91 this.overwriteOriginal_.checked = this.shouldOverwriteOriginal_();
92 this.overwriteOriginal_.addEventListener('click',
93 this.onOverwriteOriginalClick_.bind(this));
94
95 var overwriteLabel = util.createChild(overwriteOriginalBox, '', 'label');
96 overwriteLabel.textContent =
97 this.displayStringFunction_('overwrite_original');
98 overwriteLabel.setAttribute('for', 'overwrite-checkbox');
99
100 this.bubble_ = util.createChild(this.toolbar_, 'bubble');
101 this.bubble_.hidden = true;
102
103 var bubbleContent = util.createChild(this.bubble_);
104 bubbleContent.innerHTML = this.displayStringFunction_('overwrite_bubble');
105
106 util.createChild(this.bubble_, 'pointer bottom', 'span');
107
108 var bubbleClose = util.createChild(this.bubble_, 'close-x');
109 bubbleClose.addEventListener('click', this.onCloseBubble_.bind(this));
110
111 // Video player controls.
112 this.mediaSpacer_ =
113 util.createChild(this.container_, 'video-controls-spacer');
114 this.mediaToolbar_ = util.createChild(this.mediaSpacer_, 'tool');
115 this.mediaControls_ = new VideoControls(
116 this.mediaToolbar_,
117 this.showErrorBanner_.bind(this, 'VIDEO_ERROR'),
118 Gallery.toggleFullscreen,
119 this.container_);
120
121 // Ribbon and related controls.
122 this.arrowBox_ = util.createChild(this.container_, 'arrow-box');
123
124 this.arrowLeft_ =
125 util.createChild(this.arrowBox_, 'arrow left tool dimmable');
126 this.arrowLeft_.addEventListener('click',
127 this.selectNext.bind(this, -1, null));
128 util.createChild(this.arrowLeft_);
129
130 util.createChild(this.arrowBox_, 'arrow-spacer');
131
132 this.arrowRight_ =
133 util.createChild(this.arrowBox_, 'arrow right tool dimmable');
134 this.arrowRight_.addEventListener('click',
135 this.selectNext.bind(this, 1, null));
136 util.createChild(this.arrowRight_);
137
138 this.ribbonSpacer_ = util.createChild(this.toolbar_, 'ribbon-spacer');
139
140 // Error indicator.
141 var errorWrapper = util.createChild(this.container_, 'prompt-wrapper');
142 errorWrapper.setAttribute('pos', 'center');
143
144 this.errorBanner_ = util.createChild(errorWrapper, 'error-banner');
145
146 util.createChild(this.container_, 'spinner');
147
148
149 this.editButton_ = util.createChild(this.toolbar_, 'button edit');
150 this.editButton_.textContent = this.displayStringFunction_('edit');
151 this.editButton_.addEventListener('click', this.onEdit_.bind(this));
152
153 // Editor toolbar.
154
155 this.editBar_ = util.createChild(this.toolbar_, 'edit-bar');
156 this.editBarMain_ = util.createChild(this.editBar_, 'edit-main');
157
158 this.editBarMode_ = util.createChild(this.container_, 'edit-modal');
159 this.editBarModeWrapper_ = util.createChild(
160 this.editBarMode_, 'edit-modal-wrapper');
161 this.editBarModeWrapper_.hidden = true;
162
163 // Objects supporting image display and editing.
164 this.viewport_ = new Viewport();
165
166 this.imageView_ = new ImageView(
167 this.imageContainer_,
168 this.viewport_,
169 this.metadataCache_);
170
171 this.imageView_.addContentCallback(this.onImageContentChanged_.bind(this));
172
173 this.editor_ = new ImageEditor(
174 this.viewport_,
175 this.imageView_,
176 this.prompt_,
177 {
178 root: this.container_,
179 image: this.imageContainer_,
180 toolbar: this.editBarMain_,
181 mode: this.editBarModeWrapper_
182 },
183 SlideMode.editorModes,
184 this.displayStringFunction_);
185
186 this.editor_.getBuffer().addOverlay(
187 new SwipeOverlay(this.selectNext.bind(this)));
188 };
189
190 /**
191 * Load items, display the selected item.
192 *
193 * @param {Array.<Gallery.Item>} items Array of items.
194 * @param {number} selectedIndex Selected index.
195 * @param {function} callback Callback.
196 */
197 SlideMode.prototype.load = function(items, selectedIndex, callback) {
198 var selectedItem = items[selectedIndex];
199 if (!selectedItem) {
200 this.showErrorBanner_('IMAGE_ERROR');
201 return;
202 }
203
204 var loadDone = function() {
205 this.items_ = items;
206 this.setSelectedIndex_(selectedIndex);
207 this.setupNavigation_();
208 setTimeout(this.requestPrefetch.bind(this, 1 /* Next item */), 1000);
209 callback();
210 }.bind(this);
211
212 var selectedUrl = selectedItem.getUrl();
213 // Show the selected item ASAP, then complete the initialization
214 // (loading the ribbon thumbnails can take some time).
215 this.metadataCache_.get(selectedUrl, Gallery.METADATA_TYPE,
216 function(metadata) {
217 this.loadItem_(selectedUrl, metadata, 0, loadDone);
218 }.bind(this));
219 };
220
221 /**
222 * Setup navigation controls.
223 * @private
224 */
225 SlideMode.prototype.setupNavigation_ = function() {
226 this.sequenceDirection_ = 0;
227 this.sequenceLength_ = 0;
228
229 ImageUtil.setAttribute(this.arrowLeft_, 'active', this.items_.length > 1);
230 ImageUtil.setAttribute(this.arrowRight_, 'active', this.items_.length > 1);
231
232 this.ribbon_ = new Ribbon(
233 this.document_, this.metadataCache_, this.select.bind(this));
234 this.ribbonSpacer_.appendChild(this.ribbon_);
235 this.ribbon_.update(this.items_, this.selectedIndex_);
236 };
237
238 /**
239 * @return {Gallery.Item} Selected item
240 */
241 SlideMode.prototype.getSelectedItem = function() {
242 return this.items_ && this.items_[this.selectedIndex_];
243 };
244
245 /**
246 * Change the selection.
247 *
248 * Commits the current image and changes the selection.
249 *
250 * @param {number} index New selected index.
251 * @param {number} opt_slideDir Slide animation direction (-1|0|1).
252 * Derived from the new and the old selected indices if omitted.
253 * @param {function} opt_callback Callback.
254 */
255 SlideMode.prototype.select = function(index, opt_slideDir, opt_callback) {
256 if (!this.items_)
257 return; // Not fully initialized, still loading the first image.
258
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 };
265
266 /**
267 * Set the new selected index value.
268 * @param {number} index New selected index.
269 * @private
270 */
271 SlideMode.prototype.setSelectedIndex_ = function(index) {
272 this.selectedIndex_ = index;
273 cr.dispatchSimpleEvent(this, 'selection');
274
275 // For once edited image, disallow the 'overwrite' setting change.
276 ImageUtil.setAttribute(this.options_, 'saved',
277 !this.getSelectedItem().isOriginal());
278 };
279
280 /**
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
297 if (Math.abs(step) != 1) {
298 // Long leap, the sequence is broken, we have no good prefetch candidate.
299 this.sequenceDirection_ = 0;
300 this.sequenceLength_ = 0;
301 } else if (this.sequenceDirection_ == step) {
302 // Keeping going in sequence.
303 this.sequenceLength_++;
304 } else {
305 // Reversed the direction. Reset the counter.
306 this.sequenceDirection_ = step;
307 this.sequenceLength_ = 1;
308 }
309
310 if (this.sequenceLength_ <= 1) {
311 // We have just broke the sequence. Touch the current image so that it stays
312 // in the cache longer.
313 this.editor_.prefetchImage(this.getSelectedItem().getUrl());
314 }
315
316 this.setSelectedIndex_(index);
317
318 if (this.ribbon_)
319 this.ribbon_.update(this.items_, this.selectedIndex_);
320
321 function shouldPrefetch(loadType, step, sequenceLength) {
322 // Never prefetch when selecting out of sequence.
323 if (Math.abs(step) != 1)
324 return false;
325
326 // Never prefetch after a video load (decoding the next image can freeze
327 // the UI for a second or two).
328 if (loadType == ImageView.LOAD_TYPE_VIDEO_FILE)
329 return false;
330
331 // Always prefetch if the previous load was from cache.
332 if (loadType == ImageView.LOAD_TYPE_CACHED_FULL)
333 return true;
334
335 // Prefetch if we have been going in the same direction for long enough.
336 return sequenceLength >= 3;
337 }
338
339 var selectedItem = this.getSelectedItem();
340 var onMetadata = function(metadata) {
341 if (selectedItem != this.getSelectedItem()) return;
342 this.loadItem_(selectedItem.getUrl(), metadata, step,
343 function(loadType) {
344 if (selectedItem != this.getSelectedItem()) return;
345 if (shouldPrefetch(loadType, step, this.sequenceLength_)) {
346 this.requestPrefetch(step);
347 }
348 if (opt_callback) opt_callback();
349 }.bind(this));
350 }.bind(this);
351 this.metadataCache_.get(
352 selectedItem.getUrl(), Gallery.METADATA_TYPE, onMetadata);
353 };
354
355 /**
356 * @param {number} direction -1 for left, 1 for right.
357 * @return {number} Next index in the gived direction, with wrapping.
358 * @private
359 */
360 SlideMode.prototype.getNextSelectedIndex_ = function(direction) {
361 var index = this.selectedIndex_ + (direction > 0 ? 1 : -1);
362 if (index == -1) return this.items_.length - 1;
363 if (index == this.items_.length) return 0;
364 return index;
365 };
366
367 /**
368 * Select the next item.
369 * @param {number} direction -1 for left, 1 for right.
370 * @param {function} opt_callback Callback.
371 */
372 SlideMode.prototype.selectNext = function(direction, opt_callback) {
373 this.select(this.getNextSelectedIndex_(direction), direction, opt_callback);
374 };
375
376 /**
377 * Select the first item.
378 */
379 SlideMode.prototype.selectFirst = function() {
380 this.select(0);
381 };
382
383 /**
384 * Select the last item.
385 */
386 SlideMode.prototype.selectLast = function() {
387 this.select(this.items_.length - 1);
388 };
389
390 // Loading/unloading
391
392 /**
393 * Load and display an item.
394 *
395 * @param {string} url Item url.
396 * @param {Object} metadata Item metadata.
397 * @param {number} slide Slide animation direction (-1|0|1).
398 * @param {function} callback Callback.
399 * @private
400 */
401 SlideMode.prototype.loadItem_ = function(url, metadata, slide, callback) {
402 this.selectedImageMetadata_ = ImageUtil.deepCopy(metadata);
403
404 this.showSpinner_(true);
405
406 var loadDone = function(loadType) {
407 var video = this.isShowingVideo_();
408 ImageUtil.setAttribute(this.container_, 'video', video);
409
410 this.showSpinner_(false);
411 if (loadType == ImageView.LOAD_TYPE_ERROR) {
412 this.showErrorBanner_(video ? 'VIDEO_ERROR' : 'IMAGE_ERROR');
413 } else if (loadType == ImageView.LOAD_TYPE_OFFLINE) {
414 this.showErrorBanner_(video ? 'VIDEO_OFFLINE' : 'IMAGE_OFFLINE');
415 }
416
417 if (video) {
418 if (this.isEditing()) {
419 // The editor toolbar does not make sense for video, hide it.
420 this.onEdit_();
421 }
422 this.mediaControls_.attachMedia(this.imageView_.getVideo());
423 //TODO(kaznacheev): Add metrics for video playback.
424 } else {
425 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('View'));
426
427 function toMillions(number) { return Math.round(number / (1000 * 1000)) }
428
429 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MB'),
430 toMillions(metadata.filesystem.size));
431
432 var canvas = this.imageView_.getCanvas();
433 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MPix'),
434 toMillions(canvas.width * canvas.height));
435
436 var extIndex = url.lastIndexOf('.');
437 var ext = extIndex < 0 ? '' : url.substr(extIndex + 1).toLowerCase();
438 if (ext == 'jpeg') ext = 'jpg';
439 ImageUtil.metrics.recordEnum(
440 ImageUtil.getMetricName('FileType'), ext, ImageUtil.FILE_TYPES);
441 }
442
443 callback(loadType);
444 }.bind(this);
445
446 this.editor_.openSession(
447 url, metadata, slide, this.saveCurrentImage_.bind(this), loadDone);
448 };
449
450 /**
451 * Commit changes to the current item and reset all messages/indicators.
452 *
453 * @param {function} callback Callback.
454 * @private
455 */
456 SlideMode.prototype.commitItem_ = function(callback) {
457 this.showSpinner_(false);
458 this.showErrorBanner_(false);
459 this.editor_.getPrompt().hide();
460 if (this.isShowingVideo_()) {
461 this.mediaControls_.pause();
462 this.mediaControls_.detachMedia();
463 }
464 this.editor_.closeSession(callback);
465 };
466
467 /**
468 * Request a prefetch for the next image.
469 *
470 * @param {number} direction -1 or 1.
471 */
472 SlideMode.prototype.requestPrefetch = function(direction) {
473 if (this.items_.length <= 1) return;
474
475 var index = this.getNextSelectedIndex_(direction);
476 var nextItemUrl = this.items_[index].getUrl();
477
478 var selectedItem = this.getSelectedItem();
479 this.metadataCache_.get(nextItemUrl, Gallery.METADATA_TYPE,
480 function(metadata) {
481 if (selectedItem != this.getSelectedItem()) return;
482 this.editor_.prefetchImage(nextItemUrl);
483 }.bind(this));
484 };
485
486 // Event handlers.
487
488 /**
489 * Unload handler.
490 * @private
491 */
492 SlideMode.prototype.onUnload_ = function() {
493 this.saveVideoPosition_();
494 window.top.removeEventListener('beforeunload', this.onBeforeUnloadBound_);
495 window.top.removeEventListener('unload', this.onTopUnloadBound_);
496 };
497
498 /**
499 * Top window unload handler.
500 * @private
501 */
502 SlideMode.prototype.onTopUnload_ = function() {
503 this.saveVideoPosition_();
504 };
505
506 /**
507 * Top window beforeunload handler.
508 * @return {string} Message to show if there are unsaved changes.
509 * @private
510 */
511 SlideMode.prototype.onBeforeUnload_ = function() {
512 if (this.editor_.isBusy())
513 return this.displayStringFunction_('unsaved_changes');
514 return null;
515 };
516
517 /**
518 * Click handler.
519 * @private
520 */
521 SlideMode.prototype.onClick_ = function() {
522 if (this.isShowingVideo_())
523 this.mediaControls_.togglePlayStateWithFeedback();
524 };
525
526 /**
527 * Keydown handler.
528 *
529 * @param {Event} event Event.
530 * @return {boolean} True if handled.
531 */
532 SlideMode.prototype.onKeyDown = function(event) {
533 if (this.isEditing() && this.editor_.onKeyDown(event))
534 return true;
535
536 switch (util.getKeyModifiers(event) + event.keyIdentifier) {
537 case 'U+0020': // Space toggles the video playback.
538 if (this.isShowingVideo_()) {
539 this.mediaControls_.togglePlayStateWithFeedback();
540 }
541 break;
542
543 case 'U+0045': // 'e' toggles the editor
544 this.onEdit_();
545 break;
546
547 case 'U+001B': // Escape
548 if (!this.isEditing())
549 return false; // Not handled.
550 this.onEdit_();
551 break;
552
553 case 'Ctrl-U+00DD': // Ctrl+] (cryptic on purpose).
554 this.toggleSlideshow_();
555 break;
556
557 case 'Home':
558 this.selectFirst();
559 break;
560 case 'End':
561 this.selectLast();
562 break;
563 case 'Left':
564 this.selectNext(-1);
565 break;
566 case 'Right':
567 this.selectNext(1);
568 break;
569
570 default: return false;
571 }
572
573 return true;
574 };
575
576 /**
577 * Resize handler.
578 * @private
579 */
580 SlideMode.prototype.onResize_ = function() {
581 this.viewport_.sizeByFrameAndFit(this.container_);
582 this.viewport_.repaint();
583 };
584
585 // Saving
586
587 /**
588 * Save the current image to a file.
589 *
590 * @param {function} callback Callback.
591 * @private
592 */
593 SlideMode.prototype.saveCurrentImage_ = function(callback) {
594 var item = this.getSelectedItem();
595 var oldUrl = item.getUrl();
596 var canvas = this.imageView_.getCanvas();
597
598 this.showSpinner_(true);
599 var metadataEncoder = ImageEncoder.encodeMetadata(
600 this.selectedImageMetadata_.media, canvas, 1 /* quality */);
601
602 this.selectedImageMetadata_ = ContentProvider.ConvertContentMetadata(
603 metadataEncoder.getMetadata(), this.selectedImageMetadata_);
604
605 item.saveToFile(
606 this.context_.saveDirEntry,
607 this.shouldOverwriteOriginal_(),
608 canvas,
609 metadataEncoder,
610 function(success) {
611 // TODO(kaznacheev): Implement write error handling.
612 // Until then pretend that the save succeeded.
613 this.showSpinner_(false);
614 this.flashSavedLabel_();
615 var newUrl = item.getUrl();
616 this.updateSelectedUrl_(oldUrl, newUrl);
617 this.ribbon_.updateThumbnail(
618 this.selectedIndex_, newUrl, this.selectedImageMetadata_);
619 callback();
620 }.bind(this));
621 };
622
623 /**
624 * Update caches when the selected item url has changed.
625 *
626 * @param {string} oldUrl Old url.
627 * @param {string} newUrl New url.
628 * @private
629 */
630 SlideMode.prototype.updateSelectedUrl_ = function(oldUrl, newUrl) {
631 this.metadataCache_.clear(oldUrl, Gallery.METADATA_TYPE);
632
633 if (oldUrl == newUrl)
634 return;
635
636 this.imageView_.changeUrl(newUrl);
637 this.ribbon_.remapCache(oldUrl, newUrl);
638
639 // Let the gallery know that the selected item url has changed.
640 cr.dispatchSimpleEvent(this, 'selection');
641 };
642
643 /**
644 * Flash 'Saved' label briefly to indicate that the image has been saved.
645 * @private
646 */
647 SlideMode.prototype.flashSavedLabel_ = function() {
648 var setLabelHighlighted =
649 ImageUtil.setAttribute.bind(null, this.savedLabel_, 'highlighted');
650 setTimeout(setLabelHighlighted.bind(null, true), 0);
651 setTimeout(setLabelHighlighted.bind(null, false), 300);
652 };
653
654 /**
655 * Local storage key for the 'Overwrite original' setting.
656 * @type {string}
657 */
658 SlideMode.OVERWRITE_KEY = 'gallery-overwrite-original';
659
660 /**
661 * Local storage key for the number of times that
662 * the overwrite info bubble has been displayed.
663 * @type {string}
664 */
665 SlideMode.OVERWRITE_BUBBLE_KEY = 'gallery-overwrite-bubble';
666
667 /**
668 * Max number that the overwrite info bubble is shown.
669 * @type {number}
670 */
671 SlideMode.OVERWRITE_BUBBLE_MAX_TIMES = 5;
672
673 /**
674 * @return {boolean} True if 'Overwrite original' is set.
675 * @private
676 */
677 SlideMode.prototype.shouldOverwriteOriginal_ = function() {
678 return SlideMode.OVERWRITE_KEY in localStorage &&
679 (localStorage[SlideMode.OVERWRITE_KEY] == 'true');
680 };
681
682 /**
683 * 'Overwrite original' checkbox handler.
684 * @param {Event} event Event.
685 * @private
686 */
687 SlideMode.prototype.onOverwriteOriginalClick_ = function(event) {
688 localStorage['gallery-overwrite-original'] = event.target.checked;
689 };
690
691 /**
692 * Overwrite info bubble close handler.
693 * @private
694 */
695 SlideMode.prototype.onCloseBubble_ = function() {
696 this.bubble_.hidden = true;
697 localStorage[SlideMode.OVERWRITE_BUBBLE_KEY] =
698 SlideMode.OVERWRITE_BUBBLE_MAX_TIMES;
699 };
700
701 /**
702 * Callback called when the image is edited.
703 * @private
704 */
705 SlideMode.prototype.onImageContentChanged_ = function() {
706 var revision = this.imageView_.getContentRevision();
707 if (revision == 0) {
708 // Just loaded.
709 var times = SlideMode.OVERWRITE_BUBBLE_KEY in localStorage ?
710 parseInt(localStorage[SlideMode.OVERWRITE_BUBBLE_KEY], 10) : 0;
711 if (times < SlideMode.OVERWRITE_BUBBLE_MAX_TIMES) {
712 this.bubble_.hidden = false;
713 if (this.isEditing()) {
714 localStorage[SlideMode.OVERWRITE_BUBBLE_KEY] = times + 1;
715 }
716 }
717 }
718
719 if (revision == 1) {
720 // First edit.
721 ImageUtil.setAttribute(this.options_, 'saved', true);
722 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('Edit'));
723 }
724 };
725
726 // Misc
727
728 /**
729 * Start/stop the slide show.
730 * @private
731 */
732 SlideMode.prototype.toggleSlideshow_ = function() {
733 if (this.slideShowTimeout_) {
734 clearInterval(this.slideShowTimeout_);
735 this.slideShowTimeout_ = null;
736 } else {
737 var self = this;
738 function nextSlide() {
739 self.selectNext(1,
740 function() { self.slideShowTimeout_ = setTimeout(nextSlide, 5000) });
741 }
742 nextSlide();
743 }
744 };
745
746 /**
747 * @return {boolean} True if the editor is active.
748 */
749 SlideMode.prototype.isEditing = function() {
750 return this.container_.hasAttribute('editing');
751 };
752
753 /**
754 * Activate/deactivate editor.
755 * @private
756 */
757 SlideMode.prototype.onEdit_ = function() {
758 if (!this.isEditing() && this.isShowingVideo_())
759 return; // No editing for videos.
760
761 ImageUtil.setAttribute(this.container_, 'editing', !this.isEditing());
762
763 if (this.isEditing()) { // isEditing_ has just been flipped to a new value.
764 if (this.context_.readonlyDirName) {
765 this.editor_.getPrompt().showAt(
766 'top', 'readonly_warning', 0, this.context_.readonlyDirName);
767 }
768 } else {
769 this.editor_.getPrompt().hide();
770 }
771
772 ImageUtil.setAttribute(this.editButton_, 'pressed', this.isEditing());
773
774 cr.dispatchSimpleEvent(this, 'edit');
775 };
776
777 /**
778 * Display the error banner.
779 * @param {string} message Message.
780 * @private
781 */
782 SlideMode.prototype.showErrorBanner_ = function(message) {
783 if (message) {
784 this.errorBanner_.textContent = this.displayStringFunction_(message);
785 }
786 ImageUtil.setAttribute(this.container_, 'error', !!message);
787 };
788
789 /**
790 * Show/hide the busy spinner.
791 *
792 * @param {boolean} on True if show, false if hide.
793 * @private
794 */
795 SlideMode.prototype.showSpinner_ = function(on) {
796 if (this.spinnerTimer_) {
797 clearTimeout(this.spinnerTimer_);
798 this.spinnerTimer_ = null;
799 }
800
801 if (on) {
802 this.spinnerTimer_ = setTimeout(function() {
803 this.spinnerTimer_ = null;
804 ImageUtil.setAttribute(this.container_, 'spinner', true);
805 }.bind(this), 1000);
806 } else {
807 ImageUtil.setAttribute(this.container_, 'spinner', false);
808 }
809 };
810
811 /**
812 * @return {boolean} True if the current item is a video.
813 * @private
814 */
815 SlideMode.prototype.isShowingVideo_ = function() {
816 return !!this.imageView_.getVideo();
817 };
818
819 /**
820 * Save the current video position.
821 * @private
822 */
823 SlideMode.prototype.saveVideoPosition_ = function() {
824 if (this.isShowingVideo_() && this.mediaControls_.isPlaying()) {
825 this.mediaControls_.savePosition();
826 }
827 };
828
829 /**
830 * Overlay that handles swipe gestures. Changes to the next or previous file.
831 * @param {function(number)} callback A callback accepting the swipe direction
832 * (1 means left, -1 right).
833 * @constructor
834 * @implements {ImageBuffer.Overlay}
835 */
836 function SwipeOverlay(callback) {
837 this.callback_ = callback;
838 }
839
840 /**
841 * Inherit ImageBuffer.Overlay.
842 */
843 SwipeOverlay.prototype.__proto__ = ImageBuffer.Overlay.prototype;
844
845 /**
846 * @param {number} x X pointer position.
847 * @param {number} y Y pointer position.
848 * @param {boolean} touch True if dragging caused by touch.
849 * @return {function} The closure to call on drag.
850 */
851 SwipeOverlay.prototype.getDragHandler = function(x, y, touch) {
852 if (!touch)
853 return null;
854 var origin = x;
855 var done = false;
856 return function(x, y) {
857 if (!done && origin - x > SwipeOverlay.SWIPE_THRESHOLD) {
858 this.callback_(1);
859 done = true;
860 } else if (!done && x - origin > SwipeOverlay.SWIPE_THRESHOLD) {
861 this.callback_(-1);
862 done = true;
863 }
864 }.bind(this);
865 };
866
867 /**
868 * 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).
870 */
871 SwipeOverlay.SWIPE_THRESHOLD = 100;
OLDNEW
« no previous file with comments | « chrome/browser/resources/file_manager/js/photo/ribbon.js ('k') | chrome/browser/resources/file_manager/js/util.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698