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

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

Powered by Google App Engine
This is Rietveld 408576698