blob: 305def2b307b82b506aefd25ac6e265da851bb06 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Called from the main frame when unloading.
* @param {boolean=} opt_exiting True if the app is exiting.
*/
function unload(opt_exiting) { Gallery.instance.onUnload(opt_exiting); }
/**
* Overrided metadata worker's path.
* @type {string}
* @const
*/
ContentProvider.WORKER_SCRIPT = '/js/metadata_worker.js';
/**
* Data model for gallery.
*
* @constructor
* @extends {cr.ui.ArrayDataModel}
*/
function GalleryDataModel() {
cr.ui.ArrayDataModel.call(this, []);
this.metadataCache_ = null;
}
GalleryDataModel.prototype = {
__proto__: cr.ui.ArrayDataModel.prototype
};
/**
* Initializes the data model.
*
* @param {MetadataCache} metadataCache Metadata cache.
* @param {Array.<FileEntry>} entries Image entries.
* @return {Promise} Promise to be fulfilled with after initialization.
*/
GalleryDataModel.prototype.initialize = function(metadataCache, entries) {
// Store metadata cache.
this.metadataCache_ = metadataCache;
// Obtain metadata.
var metadataPromise = new Promise(function(fulfill) {
this.metadataCache_.get(entries, Gallery.METADATA_TYPE, fulfill);
}.bind(this));
// Initialize the gallery by using the metadata.
return metadataPromise.then(function(metadata) {
// Check the length of metadata.
if (entries.length !== metadata.length)
return Promise.reject('Failed to obtain metadata for the entries.');
// Obtains items.
var items = entries.map(function(entry, i) {
var clonedMetadata = MetadataCache.cloneMetadata(metadata[i]);
return new Gallery.Item(
entry, clonedMetadata, metadataCache, /* original */ true);
});
// Update the models.
this.push.apply(this, items);
}.bind(this));
};
/**
* Saves new image.
*
* @param {Gallery.Item} item Original gallery item.
* @param {Canvas} canvas Canvas containing new image.
* @param {boolean} overwrite Whether to overwrite the image to the item or not.
* @return {Promise} Promise to be fulfilled with when the operation completes.
*/
GalleryDataModel.prototype.saveItem = function(item, canvas, overwrite) {
var oldEntry = item.getEntry();
var oldMetadata = item.getMetadata();
var metadataEncoder = ImageEncoder.encodeMetadata(
item.getMetadata(), canvas, 1 /* quality */);
var newMetadata = ContentProvider.ConvertContentMetadata(
metadataEncoder.getMetadata(),
MetadataCache.cloneMetadata(item.getMetadata()));
if (newMetadata.filesystem)
newMetadata.filesystem.modificationTime = new Date();
if (newMetadata.drive)
newMetadata.drive.present = true;
return new Promise(function(fulfill, reject) {
item.saveToFile(
null,
overwrite,
canvas,
metadataEncoder,
function(success) {
if (!success) {
reject('Failed to save the image.');
return;
}
// The item's entry is updated to the latest entry. Update metadata.
item.setMetadata(newMetadata);
// Current entry is updated.
// Dispatch an event.
var event = new Event('content');
event.item = item;
event.oldEntry = oldEntry;
event.metadata = newMetadata;
this.dispatchEvent(event);
if (util.isSameEntry(oldEntry, item.getEntry())) {
// Need an update of metdataCache.
this.metadataCache_.set(
item.getEntry(),
Gallery.METADATA_TYPE,
newMetadata);
} else {
// New entry is added and the item now tracks it.
// Add another item for the old entry.
var anotherItem = new Gallery.Item(
oldEntry, oldMetadata, this.metadataCache_, item.isOriginal());
// The item must be added behind the existing item so that it does
// not change the index of the existing item.
// TODO(hirono): Update the item index of the selection model
// correctly.
this.splice(this.indexOf(item) + 1, 0, anotherItem);
}
fulfill();
}.bind(this));
}.bind(this));
};
/**
* Gallery for viewing and editing image files.
*
* @param {!VolumeManager} volumeManager The VolumeManager instance of the
* system.
* @constructor
*/
function Gallery(volumeManager) {
this.context_ = {
appWindow: chrome.app.window.current(),
onClose: function() { close(); },
onMaximize: function() {
var appWindow = chrome.app.window.current();
if (appWindow.isMaximized())
appWindow.restore();
else
appWindow.maximize();
},
onMinimize: function() { chrome.app.window.current().minimize(); },
onAppRegionChanged: function() {},
metadataCache: MetadataCache.createFull(volumeManager),
shareActions: [],
readonlyDirName: '',
displayStringFunction: function() { return ''; },
loadTimeData: {}
};
this.container_ = document.querySelector('.gallery');
this.document_ = document;
this.metadataCache_ = this.context_.metadataCache;
this.volumeManager_ = volumeManager;
this.selectedEntry_ = null;
this.metadataCacheObserverId_ = null;
this.onExternallyUnmountedBound_ = this.onExternallyUnmounted_.bind(this);
this.dataModel_ = new GalleryDataModel();
this.selectionModel_ = new cr.ui.ListSelectionModel();
this.initDom_();
this.initListeners_();
}
/**
* Gallery extends cr.EventTarget.
*/
Gallery.prototype.__proto__ = cr.EventTarget.prototype;
/**
* Tools fade-out timeout in milliseconds.
* @const
* @type {number}
*/
Gallery.FADE_TIMEOUT = 3000;
/**
* First time tools fade-out timeout in milliseconds.
* @const
* @type {number}
*/
Gallery.FIRST_FADE_TIMEOUT = 1000;
/**
* Time until mosaic is initialized in the background. Used to make gallery
* in the slide mode load faster. In miiliseconds.
* @const
* @type {number}
*/
Gallery.MOSAIC_BACKGROUND_INIT_DELAY = 1000;
/**
* Types of metadata Gallery uses (to query the metadata cache).
* @const
* @type {string}
*/
Gallery.METADATA_TYPE = 'thumbnail|filesystem|media|streaming|drive';
/**
* Initializes listeners.
* @private
*/
Gallery.prototype.initListeners_ = function() {
this.keyDownBound_ = this.onKeyDown_.bind(this);
this.document_.body.addEventListener('keydown', this.keyDownBound_);
this.inactivityWatcher_ = new MouseInactivityWatcher(
this.container_, Gallery.FADE_TIMEOUT, this.hasActiveTool.bind(this));
// Search results may contain files from different subdirectories so
// the observer is not going to work.
if (!this.context_.searchResults && this.context_.curDirEntry) {
this.metadataCacheObserverId_ = this.metadataCache_.addObserver(
this.context_.curDirEntry,
MetadataCache.CHILDREN,
'thumbnail',
this.updateThumbnails_.bind(this));
}
this.volumeManager_.addEventListener(
'externally-unmounted', this.onExternallyUnmountedBound_);
};
/**
* Closes gallery when a volume containing the selected item is unmounted.
* @param {!Event} event The unmount event.
* @private
*/
Gallery.prototype.onExternallyUnmounted_ = function(event) {
if (!this.selectedEntry_)
return;
if (this.volumeManager_.getVolumeInfo(this.selectedEntry_) ===
event.volumeInfo) {
close();
}
};
/**
* Unloads the Gallery.
* @param {boolean} exiting True if the app is exiting.
*/
Gallery.prototype.onUnload = function(exiting) {
if (this.metadataCacheObserverId_ !== null)
this.metadataCache_.removeObserver(this.metadataCacheObserverId_);
this.volumeManager_.removeEventListener(
'externally-unmounted', this.onExternallyUnmountedBound_);
this.slideMode_.onUnload(exiting);
};
/**
* Initializes DOM UI
* @private
*/
Gallery.prototype.initDom_ = function() {
// Initialize the dialog label.
cr.ui.dialogs.BaseDialog.OK_LABEL = str('GALLERY_OK_LABEL');
cr.ui.dialogs.BaseDialog.CANCEL_LABEL = str('GALLERY_CANCEL_LABEL');
var content = document.querySelector('#content');
content.addEventListener('click', this.onContentClick_.bind(this));
this.header_ = document.querySelector('#header');
this.toolbar_ = document.querySelector('#toolbar');
var preventDefault = function(event) { event.preventDefault(); };
var minimizeButton = util.createChild(this.header_,
'minimize-button tool dimmable',
'button');
minimizeButton.tabIndex = -1;
minimizeButton.addEventListener('click', this.onMinimize_.bind(this));
minimizeButton.addEventListener('mousedown', preventDefault);
var maximizeButton = util.createChild(this.header_,
'maximize-button tool dimmable',
'button');
maximizeButton.tabIndex = -1;
maximizeButton.addEventListener('click', this.onMaximize_.bind(this));
maximizeButton.addEventListener('mousedown', preventDefault);
var closeButton = util.createChild(this.header_,
'close-button tool dimmable',
'button');
closeButton.tabIndex = -1;
closeButton.addEventListener('click', this.onClose_.bind(this));
closeButton.addEventListener('mousedown', preventDefault);
this.filenameSpacer_ = this.toolbar_.querySelector('.filename-spacer');
this.filenameEdit_ = util.createChild(this.filenameSpacer_,
'namebox', 'input');
this.filenameEdit_.setAttribute('type', 'text');
this.filenameEdit_.addEventListener('blur',
this.onFilenameEditBlur_.bind(this));
this.filenameEdit_.addEventListener('focus',
this.onFilenameFocus_.bind(this));
this.filenameEdit_.addEventListener('keydown',
this.onFilenameEditKeydown_.bind(this));
var middleSpacer = this.filenameSpacer_ =
this.toolbar_.querySelector('.middle-spacer');
var buttonSpacer = this.toolbar_.querySelector('button-spacer');
this.prompt_ = new ImageEditor.Prompt(this.container_, str);
this.modeButton_ = this.toolbar_.querySelector('button.mode');
this.modeButton_.addEventListener('click', this.toggleMode_.bind(this, null));
this.mosaicMode_ = new MosaicMode(content,
this.dataModel_,
this.selectionModel_,
this.volumeManager_,
this.toggleMode_.bind(this, null));
this.slideMode_ = new SlideMode(this.container_,
content,
this.toolbar_,
this.prompt_,
this.dataModel_,
this.selectionModel_,
this.context_,
this.toggleMode_.bind(this),
str);
this.slideMode_.addEventListener('image-displayed', function() {
cr.dispatchSimpleEvent(this, 'image-displayed');
}.bind(this));
this.slideMode_.addEventListener('image-saved', function() {
cr.dispatchSimpleEvent(this, 'image-saved');
}.bind(this));
var deleteButton = this.initToolbarButton_('delete', 'GALLERY_DELETE');
deleteButton.addEventListener('click', this.delete_.bind(this));
this.shareButton_ = this.initToolbarButton_('share', 'GALLERY_SHARE');
this.shareButton_.setAttribute('disabled', '');
this.shareButton_.addEventListener('click', this.toggleShare_.bind(this));
this.shareMenu_ = util.createChild(this.container_, 'share-menu');
this.shareMenu_.hidden = true;
util.createChild(this.shareMenu_, 'bubble-point');
this.dataModel_.addEventListener('splice', this.onSplice_.bind(this));
this.dataModel_.addEventListener('content', this.onContentChange_.bind(this));
this.selectionModel_.addEventListener('change', this.onSelection_.bind(this));
this.slideMode_.addEventListener('useraction', this.onUserAction_.bind(this));
};
/**
* Initializes a toolbar button.
*
* @param {string} className Class to add.
* @param {string} title Button title.
* @return {!HTMLElement} Newly created button.
* @private
*/
Gallery.prototype.initToolbarButton_ = function(className, title) {
var button = this.toolbar_.querySelector('button.' + className);
button.title = str(title);
return button;
};
/**
* Loads the content.
*
* @param {!Array.<Entry>} entries Array of entries.
* @param {!Array.<Entry>} selectedEntries Array of selected entries.
*/
Gallery.prototype.load = function(entries, selectedEntries) {
this.dataModel_.initialize(this.metadataCache_, entries).then(function() {
// Apply selection.
this.selectionModel_.adjustLength(this.dataModel_.length);
var entryIndexesByURLs = {};
for (var index = 0; index < entries.length; index++) {
entryIndexesByURLs[entries[index].toURL()] = index;
}
for (var i = 0; i !== selectedEntries.length; i++) {
var selectedIndex = entryIndexesByURLs[selectedEntries[i].toURL()];
if (selectedIndex !== undefined)
this.selectionModel_.setIndexSelected(selectedIndex, true);
else
console.error('Cannot select ' + selectedEntries[i]);
}
if (this.selectionModel_.selectedIndexes.length === 0)
this.onSelection_();
// Determine the initial mode.
var shouldShowMosaic = selectedEntries.length > 1 ||
(this.context_.pageState &&
this.context_.pageState.gallery === 'mosaic');
this.setCurrentMode_(shouldShowMosaic ? this.mosaicMode_ : this.slideMode_);
// Init mosaic mode.
var mosaic = this.mosaicMode_.getMosaic();
mosaic.init();
// Do the initialization for each mode.
if (shouldShowMosaic) {
mosaic.show();
this.inactivityWatcher_.check(); // Show the toolbar.
cr.dispatchSimpleEvent(this, 'loaded');
} else {
this.slideMode_.enter(
null,
function() {
// Flash the toolbar briefly to show it is there.
this.inactivityWatcher_.kick(Gallery.FIRST_FADE_TIMEOUT);
}.bind(this),
function() {
cr.dispatchSimpleEvent(this, 'loaded');
}.bind(this));
}
}.bind(this)).catch(function(error) {
console.error(error.stack || error);
});
};
/**
* Handles user's 'Close' action.
* @private
*/
Gallery.prototype.onClose_ = function() {
this.executeWhenReady(this.context_.onClose);
};
/**
* Handles user's 'Maximize' action (Escape or a click on the X icon).
* @private
*/
Gallery.prototype.onMaximize_ = function() {
this.executeWhenReady(this.context_.onMaximize);
};
/**
* Handles user's 'Maximize' action (Escape or a click on the X icon).
* @private
*/
Gallery.prototype.onMinimize_ = function() {
this.executeWhenReady(this.context_.onMinimize);
};
/**
* Executes a function when the editor is done with the modifications.
* @param {function} callback Function to execute.
*/
Gallery.prototype.executeWhenReady = function(callback) {
this.currentMode_.executeWhenReady(callback);
};
/**
* @return {Object} File browser private API.
*/
Gallery.getFileBrowserPrivate = function() {
return chrome.fileBrowserPrivate || window.top.chrome.fileBrowserPrivate;
};
/**
* @return {boolean} True if some tool is currently active.
*/
Gallery.prototype.hasActiveTool = function() {
return (this.currentMode_ && this.currentMode_.hasActiveTool()) ||
this.isSharing_() || this.isRenaming_();
};
/**
* External user action event handler.
* @private
*/
Gallery.prototype.onUserAction_ = function() {
this.closeShareMenu_();
// Show the toolbar and hide it after the default timeout.
this.inactivityWatcher_.kick();
};
/**
* Sets the current mode, update the UI.
* @param {Object} mode Current mode.
* @private
*/
Gallery.prototype.setCurrentMode_ = function(mode) {
if (mode !== this.slideMode_ && mode !== this.mosaicMode_)
console.error('Invalid Gallery mode');
this.currentMode_ = mode;
this.container_.setAttribute('mode', this.currentMode_.getName());
this.updateSelectionAndState_();
this.updateButtons_();
};
/**
* Mode toggle event handler.
* @param {function=} opt_callback Callback.
* @param {Event=} opt_event Event that caused this call.
* @private
*/
Gallery.prototype.toggleMode_ = function(opt_callback, opt_event) {
if (!this.modeButton_)
return;
if (this.changingMode_) // Do not re-enter while changing the mode.
return;
if (opt_event)
this.onUserAction_();
this.changingMode_ = true;
var onModeChanged = function() {
this.changingMode_ = false;
if (opt_callback) opt_callback();
}.bind(this);
var tileIndex = Math.max(0, this.selectionModel_.selectedIndex);
var mosaic = this.mosaicMode_.getMosaic();
var tileRect = mosaic.getTileRect(tileIndex);
if (this.currentMode_ === this.slideMode_) {
this.setCurrentMode_(this.mosaicMode_);
mosaic.transform(
tileRect, this.slideMode_.getSelectedImageRect(), true /* instant */);
this.slideMode_.leave(
tileRect,
function() {
// Animate back to normal position.
mosaic.transform();
mosaic.show();
onModeChanged();
}.bind(this));
} else {
this.setCurrentMode_(this.slideMode_);
this.slideMode_.enter(
tileRect,
function() {
// Animate to zoomed position.
mosaic.transform(tileRect, this.slideMode_.getSelectedImageRect());
mosaic.hide();
}.bind(this),
onModeChanged);
}
};
/**
* Deletes the selected items.
* @private
*/
Gallery.prototype.delete_ = function() {
this.onUserAction_();
// Clone the sorted selected indexes array.
var indexesToRemove = this.selectionModel_.selectedIndexes.slice();
if (!indexesToRemove.length)
return;
/* TODO(dgozman): Implement Undo delete, Remove the confirmation dialog. */
var itemsToRemove = this.getSelectedItems();
var plural = itemsToRemove.length > 1;
var param = plural ? itemsToRemove.length : itemsToRemove[0].getFileName();
function deleteNext() {
if (!itemsToRemove.length)
return; // All deleted.
var entry = itemsToRemove.pop().getEntry();
entry.remove(deleteNext, function() {
util.flog('Error deleting: ' + entry.name, deleteNext);
});
}
// Prevent the Gallery from handling Esc and Enter.
this.document_.body.removeEventListener('keydown', this.keyDownBound_);
var restoreListener = function() {
this.document_.body.addEventListener('keydown', this.keyDownBound_);
}.bind(this);
var confirm = new cr.ui.dialogs.ConfirmDialog(this.container_);
confirm.setOkLabel(str('DELETE_BUTTON_LABEL'));
confirm.show(strf(plural ?
'GALLERY_CONFIRM_DELETE_SOME' : 'GALLERY_CONFIRM_DELETE_ONE', param),
function() {
restoreListener();
this.selectionModel_.unselectAll();
this.selectionModel_.leadIndex = -1;
// Remove items from the data model, starting from the highest index.
while (indexesToRemove.length)
this.dataModel_.splice(indexesToRemove.pop(), 1);
// Delete actual files.
deleteNext();
}.bind(this),
function() {
// Restore the listener after a timeout so that ESC is processed.
setTimeout(restoreListener, 0);
});
};
/**
* @return {Array.<Gallery.Item>} Current selection.
*/
Gallery.prototype.getSelectedItems = function() {
return this.selectionModel_.selectedIndexes.map(
this.dataModel_.item.bind(this.dataModel_));
};
/**
* @return {Array.<Entry>} Array of currently selected entries.
*/
Gallery.prototype.getSelectedEntries = function() {
return this.selectionModel_.selectedIndexes.map(function(index) {
return this.dataModel_.item(index).getEntry();
}.bind(this));
};
/**
* @return {?Gallery.Item} Current single selection.
*/
Gallery.prototype.getSingleSelectedItem = function() {
var items = this.getSelectedItems();
if (items.length > 1) {
console.error('Unexpected multiple selection');
return null;
}
return items[0];
};
/**
* Selection change event handler.
* @private
*/
Gallery.prototype.onSelection_ = function() {
this.updateSelectionAndState_();
this.updateShareMenu_();
};
/**
* Data model splice event handler.
* @private
*/
Gallery.prototype.onSplice_ = function() {
this.selectionModel_.adjustLength(this.dataModel_.length);
};
/**
* Content change event handler.
* @param {Event} event Event.
* @private
*/
Gallery.prototype.onContentChange_ = function(event) {
var index = this.dataModel_.indexOf(event.item);
if (index !== this.selectionModel_.selectedIndex)
console.error('Content changed for unselected item');
this.updateSelectionAndState_();
};
/**
* Keydown handler.
*
* @param {Event} event Event.
* @private
*/
Gallery.prototype.onKeyDown_ = function(event) {
var wasSharing = this.isSharing_();
this.closeShareMenu_();
if (this.currentMode_.onKeyDown(event))
return;
switch (util.getKeyModifiers(event) + event.keyIdentifier) {
case 'U+0008': // Backspace.
// The default handler would call history.back and close the Gallery.
event.preventDefault();
break;
case 'U+004D': // 'm' switches between Slide and Mosaic mode.
this.toggleMode_(null, event);
break;
case 'U+0056': // 'v'
case 'MediaPlayPause':
this.slideMode_.startSlideshow(SlideMode.SLIDESHOW_INTERVAL_FIRST, event);
break;
case 'U+007F': // Delete
case 'Shift-U+0033': // Shift+'3' (Delete key might be missing).
case 'U+0044': // 'd'
this.delete_();
break;
}
};
// Name box and rename support.
/**
* Updates the UI related to the selected item and the persistent state.
*
* @private
*/
Gallery.prototype.updateSelectionAndState_ = function() {
var numSelectedItems = this.selectionModel_.selectedIndexes.length;
var displayName = '';
var selectedEntryURL = null;
// If it's selecting something, update the variable values.
if (numSelectedItems) {
var selectedItem =
this.dataModel_.item(this.selectionModel_.selectedIndex);
this.selectedEntry_ = selectedItem.getEntry();
selectedEntryURL = this.selectedEntry_.toURL();
if (numSelectedItems === 1) {
window.top.document.title = this.selectedEntry_.name;
displayName = ImageUtil.getDisplayNameFromName(this.selectedEntry_.name);
} else if (this.context_.curDirEntry) {
// If the Gallery was opened on search results the search query will not
// be recorded in the app state and the relaunch will just open the
// gallery in the curDirEntry directory.
window.top.document.title = this.context_.curDirEntry.name;
displayName = strf('GALLERY_ITEMS_SELECTED', numSelectedItems);
}
}
window.top.util.updateAppState(
null, // Keep the current directory.
selectedEntryURL, // Update the selection.
{gallery: (this.currentMode_ === this.mosaicMode_ ? 'mosaic' : 'slide')});
// We can't rename files in readonly directory.
// We can only rename a single file.
this.filenameEdit_.disabled = numSelectedItems !== 1 ||
this.context_.readonlyDirName;
this.filenameEdit_.value = displayName;
};
/**
* Click event handler on filename edit box
* @private
*/
Gallery.prototype.onFilenameFocus_ = function() {
ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', true);
this.filenameEdit_.originalValue = this.filenameEdit_.value;
setTimeout(this.filenameEdit_.select.bind(this.filenameEdit_), 0);
this.onUserAction_();
};
/**
* Blur event handler on filename edit box.
*
* @param {Event} event Blur event.
* @return {boolean} if default action should be prevented.
* @private
*/
Gallery.prototype.onFilenameEditBlur_ = function(event) {
var item = this.getSingleSelectedItem();
if (item) {
var oldEntry = item.getEntry();
item.rename(this.filenameEdit_.value).then(function() {
var event = new Event('content');
event.item = item;
event.oldEntry = oldEntry;
event.metadata = null; // Metadata unchanged.
this.dataModel_.dispatchEvent(event);
}.bind(this), function(error) {
if (error === 'NOT_CHANGED')
return;
this.filenameEdit_.value =
ImageUtil.getDisplayNameFromName(item.getEntry().name);
this.filenameEdit_.focus();
if (typeof error === 'string')
this.prompt_.showStringAt('center', error, 5000);
else
return Promise.reject(error);
}.bind(this)).catch(function(error) {
console.error(error.stack || error);
});
}
ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', false);
this.onUserAction_();
};
/**
* Keydown event handler on filename edit box
* @private
*/
Gallery.prototype.onFilenameEditKeydown_ = function() {
switch (event.keyCode) {
case 27: // Escape
this.filenameEdit_.value = this.filenameEdit_.originalValue;
this.filenameEdit_.blur();
break;
case 13: // Enter
this.filenameEdit_.blur();
break;
}
event.stopPropagation();
};
/**
* @return {boolean} True if file renaming is currently in progress.
* @private
*/
Gallery.prototype.isRenaming_ = function() {
return this.filenameSpacer_.hasAttribute('renaming');
};
/**
* Content area click handler.
* @private
*/
Gallery.prototype.onContentClick_ = function() {
this.closeShareMenu_();
this.filenameEdit_.blur();
};
// Share button support.
/**
* @return {boolean} True if the Share menu is active.
* @private
*/
Gallery.prototype.isSharing_ = function() {
return !this.shareMenu_.hidden;
};
/**
* Close Share menu if it is open.
* @private
*/
Gallery.prototype.closeShareMenu_ = function() {
if (this.isSharing_())
this.toggleShare_();
};
/**
* Share button handler.
* @private
*/
Gallery.prototype.toggleShare_ = function() {
if (!this.shareButton_.hasAttribute('disabled'))
this.shareMenu_.hidden = !this.shareMenu_.hidden;
this.inactivityWatcher_.check();
};
/**
* Updates available actions list based on the currently selected urls.
* @private.
*/
Gallery.prototype.updateShareMenu_ = function() {
var entries = this.getSelectedEntries();
function isShareAction(task) {
var taskParts = task.taskId.split('|');
return taskParts[0] !== chrome.runtime.id;
}
var api = Gallery.getFileBrowserPrivate();
var createShareMenu = function(tasks) {
var wasHidden = this.shareMenu_.hidden;
this.shareMenu_.hidden = true;
var items = this.shareMenu_.querySelectorAll('.item');
for (var i = 0; i !== items.length; i++) {
items[i].parentNode.removeChild(items[i]);
}
for (var t = 0; t !== tasks.length; t++) {
var task = tasks[t];
if (!isShareAction(task))
continue;
// Filter out Files.app tasks.
// TODO(hirono): Remove the hack after the files.app's handlers are
// removed.
if (task.taskId.indexOf('hhaomjibdihmijegdhdafkllkbggdgoj|') === 0)
continue;
var item = util.createChild(this.shareMenu_, 'item');
item.textContent = task.title;
item.style.backgroundImage = 'url(' + task.iconUrl + ')';
item.addEventListener('click', function(taskId) {
this.toggleShare_(); // Hide the menu.
// TODO(hirono): Use entries instead of URLs.
this.executeWhenReady(
api.executeTask.bind(
api,
taskId,
util.entriesToURLs(entries),
function(result) {
var alertDialog =
new cr.ui.dialogs.AlertDialog(this.container_);
util.isTeleported(window).then(function(teleported) {
if (teleported)
util.showOpenInOtherDesktopAlert(alertDialog, entries);
}.bind(this));
}.bind(this)));
}.bind(this, task.taskId));
}
var empty = this.shareMenu_.querySelector('.item') === null;
ImageUtil.setAttribute(this.shareButton_, 'disabled', empty);
this.shareMenu_.hidden = wasHidden || empty;
}.bind(this);
// Create or update the share menu with a list of sharing tasks and show
// or hide the share button.
// TODO(mtomasz): Pass Entries directly, instead of URLs.
if (!entries.length)
createShareMenu([]); // Empty list of tasks, since there is no selection.
else
api.getFileTasks(util.entriesToURLs(entries), createShareMenu);
};
/**
* Updates thumbnails.
* @private
*/
Gallery.prototype.updateThumbnails_ = function() {
if (this.currentMode_ === this.slideMode_)
this.slideMode_.updateThumbnails();
if (this.mosaicMode_) {
var mosaic = this.mosaicMode_.getMosaic();
if (mosaic.isInitialized())
mosaic.reload();
}
};
/**
* Updates buttons.
* @private
*/
Gallery.prototype.updateButtons_ = function() {
if (this.modeButton_) {
var oppositeMode =
this.currentMode_ === this.slideMode_ ? this.mosaicMode_ :
this.slideMode_;
this.modeButton_.title = str(oppositeMode.getTitle());
}
};
/**
* Singleton gallery.
* @type {Gallery}
*/
var gallery = null;
/**
* Initialize the window.
* @param {Object} backgroundComponents Background components.
*/
window.initialize = function(backgroundComponents) {
window.loadTimeData.data = backgroundComponents.stringData;
gallery = new Gallery(backgroundComponents.volumeManager);
};
/**
* Loads entries.
*/
window.loadEntries = function(entries, selectedEntries) {
gallery.load(entries, selectedEntries);
};