Add ability for NetLogLogger to gather data from more than just NetLog
[chromium-blink-merge.git] / ui / file_manager / gallery / js / gallery.js
blobdefb09da118c3b7dd954c1ea0b845abf4d9d216f
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 ContentMetadataProvider.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(), readonlyDirName: string,
24 * displayStringFunction: function(), loadTimeData: Object,
25 * 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 readonlyDirName: '',
44 displayStringFunction: function() { return ''; },
45 loadTimeData: {},
46 curDirEntry: null,
47 searchResults: null
49 this.container_ = queryRequiredElement(document, '.gallery');
50 this.document_ = document;
51 this.volumeManager_ = volumeManager;
52 /**
53 * @private {!MetadataModel}
54 * @const
56 this.metadataModel_ = MetadataModel.create(volumeManager);
57 /**
58 * @private {!ThumbnailModel}
59 * @const
61 this.thumbnailModel_ = new ThumbnailModel(this.metadataModel_);
62 this.selectedEntry_ = null;
63 this.onExternallyUnmountedBound_ = this.onExternallyUnmounted_.bind(this);
64 this.initialized_ = false;
66 this.dataModel_ = new GalleryDataModel(this.metadataModel_);
67 var downloadVolumeInfo = this.volumeManager_.getCurrentProfileVolumeInfo(
68 VolumeManagerCommon.VolumeType.DOWNLOADS);
69 downloadVolumeInfo.resolveDisplayRoot().then(function(entry) {
70 this.dataModel_.fallbackSaveDirectory = entry;
71 }.bind(this)).catch(function(error) {
72 console.error(
73 'Failed to obtain the fallback directory: ' + (error.stack || error));
74 });
75 this.selectionModel_ = new cr.ui.ListSelectionModel();
77 /**
78 * @type {(SlideMode|MosaicMode)}
79 * @private
81 this.currentMode_ = null;
83 /**
84 * @type {boolean}
85 * @private
87 this.changingMode_ = false;
89 // -----------------------------------------------------------------
90 // Initializes the UI.
92 // Initialize the dialog label.
93 cr.ui.dialogs.BaseDialog.OK_LABEL = str('GALLERY_OK_LABEL');
94 cr.ui.dialogs.BaseDialog.CANCEL_LABEL = str('GALLERY_CANCEL_LABEL');
96 var content = queryRequiredElement(document, '#content');
97 content.addEventListener('click', this.onContentClick_.bind(this));
99 this.header_ = queryRequiredElement(document, '#header');
100 this.toolbar_ = queryRequiredElement(document, '#toolbar');
102 var preventDefault = function(event) { event.preventDefault(); };
104 var minimizeButton = util.createChild(this.header_,
105 'minimize-button tool dimmable',
106 'button');
107 minimizeButton.tabIndex = -1;
108 minimizeButton.addEventListener('click', this.onMinimize_.bind(this));
109 minimizeButton.addEventListener('mousedown', preventDefault);
111 var maximizeButton = util.createChild(this.header_,
112 'maximize-button tool dimmable',
113 'button');
114 maximizeButton.tabIndex = -1;
115 maximizeButton.addEventListener('click', this.onMaximize_.bind(this));
116 maximizeButton.addEventListener('mousedown', preventDefault);
118 var closeButton = util.createChild(this.header_,
119 'close-button tool dimmable',
120 'button');
121 closeButton.tabIndex = -1;
122 closeButton.addEventListener('click', this.onClose_.bind(this));
123 closeButton.addEventListener('mousedown', preventDefault);
125 this.filenameSpacer_ = queryRequiredElement(this.toolbar_,
126 '.filename-spacer');
127 this.filenameEdit_ = util.createChild(this.filenameSpacer_,
128 'namebox', 'input');
130 this.filenameEdit_.setAttribute('type', 'text');
131 this.filenameEdit_.addEventListener('blur',
132 this.onFilenameEditBlur_.bind(this));
134 this.filenameEdit_.addEventListener('focus',
135 this.onFilenameFocus_.bind(this));
137 this.filenameEdit_.addEventListener('keydown',
138 this.onFilenameEditKeydown_.bind(this));
140 var middleSpacer = queryRequiredElement(this.toolbar_, '.middle-spacer');
141 var buttonSpacer = queryRequiredElement(this.toolbar_, '.button-spacer');
143 this.prompt_ = new ImageEditor.Prompt(this.container_, strf);
145 this.errorBanner_ = new ErrorBanner(this.container_);
147 this.modeButton_ = queryRequiredElement(this.toolbar_, 'button.mode');
148 this.modeButton_.addEventListener('click',
149 this.toggleMode_.bind(this, undefined));
151 this.mosaicMode_ = new MosaicMode(content,
152 this.errorBanner_,
153 this.dataModel_,
154 this.selectionModel_,
155 this.volumeManager_,
156 this.toggleMode_.bind(this, undefined));
158 this.slideMode_ = new SlideMode(this.container_,
159 content,
160 this.toolbar_,
161 this.prompt_,
162 this.errorBanner_,
163 this.dataModel_,
164 this.selectionModel_,
165 this.metadataModel_,
166 this.thumbnailModel_,
167 this.context_,
168 this.volumeManager_,
169 this.toggleMode_.bind(this),
170 str);
172 this.slideMode_.addEventListener('image-displayed', function() {
173 cr.dispatchSimpleEvent(this, 'image-displayed');
174 }.bind(this));
175 this.slideMode_.addEventListener('image-saved', function() {
176 cr.dispatchSimpleEvent(this, 'image-saved');
177 }.bind(this));
179 this.deleteButton_ = this.initToolbarButton_('delete', 'GALLERY_DELETE');
180 this.deleteButton_.addEventListener('click', this.delete_.bind(this));
182 this.shareButton_ = this.initToolbarButton_('share', 'GALLERY_SHARE');
183 this.shareButton_.addEventListener(
184 'click', this.onShareButtonClick_.bind(this));
186 this.dataModel_.addEventListener('splice', this.onSplice_.bind(this));
187 this.dataModel_.addEventListener('content', this.onContentChange_.bind(this));
189 this.selectionModel_.addEventListener('change', this.onSelection_.bind(this));
190 this.slideMode_.addEventListener('useraction', this.onUserAction_.bind(this));
192 this.shareDialog_ = new ShareDialog(this.container_);
194 // -----------------------------------------------------------------
195 // Initialize listeners.
197 this.keyDownBound_ = this.onKeyDown_.bind(this);
198 this.document_.body.addEventListener('keydown', this.keyDownBound_);
200 this.inactivityWatcher_ = new MouseInactivityWatcher(
201 this.container_, Gallery.FADE_TIMEOUT, this.hasActiveTool.bind(this));
203 // TODO(hirono): Add observer to handle thumbnail update.
204 this.volumeManager_.addEventListener(
205 'externally-unmounted', this.onExternallyUnmountedBound_);
206 // The 'pagehide' event is called when the app window is closed.
207 window.addEventListener('pagehide', this.onPageHide_.bind(this));
211 * Gallery extends cr.EventTarget.
213 Gallery.prototype.__proto__ = cr.EventTarget.prototype;
216 * Tools fade-out timeout in milliseconds.
217 * @const
218 * @type {number}
220 Gallery.FADE_TIMEOUT = 2000;
223 * First time tools fade-out timeout in milliseconds.
224 * @const
225 * @type {number}
227 Gallery.FIRST_FADE_TIMEOUT = 1000;
230 * Time until mosaic is initialized in the background. Used to make gallery
231 * in the slide mode load faster. In milliseconds.
232 * @const
233 * @type {number}
235 Gallery.MOSAIC_BACKGROUND_INIT_DELAY = 1000;
238 * Types of metadata Gallery uses (to query the metadata cache).
239 * @const
240 * @type {!Array<string>}
242 Gallery.PREFETCH_PROPERTY_NAMES =
243 ['imageWidth', 'imageHeight', 'size', 'present'];
246 * Closes gallery when a volume containing the selected item is unmounted.
247 * @param {!Event} event The unmount event.
248 * @private
250 Gallery.prototype.onExternallyUnmounted_ = function(event) {
251 if (!this.selectedEntry_)
252 return;
254 if (this.volumeManager_.getVolumeInfo(this.selectedEntry_) ===
255 event.volumeInfo) {
256 window.close();
261 * Unloads the Gallery.
262 * @private
264 Gallery.prototype.onPageHide_ = function() {
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>} selectedEntries Array of selected entries.
288 Gallery.prototype.load = function(selectedEntries) {
289 GalleryUtil.createEntrySet(selectedEntries).then(function(allEntries) {
290 this.loadInternal_(allEntries, selectedEntries);
291 }.bind(this));
295 * Loads the content.
297 * @param {!Array.<!Entry>} entries Array of entries.
298 * @param {!Array.<!Entry>} selectedEntries Array of selected entries.
299 * @private
301 Gallery.prototype.loadInternal_ = function(entries, selectedEntries) {
302 // Obtains max chank size.
303 var maxChunkSize = 20;
304 var volumeInfo = this.volumeManager_.getVolumeInfo(entries[0]);
305 if (volumeInfo) {
306 if (volumeInfo.volumeType === VolumeManagerCommon.VolumeType.MTP)
307 maxChunkSize = 1;
308 if (volumeInfo.isReadOnly)
309 this.context_.readonlyDirName = volumeInfo.label;
312 // Make loading list.
313 var entrySet = {};
314 for (var i = 0; i < entries.length; i++) {
315 var entry = entries[i];
316 entrySet[entry.toURL()] = {
317 entry: entry,
318 selected: false,
319 index: i
322 for (var i = 0; i < selectedEntries.length; i++) {
323 var entry = selectedEntries[i];
324 entrySet[entry.toURL()] = {
325 entry: entry,
326 selected: true,
327 index: i
330 var loadingList = [];
331 for (var url in entrySet) {
332 loadingList.push(entrySet[url]);
334 loadingList = loadingList.sort(function(a, b) {
335 if (a.selected && !b.selected)
336 return -1;
337 else if (!a.selected && b.selected)
338 return 1;
339 else
340 return a.index - b.index;
343 if (loadingList.length === 0) {
344 this.dataModel_.splice(0, this.dataModel_.length);
345 return;
348 // Load entries.
349 // Use the self variable capture-by-closure because it is faster than bind.
350 var self = this;
351 var thumbnailModel = new ThumbnailModel(this.metadataModel_);
352 var loadChunk = function(firstChunk) {
353 // Extract chunk.
354 var chunk = loadingList.splice(0, maxChunkSize);
355 if (!chunk.length)
356 return;
357 var entries = chunk.map(function(chunkItem) {
358 return chunkItem.entry;
360 var metadataPromise = self.metadataModel_.get(
361 entries, Gallery.PREFETCH_PROPERTY_NAMES);
362 var thumbnailPromise = thumbnailModel.get(entries);
363 return Promise.all([metadataPromise, thumbnailPromise]).then(
364 function(metadataLists) {
365 // Remove all the previous items if it's the first chunk.
366 // Do it here because prevent a flicker between removing all the items
367 // and adding new ones.
368 if (firstChunk) {
369 self.dataModel_.splice(0, self.dataModel_.length);
370 self.updateThumbnails_(); // Remove the caches.
373 // Add items to the model.
374 var items = [];
375 chunk.forEach(function(chunkItem, index) {
376 var locationInfo = self.volumeManager_.getLocationInfo(chunkItem.entry);
377 if (!locationInfo) // Skip the item, since gone.
378 return;
379 items.push(new Gallery.Item(
380 chunkItem.entry,
381 locationInfo,
382 metadataLists[0][index],
383 metadataLists[1][index],
384 /* original */ true));
386 self.dataModel_.push.apply(self.dataModel_, items);
388 // Apply the selection.
389 var selectionUpdated = false;
390 for (var i = 0; i < chunk.length; i++) {
391 if (!chunk[i].selected)
392 continue;
393 var index = self.dataModel_.indexOf(items[i]);
394 if (index < 0)
395 continue;
396 self.selectionModel_.setIndexSelected(index, true);
397 selectionUpdated = true;
399 if (selectionUpdated)
400 self.onSelection_();
402 // Init modes after the first chunk is loaded.
403 if (firstChunk && !self.initialized_) {
404 // Determine the initial mode.
405 var shouldShowMosaic = selectedEntries.length > 1 ||
406 (self.context_.pageState &&
407 self.context_.pageState.gallery === 'mosaic');
408 self.setCurrentMode_(
409 shouldShowMosaic ? self.mosaicMode_ : self.slideMode_);
411 // Init mosaic mode.
412 var mosaic = self.mosaicMode_.getMosaic();
413 mosaic.init();
415 // Do the initialization for each mode.
416 if (shouldShowMosaic) {
417 mosaic.show();
418 self.inactivityWatcher_.check(); // Show the toolbar.
419 cr.dispatchSimpleEvent(self, 'loaded');
420 } else {
421 self.slideMode_.enter(
422 null,
423 function() {
424 // Flash the toolbar briefly to show it is there.
425 self.inactivityWatcher_.kick(Gallery.FIRST_FADE_TIMEOUT);
427 function() {
428 cr.dispatchSimpleEvent(self, 'loaded');
431 self.initialized_ = true;
434 // Continue to load chunks.
435 return loadChunk(/* firstChunk */ false);
438 loadChunk(/* firstChunk */ true).catch(function(error) {
439 console.error(error.stack || error);
444 * Handles user's 'Close' action.
445 * @private
447 Gallery.prototype.onClose_ = function() {
448 this.executeWhenReady(this.context_.onClose);
452 * Handles user's 'Maximize' action (Escape or a click on the X icon).
453 * @private
455 Gallery.prototype.onMaximize_ = function() {
456 this.executeWhenReady(this.context_.onMaximize);
460 * Handles user's 'Maximize' action (Escape or a click on the X icon).
461 * @private
463 Gallery.prototype.onMinimize_ = function() {
464 this.executeWhenReady(this.context_.onMinimize);
468 * Executes a function when the editor is done with the modifications.
469 * @param {function()} callback Function to execute.
471 Gallery.prototype.executeWhenReady = function(callback) {
472 this.currentMode_.executeWhenReady(callback);
476 * @return {!Object} File manager private API.
478 Gallery.getFileManagerPrivate = function() {
479 return chrome.fileManagerPrivate || window.top.chrome.fileManagerPrivate;
483 * @return {boolean} True if some tool is currently active.
485 Gallery.prototype.hasActiveTool = function() {
486 return (this.currentMode_ && this.currentMode_.hasActiveTool()) ||
487 this.isRenaming_();
491 * External user action event handler.
492 * @private
494 Gallery.prototype.onUserAction_ = function() {
495 // Show the toolbar and hide it after the default timeout.
496 this.inactivityWatcher_.kick();
500 * Sets the current mode, update the UI.
501 * @param {!(SlideMode|MosaicMode)} mode Current mode.
502 * @private
504 Gallery.prototype.setCurrentMode_ = function(mode) {
505 if (mode !== this.slideMode_ && mode !== this.mosaicMode_)
506 console.error('Invalid Gallery mode');
508 this.currentMode_ = mode;
509 this.container_.setAttribute('mode', this.currentMode_.getName());
510 this.updateSelectionAndState_();
511 this.updateButtons_();
515 * Mode toggle event handler.
516 * @param {function()=} opt_callback Callback.
517 * @param {Event=} opt_event Event that caused this call.
518 * @private
520 Gallery.prototype.toggleMode_ = function(opt_callback, opt_event) {
521 if (!this.modeButton_)
522 return;
524 if (this.changingMode_) // Do not re-enter while changing the mode.
525 return;
527 if (opt_event)
528 this.onUserAction_();
530 this.changingMode_ = true;
532 var onModeChanged = function() {
533 this.changingMode_ = false;
534 if (opt_callback) opt_callback();
535 }.bind(this);
537 var tileIndex = Math.max(0, this.selectionModel_.selectedIndex);
539 var mosaic = this.mosaicMode_.getMosaic();
540 var tileRect = mosaic.getTileRect(tileIndex);
542 if (this.currentMode_ === this.slideMode_) {
543 this.setCurrentMode_(this.mosaicMode_);
544 mosaic.transform(
545 tileRect, this.slideMode_.getSelectedImageRect(), true /* instant */);
546 this.slideMode_.leave(
547 tileRect,
548 function() {
549 // Animate back to normal position.
550 mosaic.transform(null, null);
551 mosaic.show();
552 onModeChanged();
553 }.bind(this));
554 } else {
555 this.setCurrentMode_(this.slideMode_);
556 this.slideMode_.enter(
557 tileRect,
558 function() {
559 // Animate to zoomed position.
560 mosaic.transform(tileRect, this.slideMode_.getSelectedImageRect());
561 mosaic.hide();
562 }.bind(this),
563 onModeChanged);
568 * Deletes the selected items.
569 * @private
571 Gallery.prototype.delete_ = function() {
572 this.onUserAction_();
574 // Clone the sorted selected indexes array.
575 var indexesToRemove = this.selectionModel_.selectedIndexes.slice();
576 if (!indexesToRemove.length)
577 return;
579 /* TODO(dgozman): Implement Undo delete, Remove the confirmation dialog. */
581 var itemsToRemove = this.getSelectedItems();
582 var plural = itemsToRemove.length > 1;
583 var param = plural ? itemsToRemove.length : itemsToRemove[0].getFileName();
585 function deleteNext() {
586 if (!itemsToRemove.length)
587 return; // All deleted.
589 var entry = itemsToRemove.pop().getEntry();
590 entry.remove(deleteNext, function() {
591 console.error('Error deleting: ' + entry.name);
592 deleteNext();
596 // Prevent the Gallery from handling Esc and Enter.
597 this.document_.body.removeEventListener('keydown', this.keyDownBound_);
598 var restoreListener = function() {
599 this.document_.body.addEventListener('keydown', this.keyDownBound_);
600 }.bind(this);
603 var confirm = new cr.ui.dialogs.ConfirmDialog(this.container_);
604 confirm.setOkLabel(str('DELETE_BUTTON_LABEL'));
605 confirm.show(strf(plural ?
606 'GALLERY_CONFIRM_DELETE_SOME' : 'GALLERY_CONFIRM_DELETE_ONE', param),
607 function() {
608 restoreListener();
609 this.selectionModel_.unselectAll();
610 this.selectionModel_.leadIndex = -1;
611 // Remove items from the data model, starting from the highest index.
612 while (indexesToRemove.length)
613 this.dataModel_.splice(indexesToRemove.pop(), 1);
614 // Delete actual files.
615 deleteNext();
616 }.bind(this),
617 function() {
618 // Restore the listener after a timeout so that ESC is processed.
619 setTimeout(restoreListener, 0);
621 null);
625 * @return {!Array.<Gallery.Item>} Current selection.
627 Gallery.prototype.getSelectedItems = function() {
628 return this.selectionModel_.selectedIndexes.map(
629 this.dataModel_.item.bind(this.dataModel_));
633 * @return {!Array.<Entry>} Array of currently selected entries.
635 Gallery.prototype.getSelectedEntries = function() {
636 return this.selectionModel_.selectedIndexes.map(function(index) {
637 return this.dataModel_.item(index).getEntry();
638 }.bind(this));
642 * @return {?Gallery.Item} Current single selection.
644 Gallery.prototype.getSingleSelectedItem = function() {
645 var items = this.getSelectedItems();
646 if (items.length > 1) {
647 console.error('Unexpected multiple selection');
648 return null;
650 return items[0];
654 * Selection change event handler.
655 * @private
657 Gallery.prototype.onSelection_ = function() {
658 this.updateSelectionAndState_();
662 * Data model splice event handler.
663 * @private
665 Gallery.prototype.onSplice_ = function() {
666 this.selectionModel_.adjustLength(this.dataModel_.length);
667 this.selectionModel_.selectedIndexes =
668 this.selectionModel_.selectedIndexes.filter(function(index) {
669 return 0 <= index && index < this.dataModel_.length;
670 }.bind(this));
674 * Content change event handler.
675 * @param {!Event} event Event.
676 * @private
678 Gallery.prototype.onContentChange_ = function(event) {
679 var index = this.dataModel_.indexOf(event.item);
680 if (index !== this.selectionModel_.selectedIndex)
681 console.error('Content changed for unselected item');
682 this.updateSelectionAndState_();
686 * Keydown handler.
688 * @param {!Event} event Event.
689 * @private
691 Gallery.prototype.onKeyDown_ = function(event) {
692 if (this.currentMode_.onKeyDown(event))
693 return;
695 switch (util.getKeyModifiers(event) + event.keyIdentifier) {
696 case 'U+0008': // Backspace.
697 // The default handler would call history.back and close the Gallery.
698 event.preventDefault();
699 break;
701 case 'U+004D': // 'm' switches between Slide and Mosaic mode.
702 this.toggleMode_(undefined, event);
703 break;
705 case 'U+0056': // 'v'
706 case 'MediaPlayPause':
707 this.slideMode_.startSlideshow(SlideMode.SLIDESHOW_INTERVAL_FIRST, event);
708 break;
710 case 'U+007F': // Delete
711 case 'Shift-U+0033': // Shift+'3' (Delete key might be missing).
712 case 'U+0044': // 'd'
713 this.delete_();
714 break;
716 case 'U+001B': // Escape
717 window.close();
718 break;
722 // Name box and rename support.
725 * Updates the UI related to the selected item and the persistent state.
727 * @private
729 Gallery.prototype.updateSelectionAndState_ = function() {
730 var numSelectedItems = this.selectionModel_.selectedIndexes.length;
731 var selectedEntryURL = null;
733 // If it's selecting something, update the variable values.
734 if (numSelectedItems) {
735 // Delete button is available when all images are NOT readOnly.
736 this.deleteButton_.disabled = !this.selectionModel_.selectedIndexes
737 .every(function(i) {
738 return !this.dataModel_.item(i).getLocationInfo().isReadOnly;
739 }, this);
741 // Obtains selected item.
742 var selectedItem =
743 this.dataModel_.item(this.selectionModel_.selectedIndex);
744 this.selectedEntry_ = selectedItem.getEntry();
745 selectedEntryURL = this.selectedEntry_.toURL();
747 // Update cache.
748 selectedItem.touch();
749 this.dataModel_.evictCache();
751 // Update the title and the display name.
752 if (numSelectedItems === 1) {
753 document.title = this.selectedEntry_.name;
754 this.filenameEdit_.disabled = selectedItem.getLocationInfo().isReadOnly;
755 this.filenameEdit_.value =
756 ImageUtil.getDisplayNameFromName(this.selectedEntry_.name);
757 this.shareButton_.hidden = !selectedItem.getLocationInfo().isDriveBased;
758 } else {
759 if (this.context_.curDirEntry) {
760 // If the Gallery was opened on search results the search query will not
761 // be recorded in the app state and the relaunch will just open the
762 // gallery in the curDirEntry directory.
763 document.title = this.context_.curDirEntry.name;
764 } else {
765 document.title = '';
767 this.filenameEdit_.disabled = true;
768 this.filenameEdit_.value =
769 strf('GALLERY_ITEMS_SELECTED', numSelectedItems);
770 this.shareButton_.hidden = true;
772 } else {
773 document.title = '';
774 this.filenameEdit_.disabled = true;
775 this.deleteButton_.disabled = true;
776 this.filenameEdit_.value = '';
777 this.shareButton_.hidden = true;
780 util.updateAppState(
781 null, // Keep the current directory.
782 selectedEntryURL, // Update the selection.
783 {gallery: (this.currentMode_ === this.mosaicMode_ ? 'mosaic' : 'slide')});
787 * Click event handler on filename edit box
788 * @private
790 Gallery.prototype.onFilenameFocus_ = function() {
791 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', true);
792 this.filenameEdit_.originalValue = this.filenameEdit_.value;
793 setTimeout(this.filenameEdit_.select.bind(this.filenameEdit_), 0);
794 this.onUserAction_();
798 * Blur event handler on filename edit box.
800 * @param {!Event} event Blur event.
801 * @private
803 Gallery.prototype.onFilenameEditBlur_ = function(event) {
804 var item = this.getSingleSelectedItem();
805 if (item) {
806 var oldEntry = item.getEntry();
808 item.rename(this.filenameEdit_.value).then(function() {
809 var event = new Event('content');
810 event.item = item;
811 event.oldEntry = oldEntry;
812 event.thumbnailChanged = false;
813 this.dataModel_.dispatchEvent(event);
814 }.bind(this), function(error) {
815 if (error === 'NOT_CHANGED')
816 return Promise.resolve();
817 this.filenameEdit_.value =
818 ImageUtil.getDisplayNameFromName(item.getEntry().name);
819 this.filenameEdit_.focus();
820 if (typeof error === 'string')
821 this.prompt_.showStringAt('center', error, 5000);
822 else
823 return Promise.reject(error);
824 }.bind(this)).catch(function(error) {
825 console.error(error.stack || error);
829 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', false);
830 this.onUserAction_();
834 * Keydown event handler on filename edit box
835 * @param {!Event} event A keyboard event.
836 * @private
838 Gallery.prototype.onFilenameEditKeydown_ = function(event) {
839 event = assertInstanceof(event, KeyboardEvent);
840 switch (event.keyCode) {
841 case 27: // Escape
842 this.filenameEdit_.value = this.filenameEdit_.originalValue;
843 this.filenameEdit_.blur();
844 break;
846 case 13: // Enter
847 this.filenameEdit_.blur();
848 break;
850 event.stopPropagation();
854 * @return {boolean} True if file renaming is currently in progress.
855 * @private
857 Gallery.prototype.isRenaming_ = function() {
858 return this.filenameSpacer_.hasAttribute('renaming');
862 * Content area click handler.
863 * @private
865 Gallery.prototype.onContentClick_ = function() {
866 this.filenameEdit_.blur();
870 * Share button handler.
871 * @private
873 Gallery.prototype.onShareButtonClick_ = function() {
874 var item = this.getSingleSelectedItem();
875 if (!item)
876 return;
877 this.shareDialog_.showEntry(item.getEntry(), function() {});
881 * Updates thumbnails.
882 * @private
884 Gallery.prototype.updateThumbnails_ = function() {
885 if (this.currentMode_ === this.slideMode_)
886 this.slideMode_.updateThumbnails();
888 if (this.mosaicMode_) {
889 var mosaic = this.mosaicMode_.getMosaic();
890 if (mosaic.isInitialized())
891 mosaic.reload();
896 * Updates buttons.
897 * @private
899 Gallery.prototype.updateButtons_ = function() {
900 if (this.modeButton_) {
901 var oppositeMode =
902 this.currentMode_ === this.slideMode_ ? this.mosaicMode_ :
903 this.slideMode_;
904 this.modeButton_.title = str(oppositeMode.getTitle());
909 * Enters the debug mode.
911 Gallery.prototype.debugMe = function() {
912 this.mosaicMode_.debugMe();
916 * Singleton gallery.
917 * @type {Gallery}
919 var gallery = null;
922 * (Re-)loads entries.
924 function reload() {
925 initializePromise.then(function() {
926 util.URLsToEntries(window.appState.urls, function(entries) {
927 gallery.load(entries);
933 * Promise to initialize the load time data.
934 * @type {!Promise}
936 var loadTimeDataPromise = new Promise(function(fulfill, reject) {
937 chrome.fileManagerPrivate.getStrings(function(strings) {
938 window.loadTimeData.data = strings;
939 i18nTemplate.process(document, loadTimeData);
940 fulfill(true);
945 * Promise to initialize volume manager.
946 * @type {!Promise}
948 var volumeManagerPromise = new Promise(function(fulfill, reject) {
949 var volumeManager = new VolumeManagerWrapper(
950 VolumeManagerWrapper.NonNativeVolumeStatus.ENABLED);
951 volumeManager.ensureInitialized(fulfill.bind(null, volumeManager));
955 * Promise to initialize both the volume manager and the load time data.
956 * @type {!Promise}
958 var initializePromise =
959 Promise.all([loadTimeDataPromise, volumeManagerPromise]).
960 then(function(args) {
961 var volumeManager = args[1];
962 gallery = new Gallery(volumeManager);
965 // Loads entries.
966 initializePromise.then(reload);
969 * Enteres the debug mode.
971 window.debugMe = function() {
972 initializePromise.then(function() {
973 gallery.debugMe();