blob: d9f20a20f16a23349d1e5b853acd35e26ab066d4 [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.
class TaskController {
/**
* @param {DialogType} dialogType
* @param {!VolumeManager} volumeManager
* @param {!FileManagerUI} ui
* @param {!MetadataModel} metadataModel
* @param {!DirectoryModel} directoryModel
* @param {!FileSelectionHandler} selectionHandler
* @param {!MetadataUpdateController} metadataUpdateController
* @param {!NamingController} namingController
* @param {!Crostini} crostini
*/
constructor(
dialogType, volumeManager, ui, metadataModel, directoryModel,
selectionHandler, metadataUpdateController, namingController, crostini) {
/**
* @private {DialogType}
* @const
*/
this.dialogType_ = dialogType;
/**
* @private {!VolumeManager}
* @const
*/
this.volumeManager_ = volumeManager;
/**
* @private {!FileManagerUI}
* @const
*/
this.ui_ = ui;
/**
* @private {!MetadataModel}
* @const
*/
this.metadataModel_ = metadataModel;
/**
* @private {!DirectoryModel}
* @const
*/
this.directoryModel_ = directoryModel;
/**
* @private {!FileSelectionHandler}
* @const
*/
this.selectionHandler_ = selectionHandler;
/**
* @type {!MetadataUpdateController}
* @const
* @private
*/
this.metadataUpdateController_ = metadataUpdateController;
/**
* @private {!NamingController}
* @const
*/
this.namingController_ = namingController;
/**
* @type {!Crostini}
* @const
* @private
*/
this.crostini_ = crostini;
/**
* @type {!TaskHistory}
* @const
* @private
*/
this.taskHistory_ = new TaskHistory();
/**
* @private {boolean}
*/
this.canExecuteDefaultTask_ = false;
/**
* @private {boolean}
*/
this.canExecuteOpenActions_ = false;
/**
* @private {boolean}
*/
this.canExecuteMoreActions_ = false;
/**
* @private {!cr.ui.Command}
* @const
*/
this.defaultTaskCommand_ = assertInstanceof(
document.querySelector('#default-task'), cr.ui.Command);
/**
* More actions command that uses #open-with as selector due to the
* open-with command used previously for the same task.
* @private {!cr.ui.Command}
* @const
*/
this.openWithCommand_ =
assertInstanceof(document.querySelector('#open-with'), cr.ui.Command);
/**
* More actions command that uses #open-with as selector due to the
* open-with command used previously for the same task.
* @private {!cr.ui.Command}
* @const
*/
this.moreActionsCommand_ = assertInstanceof(
document.querySelector('#more-actions'), cr.ui.Command);
/**
* Show sub menu command that uses #show-submenu as selector.
* @private {!cr.ui.Command}
* @const
*/
this.showSubMenuCommand_ = assertInstanceof(
document.querySelector('#show-submenu'), cr.ui.Command);
/**
* @private {Promise<!FileTasks>}
*/
this.tasks_ = null;
/**
* Entries that are used to generate FileTasks returned by this.tasks_.
* @private {!Array<!Entry>}
*/
this.tasksEntries_ = [];
/**
* Selected entries from the last time onSelectionChanged_ was called.
* @private {!Array<!Entry>}
*/
this.lastSelectedEntries_ = [];
ui.taskMenuButton.addEventListener(
'select', this.onTaskItemClicked_.bind(this));
ui.shareMenuButton.menu.addEventListener(
'activate', this.onTaskItemClicked_.bind(this));
ui.shareSubMenu.addEventListener(
'activate', this.onTaskItemClicked_.bind(this));
this.selectionHandler_.addEventListener(
FileSelectionHandler.EventType.CHANGE,
this.onSelectionChanged_.bind(this));
this.selectionHandler_.addEventListener(
FileSelectionHandler.EventType.CHANGE_THROTTLED,
this.updateTasks_.bind(this));
this.taskHistory_.addEventListener(
TaskHistory.EventType.UPDATE, this.updateTasks_.bind(this));
chrome.fileManagerPrivate.onAppsUpdated.addListener(
this.updateTasks_.bind(this));
}
/**
* Task combobox handler.
*
* @param {Object} event Event containing task which was clicked.
* @private
*/
onTaskItemClicked_(event) {
// If the clicked target has an associated command, the click event should
// not be handled here since it is handled as a command.
if (event.target && event.target.command) {
return;
}
// 'select' event from ComboButton has the item as event.item.
// 'activate' event from cr.ui.MenuButton has the item as event.target.data.
const item = event.item || event.target.data;
this.getFileTasks()
.then(tasks => {
switch (item.type) {
case FileTasks.TaskMenuButtonItemType.ShowMenu:
this.ui_.taskMenuButton.showMenu(false);
break;
case FileTasks.TaskMenuButtonItemType.RunTask:
tasks.execute(item.task);
break;
case FileTasks.TaskMenuButtonItemType.ChangeDefaultTask:
const selection = this.selectionHandler_.selection;
const extensions = [];
for (let i = 0; i < selection.entries.length; i++) {
const match = /\.(\w+)$/g.exec(selection.entries[i].toURL());
if (match) {
const ext = match[1].toUpperCase();
if (extensions.indexOf(ext) == -1) {
extensions.push(ext);
}
}
}
let format = '';
if (extensions.length == 1) {
format = extensions[0];
}
// Change default was clicked. We should open "change default"
// dialog.
tasks.showTaskPicker(
this.ui_.defaultTaskPicker,
loadTimeData.getString('CHANGE_DEFAULT_MENU_ITEM'),
strf('CHANGE_DEFAULT_CAPTION', format),
this.changeDefaultTask_.bind(this, selection),
FileTasks.TaskPickerType.ChangeDefault);
break;
default:
assertNotReached('Unknown task.');
}
})
.catch(error => {
if (error) {
console.error(error.stack || error);
}
});
}
/**
* Sets the given task as default, when this task is applicable.
*
* @param {!FileSelection} selection File selection.
* @param {Object} task Task to set as default.
* @private
*/
changeDefaultTask_(selection, task) {
const entries = selection.entries;
Promise.all(entries.map((entry) => this.getMimeType_(entry)))
.then(mimeTypes => {
chrome.fileManagerPrivate.setDefaultTask(
task.taskId, entries, mimeTypes, util.checkAPIError);
this.metadataUpdateController_.refreshCurrentDirectoryMetadata();
// Update task menu button unless the task button was updated other
// selection.
if (this.selectionHandler_.selection === selection) {
this.tasks_ = null;
this.getFileTasks()
.then(tasks => {
tasks.display(
this.ui_.taskMenuButton, this.ui_.shareMenuButton);
})
.catch(error => {
if (error) {
console.error(error.stack || error);
}
});
}
this.selectionHandler_.onFileSelectionChanged();
});
}
/**
* Executes default task.
*/
executeDefaultTask() {
this.getFileTasks()
.then(tasks => {
const task = {
taskId: /** @type {string} */ (
this.ui_.fileContextMenu.defaultTaskMenuItem.taskId),
title: /** @type {string} */ (
this.ui_.fileContextMenu.defaultTaskMenuItem.label),
};
tasks.execute(task);
})
.catch(error => {
if (error) {
console.error(error.stack || error);
}
});
}
/**
* Get MIME type for an entry. This method first tries to obtain the MIME type
* from metadata. If it fails, this falls back to obtain the MIME type from
* its content or name.
*
* @param {!Entry} entry An entry to obtain its mime type.
* @return {!Promise}
* @private
*/
getMimeType_(entry) {
return this.metadataModel_.get([entry], ['contentMimeType'])
.then(properties => {
if (properties[0].contentMimeType) {
return properties[0].contentMimeType;
}
return new Promise((fulfill, reject) => {
chrome.fileManagerPrivate.getMimeType(entry, mimeType => {
if (!chrome.runtime.lastError) {
fulfill(mimeType);
} else {
reject(chrome.runtime.lastError);
}
});
});
});
}
/**
* Handles change of selection and clears context menu.
* @private
*/
onSelectionChanged_() {
const selection = this.selectionHandler_.selection;
// Caller of update context menu task items.
// FileSelectionHandler.EventType.CHANGE
if (this.dialogType_ === DialogType.FULL_PAGE &&
(selection.directoryCount > 0 || selection.fileCount > 0)) {
// Compare entries while ignoring changes inside directories.
if (!util.isSameEntries(this.lastSelectedEntries_, selection.entries)) {
// Update the context menu if selection changed.
this.updateContextMenuTaskItems_([], []);
}
} else {
// Update context menu.
this.updateContextMenuTaskItems_([], []);
}
this.lastSelectedEntries_ = selection.entries;
}
/**
* Updates available tasks opened from context menu or the open button.
* @private
*/
updateTasks_() {
const selection = this.selectionHandler_.selection;
if (this.dialogType_ === DialogType.FULL_PAGE &&
(selection.directoryCount > 0 || selection.fileCount > 0)) {
this.getFileTasks()
.then(tasks => {
tasks.display(this.ui_.taskMenuButton, this.ui_.shareMenuButton);
this.updateContextMenuTaskItems_(
tasks.getOpenTaskItems(), tasks.getNonOpenTaskItems());
})
.catch(error => {
if (error) {
console.error(error.stack || error);
}
});
} else {
this.ui_.taskMenuButton.hidden = true;
this.ui_.shareMenuButton.hidden = true;
}
}
/**
* @return {!Promise<!FileTasks>}
* @public
*/
getFileTasks() {
const selection = this.selectionHandler_.selection;
if (this.tasks_ &&
util.isSameEntries(this.tasksEntries_, selection.entries)) {
return this.tasks_;
}
this.tasksEntries_ = selection.entries;
this.tasks_ = selection.computeAdditional(this.metadataModel_).then(() => {
if (this.selectionHandler_.selection !== selection) {
if (util.isSameEntries(this.tasksEntries_, selection.entries)) {
this.tasks_ = null;
}
return Promise.reject();
}
return FileTasks
.create(
this.volumeManager_, this.metadataModel_, this.directoryModel_,
this.ui_, selection.entries, assert(selection.mimeTypes),
this.taskHistory_, this.namingController_, this.crostini_)
.then(tasks => {
if (this.selectionHandler_.selection !== selection) {
if (util.isSameEntries(this.tasksEntries_, selection.entries)) {
this.tasks_ = null;
}
return Promise.reject();
}
return tasks;
});
});
return this.tasks_;
}
/**
* Returns whether default task command can be executed or not.
* @return {boolean} True if default task command is executable.
*/
canExecuteDefaultTask() {
return this.canExecuteDefaultTask_;
}
/**
* Returns whether open with command can be executed or not.
* @return {boolean} True if open with command is executable.
*/
canExecuteOpenActions() {
return this.canExecuteOpenActions_;
}
/**
* Returns whether open with command can be executed or not.
* @return {boolean} True if open with command is executable.
*/
canExecuteMoreActions() {
return this.canExecuteMoreActions_;
}
/**
* Returns whether show sub-menu command can be executed or not.
* @return {boolean} True if show-submenu command is executable.
*/
canExecuteShowOverflow() {
// TODO (adanilo@) extend this for general sub-menu case
return this.ui_.shareMenuButton.overflow.firstChild !== null;
}
/**
* Updates tasks menu item to match passed task items.
*
* @param {!Array<!chrome.fileManagerPrivate.FileTask>} openTasks List of OPEN
* tasks.
* @param {!Array<!chrome.fileManagerPrivate.FileTask>} nonOpenTasks List of
* non-OPEN tasks.
* @private
*/
updateContextMenuTaskItems_(openTasks, nonOpenTasks) {
const defaultTask = FileTasks.getDefaultTask(openTasks, this.taskHistory_);
if (defaultTask) {
if (defaultTask.iconType) {
this.ui_.fileContextMenu.defaultTaskMenuItem.style.backgroundImage = '';
this.ui_.fileContextMenu.defaultTaskMenuItem.setAttribute(
'file-type-icon', defaultTask.iconType);
} else if (defaultTask.iconUrl) {
this.ui_.fileContextMenu.defaultTaskMenuItem.style.backgroundImage =
'url(' + defaultTask.iconUrl + ')';
} else {
this.ui_.fileContextMenu.defaultTaskMenuItem.style.backgroundImage = '';
}
if (defaultTask.taskId === FileTasks.ZIP_ARCHIVER_UNZIP_TASK_ID) {
this.ui_.fileContextMenu.defaultTaskMenuItem.label = str('TASK_OPEN');
} else {
this.ui_.fileContextMenu.defaultTaskMenuItem.label =
defaultTask.label || defaultTask.title;
}
this.ui_.fileContextMenu.defaultTaskMenuItem.disabled =
!!defaultTask.disabled;
this.ui_.fileContextMenu.defaultTaskMenuItem.taskId = defaultTask.taskId;
}
this.canExecuteDefaultTask_ = defaultTask != null;
this.defaultTaskCommand_.canExecuteChange(this.ui_.listContainer.element);
this.canExecuteOpenActions_ = openTasks.length > 1;
this.openWithCommand_.canExecuteChange(this.ui_.listContainer.element);
this.canExecuteMoreActions_ = nonOpenTasks.length >= 1;
this.moreActionsCommand_.canExecuteChange(this.ui_.listContainer.element);
this.ui_.fileContextMenu.tasksSeparator.hidden =
openTasks.length === 0 && nonOpenTasks.length == 0;
}
/**
* @param {FileEntry} entry
*/
executeEntryTask(entry) {
this.metadataModel_.get([entry], ['contentMimeType']).then(props => {
FileTasks
.create(
this.volumeManager_, this.metadataModel_, this.directoryModel_,
this.ui_, [entry], [props[0].contentMimeType || null],
this.taskHistory_, this.namingController_, this.crostini_)
.then(tasks => {
tasks.executeDefault();
});
});
}
}