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.
6 * Overrided metadata worker's path.
9 ContentMetadataProvider.WORKER_SCRIPT = '/js/metadata_worker.js';
12 * Gallery for viewing and editing image files.
14 * @param {!VolumeManagerWrapper} volumeManager
18 function Gallery(volumeManager) {
20 * @type {{appWindow: chrome.app.window.AppWindow, readonlyDirName: string,
21 * displayStringFunction: function(), loadTimeData: Object}}
25 appWindow: chrome.app.window.current(),
27 displayStringFunction: function() { return ''; },
30 this.container_ = queryRequiredElement('.gallery');
31 this.document_ = document;
32 this.volumeManager_ = volumeManager;
34 * @private {!MetadataModel}
37 this.metadataModel_ = MetadataModel.create(volumeManager);
39 * @private {!ThumbnailModel}
42 this.thumbnailModel_ = new ThumbnailModel(this.metadataModel_);
43 this.selectedEntry_ = null;
44 this.onExternallyUnmountedBound_ = this.onExternallyUnmounted_.bind(this);
45 this.initialized_ = false;
47 this.dataModel_ = new GalleryDataModel(this.metadataModel_);
48 var downloadVolumeInfo = this.volumeManager_.getCurrentProfileVolumeInfo(
49 VolumeManagerCommon.VolumeType.DOWNLOADS);
50 downloadVolumeInfo.resolveDisplayRoot().then(function(entry) {
51 this.dataModel_.fallbackSaveDirectory = entry;
52 }.bind(this)).catch(function(error) {
54 'Failed to obtain the fallback directory: ' + (error.stack || error));
56 this.selectionModel_ = new cr.ui.ListSelectionModel();
59 * @type {(SlideMode|ThumbnailMode)}
62 this.currentMode_ = null;
68 this.changingMode_ = false;
70 // -----------------------------------------------------------------
71 // Initializes the UI.
73 // Initialize the dialog label.
74 cr.ui.dialogs.BaseDialog.OK_LABEL = str('GALLERY_OK_LABEL');
75 cr.ui.dialogs.BaseDialog.CANCEL_LABEL = str('GALLERY_CANCEL_LABEL');
77 var content = getRequiredElement('content');
78 content.addEventListener('click', this.onContentClick_.bind(this));
80 this.topToolbar_ = getRequiredElement('top-toolbar');
81 this.bottomToolbar_ = getRequiredElement('bottom-toolbar');
83 this.filenameSpacer_ = queryRequiredElement('.filename-spacer',
87 * @private {HTMLInputElement}
90 this.filenameEdit_ = /** @type {HTMLInputElement} */
91 (queryRequiredElement('input', this.filenameSpacer_));
93 this.filenameCanvas_ = document.createElement('canvas');
94 this.filenameCanvasContext_ = this.filenameCanvas_.getContext('2d');
96 // Set font style of canvas context to same font style with rename field.
97 var filenameEditComputedStyle = window.getComputedStyle(this.filenameEdit_);
98 this.filenameCanvasContext_.font = filenameEditComputedStyle.font;
100 this.filenameEdit_.addEventListener('blur',
101 this.onFilenameEditBlur_.bind(this));
102 this.filenameEdit_.addEventListener('focus',
103 this.onFilenameFocus_.bind(this));
104 this.filenameEdit_.addEventListener('input',
105 this.resizeRenameField_.bind(this));
106 this.filenameEdit_.addEventListener('keydown',
107 this.onFilenameEditKeydown_.bind(this));
109 var buttonSpacer = queryRequiredElement('.button-spacer', this.topToolbar_);
111 this.prompt_ = new ImageEditor.Prompt(this.container_, strf);
113 this.errorBanner_ = new ErrorBanner(this.container_);
116 * @private {!HTMLElement}
119 this.modeSwitchButton_ = queryRequiredElement('button.mode',
121 GalleryUtil.decorateMouseFocusHandling(this.modeSwitchButton_);
122 this.modeSwitchButton_.addEventListener('click',
123 this.onModeSwitchButtonClicked_.bind(this));
126 * @private {!PaperRipple}
128 this.modeSwitchButtonRipple_ = /** @type {!PaperRipple} */
129 (queryRequiredElement('paper-ripple', this.modeSwitchButton_));
132 * @private {!DimmableUIController}
135 this.dimmableUIController_ = new DimmableUIController(this.container_);
137 this.thumbnailMode_ = new ThumbnailMode(
138 assertInstanceof(document.querySelector('.thumbnail-view'), HTMLElement),
141 this.selectionModel_,
142 this.onChangeToSlideMode_.bind(this));
143 this.thumbnailMode_.hide();
145 this.slideMode_ = new SlideMode(this.container_,
152 this.selectionModel_,
154 this.thumbnailModel_,
157 this.toggleMode_.bind(this),
159 this.dimmableUIController_);
160 this.slideMode_.addEventListener('image-displayed', function() {
161 cr.dispatchSimpleEvent(this, 'image-displayed');
165 * @private {!HTMLElement}
168 this.deleteButton_ = queryRequiredElement(
169 'paper-button.delete', this.topToolbar_);
170 this.deleteButton_.addEventListener('click', this.delete_.bind(this));
173 * @private {!HTMLElement}
176 this.slideshowButton_ = queryRequiredElement('paper-button.slideshow',
180 * @private {!HTMLElement}
183 this.shareButton_ = queryRequiredElement(
184 'paper-button.share', this.topToolbar_);
185 this.shareButton_.addEventListener(
186 'click', this.onShareButtonClick_.bind(this));
188 this.dataModel_.addEventListener('splice', this.onSplice_.bind(this));
189 this.dataModel_.addEventListener('content', this.onContentChange_.bind(this));
191 this.selectionModel_.addEventListener('change', this.onSelection_.bind(this));
192 this.slideMode_.addEventListener('useraction', this.onUserAction_.bind(this));
194 this.shareDialog_ = new ShareDialog(this.container_);
196 // -----------------------------------------------------------------
197 // Initialize listeners.
199 this.keyDownBound_ = this.onKeyDown_.bind(this);
200 this.document_.body.addEventListener('keydown', this.keyDownBound_);
202 // TODO(hirono): Add observer to handle thumbnail update.
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));
208 window.addEventListener('resize', this.resizeRenameField_.bind(this));
210 // We must call this method after elements of all tools have been attached to
212 this.dimmableUIController_.setTools(document.querySelectorAll('.tool'));
216 * Gallery extends cr.EventTarget.
218 Gallery.prototype.__proto__ = cr.EventTarget.prototype;
221 * Tools fade-out timeout in milliseconds.
225 Gallery.FADE_TIMEOUT = 2000;
228 * First time tools fade-out timeout in milliseconds.
232 Gallery.FIRST_FADE_TIMEOUT = 1000;
235 * Time until mosaic is initialized in the background. Used to make gallery
236 * in the slide mode load faster. In milliseconds.
240 Gallery.MOSAIC_BACKGROUND_INIT_DELAY = 1000;
243 * Types of metadata Gallery uses (to query the metadata cache).
245 * @type {!Array<string>}
247 Gallery.PREFETCH_PROPERTY_NAMES =
248 ['imageWidth', 'imageHeight', 'imageRotation', 'size', 'present'];
251 * Closes gallery when a volume containing the selected item is unmounted.
252 * @param {!Event} event The unmount event.
255 Gallery.prototype.onExternallyUnmounted_ = function(event) {
256 if (!this.selectedEntry_)
259 if (this.volumeManager_.getVolumeInfo(this.selectedEntry_) ===
266 * Unloads the Gallery.
269 Gallery.prototype.onPageHide_ = function() {
270 this.volumeManager_.removeEventListener(
271 'externally-unmounted', this.onExternallyUnmountedBound_);
272 this.volumeManager_.dispose();
278 * @param {!Array<!Entry>} selectedEntries Array of selected entries.
280 Gallery.prototype.load = function(selectedEntries) {
281 GalleryUtil.createEntrySet(selectedEntries).then(function(allEntries) {
282 this.loadInternal_(allEntries, selectedEntries);
289 * @param {!Array<!FileEntry>} entries Array of entries.
290 * @param {!Array<!FileEntry>} selectedEntries Array of selected entries.
293 Gallery.prototype.loadInternal_ = function(entries, selectedEntries) {
294 // Add the entries to data model.
296 for (var i = 0; i < entries.length; i++) {
297 var locationInfo = this.volumeManager_.getLocationInfo(entries[i]);
298 if (!locationInfo) // Skip the item, since gone.
300 items.push(new Gallery.Item(
307 this.dataModel_.splice(0, this.dataModel_.length);
308 this.updateThumbnails_(); // Remove the caches.
310 GalleryDataModel.prototype.splice.apply(
311 this.dataModel_, [0, 0].concat(items));
313 // Apply the selection.
314 var selectedSet = {};
315 for (var i = 0; i < selectedEntries.length; i++) {
316 selectedSet[selectedEntries[i].toURL()] = true;
318 for (var i = 0; i < items.length; i++) {
319 if (!selectedSet[items[i].getEntry().toURL()])
321 this.selectionModel_.setIndexSelected(i, true);
325 // Obtains max chank size.
326 var maxChunkSize = 20;
327 var volumeInfo = this.volumeManager_.getVolumeInfo(entries[0]);
329 if (GalleryUtil.isOnMTPVolume(entries[0], this.volumeManager_))
332 if (volumeInfo.isReadOnly ||
333 GalleryUtil.isOnMTPVolume(entries[0], this.volumeManager_)) {
334 this.context_.readonlyDirName = volumeInfo.label;
338 // If items are empty, stop initialization.
339 if (items.length === 0) {
340 this.dataModel_.splice(0, this.dataModel_.length);
345 // Use the self variable capture-by-closure because it is faster than bind.
347 var thumbnailModel = new ThumbnailModel(this.metadataModel_);
348 var loadChunk = function(firstChunk) {
350 var chunk = items.splice(0, maxChunkSize);
353 var entries = chunk.map(function(chunkItem) {
354 return chunkItem.getEntry();
356 var metadataPromise = self.metadataModel_.get(
357 entries, Gallery.PREFETCH_PROPERTY_NAMES);
358 var thumbnailPromise = thumbnailModel.get(entries);
359 return Promise.all([metadataPromise, thumbnailPromise]).then(
360 function(metadataLists) {
361 // Add items to the model.
362 chunk.forEach(function(chunkItem, index) {
363 chunkItem.setMetadataItem(metadataLists[0][index]);
364 chunkItem.setThumbnailMetadataItem(metadataLists[1][index]);
366 var event = new Event('content');
367 event.item = chunkItem;
368 event.oldEntry = chunkItem.getEntry();
369 event.thumbnailChanged = true;
370 self.dataModel_.dispatchEvent(event);
373 // Init modes after the first chunk is loaded.
374 if (firstChunk && !self.initialized_) {
375 // Determine the initial mode.
376 var shouldShowThumbnail = selectedEntries.length > 1 ||
377 (self.context_.pageState &&
378 self.context_.pageState.gallery === 'thumbnail');
379 self.setCurrentMode_(
380 shouldShowThumbnail ? self.thumbnailMode_ : self.slideMode_);
382 // Do the initialization for each mode.
383 if (shouldShowThumbnail) {
384 self.thumbnailMode_.show();
385 cr.dispatchSimpleEvent(self, 'loaded');
387 self.slideMode_.enter(
390 // Flash the toolbar briefly to show it is there.
391 self.dimmableUIController_.kick(Gallery.FIRST_FADE_TIMEOUT);
394 cr.dispatchSimpleEvent(self, 'loaded');
397 self.initialized_ = true;
400 // Continue to load chunks.
401 return loadChunk(/* firstChunk */ false);
404 loadChunk(/* firstChunk */ true).catch(function(error) {
405 console.error(error.stack || error);
410 * @return {boolean} True if some tool is currently active.
412 Gallery.prototype.hasActiveTool = function() {
413 return (this.currentMode_ && this.currentMode_.hasActiveTool()) ||
418 * External user action event handler.
421 Gallery.prototype.onUserAction_ = function() {
422 // Show the toolbar and hide it after the default timeout.
423 this.dimmableUIController_.kick();
427 * Sets the current mode, update the UI.
428 * @param {!(SlideMode|ThumbnailMode)} mode Current mode.
431 * TODO(yawano): Since this method is confusing with changeCurrentMode_. Rename
432 * or remove this method.
434 Gallery.prototype.setCurrentMode_ = function(mode) {
435 if (mode !== this.slideMode_ && mode !== this.thumbnailMode_)
436 console.error('Invalid Gallery mode');
438 this.currentMode_ = mode;
439 this.container_.setAttribute('mode', this.currentMode_.getName());
440 this.dimmableUIController_.setDisabled(this.currentMode_ !== this.slideMode_);
441 this.updateSelectionAndState_();
445 * Handles click event of mode switch button.
446 * @param {!Event} event An event.
449 Gallery.prototype.onModeSwitchButtonClicked_ = function(event) {
450 this.modeSwitchButtonRipple_.simulatedRipple();
451 this.toggleMode_(undefined /* callback */, event);
455 * Change to slide mode.
458 Gallery.prototype.onChangeToSlideMode_ = function() {
459 if (this.modeSwitchButton_.disabled)
462 this.changeCurrentMode_(this.slideMode_);
466 * Change current mode.
467 * @param {!(SlideMode|ThumbnailMode)} mode Target mode.
468 * @param {Event=} opt_event Event that caused this call.
471 Gallery.prototype.changeCurrentMode_ = function(mode, opt_event) {
472 return new Promise(function(fulfill, reject) {
473 // Do not re-enter while changing the mode.
474 if (this.currentMode_ === mode || this.changingMode_) {
480 this.onUserAction_();
482 this.changingMode_ = true;
484 var onModeChanged = function() {
485 this.changingMode_ = false;
489 var thumbnailIndex = Math.max(0, this.selectionModel_.selectedIndex);
490 var thumbnailRect = ImageRect.createFromBounds(
491 this.thumbnailMode_.getThumbnailRect(thumbnailIndex));
493 if (mode === this.thumbnailMode_) {
494 this.setCurrentMode_(this.thumbnailMode_);
495 this.slideMode_.leave(
498 // Show thumbnail mode and perform animation.
499 this.thumbnailMode_.show();
500 var fromRect = this.slideMode_.getSelectedImageRect();
502 this.thumbnailMode_.performEnterAnimation(
503 thumbnailIndex, fromRect);
505 this.thumbnailMode_.focus();
509 this.bottomToolbar_.hidden = true;
511 // TODO(yawano): Make animation smooth. With this implementation,
512 // animation starts after the image is fully loaded.
513 this.setCurrentMode_(this.slideMode_);
514 this.slideMode_.enter(
517 // Animate to zoomed position.
518 this.thumbnailMode_.hide();
521 this.bottomToolbar_.hidden = false;
527 * Mode toggle event handler.
528 * @param {function()=} opt_callback Callback.
529 * @param {Event=} opt_event Event that caused this call.
532 Gallery.prototype.toggleMode_ = function(opt_callback, opt_event) {
533 // If it's in editing, leave edit mode.
534 if (this.slideMode_.isEditing())
535 this.slideMode_.toggleEditor();
537 var targetMode = this.currentMode_ === this.slideMode_ ?
538 this.thumbnailMode_ : this.slideMode_;
540 this.changeCurrentMode_(targetMode, opt_event).then(function() {
547 * Deletes the selected items.
550 Gallery.prototype.delete_ = function() {
551 this.onUserAction_();
553 // Clone the sorted selected indexes array.
554 var indexesToRemove = this.selectionModel_.selectedIndexes.slice();
555 if (!indexesToRemove.length)
558 /* TODO(dgozman): Implement Undo delete, Remove the confirmation dialog. */
560 var itemsToRemove = this.getSelectedItems();
561 var plural = itemsToRemove.length > 1;
562 var param = plural ? itemsToRemove.length : itemsToRemove[0].getFileName();
564 function deleteNext() {
565 if (!itemsToRemove.length)
566 return; // All deleted.
568 var entry = itemsToRemove.pop().getEntry();
569 entry.remove(deleteNext, function() {
570 console.error('Error deleting: ' + entry.name);
575 // Prevent the Gallery from handling Esc and Enter.
576 this.document_.body.removeEventListener('keydown', this.keyDownBound_);
577 var restoreListener = function() {
578 this.document_.body.addEventListener('keydown', this.keyDownBound_);
582 var confirm = new cr.ui.dialogs.ConfirmDialog(this.container_);
583 confirm.setOkLabel(str('DELETE_BUTTON_LABEL'));
584 confirm.show(strf(plural ?
585 'GALLERY_CONFIRM_DELETE_SOME' : 'GALLERY_CONFIRM_DELETE_ONE', param),
588 this.selectionModel_.unselectAll();
589 this.selectionModel_.leadIndex = -1;
590 // Remove items from the data model, starting from the highest index.
591 while (indexesToRemove.length)
592 this.dataModel_.splice(indexesToRemove.pop(), 1);
593 // Delete actual files.
597 // Restore the listener after a timeout so that ESC is processed.
598 setTimeout(restoreListener, 0);
604 * @return {!Array<Gallery.Item>} Current selection.
606 Gallery.prototype.getSelectedItems = function() {
607 return this.selectionModel_.selectedIndexes.map(
608 this.dataModel_.item.bind(this.dataModel_));
612 * @return {!Array<Entry>} Array of currently selected entries.
614 Gallery.prototype.getSelectedEntries = function() {
615 return this.selectionModel_.selectedIndexes.map(function(index) {
616 return this.dataModel_.item(index).getEntry();
621 * @return {?Gallery.Item} Current single selection.
623 Gallery.prototype.getSingleSelectedItem = function() {
624 var items = this.getSelectedItems();
625 if (items.length > 1) {
626 console.error('Unexpected multiple selection');
633 * Selection change event handler.
636 Gallery.prototype.onSelection_ = function() {
637 this.updateSelectionAndState_();
641 * Data model splice event handler.
644 Gallery.prototype.onSplice_ = function() {
645 this.selectionModel_.adjustLength(this.dataModel_.length);
646 this.selectionModel_.selectedIndexes =
647 this.selectionModel_.selectedIndexes.filter(function(index) {
648 return 0 <= index && index < this.dataModel_.length;
651 // Disable mode switch button if there is no image.
652 this.modeSwitchButton_.disabled = this.dataModel_.length === 0;
656 * Content change event handler.
657 * @param {!Event} event Event.
660 Gallery.prototype.onContentChange_ = function(event) {
661 this.updateSelectionAndState_();
667 * @param {!Event} event
670 Gallery.prototype.onKeyDown_ = function(event) {
671 var keyString = util.getKeyModifiers(event) + event.keyIdentifier;
673 // Handle debug shortcut keys.
675 case 'Ctrl-Shift-U+0049': // Ctrl+Shift+I
676 chrome.fileManagerPrivate.openInspector('normal');
678 case 'Ctrl-Shift-U+004A': // Ctrl+Shift+J
679 chrome.fileManagerPrivate.openInspector('console');
681 case 'Ctrl-Shift-U+0043': // Ctrl+Shift+C
682 chrome.fileManagerPrivate.openInspector('element');
684 case 'Ctrl-Shift-U+0042': // Ctrl+Shift+B
685 chrome.fileManagerPrivate.openInspector('background');
689 // Do not capture keys when share dialog is shown.
690 if (this.shareDialog_.isShowing())
693 // Show UIs when user types any key.
694 this.dimmableUIController_.kick();
696 // Handle mode specific shortcut keys.
697 if (this.currentMode_.onKeyDown(event)) {
698 event.preventDefault();
702 // Handle application wide shortcut keys.
704 case 'U+0008': // Backspace.
705 // The default handler would call history.back and close the Gallery.
706 event.preventDefault();
709 case 'U+004D': // 'm' switches between Slide and Mosaic mode.
710 if (!this.modeSwitchButton_.disabled)
711 this.toggleMode_(undefined, event);
714 case 'U+0056': // 'v'
715 case 'MediaPlayPause':
716 if (!this.slideshowButton_.disabled) {
717 this.slideMode_.startSlideshow(
718 SlideMode.SLIDESHOW_INTERVAL_FIRST, event);
722 case 'U+007F': // Delete
723 case 'Shift-U+0033': // Shift+'3' (Delete key might be missing).
724 case 'U+0044': // 'd'
725 if (!this.deleteButton_.disabled)
729 case 'U+001B': // Escape
735 // Name box and rename support.
738 * Updates the UI related to the selected item and the persistent state.
742 Gallery.prototype.updateSelectionAndState_ = function() {
743 var numSelectedItems = this.selectionModel_.selectedIndexes.length;
744 var selectedEntryURL = null;
746 // If it's selecting something, update the variable values.
747 if (numSelectedItems) {
748 // Enable slideshow button.
749 this.slideshowButton_.disabled = false;
751 // Delete button is available when all images are NOT readOnly.
752 this.deleteButton_.disabled = !this.selectionModel_.selectedIndexes
754 return !this.dataModel_.item(i).getLocationInfo().isReadOnly;
757 // Obtains selected item.
759 this.dataModel_.item(this.selectionModel_.selectedIndex);
760 this.selectedEntry_ = selectedItem.getEntry();
761 selectedEntryURL = this.selectedEntry_.toURL();
764 selectedItem.touch();
765 this.dataModel_.evictCache();
767 // Update the title and the display name.
768 if (numSelectedItems === 1) {
769 document.title = this.selectedEntry_.name;
770 this.filenameEdit_.disabled = selectedItem.getLocationInfo().isReadOnly;
771 this.filenameEdit_.value =
772 ImageUtil.getDisplayNameFromName(this.selectedEntry_.name);
773 this.resizeRenameField_();
775 this.shareButton_.disabled = !selectedItem.getLocationInfo().isDriveBased;
777 if (this.context_.curDirEntry) {
778 // If the Gallery was opened on search results the search query will not
779 // be recorded in the app state and the relaunch will just open the
780 // gallery in the curDirEntry directory.
781 document.title = this.context_.curDirEntry.name;
785 this.filenameEdit_.disabled = true;
786 this.filenameEdit_.value =
787 strf('GALLERY_ITEMS_SELECTED', numSelectedItems);
788 this.resizeRenameField_();
790 this.shareButton_.disabled = true;
794 this.filenameEdit_.disabled = true;
795 this.filenameEdit_.value = '';
796 this.resizeRenameField_();
798 this.deleteButton_.disabled = true;
799 this.slideshowButton_.disabled = true;
800 this.shareButton_.disabled = true;
804 null, // Keep the current directory.
805 selectedEntryURL, // Update the selection.
807 gallery: (this.currentMode_ === this.thumbnailMode_ ?
808 'thumbnail' : 'slide')
813 * Click event handler on filename edit box
816 Gallery.prototype.onFilenameFocus_ = function() {
817 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', true);
818 this.filenameEdit_.originalValue = this.filenameEdit_.value;
819 setTimeout(this.filenameEdit_.select.bind(this.filenameEdit_), 0);
820 this.onUserAction_();
824 * Blur event handler on filename edit box.
826 * @param {!Event} event Blur event.
829 Gallery.prototype.onFilenameEditBlur_ = function(event) {
830 var item = this.getSingleSelectedItem();
832 var oldEntry = item.getEntry();
834 item.rename(this.filenameEdit_.value).then(function() {
835 var event = new Event('content');
837 event.oldEntry = oldEntry;
838 event.thumbnailChanged = false;
839 this.dataModel_.dispatchEvent(event);
840 }.bind(this), function(error) {
841 if (error === 'NOT_CHANGED')
842 return Promise.resolve();
843 this.filenameEdit_.value =
844 ImageUtil.getDisplayNameFromName(item.getEntry().name);
845 this.resizeRenameField_();
846 this.filenameEdit_.focus();
847 if (typeof error === 'string')
848 this.prompt_.showStringAt('center', error, 5000);
850 return Promise.reject(error);
851 }.bind(this)).catch(function(error) {
852 console.error(error.stack || error);
856 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', false);
857 this.onUserAction_();
861 * Minimum width of rename field.
864 Gallery.MIN_WIDTH_RENAME_FIELD = 160; // px
867 * End padding for rename field.
870 Gallery.END_PADDING_RENAME_FIELD = 20; // px
873 * Resize rename field depending on its content.
876 Gallery.prototype.resizeRenameField_ = function() {
877 var size = this.filenameCanvasContext_.measureText(this.filenameEdit_.value);
879 var width = Math.min(Math.max(
880 size.width + Gallery.END_PADDING_RENAME_FIELD,
881 Gallery.MIN_WIDTH_RENAME_FIELD), window.innerWidth / 2);
883 this.filenameEdit_.style.width = width + 'px';
887 * Keydown event handler on filename edit box
888 * @param {!Event} event A keyboard event.
891 Gallery.prototype.onFilenameEditKeydown_ = function(event) {
892 event = assertInstanceof(event, KeyboardEvent);
893 switch (event.keyCode) {
895 this.filenameEdit_.value = this.filenameEdit_.originalValue;
896 this.resizeRenameField_();
897 this.filenameEdit_.blur();
901 this.filenameEdit_.blur();
904 event.stopPropagation();
908 * @return {boolean} True if file renaming is currently in progress.
911 Gallery.prototype.isRenaming_ = function() {
912 return this.filenameSpacer_.hasAttribute('renaming');
916 * Content area click handler.
919 Gallery.prototype.onContentClick_ = function() {
920 this.filenameEdit_.blur();
924 * Share button handler.
927 Gallery.prototype.onShareButtonClick_ = function() {
928 var item = this.getSingleSelectedItem();
931 this.shareDialog_.showEntry(item.getEntry(), function() {});
935 * Updates thumbnails.
938 Gallery.prototype.updateThumbnails_ = function() {
939 if (this.currentMode_ === this.slideMode_)
940 this.slideMode_.updateThumbnails();
950 * (Re-)loads entries.
953 initializePromise.then(function() {
954 util.URLsToEntries(window.appState.urls, function(entries) {
955 gallery.load(entries);
961 * Promise to initialize the load time data.
964 var loadTimeDataPromise = new Promise(function(fulfill, reject) {
965 chrome.fileManagerPrivate.getStrings(function(strings) {
966 window.loadTimeData.data = strings;
967 i18nTemplate.process(document, loadTimeData);
973 * Promise to initialize volume manager.
976 var volumeManagerPromise = new Promise(function(fulfill, reject) {
977 var volumeManager = new VolumeManagerWrapper(
978 VolumeManagerWrapper.NonNativeVolumeStatus.ENABLED);
979 volumeManager.ensureInitialized(fulfill.bind(null, volumeManager));
983 * Promise to initialize both the volume manager and the load time data.
986 var initializePromise =
987 Promise.all([loadTimeDataPromise, volumeManagerPromise]).
988 then(function(args) {
989 var volumeManager = args[1];
990 gallery = new Gallery(volumeManager);
994 initializePromise.then(reload);