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.
8 * Called from the main frame when unloading.
9 * @param {boolean=} opt_exiting True if the app is exiting.
11 function unload(opt_exiting) { Gallery.instance.onUnload(opt_exiting); }
14 * Overrided metadata worker's path.
18 ContentProvider.WORKER_SCRIPT = '/js/metadata_worker.js';
21 * Data model for gallery.
23 * @param {MetadataCache} metadataCache Metadata cache.
25 * @extends {cr.ui.ArrayDataModel}
27 function GalleryDataModel(metadataCache) {
28 cr.ui.ArrayDataModel.call(this, []);
32 * @type {MetadataCache}
35 this.metadataCache_ = metadataCache;
38 * Directory where the image is saved if the image is located in a read-only
40 * @type {DirectoryEntry}
42 this.fallbackSaveDirectory = null;
46 * Maximum number of full size image cache.
51 GalleryDataModel.MAX_FULL_IMAGE_CACHE_ = 3;
54 * Maximum number of screen size image cache.
59 GalleryDataModel.MAX_SCREEN_IMAGE_CACHE_ = 5;
61 GalleryDataModel.prototype = {
62 __proto__: cr.ui.ArrayDataModel.prototype
68 * @param {Gallery.Item} item Original gallery item.
69 * @param {Canvas} canvas Canvas containing new image.
70 * @param {boolean} overwrite Whether to overwrite the image to the item or not.
71 * @return {Promise} Promise to be fulfilled with when the operation completes.
73 GalleryDataModel.prototype.saveItem = function(item, canvas, overwrite) {
74 var oldEntry = item.getEntry();
75 var oldMetadata = item.getMetadata();
76 var metadataEncoder = ImageEncoder.encodeMetadata(
77 item.getMetadata(), canvas, 1 /* quality */);
78 var newMetadata = ContentProvider.ConvertContentMetadata(
79 metadataEncoder.getMetadata(),
80 MetadataCache.cloneMetadata(item.getMetadata()));
81 if (newMetadata.filesystem)
82 newMetadata.filesystem.modificationTime = new Date();
83 if (newMetadata.drive)
84 newMetadata.drive.present = true;
86 return new Promise(function(fulfill, reject) {
88 this.fallbackSaveDirectory,
94 reject('Failed to save the image.');
98 // The item's entry is updated to the latest entry. Update metadata.
99 item.setMetadata(newMetadata);
101 // Current entry is updated.
102 // Dispatch an event.
103 var event = new Event('content');
105 event.oldEntry = oldEntry;
106 event.metadata = newMetadata;
107 this.dispatchEvent(event);
109 if (util.isSameEntry(oldEntry, item.getEntry())) {
110 // Need an update of metdataCache.
111 this.metadataCache_.set(
113 Gallery.METADATA_TYPE,
116 // New entry is added and the item now tracks it.
117 // Add another item for the old entry.
118 var anotherItem = new Gallery.Item(
124 // The item must be added behind the existing item so that it does
125 // not change the index of the existing item.
126 // TODO(hirono): Update the item index of the selection model
128 this.splice(this.indexOf(item) + 1, 0, anotherItem);
137 * Evicts image caches in the items.
138 * @param {Gallery.Item} currentSelectedItem Current selected item.
140 GalleryDataModel.prototype.evictCache = function(currentSelectedItem) {
141 // Sort the item by the last accessed date.
142 var sorted = this.slice().sort(function(a, b) {
143 return b.getLastAccessedDate() - a.getLastAccessedDate();
147 var contentCacheCount = 0;
148 var screenCacheCount = 0;
149 for (var i = 0; i < sorted.length; i++) {
150 if (sorted[i].contentImage) {
151 if (++contentCacheCount > GalleryDataModel.MAX_FULL_IMAGE_CACHE_) {
152 if (sorted[i].contentImage.parentNode) {
153 console.error('The content image has a parent node.');
155 // Force to free the buffer of the canvas by assigning zero size.
156 sorted[i].contentImage.width = 0;
157 sorted[i].contentImage.height = 0;
158 sorted[i].contentImage = null;
162 if (sorted[i].screenImage) {
163 if (++screenCacheCount > GalleryDataModel.MAX_SCREEN_IMAGE_CACHE_) {
164 if (sorted[i].screenImage.parentNode) {
165 console.error('The screen image has a parent node.');
167 // Force to free the buffer of the canvas by assigning zero size.
168 sorted[i].screenImage.width = 0;
169 sorted[i].screenImage.height = 0;
170 sorted[i].screenImage = null;
178 * Gallery for viewing and editing image files.
180 * @param {!VolumeManager} volumeManager The VolumeManager instance of the
184 function Gallery(volumeManager) {
186 appWindow: chrome.app.window.current(),
187 onClose: function() { close(); },
188 onMaximize: function() {
189 var appWindow = chrome.app.window.current();
190 if (appWindow.isMaximized())
193 appWindow.maximize();
195 onMinimize: function() { chrome.app.window.current().minimize(); },
196 onAppRegionChanged: function() {},
197 metadataCache: MetadataCache.createFull(volumeManager),
199 displayStringFunction: function() { return ''; },
202 this.container_ = document.querySelector('.gallery');
203 this.document_ = document;
204 this.metadataCache_ = this.context_.metadataCache;
205 this.volumeManager_ = volumeManager;
206 this.selectedEntry_ = null;
207 this.metadataCacheObserverId_ = null;
208 this.onExternallyUnmountedBound_ = this.onExternallyUnmounted_.bind(this);
210 this.dataModel_ = new GalleryDataModel(this.context_.metadataCache);
211 var downloadVolumeInfo = this.volumeManager_.getCurrentProfileVolumeInfo(
212 VolumeManagerCommon.VolumeType.DOWNLOADS);
213 downloadVolumeInfo.resolveDisplayRoot().then(function(entry) {
214 this.dataModel_.fallbackSaveDirectory = entry;
215 }.bind(this)).catch(function(error) {
217 'Failed to obtain the fallback directory: ' + (error.stack || error));
219 this.selectionModel_ = new cr.ui.ListSelectionModel();
222 this.initListeners_();
226 * Gallery extends cr.EventTarget.
228 Gallery.prototype.__proto__ = cr.EventTarget.prototype;
231 * Tools fade-out timeout in milliseconds.
235 Gallery.FADE_TIMEOUT = 3000;
238 * First time tools fade-out timeout in milliseconds.
242 Gallery.FIRST_FADE_TIMEOUT = 1000;
245 * Time until mosaic is initialized in the background. Used to make gallery
246 * in the slide mode load faster. In milliseconds.
250 Gallery.MOSAIC_BACKGROUND_INIT_DELAY = 1000;
253 * Types of metadata Gallery uses (to query the metadata cache).
257 Gallery.METADATA_TYPE = 'thumbnail|filesystem|media|streaming|drive';
260 * Initializes listeners.
263 Gallery.prototype.initListeners_ = function() {
264 this.keyDownBound_ = this.onKeyDown_.bind(this);
265 this.document_.body.addEventListener('keydown', this.keyDownBound_);
267 this.inactivityWatcher_ = new MouseInactivityWatcher(
268 this.container_, Gallery.FADE_TIMEOUT, this.hasActiveTool.bind(this));
270 // Search results may contain files from different subdirectories so
271 // the observer is not going to work.
272 if (!this.context_.searchResults && this.context_.curDirEntry) {
273 this.metadataCacheObserverId_ = this.metadataCache_.addObserver(
274 this.context_.curDirEntry,
275 MetadataCache.CHILDREN,
277 this.updateThumbnails_.bind(this));
279 this.volumeManager_.addEventListener(
280 'externally-unmounted', this.onExternallyUnmountedBound_);
284 * Closes gallery when a volume containing the selected item is unmounted.
285 * @param {!Event} event The unmount event.
288 Gallery.prototype.onExternallyUnmounted_ = function(event) {
289 if (!this.selectedEntry_)
292 if (this.volumeManager_.getVolumeInfo(this.selectedEntry_) ===
299 * Unloads the Gallery.
300 * @param {boolean} exiting True if the app is exiting.
302 Gallery.prototype.onUnload = function(exiting) {
303 if (this.metadataCacheObserverId_ !== null)
304 this.metadataCache_.removeObserver(this.metadataCacheObserverId_);
305 this.volumeManager_.removeEventListener(
306 'externally-unmounted', this.onExternallyUnmountedBound_);
307 this.slideMode_.onUnload(exiting);
314 Gallery.prototype.initDom_ = function() {
315 // Initialize the dialog label.
316 cr.ui.dialogs.BaseDialog.OK_LABEL = str('GALLERY_OK_LABEL');
317 cr.ui.dialogs.BaseDialog.CANCEL_LABEL = str('GALLERY_CANCEL_LABEL');
319 var content = document.querySelector('#content');
320 content.addEventListener('click', this.onContentClick_.bind(this));
322 this.header_ = document.querySelector('#header');
323 this.toolbar_ = document.querySelector('#toolbar');
325 var preventDefault = function(event) { event.preventDefault(); };
327 var minimizeButton = util.createChild(this.header_,
328 'minimize-button tool dimmable',
330 minimizeButton.tabIndex = -1;
331 minimizeButton.addEventListener('click', this.onMinimize_.bind(this));
332 minimizeButton.addEventListener('mousedown', preventDefault);
334 var maximizeButton = util.createChild(this.header_,
335 'maximize-button tool dimmable',
337 maximizeButton.tabIndex = -1;
338 maximizeButton.addEventListener('click', this.onMaximize_.bind(this));
339 maximizeButton.addEventListener('mousedown', preventDefault);
341 var closeButton = util.createChild(this.header_,
342 'close-button tool dimmable',
344 closeButton.tabIndex = -1;
345 closeButton.addEventListener('click', this.onClose_.bind(this));
346 closeButton.addEventListener('mousedown', preventDefault);
348 this.filenameSpacer_ = this.toolbar_.querySelector('.filename-spacer');
349 this.filenameEdit_ = util.createChild(this.filenameSpacer_,
352 this.filenameEdit_.setAttribute('type', 'text');
353 this.filenameEdit_.addEventListener('blur',
354 this.onFilenameEditBlur_.bind(this));
356 this.filenameEdit_.addEventListener('focus',
357 this.onFilenameFocus_.bind(this));
359 this.filenameEdit_.addEventListener('keydown',
360 this.onFilenameEditKeydown_.bind(this));
362 var middleSpacer = this.filenameSpacer_ =
363 this.toolbar_.querySelector('.middle-spacer');
364 var buttonSpacer = this.toolbar_.querySelector('button-spacer');
366 this.prompt_ = new ImageEditor.Prompt(this.container_, strf);
368 this.modeButton_ = this.toolbar_.querySelector('button.mode');
369 this.modeButton_.addEventListener('click', this.toggleMode_.bind(this, null));
371 this.mosaicMode_ = new MosaicMode(content,
373 this.selectionModel_,
375 this.toggleMode_.bind(this, null));
377 this.slideMode_ = new SlideMode(this.container_,
382 this.selectionModel_,
384 this.toggleMode_.bind(this),
387 this.slideMode_.addEventListener('image-displayed', function() {
388 cr.dispatchSimpleEvent(this, 'image-displayed');
390 this.slideMode_.addEventListener('image-saved', function() {
391 cr.dispatchSimpleEvent(this, 'image-saved');
394 var deleteButton = this.initToolbarButton_('delete', 'GALLERY_DELETE');
395 deleteButton.addEventListener('click', this.delete_.bind(this));
397 this.shareButton_ = this.initToolbarButton_('share', 'GALLERY_SHARE');
398 this.shareButton_.addEventListener(
399 'click', this.onShareButtonClick_.bind(this));
401 this.dataModel_.addEventListener('splice', this.onSplice_.bind(this));
402 this.dataModel_.addEventListener('content', this.onContentChange_.bind(this));
404 this.selectionModel_.addEventListener('change', this.onSelection_.bind(this));
405 this.slideMode_.addEventListener('useraction', this.onUserAction_.bind(this));
407 this.shareDialog_ = new ShareDialog(this.container_);
411 * Initializes a toolbar button.
413 * @param {string} className Class to add.
414 * @param {string} title Button title.
415 * @return {!HTMLElement} Newly created button.
418 Gallery.prototype.initToolbarButton_ = function(className, title) {
419 var button = this.toolbar_.querySelector('button.' + className);
420 button.title = str(title);
427 * @param {!Array.<Entry>} entries Array of entries.
428 * @param {!Array.<Entry>} selectedEntries Array of selected entries.
430 Gallery.prototype.load = function(entries, selectedEntries) {
431 // Obtains max chank size.
432 var maxChunkSize = 20;
433 var volumeInfo = this.volumeManager_.getVolumeInfo(entries[0]);
435 volumeInfo.volumeType === VolumeManagerCommon.VolumeType.MTP) {
438 if (volumeInfo.isReadOnly)
439 this.context_.readonlyDirName = volumeInfo.label;
441 // Make loading list.
443 for (var i = 0; i < entries.length; i++) {
444 var entry = entries[i];
445 entrySet[entry.toURL()] = {
451 for (var i = 0; i < selectedEntries.length; i++) {
452 var entry = selectedEntries[i];
453 entrySet[entry.toURL()] = {
459 var loadingList = [];
460 for (var url in entrySet) {
461 loadingList.push(entrySet[url]);
463 loadingList = loadingList.sort(function(a, b) {
464 if (a.selected && !b.selected)
466 else if (!a.selected && b.selected)
469 return a.index - b.index;
473 // Use the self variable capture-by-closure because it is faster than bind.
475 var loadChunk = function(firstChunk) {
477 var chunk = loadingList.splice(0, maxChunkSize);
481 return new Promise(function(fulfill) {
482 // Obtains metadata for chunk.
483 var entries = chunk.map(function(chunkItem) {
484 return chunkItem.entry;
486 self.metadataCache_.get(entries, Gallery.METADATA_TYPE, fulfill);
487 }).then(function(metadataList) {
488 if (chunk.length !== metadataList.length)
489 return Promise.reject('Failed to load metadata.');
491 // Add items to the model.
492 var items = chunk.map(function(chunkItem, index) {
493 var volumeInfo = self.volumeManager_.getVolumeInfo(chunkItem.entry);
494 var clonedMetadata = MetadataCache.cloneMetadata(metadataList[index]);
495 return new Gallery.Item(
500 /* readonly */ !!(volumeInfo && volumeInfo.isReadOnly));
502 self.dataModel_.push.apply(self.dataModel_, items);
504 // Apply the selection.
505 var selectionUpdated = false;
506 for (var i = 0; i < chunk.length; i++) {
507 if (!chunk[i].selected)
509 var index = self.dataModel_.indexOf(items[i]);
512 self.selectionModel_.setIndexSelected(index, true);
513 selectionUpdated = true;
515 if (selectionUpdated)
518 // Init modes after the first chunk is loaded.
520 // Determine the initial mode.
521 var shouldShowMosaic = selectedEntries.length > 1 ||
522 (self.context_.pageState &&
523 self.context_.pageState.gallery === 'mosaic');
524 self.setCurrentMode_(
525 shouldShowMosaic ? self.mosaicMode_ : self.slideMode_);
528 var mosaic = self.mosaicMode_.getMosaic();
531 // Do the initialization for each mode.
532 if (shouldShowMosaic) {
534 self.inactivityWatcher_.check(); // Show the toolbar.
535 cr.dispatchSimpleEvent(self, 'loaded');
537 self.slideMode_.enter(
540 // Flash the toolbar briefly to show it is there.
541 self.inactivityWatcher_.kick(Gallery.FIRST_FADE_TIMEOUT);
544 cr.dispatchSimpleEvent(self, 'loaded');
549 // Continue to load chunks.
550 return loadChunk(/* firstChunk */ false);
553 loadChunk(/* firstChunk */ true).catch(function(error) {
554 console.error(error.stack || error);
559 * Handles user's 'Close' action.
562 Gallery.prototype.onClose_ = function() {
563 this.executeWhenReady(this.context_.onClose);
567 * Handles user's 'Maximize' action (Escape or a click on the X icon).
570 Gallery.prototype.onMaximize_ = function() {
571 this.executeWhenReady(this.context_.onMaximize);
575 * Handles user's 'Maximize' action (Escape or a click on the X icon).
578 Gallery.prototype.onMinimize_ = function() {
579 this.executeWhenReady(this.context_.onMinimize);
583 * Executes a function when the editor is done with the modifications.
584 * @param {function} callback Function to execute.
586 Gallery.prototype.executeWhenReady = function(callback) {
587 this.currentMode_.executeWhenReady(callback);
591 * @return {Object} File browser private API.
593 Gallery.getFileBrowserPrivate = function() {
594 return chrome.fileBrowserPrivate || window.top.chrome.fileBrowserPrivate;
598 * @return {boolean} True if some tool is currently active.
600 Gallery.prototype.hasActiveTool = function() {
601 return (this.currentMode_ && this.currentMode_.hasActiveTool()) ||
606 * External user action event handler.
609 Gallery.prototype.onUserAction_ = function() {
610 // Show the toolbar and hide it after the default timeout.
611 this.inactivityWatcher_.kick();
615 * Sets the current mode, update the UI.
616 * @param {Object} mode Current mode.
619 Gallery.prototype.setCurrentMode_ = function(mode) {
620 if (mode !== this.slideMode_ && mode !== this.mosaicMode_)
621 console.error('Invalid Gallery mode');
623 this.currentMode_ = mode;
624 this.container_.setAttribute('mode', this.currentMode_.getName());
625 this.updateSelectionAndState_();
626 this.updateButtons_();
630 * Mode toggle event handler.
631 * @param {function=} opt_callback Callback.
632 * @param {Event=} opt_event Event that caused this call.
635 Gallery.prototype.toggleMode_ = function(opt_callback, opt_event) {
636 if (!this.modeButton_)
639 if (this.changingMode_) // Do not re-enter while changing the mode.
643 this.onUserAction_();
645 this.changingMode_ = true;
647 var onModeChanged = function() {
648 this.changingMode_ = false;
649 if (opt_callback) opt_callback();
652 var tileIndex = Math.max(0, this.selectionModel_.selectedIndex);
654 var mosaic = this.mosaicMode_.getMosaic();
655 var tileRect = mosaic.getTileRect(tileIndex);
657 if (this.currentMode_ === this.slideMode_) {
658 this.setCurrentMode_(this.mosaicMode_);
660 tileRect, this.slideMode_.getSelectedImageRect(), true /* instant */);
661 this.slideMode_.leave(
664 // Animate back to normal position.
670 this.setCurrentMode_(this.slideMode_);
671 this.slideMode_.enter(
674 // Animate to zoomed position.
675 mosaic.transform(tileRect, this.slideMode_.getSelectedImageRect());
683 * Deletes the selected items.
686 Gallery.prototype.delete_ = function() {
687 this.onUserAction_();
689 // Clone the sorted selected indexes array.
690 var indexesToRemove = this.selectionModel_.selectedIndexes.slice();
691 if (!indexesToRemove.length)
694 /* TODO(dgozman): Implement Undo delete, Remove the confirmation dialog. */
696 var itemsToRemove = this.getSelectedItems();
697 var plural = itemsToRemove.length > 1;
698 var param = plural ? itemsToRemove.length : itemsToRemove[0].getFileName();
700 function deleteNext() {
701 if (!itemsToRemove.length)
702 return; // All deleted.
704 var entry = itemsToRemove.pop().getEntry();
705 entry.remove(deleteNext, function() {
706 util.flog('Error deleting: ' + entry.name, deleteNext);
710 // Prevent the Gallery from handling Esc and Enter.
711 this.document_.body.removeEventListener('keydown', this.keyDownBound_);
712 var restoreListener = function() {
713 this.document_.body.addEventListener('keydown', this.keyDownBound_);
717 var confirm = new cr.ui.dialogs.ConfirmDialog(this.container_);
718 confirm.setOkLabel(str('DELETE_BUTTON_LABEL'));
719 confirm.show(strf(plural ?
720 'GALLERY_CONFIRM_DELETE_SOME' : 'GALLERY_CONFIRM_DELETE_ONE', param),
723 this.selectionModel_.unselectAll();
724 this.selectionModel_.leadIndex = -1;
725 // Remove items from the data model, starting from the highest index.
726 while (indexesToRemove.length)
727 this.dataModel_.splice(indexesToRemove.pop(), 1);
728 // Delete actual files.
732 // Restore the listener after a timeout so that ESC is processed.
733 setTimeout(restoreListener, 0);
738 * @return {Array.<Gallery.Item>} Current selection.
740 Gallery.prototype.getSelectedItems = function() {
741 return this.selectionModel_.selectedIndexes.map(
742 this.dataModel_.item.bind(this.dataModel_));
746 * @return {Array.<Entry>} Array of currently selected entries.
748 Gallery.prototype.getSelectedEntries = function() {
749 return this.selectionModel_.selectedIndexes.map(function(index) {
750 return this.dataModel_.item(index).getEntry();
755 * @return {?Gallery.Item} Current single selection.
757 Gallery.prototype.getSingleSelectedItem = function() {
758 var items = this.getSelectedItems();
759 if (items.length > 1) {
760 console.error('Unexpected multiple selection');
767 * Selection change event handler.
770 Gallery.prototype.onSelection_ = function() {
771 this.updateSelectionAndState_();
775 * Data model splice event handler.
778 Gallery.prototype.onSplice_ = function() {
779 this.selectionModel_.adjustLength(this.dataModel_.length);
783 * Content change event handler.
784 * @param {Event} event Event.
787 Gallery.prototype.onContentChange_ = function(event) {
788 var index = this.dataModel_.indexOf(event.item);
789 if (index !== this.selectionModel_.selectedIndex)
790 console.error('Content changed for unselected item');
791 this.updateSelectionAndState_();
797 * @param {Event} event Event.
800 Gallery.prototype.onKeyDown_ = function(event) {
801 if (this.currentMode_.onKeyDown(event))
804 switch (util.getKeyModifiers(event) + event.keyIdentifier) {
805 case 'U+0008': // Backspace.
806 // The default handler would call history.back and close the Gallery.
807 event.preventDefault();
810 case 'U+004D': // 'm' switches between Slide and Mosaic mode.
811 this.toggleMode_(null, event);
814 case 'U+0056': // 'v'
815 case 'MediaPlayPause':
816 this.slideMode_.startSlideshow(SlideMode.SLIDESHOW_INTERVAL_FIRST, event);
819 case 'U+007F': // Delete
820 case 'Shift-U+0033': // Shift+'3' (Delete key might be missing).
821 case 'U+0044': // 'd'
827 // Name box and rename support.
830 * Updates the UI related to the selected item and the persistent state.
834 Gallery.prototype.updateSelectionAndState_ = function() {
835 var numSelectedItems = this.selectionModel_.selectedIndexes.length;
836 var selectedEntryURL = null;
838 // If it's selecting something, update the variable values.
839 if (numSelectedItems) {
840 // Obtains selected item.
842 this.dataModel_.item(this.selectionModel_.selectedIndex);
843 this.selectedEntry_ = selectedItem.getEntry();
844 selectedEntryURL = this.selectedEntry_.toURL();
847 selectedItem.touch();
848 this.dataModel_.evictCache();
850 // Update the title and the display name.
851 if (numSelectedItems === 1) {
852 document.title = this.selectedEntry_.name;
853 this.filenameEdit_.disabled = selectedItem.isReadOnly();
854 this.filenameEdit_.value =
855 ImageUtil.getDisplayNameFromName(this.selectedEntry_.name);
856 this.shareButton_.hidden = !selectedItem.isOnDrive();
858 if (this.context_.curDirEntry) {
859 // If the Gallery was opened on search results the search query will not
860 // be recorded in the app state and the relaunch will just open the
861 // gallery in the curDirEntry directory.
862 document.title = this.context_.curDirEntry.name;
866 this.filenameEdit_.disabled = true;
867 this.filenameEdit_.value =
868 strf('GALLERY_ITEMS_SELECTED', numSelectedItems);
869 this.shareButton_.hidden = true;
873 this.filenameEdit_.value = '';
874 this.shareButton_.hidden = true;
878 null, // Keep the current directory.
879 selectedEntryURL, // Update the selection.
880 {gallery: (this.currentMode_ === this.mosaicMode_ ? 'mosaic' : 'slide')});
884 * Click event handler on filename edit box
887 Gallery.prototype.onFilenameFocus_ = function() {
888 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', true);
889 this.filenameEdit_.originalValue = this.filenameEdit_.value;
890 setTimeout(this.filenameEdit_.select.bind(this.filenameEdit_), 0);
891 this.onUserAction_();
895 * Blur event handler on filename edit box.
897 * @param {Event} event Blur event.
898 * @return {boolean} if default action should be prevented.
901 Gallery.prototype.onFilenameEditBlur_ = function(event) {
902 var item = this.getSingleSelectedItem();
904 var oldEntry = item.getEntry();
906 item.rename(this.filenameEdit_.value).then(function() {
907 var event = new Event('content');
909 event.oldEntry = oldEntry;
910 event.metadata = null; // Metadata unchanged.
911 this.dataModel_.dispatchEvent(event);
912 }.bind(this), function(error) {
913 if (error === 'NOT_CHANGED')
915 this.filenameEdit_.value =
916 ImageUtil.getDisplayNameFromName(item.getEntry().name);
917 this.filenameEdit_.focus();
918 if (typeof error === 'string')
919 this.prompt_.showStringAt('center', error, 5000);
921 return Promise.reject(error);
922 }.bind(this)).catch(function(error) {
923 console.error(error.stack || error);
927 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', false);
928 this.onUserAction_();
932 * Keydown event handler on filename edit box
935 Gallery.prototype.onFilenameEditKeydown_ = function() {
936 switch (event.keyCode) {
938 this.filenameEdit_.value = this.filenameEdit_.originalValue;
939 this.filenameEdit_.blur();
943 this.filenameEdit_.blur();
946 event.stopPropagation();
950 * @return {boolean} True if file renaming is currently in progress.
953 Gallery.prototype.isRenaming_ = function() {
954 return this.filenameSpacer_.hasAttribute('renaming');
958 * Content area click handler.
961 Gallery.prototype.onContentClick_ = function() {
962 this.filenameEdit_.blur();
966 * Share button handler.
969 Gallery.prototype.onShareButtonClick_ = function() {
970 var item = this.getSingleSelectedItem();
973 this.shareDialog_.show(item.getEntry(), function() {});
977 * Updates thumbnails.
980 Gallery.prototype.updateThumbnails_ = function() {
981 if (this.currentMode_ === this.slideMode_)
982 this.slideMode_.updateThumbnails();
984 if (this.mosaicMode_) {
985 var mosaic = this.mosaicMode_.getMosaic();
986 if (mosaic.isInitialized())
995 Gallery.prototype.updateButtons_ = function() {
996 if (this.modeButton_) {
998 this.currentMode_ === this.slideMode_ ? this.mosaicMode_ :
1000 this.modeButton_.title = str(oppositeMode.getTitle());
1005 * Singleton gallery.
1011 * Initialize the window.
1012 * @param {Object} backgroundComponents Background components.
1014 window.initialize = function(backgroundComponents) {
1015 window.loadTimeData.data = backgroundComponents.stringData;
1016 gallery = new Gallery(backgroundComponents.volumeManager);
1022 window.loadEntries = function(entries, selectedEntries) {
1023 gallery.load(entries, selectedEntries);