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 ContentProvider
.WORKER_SCRIPT
= '/js/metadata_worker.js';
12 * Gallery for viewing and editing image files.
14 * @param {!VolumeManager} volumeManager The VolumeManager instance of the
19 function Gallery(volumeManager
) {
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: *}}
28 * TODO(yawano): curDirEntry and searchResults seem not to be used.
29 * Investigate them and remove them if possible.
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())
41 onMinimize: function() { chrome
.app
.window
.current().minimize(); },
42 onAppRegionChanged: function() {},
43 metadataCache
: MetadataCache
.createFull(volumeManager
),
45 displayStringFunction: function() { return ''; },
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
) {
66 'Failed to obtain the fallback directory: ' + (error
.stack
|| error
));
68 this.selectionModel_
= new cr
.ui
.ListSelectionModel();
71 * @type {(SlideMode|MosaicMode)}
74 this.currentMode_
= null;
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',
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',
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',
114 closeButton
.tabIndex
= -1;
115 closeButton
.addEventListener('click', this.onClose_
.bind(this));
116 closeButton
.addEventListener('mousedown', preventDefault
);
118 this.filenameSpacer_
= queryRequiredElement(this.toolbar_
,
120 this.filenameEdit_
= util
.createChild(this.filenameSpacer_
,
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
,
147 this.selectionModel_
,
149 this.toggleMode_
.bind(this, undefined));
151 this.slideMode_
= new SlideMode(this.container_
,
157 this.selectionModel_
,
160 this.toggleMode_
.bind(this),
163 this.slideMode_
.addEventListener('image-displayed', function() {
164 cr
.dispatchSimpleEvent(this, 'image-displayed');
166 this.slideMode_
.addEventListener('image-saved', function() {
167 cr
.dispatchSimpleEvent(this, 'image-saved');
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
,
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.
219 Gallery
.FADE_TIMEOUT
= 2000;
222 * First time tools fade-out timeout in milliseconds.
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.
234 Gallery
.MOSAIC_BACKGROUND_INIT_DELAY
= 1000;
237 * Types of metadata Gallery uses (to query the metadata cache).
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.
248 Gallery
.prototype.onExternallyUnmounted_ = function(event
) {
249 if (!this.selectedEntry_
)
252 if (this.volumeManager_
.getVolumeInfo(this.selectedEntry_
) ===
259 * Unloads the Gallery.
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.
277 Gallery
.prototype.initToolbarButton_ = function(className
, title
) {
278 var button
= queryRequiredElement(this.toolbar_
, 'button.' + className
);
279 button
.title
= str(title
);
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]);
294 volumeInfo
.volumeType
=== VolumeManagerCommon
.VolumeType
.MTP
) {
297 if (volumeInfo
.isReadOnly
)
298 this.context_
.readonlyDirName
= volumeInfo
.label
;
300 // Make loading list.
302 for (var i
= 0; i
< entries
.length
; i
++) {
303 var entry
= entries
[i
];
304 entrySet
[entry
.toURL()] = {
310 for (var i
= 0; i
< selectedEntries
.length
; i
++) {
311 var entry
= selectedEntries
[i
];
312 entrySet
[entry
.toURL()] = {
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
)
325 else if (!a
.selected
&& b
.selected
)
328 return a
.index
- b
.index
;
332 // Use the self variable capture-by-closure because it is faster than bind.
334 var loadChunk = function(firstChunk
) {
336 var chunk
= loadingList
.splice(0, maxChunkSize
);
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.
352 chunk
.forEach(function(chunkItem
, index
) {
353 var locationInfo
= self
.volumeManager_
.getLocationInfo(chunkItem
.entry
);
354 if (!locationInfo
) // Skip the item, since gone.
356 var clonedMetadata
= MetadataCache
.cloneMetadata(metadataList
[index
]);
357 items
.push(new Gallery
.Item(
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
)
371 var index
= self
.dataModel_
.indexOf(items
[i
]);
374 self
.selectionModel_
.setIndexSelected(index
, true);
375 selectionUpdated
= true;
377 if (selectionUpdated
)
380 // Init modes after the first chunk is loaded.
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_
);
390 var mosaic
= self
.mosaicMode_
.getMosaic();
393 // Do the initialization for each mode.
394 if (shouldShowMosaic
) {
396 self
.inactivityWatcher_
.check(); // Show the toolbar.
397 cr
.dispatchSimpleEvent(self
, 'loaded');
399 self
.slideMode_
.enter(
402 // Flash the toolbar briefly to show it is there.
403 self
.inactivityWatcher_
.kick(Gallery
.FIRST_FADE_TIMEOUT
);
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.
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).
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).
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()) ||
468 * External user action event handler.
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.
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.
497 Gallery
.prototype.toggleMode_ = function(opt_callback
, opt_event
) {
498 if (!this.modeButton_
)
501 if (this.changingMode_
) // Do not re-enter while changing the mode.
505 this.onUserAction_();
507 this.changingMode_
= true;
509 var onModeChanged = function() {
510 this.changingMode_
= false;
511 if (opt_callback
) opt_callback();
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_
);
522 tileRect
, this.slideMode_
.getSelectedImageRect(), true /* instant */);
523 this.slideMode_
.leave(
526 // Animate back to normal position.
527 mosaic
.transform(null, null);
532 this.setCurrentMode_(this.slideMode_
);
533 this.slideMode_
.enter(
536 // Animate to zoomed position.
537 mosaic
.transform(tileRect
, this.slideMode_
.getSelectedImageRect());
545 * Deletes the selected items.
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
)
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
);
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_
);
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
),
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.
595 // Restore the listener after a timeout so that ESC is processed.
596 setTimeout(restoreListener
, 0);
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();
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');
631 * Selection change event handler.
634 Gallery
.prototype.onSelection_ = function() {
635 this.updateSelectionAndState_();
639 * Data model splice event handler.
642 Gallery
.prototype.onSplice_ = function() {
643 this.selectionModel_
.adjustLength(this.dataModel_
.length
);
647 * Content change event handler.
648 * @param {!Event} event Event.
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_();
661 * @param {!Event} event Event.
664 Gallery
.prototype.onKeyDown_ = function(event
) {
665 if (this.currentMode_
.onKeyDown(event
))
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();
674 case 'U+004D': // 'm' switches between Slide and Mosaic mode.
675 this.toggleMode_(undefined, event
);
678 case 'U+0056': // 'v'
679 case 'MediaPlayPause':
680 this.slideMode_
.startSlideshow(SlideMode
.SLIDESHOW_INTERVAL_FIRST
, event
);
683 case 'U+007F': // Delete
684 case 'Shift-U+0033': // Shift+'3' (Delete key might be missing).
685 case 'U+0044': // 'd'
689 case 'U+001B': // Escape
695 // Name box and rename support.
698 * Updates the UI related to the selected item and the persistent state.
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
711 return !this.dataModel_
.item(i
).getLocationInfo().isReadOnly
;
714 // Obtains selected item.
716 this.dataModel_
.item(this.selectionModel_
.selectedIndex
);
717 this.selectedEntry_
= selectedItem
.getEntry();
718 selectedEntryURL
= this.selectedEntry_
.toURL();
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
;
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
;
740 this.filenameEdit_
.disabled
= true;
741 this.filenameEdit_
.value
=
742 strf('GALLERY_ITEMS_SELECTED', numSelectedItems
);
743 this.shareButton_
.hidden
= true;
747 this.filenameEdit_
.disabled
= true;
748 this.deleteButton_
.disabled
= true;
749 this.filenameEdit_
.value
= '';
750 this.shareButton_
.hidden
= true;
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
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.
776 Gallery
.prototype.onFilenameEditBlur_ = function(event
) {
777 var item
= this.getSingleSelectedItem();
779 var oldEntry
= item
.getEntry();
781 item
.rename(this.filenameEdit_
.value
).then(function() {
782 var event
= new Event('content');
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);
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.
811 Gallery
.prototype.onFilenameEditKeydown_ = function(event
) {
812 event
= assertInstanceof(event
, KeyboardEvent
);
813 switch (event
.keyCode
) {
815 this.filenameEdit_
.value
= this.filenameEdit_
.originalValue
;
816 this.filenameEdit_
.blur();
820 this.filenameEdit_
.blur();
823 event
.stopPropagation();
827 * @return {boolean} True if file renaming is currently in progress.
830 Gallery
.prototype.isRenaming_ = function() {
831 return this.filenameSpacer_
.hasAttribute('renaming');
835 * Content area click handler.
838 Gallery
.prototype.onContentClick_ = function() {
839 this.filenameEdit_
.blur();
843 * Share button handler.
846 Gallery
.prototype.onShareButtonClick_ = function() {
847 var item
= this.getSingleSelectedItem();
850 this.shareDialog_
.show(item
.getEntry(), function() {});
854 * Updates thumbnails.
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())
872 Gallery
.prototype.updateButtons_ = function() {
873 if (this.modeButton_
) {
875 this.currentMode_
=== this.slideMode_
? this.mosaicMode_
:
877 this.modeButton_
.title
= str(oppositeMode
.getTitle());
882 * Enters the debug mode.
884 Gallery
.prototype.debugMe = function() {
885 this.mosaicMode_
.debugMe();
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
);
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() {