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

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

Powered by Google App Engine
This is Rietveld 408576698