Landing Recent QUIC Changes.
[chromium-blink-merge.git] / ui / file_manager / gallery / js / gallery.js
blob8d53967b5f8f9f923f0998e5031c5f7e0b7a5636
1 // Copyright 2014 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.
5 /**
6 * Overrided metadata worker's path.
7 * @type {string}
8 */
9 ContentProvider.WORKER_SCRIPT = '/js/metadata_worker.js';
11 /**
12 * Gallery for viewing and editing image files.
14 * @param {!VolumeManager} volumeManager The VolumeManager instance of the
15 * system.
16 * @constructor
17 * @struct
19 function Gallery(volumeManager) {
20 /**
21 * @type {{appWindow: chrome.app.window.AppWindow, onClose: function(),
22 * onMaximize: function(), onMinimize: function(),
23 * onAppRegionChanged: function(), metadataCache: MetadataCache,
24 * readonlyDirName: string, displayStringFunction: function(),
25 * loadTimeData: Object, curDirEntry: Entry, searchResults: *}}
26 * @private
28 * TODO(yawano): curDirEntry and searchResults seem not to be used.
29 * Investigate them and remove them if possible.
31 this.context_ = {
32 appWindow: chrome.app.window.current(),
33 onClose: function() { window.close(); },
34 onMaximize: function() {
35 var appWindow = chrome.app.window.current();
36 if (appWindow.isMaximized())
37 appWindow.restore();
38 else
39 appWindow.maximize();
41 onMinimize: function() { chrome.app.window.current().minimize(); },
42 onAppRegionChanged: function() {},
43 metadataCache: MetadataCache.createFull(volumeManager),
44 readonlyDirName: '',
45 displayStringFunction: function() { return ''; },
46 loadTimeData: {},
47 curDirEntry: null,
48 searchResults: null
50 this.container_ = queryRequiredElement(document, '.gallery');
51 this.document_ = document;
52 this.metadataCache_ = this.context_.metadataCache;
53 this.volumeManager_ = volumeManager;
54 this.selectedEntry_ = null;
55 this.metadataCacheObserverId_ = null;
56 this.onExternallyUnmountedBound_ = this.onExternallyUnmounted_.bind(this);
58 this.dataModel_ = new GalleryDataModel(
59 this.context_.metadataCache);
60 var downloadVolumeInfo = this.volumeManager_.getCurrentProfileVolumeInfo(
61 VolumeManagerCommon.VolumeType.DOWNLOADS);
62 downloadVolumeInfo.resolveDisplayRoot().then(function(entry) {
63 this.dataModel_.fallbackSaveDirectory = entry;
64 }.bind(this)).catch(function(error) {
65 console.error(
66 'Failed to obtain the fallback directory: ' + (error.stack || error));
67 });
68 this.selectionModel_ = new cr.ui.ListSelectionModel();
70 /**
71 * @type {(SlideMode|MosaicMode)}
72 * @private
74 this.currentMode_ = null;
76 /**
77 * @type {boolean}
78 * @private
80 this.changingMode_ = false;
82 // -----------------------------------------------------------------
83 // Initializes the UI.
85 // Initialize the dialog label.
86 cr.ui.dialogs.BaseDialog.OK_LABEL = str('GALLERY_OK_LABEL');
87 cr.ui.dialogs.BaseDialog.CANCEL_LABEL = str('GALLERY_CANCEL_LABEL');
89 var content = queryRequiredElement(document, '#content');
90 content.addEventListener('click', this.onContentClick_.bind(this));
92 this.header_ = queryRequiredElement(document, '#header');
93 this.toolbar_ = queryRequiredElement(document, '#toolbar');
95 var preventDefault = function(event) { event.preventDefault(); };
97 var minimizeButton = util.createChild(this.header_,
98 'minimize-button tool dimmable',
99 'button');
100 minimizeButton.tabIndex = -1;
101 minimizeButton.addEventListener('click', this.onMinimize_.bind(this));
102 minimizeButton.addEventListener('mousedown', preventDefault);
104 var maximizeButton = util.createChild(this.header_,
105 'maximize-button tool dimmable',
106 'button');
107 maximizeButton.tabIndex = -1;
108 maximizeButton.addEventListener('click', this.onMaximize_.bind(this));
109 maximizeButton.addEventListener('mousedown', preventDefault);
111 var closeButton = util.createChild(this.header_,
112 'close-button tool dimmable',
113 'button');
114 closeButton.tabIndex = -1;
115 closeButton.addEventListener('click', this.onClose_.bind(this));
116 closeButton.addEventListener('mousedown', preventDefault);
118 this.filenameSpacer_ = queryRequiredElement(this.toolbar_,
119 '.filename-spacer');
120 this.filenameEdit_ = util.createChild(this.filenameSpacer_,
121 'namebox', 'input');
123 this.filenameEdit_.setAttribute('type', 'text');
124 this.filenameEdit_.addEventListener('blur',
125 this.onFilenameEditBlur_.bind(this));
127 this.filenameEdit_.addEventListener('focus',
128 this.onFilenameFocus_.bind(this));
130 this.filenameEdit_.addEventListener('keydown',
131 this.onFilenameEditKeydown_.bind(this));
133 var middleSpacer = queryRequiredElement(this.toolbar_, '.middle-spacer');
134 var buttonSpacer = queryRequiredElement(this.toolbar_, '.button-spacer');
136 this.prompt_ = new ImageEditor.Prompt(this.container_, strf);
138 this.errorBanner_ = new ErrorBanner(this.container_);
140 this.modeButton_ = queryRequiredElement(this.toolbar_, 'button.mode');
141 this.modeButton_.addEventListener('click',
142 this.toggleMode_.bind(this, undefined));
144 this.mosaicMode_ = new MosaicMode(content,
145 this.errorBanner_,
146 this.dataModel_,
147 this.selectionModel_,
148 this.volumeManager_,
149 this.toggleMode_.bind(this, undefined));
151 this.slideMode_ = new SlideMode(this.container_,
152 content,
153 this.toolbar_,
154 this.prompt_,
155 this.errorBanner_,
156 this.dataModel_,
157 this.selectionModel_,
158 this.context_,
159 this.volumeManager_,
160 this.toggleMode_.bind(this),
161 str);
163 this.slideMode_.addEventListener('image-displayed', function() {
164 cr.dispatchSimpleEvent(this, 'image-displayed');
165 }.bind(this));
166 this.slideMode_.addEventListener('image-saved', function() {
167 cr.dispatchSimpleEvent(this, 'image-saved');
168 }.bind(this));
170 this.deleteButton_ = this.initToolbarButton_('delete', 'GALLERY_DELETE');
171 this.deleteButton_.addEventListener('click', this.delete_.bind(this));
173 this.shareButton_ = this.initToolbarButton_('share', 'GALLERY_SHARE');
174 this.shareButton_.addEventListener(
175 'click', this.onShareButtonClick_.bind(this));
177 this.dataModel_.addEventListener('splice', this.onSplice_.bind(this));
178 this.dataModel_.addEventListener('content', this.onContentChange_.bind(this));
180 this.selectionModel_.addEventListener('change', this.onSelection_.bind(this));
181 this.slideMode_.addEventListener('useraction', this.onUserAction_.bind(this));
183 this.shareDialog_ = new ShareDialog(this.container_);
185 // -----------------------------------------------------------------
186 // Initialize listeners.
188 this.keyDownBound_ = this.onKeyDown_.bind(this);
189 this.document_.body.addEventListener('keydown', this.keyDownBound_);
191 this.inactivityWatcher_ = new MouseInactivityWatcher(
192 this.container_, Gallery.FADE_TIMEOUT, this.hasActiveTool.bind(this));
194 // Search results may contain files from different subdirectories so
195 // the observer is not going to work.
196 if (!this.context_.searchResults && this.context_.curDirEntry) {
197 this.metadataCacheObserverId_ = this.metadataCache_.addObserver(
198 this.context_.curDirEntry,
199 MetadataCache.CHILDREN,
200 'thumbnail',
201 this.updateThumbnails_.bind(this));
203 this.volumeManager_.addEventListener(
204 'externally-unmounted', this.onExternallyUnmountedBound_);
205 // The 'pagehide' event is called when the app window is closed.
206 window.addEventListener('pagehide', this.onPageHide_.bind(this));
210 * Gallery extends cr.EventTarget.
212 Gallery.prototype.__proto__ = cr.EventTarget.prototype;
215 * Tools fade-out timeout in milliseconds.
216 * @const
217 * @type {number}
219 Gallery.FADE_TIMEOUT = 2000;
222 * First time tools fade-out timeout in milliseconds.
223 * @const
224 * @type {number}
226 Gallery.FIRST_FADE_TIMEOUT = 1000;
229 * Time until mosaic is initialized in the background. Used to make gallery
230 * in the slide mode load faster. In milliseconds.
231 * @const
232 * @type {number}
234 Gallery.MOSAIC_BACKGROUND_INIT_DELAY = 1000;
237 * Types of metadata Gallery uses (to query the metadata cache).
238 * @const
239 * @type {string}
241 Gallery.METADATA_TYPE = 'thumbnail|filesystem|media|external';
244 * Closes gallery when a volume containing the selected item is unmounted.
245 * @param {!Event} event The unmount event.
246 * @private
248 Gallery.prototype.onExternallyUnmounted_ = function(event) {
249 if (!this.selectedEntry_)
250 return;
252 if (this.volumeManager_.getVolumeInfo(this.selectedEntry_) ===
253 event.volumeInfo) {
254 window.close();
259 * Unloads the Gallery.
260 * @private
262 Gallery.prototype.onPageHide_ = function() {
263 if (this.metadataCacheObserverId_ !== null)
264 this.metadataCache_.removeObserver(this.metadataCacheObserverId_);
265 this.volumeManager_.removeEventListener(
266 'externally-unmounted', this.onExternallyUnmountedBound_);
270 * Initializes a toolbar button.
272 * @param {string} className Class to add.
273 * @param {string} title Button title.
274 * @return {!HTMLElement} Newly created button.
275 * @private
277 Gallery.prototype.initToolbarButton_ = function(className, title) {
278 var button = queryRequiredElement(this.toolbar_, 'button.' + className);
279 button.title = str(title);
280 return button;
284 * Loads the content.
286 * @param {!Array.<Entry>} entries Array of entries.
287 * @param {!Array.<Entry>} selectedEntries Array of selected entries.
289 Gallery.prototype.load = function(entries, selectedEntries) {
290 // Obtains max chank size.
291 var maxChunkSize = 20;
292 var volumeInfo = this.volumeManager_.getVolumeInfo(entries[0]);
293 if (volumeInfo &&
294 volumeInfo.volumeType === VolumeManagerCommon.VolumeType.MTP) {
295 maxChunkSize = 1;
297 if (volumeInfo.isReadOnly)
298 this.context_.readonlyDirName = volumeInfo.label;
300 // Make loading list.
301 var entrySet = {};
302 for (var i = 0; i < entries.length; i++) {
303 var entry = entries[i];
304 entrySet[entry.toURL()] = {
305 entry: entry,
306 selected: false,
307 index: i
310 for (var i = 0; i < selectedEntries.length; i++) {
311 var entry = selectedEntries[i];
312 entrySet[entry.toURL()] = {
313 entry: entry,
314 selected: true,
315 index: i
318 var loadingList = [];
319 for (var url in entrySet) {
320 loadingList.push(entrySet[url]);
322 loadingList = loadingList.sort(function(a, b) {
323 if (a.selected && !b.selected)
324 return -1;
325 else if (!a.selected && b.selected)
326 return 1;
327 else
328 return a.index - b.index;
331 // Load entries.
332 // Use the self variable capture-by-closure because it is faster than bind.
333 var self = this;
334 var loadChunk = function(firstChunk) {
335 // Extract chunk.
336 var chunk = loadingList.splice(0, maxChunkSize);
337 if (!chunk.length)
338 return;
340 return new Promise(function(fulfill) {
341 // Obtains metadata for chunk.
342 var entries = chunk.map(function(chunkItem) {
343 return chunkItem.entry;
345 self.metadataCache_.get(entries, Gallery.METADATA_TYPE, fulfill);
346 }).then(function(metadataList) {
347 if (chunk.length !== metadataList.length)
348 return Promise.reject('Failed to load metadata.');
350 // Add items to the model.
351 var items = [];
352 chunk.forEach(function(chunkItem, index) {
353 var locationInfo = self.volumeManager_.getLocationInfo(chunkItem.entry);
354 if (!locationInfo) // Skip the item, since gone.
355 return;
356 var clonedMetadata = MetadataCache.cloneMetadata(metadataList[index]);
357 items.push(new Gallery.Item(
358 chunkItem.entry,
359 locationInfo,
360 clonedMetadata,
361 self.metadataCache_,
362 /* original */ true));
364 self.dataModel_.push.apply(self.dataModel_, items);
366 // Apply the selection.
367 var selectionUpdated = false;
368 for (var i = 0; i < chunk.length; i++) {
369 if (!chunk[i].selected)
370 continue;
371 var index = self.dataModel_.indexOf(items[i]);
372 if (index < 0)
373 continue;
374 self.selectionModel_.setIndexSelected(index, true);
375 selectionUpdated = true;
377 if (selectionUpdated)
378 self.onSelection_();
380 // Init modes after the first chunk is loaded.
381 if (firstChunk) {
382 // Determine the initial mode.
383 var shouldShowMosaic = selectedEntries.length > 1 ||
384 (self.context_.pageState &&
385 self.context_.pageState.gallery === 'mosaic');
386 self.setCurrentMode_(
387 shouldShowMosaic ? self.mosaicMode_ : self.slideMode_);
389 // Init mosaic mode.
390 var mosaic = self.mosaicMode_.getMosaic();
391 mosaic.init();
393 // Do the initialization for each mode.
394 if (shouldShowMosaic) {
395 mosaic.show();
396 self.inactivityWatcher_.check(); // Show the toolbar.
397 cr.dispatchSimpleEvent(self, 'loaded');
398 } else {
399 self.slideMode_.enter(
400 null,
401 function() {
402 // Flash the toolbar briefly to show it is there.
403 self.inactivityWatcher_.kick(Gallery.FIRST_FADE_TIMEOUT);
405 function() {
406 cr.dispatchSimpleEvent(self, 'loaded');
411 // Continue to load chunks.
412 return loadChunk(/* firstChunk */ false);
415 loadChunk(/* firstChunk */ true).catch(function(error) {
416 console.error(error.stack || error);
421 * Handles user's 'Close' action.
422 * @private
424 Gallery.prototype.onClose_ = function() {
425 this.executeWhenReady(this.context_.onClose);
429 * Handles user's 'Maximize' action (Escape or a click on the X icon).
430 * @private
432 Gallery.prototype.onMaximize_ = function() {
433 this.executeWhenReady(this.context_.onMaximize);
437 * Handles user's 'Maximize' action (Escape or a click on the X icon).
438 * @private
440 Gallery.prototype.onMinimize_ = function() {
441 this.executeWhenReady(this.context_.onMinimize);
445 * Executes a function when the editor is done with the modifications.
446 * @param {function()} callback Function to execute.
448 Gallery.prototype.executeWhenReady = function(callback) {
449 this.currentMode_.executeWhenReady(callback);
453 * @return {!Object} File manager private API.
455 Gallery.getFileManagerPrivate = function() {
456 return chrome.fileManagerPrivate || window.top.chrome.fileManagerPrivate;
460 * @return {boolean} True if some tool is currently active.
462 Gallery.prototype.hasActiveTool = function() {
463 return (this.currentMode_ && this.currentMode_.hasActiveTool()) ||
464 this.isRenaming_();
468 * External user action event handler.
469 * @private
471 Gallery.prototype.onUserAction_ = function() {
472 // Show the toolbar and hide it after the default timeout.
473 this.inactivityWatcher_.kick();
477 * Sets the current mode, update the UI.
478 * @param {!(SlideMode|MosaicMode)} mode Current mode.
479 * @private
481 Gallery.prototype.setCurrentMode_ = function(mode) {
482 if (mode !== this.slideMode_ && mode !== this.mosaicMode_)
483 console.error('Invalid Gallery mode');
485 this.currentMode_ = mode;
486 this.container_.setAttribute('mode', this.currentMode_.getName());
487 this.updateSelectionAndState_();
488 this.updateButtons_();
492 * Mode toggle event handler.
493 * @param {function()=} opt_callback Callback.
494 * @param {Event=} opt_event Event that caused this call.
495 * @private
497 Gallery.prototype.toggleMode_ = function(opt_callback, opt_event) {
498 if (!this.modeButton_)
499 return;
501 if (this.changingMode_) // Do not re-enter while changing the mode.
502 return;
504 if (opt_event)
505 this.onUserAction_();
507 this.changingMode_ = true;
509 var onModeChanged = function() {
510 this.changingMode_ = false;
511 if (opt_callback) opt_callback();
512 }.bind(this);
514 var tileIndex = Math.max(0, this.selectionModel_.selectedIndex);
516 var mosaic = this.mosaicMode_.getMosaic();
517 var tileRect = mosaic.getTileRect(tileIndex);
519 if (this.currentMode_ === this.slideMode_) {
520 this.setCurrentMode_(this.mosaicMode_);
521 mosaic.transform(
522 tileRect, this.slideMode_.getSelectedImageRect(), true /* instant */);
523 this.slideMode_.leave(
524 tileRect,
525 function() {
526 // Animate back to normal position.
527 mosaic.transform(null, null);
528 mosaic.show();
529 onModeChanged();
530 }.bind(this));
531 } else {
532 this.setCurrentMode_(this.slideMode_);
533 this.slideMode_.enter(
534 tileRect,
535 function() {
536 // Animate to zoomed position.
537 mosaic.transform(tileRect, this.slideMode_.getSelectedImageRect());
538 mosaic.hide();
539 }.bind(this),
540 onModeChanged);
545 * Deletes the selected items.
546 * @private
548 Gallery.prototype.delete_ = function() {
549 this.onUserAction_();
551 // Clone the sorted selected indexes array.
552 var indexesToRemove = this.selectionModel_.selectedIndexes.slice();
553 if (!indexesToRemove.length)
554 return;
556 /* TODO(dgozman): Implement Undo delete, Remove the confirmation dialog. */
558 var itemsToRemove = this.getSelectedItems();
559 var plural = itemsToRemove.length > 1;
560 var param = plural ? itemsToRemove.length : itemsToRemove[0].getFileName();
562 function deleteNext() {
563 if (!itemsToRemove.length)
564 return; // All deleted.
566 var entry = itemsToRemove.pop().getEntry();
567 entry.remove(deleteNext, function() {
568 console.error('Error deleting: ' + entry.name);
569 deleteNext();
573 // Prevent the Gallery from handling Esc and Enter.
574 this.document_.body.removeEventListener('keydown', this.keyDownBound_);
575 var restoreListener = function() {
576 this.document_.body.addEventListener('keydown', this.keyDownBound_);
577 }.bind(this);
580 var confirm = new cr.ui.dialogs.ConfirmDialog(this.container_);
581 confirm.setOkLabel(str('DELETE_BUTTON_LABEL'));
582 confirm.show(strf(plural ?
583 'GALLERY_CONFIRM_DELETE_SOME' : 'GALLERY_CONFIRM_DELETE_ONE', param),
584 function() {
585 restoreListener();
586 this.selectionModel_.unselectAll();
587 this.selectionModel_.leadIndex = -1;
588 // Remove items from the data model, starting from the highest index.
589 while (indexesToRemove.length)
590 this.dataModel_.splice(indexesToRemove.pop(), 1);
591 // Delete actual files.
592 deleteNext();
593 }.bind(this),
594 function() {
595 // Restore the listener after a timeout so that ESC is processed.
596 setTimeout(restoreListener, 0);
598 null);
602 * @return {!Array.<Gallery.Item>} Current selection.
604 Gallery.prototype.getSelectedItems = function() {
605 return this.selectionModel_.selectedIndexes.map(
606 this.dataModel_.item.bind(this.dataModel_));
610 * @return {!Array.<Entry>} Array of currently selected entries.
612 Gallery.prototype.getSelectedEntries = function() {
613 return this.selectionModel_.selectedIndexes.map(function(index) {
614 return this.dataModel_.item(index).getEntry();
615 }.bind(this));
619 * @return {?Gallery.Item} Current single selection.
621 Gallery.prototype.getSingleSelectedItem = function() {
622 var items = this.getSelectedItems();
623 if (items.length > 1) {
624 console.error('Unexpected multiple selection');
625 return null;
627 return items[0];
631 * Selection change event handler.
632 * @private
634 Gallery.prototype.onSelection_ = function() {
635 this.updateSelectionAndState_();
639 * Data model splice event handler.
640 * @private
642 Gallery.prototype.onSplice_ = function() {
643 this.selectionModel_.adjustLength(this.dataModel_.length);
647 * Content change event handler.
648 * @param {!Event} event Event.
649 * @private
651 Gallery.prototype.onContentChange_ = function(event) {
652 var index = this.dataModel_.indexOf(event.item);
653 if (index !== this.selectionModel_.selectedIndex)
654 console.error('Content changed for unselected item');
655 this.updateSelectionAndState_();
659 * Keydown handler.
661 * @param {!Event} event Event.
662 * @private
664 Gallery.prototype.onKeyDown_ = function(event) {
665 if (this.currentMode_.onKeyDown(event))
666 return;
668 switch (util.getKeyModifiers(event) + event.keyIdentifier) {
669 case 'U+0008': // Backspace.
670 // The default handler would call history.back and close the Gallery.
671 event.preventDefault();
672 break;
674 case 'U+004D': // 'm' switches between Slide and Mosaic mode.
675 this.toggleMode_(undefined, event);
676 break;
678 case 'U+0056': // 'v'
679 case 'MediaPlayPause':
680 this.slideMode_.startSlideshow(SlideMode.SLIDESHOW_INTERVAL_FIRST, event);
681 break;
683 case 'U+007F': // Delete
684 case 'Shift-U+0033': // Shift+'3' (Delete key might be missing).
685 case 'U+0044': // 'd'
686 this.delete_();
687 break;
689 case 'U+001B': // Escape
690 window.close();
691 break;
695 // Name box and rename support.
698 * Updates the UI related to the selected item and the persistent state.
700 * @private
702 Gallery.prototype.updateSelectionAndState_ = function() {
703 var numSelectedItems = this.selectionModel_.selectedIndexes.length;
704 var selectedEntryURL = null;
706 // If it's selecting something, update the variable values.
707 if (numSelectedItems) {
708 // Delete button is available when all images are NOT readOnly.
709 this.deleteButton_.disabled = !this.selectionModel_.selectedIndexes
710 .every(function(i) {
711 return !this.dataModel_.item(i).getLocationInfo().isReadOnly;
712 }, this);
714 // Obtains selected item.
715 var selectedItem =
716 this.dataModel_.item(this.selectionModel_.selectedIndex);
717 this.selectedEntry_ = selectedItem.getEntry();
718 selectedEntryURL = this.selectedEntry_.toURL();
720 // Update cache.
721 selectedItem.touch();
722 this.dataModel_.evictCache();
724 // Update the title and the display name.
725 if (numSelectedItems === 1) {
726 document.title = this.selectedEntry_.name;
727 this.filenameEdit_.disabled = selectedItem.getLocationInfo().isReadOnly;
728 this.filenameEdit_.value =
729 ImageUtil.getDisplayNameFromName(this.selectedEntry_.name);
730 this.shareButton_.hidden = !selectedItem.getLocationInfo().isDriveBased;
731 } else {
732 if (this.context_.curDirEntry) {
733 // If the Gallery was opened on search results the search query will not
734 // be recorded in the app state and the relaunch will just open the
735 // gallery in the curDirEntry directory.
736 document.title = this.context_.curDirEntry.name;
737 } else {
738 document.title = '';
740 this.filenameEdit_.disabled = true;
741 this.filenameEdit_.value =
742 strf('GALLERY_ITEMS_SELECTED', numSelectedItems);
743 this.shareButton_.hidden = true;
745 } else {
746 document.title = '';
747 this.filenameEdit_.disabled = true;
748 this.deleteButton_.disabled = true;
749 this.filenameEdit_.value = '';
750 this.shareButton_.hidden = true;
753 util.updateAppState(
754 null, // Keep the current directory.
755 selectedEntryURL, // Update the selection.
756 {gallery: (this.currentMode_ === this.mosaicMode_ ? 'mosaic' : 'slide')});
760 * Click event handler on filename edit box
761 * @private
763 Gallery.prototype.onFilenameFocus_ = function() {
764 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', true);
765 this.filenameEdit_.originalValue = this.filenameEdit_.value;
766 setTimeout(this.filenameEdit_.select.bind(this.filenameEdit_), 0);
767 this.onUserAction_();
771 * Blur event handler on filename edit box.
773 * @param {!Event} event Blur event.
774 * @private
776 Gallery.prototype.onFilenameEditBlur_ = function(event) {
777 var item = this.getSingleSelectedItem();
778 if (item) {
779 var oldEntry = item.getEntry();
781 item.rename(this.filenameEdit_.value).then(function() {
782 var event = new Event('content');
783 event.item = item;
784 event.oldEntry = oldEntry;
785 event.metadata = null; // Metadata unchanged.
786 this.dataModel_.dispatchEvent(event);
787 }.bind(this), function(error) {
788 if (error === 'NOT_CHANGED')
789 return Promise.resolve();
790 this.filenameEdit_.value =
791 ImageUtil.getDisplayNameFromName(item.getEntry().name);
792 this.filenameEdit_.focus();
793 if (typeof error === 'string')
794 this.prompt_.showStringAt('center', error, 5000);
795 else
796 return Promise.reject(error);
797 }.bind(this)).catch(function(error) {
798 console.error(error.stack || error);
802 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', false);
803 this.onUserAction_();
807 * Keydown event handler on filename edit box
808 * @param {!Event} event A keyboard event.
809 * @private
811 Gallery.prototype.onFilenameEditKeydown_ = function(event) {
812 event = assertInstanceof(event, KeyboardEvent);
813 switch (event.keyCode) {
814 case 27: // Escape
815 this.filenameEdit_.value = this.filenameEdit_.originalValue;
816 this.filenameEdit_.blur();
817 break;
819 case 13: // Enter
820 this.filenameEdit_.blur();
821 break;
823 event.stopPropagation();
827 * @return {boolean} True if file renaming is currently in progress.
828 * @private
830 Gallery.prototype.isRenaming_ = function() {
831 return this.filenameSpacer_.hasAttribute('renaming');
835 * Content area click handler.
836 * @private
838 Gallery.prototype.onContentClick_ = function() {
839 this.filenameEdit_.blur();
843 * Share button handler.
844 * @private
846 Gallery.prototype.onShareButtonClick_ = function() {
847 var item = this.getSingleSelectedItem();
848 if (!item)
849 return;
850 this.shareDialog_.show(item.getEntry(), function() {});
854 * Updates thumbnails.
855 * @private
857 Gallery.prototype.updateThumbnails_ = function() {
858 if (this.currentMode_ === this.slideMode_)
859 this.slideMode_.updateThumbnails();
861 if (this.mosaicMode_) {
862 var mosaic = this.mosaicMode_.getMosaic();
863 if (mosaic.isInitialized())
864 mosaic.reload();
869 * Updates buttons.
870 * @private
872 Gallery.prototype.updateButtons_ = function() {
873 if (this.modeButton_) {
874 var oppositeMode =
875 this.currentMode_ === this.slideMode_ ? this.mosaicMode_ :
876 this.slideMode_;
877 this.modeButton_.title = str(oppositeMode.getTitle());
882 * Enters the debug mode.
884 Gallery.prototype.debugMe = function() {
885 this.mosaicMode_.debugMe();
889 * Singleton gallery.
890 * @type {Gallery}
892 var gallery = null;
895 * Initialize the window.
896 * @param {!BackgroundComponents} backgroundComponents Background components.
898 window.initialize = function(backgroundComponents) {
899 window.loadTimeData.data = backgroundComponents.stringData;
900 gallery = new Gallery(backgroundComponents.volumeManager);
904 * Loads entries.
905 * @param {!Array.<Entry>} entries Array of entries.
906 * @param {!Array.<Entry>} selectedEntries Array of selected entries.
908 window.loadEntries = function(entries, selectedEntries) {
909 gallery.load(entries, selectedEntries);
913 * Enteres the debug mode.
915 window.debugMe = function() {
916 gallery.debugMe();