// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-'use strict';
-
/**
* FileManager constructor.
*
* latter is not yet implemented).
*
* @constructor
+ * @struct
*/
function FileManager() {
- this.initializeQueue_ = new AsyncUtil.Group();
+ // --------------------------------------------------------------------------
+ // Services FileManager depends on.
+
+ /**
+ * Volume manager.
+ * @type {VolumeManagerWrapper}
+ * @private
+ */
+ this.volumeManager_ = null;
+
+ /**
+ * Metadata cache.
+ * @type {MetadataCache}
+ * @private
+ */
+ this.metadataCache_ = null;
+
+ /**
+ * File operation manager.
+ * @type {FileOperationManager}
+ * @private
+ */
+ this.fileOperationManager_ = null;
+
+ /**
+ * File transfer controller.
+ * @type {FileTransferController}
+ * @private
+ */
+ this.fileTransferController_ = null;
+
+ /**
+ * File filter.
+ * @type {FileFilter}
+ * @private
+ */
+ this.fileFilter_ = null;
+
+ /**
+ * File watcher.
+ * @type {FileWatcher}
+ * @private
+ */
+ this.fileWatcher_ = null;
+
+ /**
+ * Model of current directory.
+ * @type {DirectoryModel}
+ * @private
+ */
+ this.directoryModel_ = null;
+
+ /**
+ * Model of folder shortcuts.
+ * @type {FolderShortcutsDataModel}
+ * @private
+ */
+ this.folderShortcutsModel_ = null;
+
+ /**
+ * VolumeInfo of the current volume.
+ * @type {VolumeInfo}
+ * @private
+ */
+ this.currentVolumeInfo_ = null;
+
+ /**
+ * Handler for command events.
+ * @type {CommandHandler}
+ */
+ this.commandHandler = null;
+
+ /**
+ * Handler for the change of file selection.
+ * @type {FileSelectionHandler}
+ * @private
+ */
+ this.selectionHandler_ = null;
+
+ /**
+ * Dialog action controller.
+ * @type {DialogActionController}
+ * @private
+ */
+ this.dialogActionController_ = null;
+
+ // --------------------------------------------------------------------------
+ // Parameters determining the type of file manager.
+
+ /**
+ * Dialog type of this window.
+ * @type {DialogType}
+ */
+ this.dialogType = DialogType.FULL_PAGE;
+
+ /**
+ * List of acceptable file types for open dialog.
+ * @type {!Array.<Object>}
+ * @private
+ */
+ this.fileTypes_ = [];
+
+ /**
+ * Startup parameters for this application.
+ * @type {?{includeAllFiles:boolean,
+ * action:string,
+ * shouldReturnLocalPath:boolean}}
+ * @private
+ */
+ this.params_ = null;
+
+ /**
+ * Startup preference about the view.
+ * @type {Object}
+ * @private
+ */
+ this.viewOptions_ = {};
+
+ /**
+ * The user preference.
+ * @type {Object}
+ * @private
+ */
+ this.preferences_ = null;
+
+ // --------------------------------------------------------------------------
+ // UI components.
+
+ /**
+ * UI management class of file manager.
+ * @type {FileManagerUI}
+ * @private
+ */
+ this.ui_ = null;
+
+ /**
+ * Progress center panel.
+ * @type {ProgressCenterPanel}
+ * @private
+ */
+ this.progressCenterPanel_ = null;
+
+ /**
+ * Directory tree.
+ * @type {DirectoryTree}
+ * @private
+ */
+ this.directoryTree_ = null;
+
+ /**
+ * Naming controller.
+ * @type {NamingController}
+ * @private
+ */
+ this.namingController_ = null;
+
+ /**
+ * Controller for search UI.
+ * @type {SearchController}
+ * @private
+ */
+ this.searchController_ = null;
+
+ /**
+ * Controller for directory scan.
+ * @type {ScanController}
+ * @private
+ */
+ this.scanController_ = null;
+
+ /**
+ * Controller for spinner.
+ * @type {SpinnerController}
+ * @private
+ */
+ this.spinnerController_ = null;
+
+ /**
+ * Banners in the file list.
+ * @type {FileListBannerController}
+ * @private
+ */
+ this.bannersController_ = null;
+
+ // --------------------------------------------------------------------------
+ // Dialogs.
+
+ /**
+ * Error dialog.
+ * @type {ErrorDialog}
+ */
+ this.error = null;
+
+ /**
+ * Alert dialog.
+ * @type {cr.ui.dialogs.AlertDialog}
+ */
+ this.alert = null;
+
+ /**
+ * Confirm dialog.
+ * @type {cr.ui.dialogs.ConfirmDialog}
+ */
+ this.confirm = null;
+
+ /**
+ * Prompt dialog.
+ * @type {cr.ui.dialogs.PromptDialog}
+ */
+ this.prompt = null;
+
+ /**
+ * Share dialog.
+ * @type {ShareDialog}
+ * @private
+ */
+ this.shareDialog_ = null;
+
+ /**
+ * Default task picker.
+ * @type {cr.filebrowser.DefaultActionDialog}
+ */
+ this.defaultTaskPicker = null;
+
+ /**
+ * Suggest apps dialog.
+ * @type {SuggestAppsDialog}
+ */
+ this.suggestAppsDialog = null;
+
+ // --------------------------------------------------------------------------
+ // Menus.
+
+ /**
+ * Context menu for texts.
+ * @type {cr.ui.Menu}
+ * @private
+ */
+ this.textContextMenu_ = null;
+
+ // --------------------------------------------------------------------------
+ // DOM elements.
+
+ /**
+ * Background page.
+ * @type {BackgroundWindow}
+ * @private
+ */
+ this.backgroundPage_ = null;
+
+ /**
+ * The root DOM element of this app.
+ * @type {HTMLBodyElement}
+ * @private
+ */
+ this.dialogDom_ = null;
+
+ /**
+ * The document object of this app.
+ * @type {HTMLDocument}
+ * @private
+ */
+ this.document_ = null;
+
+ /**
+ * The menu item to toggle "Do not use mobile data for sync".
+ * @type {HTMLMenuItemElement}
+ */
+ this.syncButton = null;
+
+ /**
+ * The menu item to toggle "Show Google Docs files".
+ * @type {HTMLMenuItemElement}
+ */
+ this.hostedButton = null;
+
+ /**
+ * The menu item for doing an action.
+ * @type {HTMLMenuItemElement}
+ * @private
+ */
+ this.actionMenuItem_ = null;
+
+ /**
+ * The button to open gear menu.
+ * @type {cr.ui.MenuButton}
+ * @private
+ */
+ this.gearButton_ = null;
+
+ /**
+ * The combo button to specify the task.
+ * @type {HTMLButtonElement}
+ * @private
+ */
+ this.taskItems_ = null;
+
+ /**
+ * The container element of the dialog.
+ * @type {HTMLDivElement}
+ * @private
+ */
+ this.dialogContainer_ = null;
+
+ /**
+ * Open-with command in the context menu.
+ * @type {cr.ui.Command}
+ * @private
+ */
+ this.openWithCommand_ = null;
+
+ // --------------------------------------------------------------------------
+ // Bound functions.
+
+ /**
+ * Bound function for onCopyProgress_.
+ * @type {?function(this:FileManager, Event)}
+ * @private
+ */
+ this.onCopyProgressBound_ = null;
+
+ /**
+ * Bound function for onEntriesChanged_.
+ * @type {?function(this:FileManager, Event)}
+ * @private
+ */
+ this.onEntriesChangedBound_ = null;
+
+ // --------------------------------------------------------------------------
+ // Miscellaneous FileManager's states.
/**
- * Current list type.
- * @type {ListType}
+ * Queue for ordering FileManager's initialization process.
+ * @type {AsyncUtil.Group}
* @private
*/
- this.listType_ = null;
+ this.initializeQueue_ = new AsyncUtil.Group();
/**
* True while a user is pressing <Tab>.
* True while a user is pressing <Ctrl>.
*
* TODO(fukino): This key is used only for controlling gear menu, so it
- * shoudl be moved to GearMenu class. crbug.com/366032.
+ * should be moved to GearMenu class. crbug.com/366032.
*
* @type {boolean}
* @private
this.isSecretGearMenuShown_ = false;
/**
- * SelectionHandler.
- * @type {SelectionHandler}
+ * The last clicked item in the file list.
+ * @type {HTMLLIElement}
* @private
*/
- this.selectionHandler_ = null;
+ this.lastClickedItem_ = null;
/**
- * VolumeInfo of the current volume.
- * @type {VolumeInfo}
+ * Count of the SourceNotFound error.
+ * @type {number}
* @private
*/
- this.currentVolumeInfo_ = null;
+ this.sourceNotFoundErrorCount_ = 0;
+
+ /**
+ * Whether the app should be closed on unmount.
+ * @type {boolean}
+ * @private
+ */
+ this.closeOnUnmount_ = false;
+
+ /**
+ * The key for storing startup preference.
+ * @type {string}
+ * @private
+ */
+ this.startupPrefName_ = '';
+
+ /**
+ * URL of directory which should be initial current directory.
+ * @type {string}
+ * @private
+ */
+ this.initCurrentDirectoryURL_ = '';
+
+ /**
+ * URL of entry which should be initially selected.
+ * @type {string}
+ * @private
+ */
+ this.initSelectionURL_ = '';
+
+ /**
+ * The name of target entry (not URL).
+ * @type {string}
+ * @private
+ */
+ this.initTargetName_ = '';
+
+
+ // Object.seal() has big performance/memory overhead for now, so we use
+ // Object.preventExtensions() here. crbug.com/412239.
+ Object.preventExtensions(this);
}
-FileManager.prototype = {
+FileManager.prototype = /** @struct */ {
__proto__: cr.EventTarget.prototype,
+ /**
+ * @return {DirectoryModel}
+ */
get directoryModel() {
return this.directoryModel_;
},
- get navigationList() {
- return this.navigationList_;
+ /**
+ * @return {DirectoryTree}
+ */
+ get directoryTree() {
+ return this.directoryTree_;
},
+ /**
+ * @return {HTMLDocument}
+ */
get document() {
return this.document_;
},
+ /**
+ * @return {FileTransferController}
+ */
get fileTransferController() {
return this.fileTransferController_;
},
+ /**
+ * @return {NamingController}
+ */
+ get namingController() {
+ return this.namingController_;
+ },
+ /**
+ * @return {FileOperationManager}
+ */
+ get fileOperationManager() {
+ return this.fileOperationManager_;
+ },
+ /**
+ * @return {BackgroundWindow}
+ */
get backgroundPage() {
return this.backgroundPage_;
},
+ /**
+ * @return {VolumeManagerWrapper}
+ */
get volumeManager() {
return this.volumeManager_;
},
+ /**
+ * @return {FileManagerUI}
+ */
get ui() {
return this.ui_;
}
* FULL_PAGE which is specific to this code.
*
* @enum {string}
+ * @const
*/
var DialogType = {
SELECT_FOLDER: 'folder',
};
/**
- * @param {string} type Dialog type.
+ * @param {DialogType} type Dialog type.
* @return {boolean} Whether the type is modal.
*/
DialogType.isModal = function(type) {
};
/**
- * @param {string} type Dialog type.
+ * @param {DialogType} type Dialog type.
* @return {boolean} Whether the type is open dialog.
*/
DialogType.isOpenDialog = function(type) {
};
/**
- * @param {string} type Dialog type.
+ * @param {DialogType} type Dialog type.
+ * @return {boolean} Whether the type is open dialog for file(s).
+ */
+DialogType.isOpenFileDialog = function(type) {
+ return type == DialogType.SELECT_OPEN_FILE ||
+ type == DialogType.SELECT_OPEN_MULTI_FILE;
+};
+
+/**
+ * @param {DialogType} type Dialog type.
* @return {boolean} Whether the type is folder selection dialog.
*/
DialogType.isFolderDialog = function(type) {
type == DialogType.SELECT_UPLOAD_FOLDER;
};
+Object.freeze(DialogType);
+
/**
* Bottom margin of the list and tree for transparent preview panel.
* @const
// Anonymous "namespace".
(function() {
-
// Private variables and helper functions.
/**
var DOUBLE_CLICK_TIMEOUT = 200;
/**
- * Update the element to display the information about remaining space for
+ * Updates the element to display the information about remaining space for
* the storage.
+ *
+ * @param {!Object<string, number>} sizeStatsResult Map containing remaining
+ * space information.
* @param {!Element} spaceInnerBar Block element for a percentage bar
- * representing the remaining space.
+ * representing the remaining space.
* @param {!Element} spaceInfoLabel Inline element to contain the message.
* @param {!Element} spaceOuterBar Block element around the percentage bar.
*/
- var updateSpaceInfo = function(
+ var updateSpaceInfo = function(
sizeStatsResult, spaceInnerBar, spaceInfoLabel, spaceOuterBar) {
spaceInnerBar.removeAttribute('pending');
if (sizeStatsResult) {
}
};
- // Public statics.
-
- FileManager.ListType = {
- DETAIL: 'detail',
- THUMBNAIL: 'thumb'
- };
-
FileManager.prototype.initPreferences_ = function(callback) {
var group = new AsyncUtil.Group();
group.add(this.getPreferences_.bind(this));
// Get startup preferences.
- this.viewOptions_ = {};
group.add(function(done) {
- util.platform.getPreference(this.startupPrefName_, function(value) {
+ chrome.storage.local.get(this.startupPrefName_, function(values) {
+ var value = values[this.startupPrefName_];
+ if (!value) {
+ done();
+ return;
+ }
// Load the global default options.
try {
this.viewOptions_ = JSON.parse(value);
* @private
*/
FileManager.prototype.initFileSystemUI_ = function(callback) {
- this.table_.startBatchUpdates();
- this.grid_.startBatchUpdates();
+ this.ui_.listContainer.startBatchUpdates();
this.initFileList_();
this.setupCurrentDirectory_();
// PyAuto tests monitor this state by polling this variable
this.__defineGetter__('workerInitialized_', function() {
- return this.metadataCache_.isInitialized();
+ return this.metadataCache_.isInitialized();
}.bind(this));
this.initDateTimeFormatters_();
// FileListBannerController.
this.getPreferences_(function(pref) {
/** @type {boolean} */
- var showOffers = pref['allowRedeemOffers'];
+ var showOffers = !!pref['allowRedeemOffers'];
self.bannersController_ = new FileListBannerController(
self.directoryModel_, self.volumeManager_, self.document_,
showOffers);
var dm = this.directoryModel_;
dm.addEventListener('directory-changed',
this.onDirectoryChanged_.bind(this));
+
+ var listBeingUpdated = null;
dm.addEventListener('begin-update-files', function() {
- self.currentList_.startBatchUpdates();
+ self.ui_.listContainer.currentList.startBatchUpdates();
+ // Remember the list which was used when updating files started, so
+ // endBatchUpdates() is called on the same list.
+ listBeingUpdated = self.ui_.listContainer.currentList;
});
dm.addEventListener('end-update-files', function() {
- self.restoreItemBeingRenamed_();
- self.currentList_.endBatchUpdates();
+ self.namingController_.restoreItemBeingRenamed();
+ listBeingUpdated.endBatchUpdates();
+ listBeingUpdated = null;
});
- dm.addEventListener('scan-started', this.onScanStarted_.bind(this));
- dm.addEventListener('scan-completed', this.onScanCompleted_.bind(this));
- dm.addEventListener('scan-failed', this.onScanCancelled_.bind(this));
- dm.addEventListener('scan-cancelled', this.onScanCancelled_.bind(this));
- dm.addEventListener('scan-updated', this.onScanUpdated_.bind(this));
- dm.addEventListener('rescan-completed',
- this.onRescanCompleted_.bind(this));
+
+ this.initContextMenus_();
+ this.initCommands_();
+ assert(this.directoryModel_);
+ assert(this.spinnerController_);
+ assert(this.commandHandler);
+ assert(this.selectionHandler_);
+ this.scanController_ = new ScanController(
+ this.directoryModel_,
+ this.ui_.listContainer,
+ this.spinnerController_,
+ this.commandHandler,
+ this.selectionHandler_);
this.directoryTree_.addEventListener('change', function() {
this.ensureDirectoryTreeItemNotBehindPreviewPanel_();
var stateChangeHandler =
this.onPreferencesChanged_.bind(this);
- chrome.fileBrowserPrivate.onPreferencesChanged.addListener(
+ chrome.fileManagerPrivate.onPreferencesChanged.addListener(
stateChangeHandler);
stateChangeHandler();
this.initDataTransferOperations_();
- this.initContextMenus_();
- this.initCommands_();
-
this.updateFileTypeFilter_();
-
this.selectionHandler_.onFileSelectionChanged();
-
- this.table_.endBatchUpdates();
- this.grid_.endBatchUpdates();
+ this.ui_.listContainer.endBatchUpdates();
callback();
};
*/
FileManager.prototype.initDateTimeFormatters_ = function() {
var use12hourClock = !this.preferences_['use24hourClock'];
- this.table_.setDateTimeFormat(use12hourClock);
+ this.ui_.listContainer.table.setDateTimeFormat(use12hourClock);
};
/**
this.fileOperationManager_.addEventListener(
'copy-progress', this.onCopyProgressBound_);
- this.onEntryChangedBound_ = this.onEntryChanged_.bind(this);
+ this.onEntriesChangedBound_ = this.onEntriesChanged_.bind(this);
this.fileOperationManager_.addEventListener(
- 'entry-changed', this.onEntryChangedBound_);
+ 'entries-changed', this.onEntriesChangedBound_);
var controller = this.fileTransferController_ =
- new FileTransferController(this.document_,
- this.fileOperationManager_,
- this.metadataCache_,
- this.directoryModel_,
- this.volumeManager_,
- this.ui_.multiProfileShareDialog);
- controller.attachDragSource(this.table_.list);
- controller.attachFileListDropTarget(this.table_.list);
- controller.attachDragSource(this.grid_);
- controller.attachFileListDropTarget(this.grid_);
+ new FileTransferController(
+ this.document_,
+ this.fileOperationManager_,
+ this.metadataCache_,
+ this.directoryModel_,
+ this.volumeManager_,
+ this.ui_.multiProfileShareDialog,
+ this.backgroundPage_.background.progressCenter);
+ controller.attachDragSource(this.ui_.listContainer.table.list);
+ controller.attachFileListDropTarget(this.ui_.listContainer.table.list);
+ controller.attachDragSource(this.ui_.listContainer.grid);
+ controller.attachFileListDropTarget(this.ui_.listContainer.grid);
controller.attachTreeDropTarget(this.directoryTree_);
- controller.attachNavigationListDropTarget(this.navigationList_, true);
controller.attachCopyPasteHandlers();
controller.addEventListener('selection-copied',
this.blinkSelection.bind(this));
* @private
*/
FileManager.prototype.onSourceNotFound_ = function(event) {
- // Ensure this.sourceNotFoundErrorCount_ is integer.
- this.sourceNotFoundErrorCount_ = ~~this.sourceNotFoundErrorCount_;
var item = new ProgressCenterItem();
item.id = 'source-not-found-' + this.sourceNotFoundErrorCount_;
if (event.progressType === ProgressItemType.COPY)
* @private
*/
FileManager.prototype.initContextMenus_ = function() {
- this.fileContextMenu_ = this.dialogDom_.querySelector('#file-context-menu');
- cr.ui.Menu.decorate(this.fileContextMenu_);
+ assert(this.ui_.listContainer.grid);
+ assert(this.ui_.listContainer.table);
+ assert(this.document_);
+ assert(this.dialogDom_);
- cr.ui.contextMenuHandler.setContextMenu(this.grid_, this.fileContextMenu_);
- cr.ui.contextMenuHandler.setContextMenu(this.table_.querySelector('.list'),
- this.fileContextMenu_);
- cr.ui.contextMenuHandler.setContextMenu(
- this.document_.querySelector('.drive-welcome.page'),
- this.fileContextMenu_);
-
- this.rootsContextMenu_ =
- this.dialogDom_.querySelector('#roots-context-menu');
- cr.ui.Menu.decorate(this.rootsContextMenu_);
- this.navigationList_.setContextMenu(this.rootsContextMenu_);
-
- this.directoryTreeContextMenu_ =
- this.dialogDom_.querySelector('#directory-tree-context-menu');
- cr.ui.Menu.decorate(this.directoryTreeContextMenu_);
- this.directoryTree_.contextMenuForSubitems = this.directoryTreeContextMenu_;
-
- this.textContextMenu_ =
- this.dialogDom_.querySelector('#text-context-menu');
- cr.ui.Menu.decorate(this.textContextMenu_);
-
- this.gearButton_ = this.dialogDom_.querySelector('#gear-button');
- this.gearButton_.addEventListener('menushow',
- this.onShowGearMenu_.bind(this));
- chrome.fileBrowserPrivate.onDesktopChanged.addListener(function() {
- this.updateVisitDesktopMenus_();
- this.ui_.updateProfileBadge();
- }.bind(this));
- chrome.fileBrowserPrivate.onProfileAdded.addListener(
- this.updateVisitDesktopMenus_.bind(this));
- this.updateVisitDesktopMenus_();
+ // Set up the context menu for the file list.
+ var fileContextMenu = queryRequiredElement(
+ this.dialogDom_, '#file-context-menu');
+ cr.ui.Menu.decorate(fileContextMenu);
+ fileContextMenu = /** @type {!cr.ui.Menu} */ (fileContextMenu);
+ cr.ui.contextMenuHandler.setContextMenu(
+ this.ui_.listContainer.grid, fileContextMenu);
+ cr.ui.contextMenuHandler.setContextMenu(
+ this.ui_.listContainer.table.list, fileContextMenu);
+ cr.ui.contextMenuHandler.setContextMenu(
+ queryRequiredElement(this.document_, '.drive-welcome.page'),
+ fileContextMenu);
+
+ // Set up the context menu for the volume/shortcut items in directory tree.
+ var rootsContextMenu = queryRequiredElement(
+ this.dialogDom_, '#roots-context-menu');
+ cr.ui.Menu.decorate(rootsContextMenu);
+ rootsContextMenu = /** @type {!cr.ui.Menu} */ (rootsContextMenu);
+
+ this.directoryTree_.contextMenuForRootItems = rootsContextMenu;
+
+ // Set up the context menu for the folder items in directory tree.
+ var directoryTreeContextMenu = queryRequiredElement(
+ this.dialogDom_, '#directory-tree-context-menu');
+ cr.ui.Menu.decorate(directoryTreeContextMenu);
+ directoryTreeContextMenu =
+ /** @type {!cr.ui.Menu} */ (directoryTreeContextMenu);
+
+ this.directoryTree_.contextMenuForSubitems = directoryTreeContextMenu;
+
+ // Set up the context menu for the text editing.
+ var textContextMenu = queryRequiredElement(
+ this.dialogDom_, '#text-context-menu');
+ cr.ui.Menu.decorate(textContextMenu);
+ this.textContextMenu_ = /** @type {!cr.ui.Menu} */ (textContextMenu);
+
+ var gearButton = queryRequiredElement(this.dialogDom_, '#gear-button');
+ gearButton.addEventListener('menushow', this.onShowGearMenu_.bind(this));
this.dialogDom_.querySelector('#gear-menu').menuItemSelector =
'menuitem, hr';
- cr.ui.decorate(this.gearButton_, cr.ui.MenuButton);
-
- if (this.dialogType == DialogType.FULL_PAGE) {
- // This is to prevent the buttons from stealing focus on mouse down.
- var preventFocus = function(event) {
- event.preventDefault();
- };
-
- var minimizeButton = this.dialogDom_.querySelector('#minimize-button');
- minimizeButton.addEventListener('click', this.onMinimize.bind(this));
- minimizeButton.addEventListener('mousedown', preventFocus);
-
- var maximizeButton = this.dialogDom_.querySelector('#maximize-button');
- maximizeButton.addEventListener('click', this.onMaximize.bind(this));
- maximizeButton.addEventListener('mousedown', preventFocus);
-
- var closeButton = this.dialogDom_.querySelector('#close-button');
- closeButton.addEventListener('click', this.onClose.bind(this));
- closeButton.addEventListener('mousedown', preventFocus);
- }
+ cr.ui.decorate(gearButton, cr.ui.MenuButton);
+ this.gearButton_ = /** @type {!cr.ui.MenuButton} */ (gearButton);
this.syncButton.checkable = true;
this.hostedButton.checkable = true;
- this.detailViewButton_.checkable = true;
- this.thumbnailViewButton_.checkable = true;
- if (util.platform.runningInBrowser()) {
+ if (util.runningInBrowser()) {
// Suppresses the default context menu.
this.dialogDom_.addEventListener('contextmenu', function(e) {
e.preventDefault();
}
};
- FileManager.prototype.onMinimize = function() {
- chrome.app.window.current().minimize();
- };
-
- FileManager.prototype.onMaximize = function() {
- var appWindow = chrome.app.window.current();
- if (appWindow.isMaximized())
- appWindow.restore();
- else
- appWindow.maximize();
- };
-
- FileManager.prototype.onClose = function() {
- window.close();
- };
-
FileManager.prototype.onShowGearMenu_ = function() {
this.refreshRemainingSpace_(false); /* Without loading caption. */
* @private
*/
FileManager.prototype.initCommands_ = function() {
+ assert(this.textContextMenu_);
+
this.commandHandler = new CommandHandler(this);
// TODO(hirono): Move the following block to the UI part.
this.registerInputCommands_(inputs[i]);
}
- cr.ui.contextMenuHandler.setContextMenu(this.renameInput_,
+ cr.ui.contextMenuHandler.setContextMenu(this.ui_.listContainer.renameInput,
this.textContextMenu_);
- this.registerInputCommands_(this.renameInput_);
- this.document_.addEventListener('command',
- this.setNoHover_.bind(this, true));
+ this.registerInputCommands_(this.ui_.listContainer.renameInput);
+ this.document_.addEventListener(
+ 'command',
+ this.ui_.listContainer.clearHover.bind(this.ui_.listContainer));
};
/**
this.backgroundPage_ = backgroundPage;
this.backgroundPage_.background.ready(function() {
loadTimeData.data = this.backgroundPage_.background.stringData;
+ if (util.runningInBrowser())
+ this.backgroundPage_.registerDialog(window);
callback();
}.bind(this));
}.bind(this));
var driveEnabled =
!noLocalPathResolution || !this.params_.shouldReturnLocalPath;
this.volumeManager_ = new VolumeManagerWrapper(
- driveEnabled, this.backgroundPage_);
+ /** @type {VolumeManagerWrapper.DriveEnabledStatus} */ (driveEnabled),
+ this.backgroundPage_);
callback();
};
this.metadataCache_ = MetadataCache.createFull(this.volumeManager_);
// Create the root view of FileManager.
+ assert(this.dialogDom_);
this.ui_ = new FileManagerUI(this.dialogDom_, this.dialogType);
- this.fileTypeSelector_ = this.ui_.fileTypeSelector;
- this.okButton_ = this.ui_.okButton;
- this.cancelButton_ = this.ui_.cancelButton;
// Show the window as soon as the UI pre-initialization is done.
- if (this.dialogType == DialogType.FULL_PAGE &&
- !util.platform.runningInBrowser()) {
+ if (this.dialogType == DialogType.FULL_PAGE && !util.runningInBrowser()) {
chrome.app.window.current().show();
setTimeout(callback, 100); // Wait until the animation is finished.
} else {
* @private
*/
FileManager.prototype.initAdditionalUI_ = function(callback) {
+ // Cache nodes we'll be manipulating.
+ var dom = this.dialogDom_;
+ assert(dom);
+
this.initDialogs_();
- this.ui_.initAdditionalUI();
- this.dialogDom_.addEventListener('drop', function(e) {
- // Prevent opening an URL by dropping it onto the page.
- e.preventDefault();
- });
+ var table = queryRequiredElement(dom, '.detail-table');
+ FileTable.decorate(
+ table,
+ this.metadataCache_,
+ this.volumeManager_,
+ this.dialogType == DialogType.FULL_PAGE);
+ var grid = queryRequiredElement(dom, '.thumbnail-grid');
+ FileGrid.decorate(grid, this.metadataCache_, this.volumeManager_);
+
+ this.ui_.initAdditionalUI(
+ assertInstanceof(table, FileTable),
+ assertInstanceof(grid, FileGrid),
+ new PreviewPanel(
+ queryRequiredElement(dom, '.preview-panel'),
+ DialogType.isOpenDialog(this.dialogType) ?
+ PreviewPanel.VisibilityType.ALWAYS_VISIBLE :
+ PreviewPanel.VisibilityType.AUTO,
+ this.metadataCache_,
+ this.volumeManager_));
this.dialogDom_.addEventListener('click',
this.onExternalLinkClick_.bind(this));
- // Cache nodes we'll be manipulating.
- var dom = this.dialogDom_;
- this.filenameInput_ = dom.querySelector('#filename-input-box input');
- this.taskItems_ = dom.querySelector('#tasks');
- this.table_ = dom.querySelector('.detail-table');
- this.grid_ = dom.querySelector('.thumbnail-grid');
- this.spinner_ = dom.querySelector('#list-container > .spinner-layer');
- this.showSpinner_(true);
+ var taskItems = queryRequiredElement(dom, '#tasks');
+ this.taskItems_ = /** @type {HTMLButtonElement} */ (taskItems);
- var fullPage = this.dialogType == DialogType.FULL_PAGE;
- FileTable.decorate(this.table_, this.metadataCache_, fullPage);
- FileGrid.decorate(this.grid_, this.metadataCache_, this.volumeManager_);
-
- this.previewPanel_ = new PreviewPanel(
- dom.querySelector('.preview-panel'),
- DialogType.isOpenDialog(this.dialogType) ?
- PreviewPanel.VisibilityType.ALWAYS_VISIBLE :
- PreviewPanel.VisibilityType.AUTO,
+ this.ui_.locationLine = new LocationLine(
+ queryRequiredElement(dom, '#location-breadcrumbs'),
+ queryRequiredElement(dom, '#location-volume-icon'),
this.metadataCache_,
this.volumeManager_);
- this.previewPanel_.addEventListener(
- PreviewPanel.Event.VISIBILITY_CHANGE,
- this.onPreviewPanelVisibilityChange_.bind(this));
- this.previewPanel_.initialize();
-
- this.previewPanel_.breadcrumbs.addEventListener(
- 'pathclick', this.onBreadcrumbClick_.bind(this));
+ this.ui_.locationLine.addEventListener(
+ 'pathclick', this.onBreadcrumbClick_.bind(this));
// Initialize progress center panel.
this.progressCenterPanel_ = new ProgressCenterPanel(
- dom.querySelector('#progress-center'));
+ queryRequiredElement(dom, '#progress-center'));
this.backgroundPage_.background.progressCenter.addPanel(
this.progressCenterPanel_);
this.document_.addEventListener('keydown', this.onKeyDown_.bind(this));
this.document_.addEventListener('keyup', this.onKeyUp_.bind(this));
- this.renameInput_ = this.document_.createElement('input');
- this.renameInput_.className = 'rename';
-
- this.renameInput_.addEventListener(
- 'keydown', this.onRenameInputKeyDown_.bind(this));
- this.renameInput_.addEventListener(
- 'blur', this.onRenameInputBlur_.bind(this));
+ this.ui_.listContainer.element.addEventListener(
+ 'keydown', this.onListKeyDown_.bind(this));
+ this.ui_.listContainer.element.addEventListener(
+ ListContainer.EventType.TEXT_SEARCH, this.onTextSearch_.bind(this));
// TODO(hirono): Rename the handler after creating the DialogFooter class.
- this.filenameInput_.addEventListener(
+ this.ui_.dialogFooter.filenameInput.addEventListener(
'input', this.onFilenameInputInput_.bind(this));
- this.filenameInput_.addEventListener(
+ this.ui_.dialogFooter.filenameInput.addEventListener(
'keydown', this.onFilenameInputKeyDown_.bind(this));
- this.filenameInput_.addEventListener(
+ this.ui_.dialogFooter.filenameInput.addEventListener(
'focus', this.onFilenameInputFocus_.bind(this));
- this.listContainer_ = this.dialogDom_.querySelector('#list-container');
- this.listContainer_.addEventListener(
- 'keydown', this.onListKeyDown_.bind(this));
- this.listContainer_.addEventListener(
- 'keypress', this.onListKeyPress_.bind(this));
- this.listContainer_.addEventListener(
- 'mousemove', this.onListMouseMove_.bind(this));
-
- this.okButton_.addEventListener('click', this.onOk_.bind(this));
- this.onCancelBound_ = this.onCancel_.bind(this);
- this.cancelButton_.addEventListener('click', this.onCancelBound_);
-
this.decorateSplitter(
this.dialogDom_.querySelector('#navigation-list-splitter'));
- this.decorateSplitter(
- this.dialogDom_.querySelector('#middlebar-splitter'));
- this.dialogContainer_ = this.dialogDom_.querySelector('.dialog-container');
+ this.dialogContainer_ = /** @type {!HTMLDivElement} */
+ (this.dialogDom_.querySelector('.dialog-container'));
- this.syncButton = this.dialogDom_.querySelector(
- '#gear-menu-drive-sync-settings');
- this.hostedButton = this.dialogDom_.querySelector(
- '#gear-menu-drive-hosted-settings');
+ this.syncButton = /** @type {!HTMLMenuItemElement} */
+ (queryRequiredElement(this.dialogDom_,
+ '#gear-menu-drive-sync-settings'));
+ this.hostedButton = /** @type {!HTMLMenuItemElement} */
+ (queryRequiredElement(this.dialogDom_,
+ '#gear-menu-drive-hosted-settings'));
- this.detailViewButton_ =
- this.dialogDom_.querySelector('#detail-view');
- this.detailViewButton_.addEventListener('activate',
- this.onDetailViewButtonClick_.bind(this));
-
- this.thumbnailViewButton_ =
- this.dialogDom_.querySelector('#thumbnail-view');
- this.thumbnailViewButton_.addEventListener('activate',
- this.onThumbnailViewButtonClick_.bind(this));
+ this.ui_.toggleViewButton.addEventListener('click',
+ this.onToggleViewButtonClick_.bind(this));
cr.ui.ComboButton.decorate(this.taskItems_);
this.taskItems_.showMenu = function(shouldSetFocus) {
this.dialogDom_.ownerDocument.defaultView.addEventListener(
'resize', this.onResize_.bind(this));
- this.filePopup_ = null;
-
- this.searchBoxWrapper_ = this.ui_.searchBox.element;
- this.searchBox_ = this.ui_.searchBox.inputElement;
- this.searchBox_.addEventListener(
- 'input', this.onSearchBoxUpdate_.bind(this));
- this.ui_.searchBox.clearButton.addEventListener(
- 'click', this.onSearchClearButtonClick_.bind(this));
-
- this.lastSearchQuery_ = '';
-
- this.autocompleteList_ = this.ui_.searchBox.autocompleteList;
- this.autocompleteList_.requestSuggestions =
- this.requestAutocompleteSuggestions_.bind(this);
-
- // Instead, open the suggested item when Enter key is pressed or
- // mouse-clicked.
- this.autocompleteList_.handleEnterKeydown = function(event) {
- this.openAutocompleteSuggestion_();
- this.lastAutocompleteQuery_ = '';
- this.autocompleteList_.suggestions = [];
- }.bind(this);
- this.autocompleteList_.addEventListener('mousedown', function(event) {
- this.openAutocompleteSuggestion_();
- this.lastAutocompleteQuery_ = '';
- this.autocompleteList_.suggestions = [];
- }.bind(this));
-
- this.defaultActionMenuItem_ =
- this.dialogDom_.querySelector('#default-action');
+ this.actionMenuItem_ = /** @type {!HTMLMenuItemElement} */
+ (queryRequiredElement(this.dialogDom_, '#default-action'));
- this.openWithCommand_ =
- this.dialogDom_.querySelector('#open-with');
+ this.openWithCommand_ = /** @type {cr.ui.Command} */
+ (this.dialogDom_.querySelector('#open-with'));
- this.driveBuyMoreStorageCommand_ =
- this.dialogDom_.querySelector('#drive-buy-more-space');
+ this.actionMenuItem_.addEventListener('activate',
+ this.onActionMenuItemActivated_.bind(this));
- this.defaultActionMenuItem_.addEventListener('activate',
- this.dispatchSelectionAction_.bind(this));
-
- this.initFileTypeFilter_();
+ this.ui_.dialogFooter.initFileTypeFilter(
+ this.fileTypes_, this.params_.includeAllFiles);
+ this.ui_.dialogFooter.fileTypeSelector.addEventListener(
+ 'change', this.updateFileTypeFilter_.bind(this));
util.addIsFocusedMethod();
i18nTemplate.process(this.document_, loadTimeData);
// Arrange the file list.
- this.table_.normalizeColumns();
- this.table_.redraw();
+ this.ui_.listContainer.table.normalizeColumns();
+ this.ui_.listContainer.table.redraw();
callback();
};
* @private
**/
FileManager.prototype.initFileList_ = function() {
- // Always sharing the data model between the detail/thumb views confuses
- // them. Instead we maintain this bogus data model, and hook it up to the
- // view that is not in use.
- this.emptyDataModel_ = new cr.ui.ArrayDataModel([]);
- this.emptySelectionModel_ = new cr.ui.ListSelectionModel();
-
var singleSelection =
this.dialogType == DialogType.SELECT_OPEN_FILE ||
this.dialogType == DialogType.SELECT_FOLDER ||
this.dialogType == DialogType.SELECT_UPLOAD_FOLDER ||
this.dialogType == DialogType.SELECT_SAVEAS_FILE;
+ assert(this.metadataCache_);
this.fileFilter_ = new FileFilter(
this.metadataCache_,
- false /* Don't show dot files by default. */);
+ false /* Don't show dot files and *.crdownload by default. */);
this.fileWatcher_ = new FileWatcher(this.metadataCache_);
this.fileWatcher_.addEventListener(
this.selectionHandler_ = new FileSelectionHandler(this);
var dataModel = this.directoryModel_.getFileList();
-
- this.table_.setupCompareFunctions(dataModel);
-
dataModel.addEventListener('permuted',
this.updateStartupPrefs_.bind(this));
this.selectionHandler_.onFileSelectionChanged.bind(
this.selectionHandler_));
- this.initList_(this.grid_);
- this.initList_(this.table_.list);
+ var onDetailClickBound = this.onDetailClick_.bind(this);
+ this.ui_.listContainer.table.list.addEventListener(
+ 'click', onDetailClickBound);
+ this.ui_.listContainer.grid.addEventListener(
+ 'click', onDetailClickBound);
var fileListFocusBound = this.onFileListFocus_.bind(this);
- this.table_.list.addEventListener('focus', fileListFocusBound);
- this.grid_.addEventListener('focus', fileListFocusBound);
-
- var dragStartBound = this.onDragStart_.bind(this);
- this.table_.list.addEventListener('dragstart', dragStartBound);
- this.grid_.addEventListener('dragstart', dragStartBound);
-
- var dragEndBound = this.onDragEnd_.bind(this);
- this.table_.list.addEventListener('dragend', dragEndBound);
- this.grid_.addEventListener('dragend', dragEndBound);
- // This event is published by DragSelector because drag end event is not
- // published at the end of drag selection.
- this.table_.list.addEventListener('dragselectionend', dragEndBound);
- this.grid_.addEventListener('dragselectionend', dragEndBound);
+ this.ui_.listContainer.table.list.addEventListener(
+ 'focus', fileListFocusBound);
+ this.ui_.listContainer.grid.addEventListener('focus', fileListFocusBound);
// TODO(mtomasz, yoshiki): Create navigation list earlier, and here just
// attach the directory model.
- this.initNavigationList_();
+ this.initDirectoryTree_();
- this.table_.addEventListener('column-resize-end',
+ this.ui_.listContainer.table.addEventListener('column-resize-end',
this.updateStartupPrefs_.bind(this));
// Restore preferences.
this.viewOptions_.sortField || 'modificationTime',
this.viewOptions_.sortDirection || 'desc');
if (this.viewOptions_.columns) {
- var cm = this.table_.columnModel;
+ var cm = this.ui_.listContainer.table.columnModel;
for (var i = 0; i < cm.totalSize; i++) {
if (this.viewOptions_.columns[i] > 0)
cm.setWidth(i, this.viewOptions_.columns[i]);
}
}
- this.setListType(this.viewOptions_.listType || FileManager.ListType.DETAIL);
- this.textSearchState_ = {text: '', date: new Date()};
+ this.ui_.listContainer.dataModel = this.directoryModel_.getFileList();
+ this.ui_.listContainer.selectionModel =
+ this.directoryModel_.getFileListSelection();
+ this.setListType(
+ this.viewOptions_.listType || ListContainer.ListType.DETAIL);
+
this.closeOnUnmount_ = (this.params_.action == 'auto-open');
if (this.closeOnUnmount_) {
this.volumeManager_.addEventListener('externally-unmounted',
- this.onExternallyUnmounted_.bind(this));
- }
+ this.onExternallyUnmounted_.bind(this));
+ }
+
+ // Create search controller.
+ this.searchController_ = new SearchController(
+ this.ui_.searchBox,
+ this.ui_.locationLine,
+ this.directoryModel_,
+ this.volumeManager_,
+ {
+ // TODO (hirono): Make the real task controller and pass it here.
+ doAction: this.doEntryAction_.bind(this)
+ });
+
+ // Create naming controller.
+ assert(this.ui_.alertDialog);
+ assert(this.ui_.confirmDialog);
+ this.namingController_ = new NamingController(
+ this.ui_.listContainer,
+ this.ui_.alertDialog,
+ this.ui_.confirmDialog,
+ this.directoryModel_,
+ this.fileFilter_,
+ this.selectionHandler_);
+
+ // Create spinner controller.
+ this.spinnerController_ = new SpinnerController(
+ this.ui_.listContainer.spinner, this.directoryModel_);
+ this.spinnerController_.show();
+
+ // Create dialog action controller.
+ this.dialogActionController_ = new DialogActionController(
+ this.dialogType,
+ this.ui_.dialogFooter,
+ this.directoryModel_,
+ this.metadataCache_,
+ this.namingController_,
+ this.params_.shouldReturnLocalPath);
// Update metadata to change 'Today' and 'Yesterday' dates.
var today = new Date();
/**
* @private
*/
- FileManager.prototype.initNavigationList_ = function() {
- this.directoryTree_ = this.dialogDom_.querySelector('#directory-tree');
+ FileManager.prototype.initDirectoryTree_ = function() {
+ var fakeEntriesVisible =
+ this.dialogType !== DialogType.SELECT_SAVEAS_FILE;
+ this.directoryTree_ = /** @type {DirectoryTree} */
+ (this.dialogDom_.querySelector('#directory-tree'));
DirectoryTree.decorate(this.directoryTree_,
this.directoryModel_,
- this.volumeManager_);
-
- this.navigationList_ = this.dialogDom_.querySelector('#navigation-list');
- NavigationList.decorate(this.navigationList_,
- this.volumeManager_,
- this.directoryModel_);
- this.navigationList_.fileManager = this;
- this.navigationList_.dataModel = new NavigationListModel(
+ this.volumeManager_,
+ this.metadataCache_,
+ fakeEntriesVisible);
+ this.directoryTree_.dataModel = new NavigationListModel(
this.volumeManager_, this.folderShortcutsModel_);
- };
-
- /**
- * @private
- */
- FileManager.prototype.updateMiddleBarVisibility_ = function() {
- var entry = this.directoryModel_.getCurrentDirEntry();
- if (!entry)
- return;
- var driveVolume = this.volumeManager_.getVolumeInfo(entry);
- var visible = driveVolume && !driveVolume.error &&
- driveVolume.volumeType === VolumeManagerCommon.VolumeType.DRIVE;
- this.dialogDom_.
- querySelector('.dialog-middlebar-contents').hidden = !visible;
- this.dialogDom_.querySelector('#middlebar-splitter').hidden = !visible;
- this.onResize_();
+ // Visible height of the directory tree depends on the size of progress
+ // center panel. When the size of progress center panel changes, directory
+ // tree has to be notified to adjust its components (e.g. progress bar).
+ var observer = new MutationObserver(
+ this.directoryTree_.relayout.bind(this.directoryTree_));
+ observer.observe(this.progressCenterPanel_.element,
+ /** @type {MutationObserverInit} */
+ ({subtree: true, attributes: true, childList: true}));
};
/**
sortField: sortStatus.field,
sortDirection: sortStatus.direction,
columns: [],
- listType: this.listType_
+ listType: this.ui_.listContainer.currentListType
};
- var cm = this.table_.columnModel;
+ var cm = this.ui_.listContainer.table.columnModel;
for (var i = 0; i < cm.totalSize; i++) {
prefs.columns.push(cm.getWidth(i));
}
// Save the global default.
- util.platform.setPreference(this.startupPrefName_, JSON.stringify(prefs));
+ var items = {};
+ items[this.startupPrefName_] = JSON.stringify(prefs);
+ chrome.storage.local.set(items);
// Save the window-specific preference.
if (window.appState) {
FileManager.prototype.refocus = function() {
var targetElement;
if (this.dialogType == DialogType.SELECT_SAVEAS_FILE)
- targetElement = this.filenameInput_;
+ targetElement = this.ui_.dialogFooter.filenameInput;
else
- targetElement = this.currentList_;
+ targetElement = this.ui.listContainer.currentList;
// Hack: if the tabIndex is disabled, we can assume a modal dialog is
// shown. Focus to a button on the dialog instead.
};
/**
- * Index of selected item in the typeList of the dialog params.
- *
- * @return {number} 1-based index of selected type or 0 if no type selected.
- * @private
+ * Sets the current list type.
+ * @param {ListContainer.ListType} type New list type.
*/
- FileManager.prototype.getSelectedFilterIndex_ = function() {
- var index = Number(this.fileTypeSelector_.selectedIndex);
- if (index < 0) // Nothing selected.
- return 0;
- if (this.params_.includeAllFiles) // Already 1-based.
- return index;
- return index + 1; // Convert to 1-based;
- };
-
FileManager.prototype.setListType = function(type) {
- if (type && type == this.listType_)
+ if ((type && type == this.ui_.listContainer.currentListType) ||
+ !this.directoryModel_) {
return;
-
- this.table_.list.startBatchUpdates();
- this.grid_.startBatchUpdates();
-
- // TODO(dzvorygin): style.display and dataModel setting order shouldn't
- // cause any UI bugs. Currently, the only right way is first to set display
- // style and only then set dataModel.
-
- if (type == FileManager.ListType.DETAIL) {
- this.table_.dataModel = this.directoryModel_.getFileList();
- this.table_.selectionModel = this.directoryModel_.getFileListSelection();
- this.table_.hidden = false;
- this.grid_.hidden = true;
- this.grid_.selectionModel = this.emptySelectionModel_;
- this.grid_.dataModel = this.emptyDataModel_;
- this.table_.hidden = false;
- /** @type {cr.ui.List} */
- this.currentList_ = this.table_.list;
- this.detailViewButton_.setAttribute('checked', '');
- this.thumbnailViewButton_.removeAttribute('checked');
- this.detailViewButton_.setAttribute('disabled', '');
- this.thumbnailViewButton_.removeAttribute('disabled');
- } else if (type == FileManager.ListType.THUMBNAIL) {
- this.grid_.dataModel = this.directoryModel_.getFileList();
- this.grid_.selectionModel = this.directoryModel_.getFileListSelection();
- this.grid_.hidden = false;
- this.table_.hidden = true;
- this.table_.selectionModel = this.emptySelectionModel_;
- this.table_.dataModel = this.emptyDataModel_;
- this.grid_.hidden = false;
- /** @type {cr.ui.List} */
- this.currentList_ = this.grid_;
- this.thumbnailViewButton_.setAttribute('checked', '');
- this.detailViewButton_.removeAttribute('checked');
- this.thumbnailViewButton_.setAttribute('disabled', '');
- this.detailViewButton_.removeAttribute('disabled');
- } else {
- throw new Error('Unknown list type: ' + type);
}
- this.listType_ = type;
+ this.ui_.setCurrentListType(type);
this.updateStartupPrefs_();
this.onResize_();
-
- this.table_.list.endBatchUpdates();
- this.grid_.endBatchUpdates();
- };
-
- /**
- * Initialize the file list table or grid.
- *
- * @param {cr.ui.List} list The list.
- * @private
- */
- FileManager.prototype.initList_ = function(list) {
- // Overriding the default role 'list' to 'listbox' for better accessibility
- // on ChromeOS.
- list.setAttribute('role', 'listbox');
- list.addEventListener('click', this.onDetailClick_.bind(this));
- list.id = 'file-list';
};
/**
* @param {Event} event An event for the entry change.
* @private
*/
- FileManager.prototype.onEntryChanged_ = function(event) {
+ FileManager.prototype.onEntriesChanged_ = function(event) {
var kind = event.kind;
- var entry = event.entry;
- this.directoryModel_.onEntryChanged(kind, entry);
+ var entries = event.entries;
+ this.directoryModel_.onEntriesChanged(kind, entries);
this.selectionHandler_.onFileSelectionChanged();
- if (kind === util.EntryChangedKind.CREATED && FileType.isImage(entry)) {
- // Preload a thumbnail if the new copied entry an image.
+ if (kind !== util.EntryChangedKind.CREATED)
+ return;
+
+ var preloadThumbnail = function(entry) {
var locationInfo = this.volumeManager_.getLocationInfo(entry);
if (!locationInfo)
return;
- this.metadataCache_.get(entry, 'thumbnail|drive', function(metadata) {
- var thumbnailLoader_ = new ThumbnailLoader(
- entry,
- ThumbnailLoader.LoaderType.CANVAS,
- metadata,
- undefined, // Media type.
- // TODO(mtomasz): Use Entry instead of paths.
- locationInfo.isDriveBased ?
- ThumbnailLoader.UseEmbedded.USE_EMBEDDED :
- ThumbnailLoader.UseEmbedded.NO_EMBEDDED,
- 10); // Very low priority.
- thumbnailLoader_.loadDetachedImage(function(success) {});
- });
- }
- };
-
- /**
- * Fills the file type list or hides it.
- * @private
- */
- FileManager.prototype.initFileTypeFilter_ = function() {
- if (this.params_.includeAllFiles) {
- var option = this.document_.createElement('option');
- option.innerText = str('ALL_FILES_FILTER');
- this.fileTypeSelector_.appendChild(option);
- option.value = 0;
- }
-
- for (var i = 0; i !== this.fileTypes_.length; i++) {
- var fileType = this.fileTypes_[i];
- var option = this.document_.createElement('option');
- var description = fileType.description;
- if (!description) {
- // See if all the extensions in the group have the same description.
- for (var j = 0; j !== fileType.extensions.length; j++) {
- var currentDescription = FileType.typeToString(
- FileType.getTypeForName('.' + fileType.extensions[j]));
- if (!description) // Set the first time.
- description = currentDescription;
- else if (description != currentDescription) {
- // No single description, fall through to the extension list.
- description = null;
- break;
- }
- }
-
- if (!description)
- // Convert ['jpg', 'png'] to '*.jpg, *.png'.
- description = fileType.extensions.map(function(s) {
- return '*.' + s;
- }).join(', ');
- }
- option.innerText = description;
-
- option.value = i + 1;
-
- if (fileType.selected)
- option.selected = true;
-
- this.fileTypeSelector_.appendChild(option);
- }
-
- var options = this.fileTypeSelector_.querySelectorAll('option');
- if (options.length >= 2) {
- // There is in fact no choice, show the selector.
- this.fileTypeSelector_.hidden = false;
+ this.metadataCache_.getOne(entry, 'thumbnail|external',
+ function(metadata) {
+ var thumbnailLoader_ = new ThumbnailLoader(
+ entry,
+ ThumbnailLoader.LoaderType.CANVAS,
+ metadata,
+ undefined, // Media type.
+ locationInfo.isDriveBased ?
+ ThumbnailLoader.UseEmbedded.USE_EMBEDDED :
+ ThumbnailLoader.UseEmbedded.NO_EMBEDDED,
+ 10); // Very low priority.
+ thumbnailLoader_.loadDetachedImage(function(success) {});
+ });
+ }.bind(this);
- this.fileTypeSelector_.addEventListener('change',
- this.updateFileTypeFilter_.bind(this));
+ for (var i = 0; i < entries.length; i++) {
+ // Preload a thumbnail if the new copied entry an image.
+ if (FileType.isImage(entries[i]))
+ preloadThumbnail(entries[i]);
}
};
*/
FileManager.prototype.updateFileTypeFilter_ = function() {
this.fileFilter_.removeFilter('fileType');
- var selectedIndex = this.getSelectedFilterIndex_();
+ var selectedIndex = this.ui_.dialogFooter.selectedFilterIndex;
if (selectedIndex > 0) { // Specific filter selected.
- var regexp = new RegExp('.*(' +
+ var regexp = new RegExp('\\.(' +
this.fileTypes_[selectedIndex - 1].extensions.join('|') + ')$', 'i');
var filter = function(entry) {
return entry.isDirectory || regexp.test(entry.name);
};
this.fileFilter_.addFilter('fileType', filter);
+
+ // In save dialog, update the destination name extension.
+ if (this.dialogType === DialogType.SELECT_SAVEAS_FILE) {
+ var current = this.ui_.dialogFooter.filenameInput.value;
+ var newExt = this.fileTypes_[selectedIndex - 1].extensions[0];
+ if (newExt && !regexp.test(current)) {
+ var i = current.lastIndexOf('.');
+ if (i >= 0) {
+ this.ui_.dialogFooter.filenameInput.value =
+ current.substr(0, i) + '.' + newExt;
+ this.selectTargetNameInFilenameInput_();
+ }
+ }
+ }
}
};
* @private
*/
FileManager.prototype.onResize_ = function() {
- if (this.listType_ == FileManager.ListType.THUMBNAIL)
- this.grid_.relayout();
- else
- this.table_.relayout();
-
// May not be available during initialization.
if (this.directoryTree_)
this.directoryTree_.relayout();
- // TODO(mtomasz, yoshiki): Initialize navigation list earlier, before
- // file system is available.
- if (this.navigationList_)
- this.navigationList_.redraw();
-
- this.previewPanel_.breadcrumbs.truncate();
+ this.ui_.relayout();
};
/**
* Handles local metadata changes in the currect directory.
* @param {Event} event Change event.
+ * @this {FileManager}
* @private
*/
FileManager.prototype.onWatcherMetadataChanged_ = function(event) {
- this.updateMetadataInUI_(
- event.metadataType, event.entries, event.properties);
- };
-
- /**
- * Resize details and thumb views to fit the new window size.
- * @private
- */
- FileManager.prototype.onPreviewPanelVisibilityChange_ = function() {
- // This method may be called on initialization. Some object may not be
- // initialized.
-
- var panelHeight = this.previewPanel_.visible ?
- this.previewPanel_.height : 0;
- if (this.grid_)
- this.grid_.setBottomMarginForPanel(panelHeight);
- if (this.table_)
- this.table_.setBottomMarginForPanel(panelHeight);
-
- if (this.directoryTree_) {
- this.directoryTree_.setBottomMarginForPanel(panelHeight);
- this.ensureDirectoryTreeItemNotBehindPreviewPanel_();
- }
- };
-
- /**
- * Invoked when the drag is started on the list or the grid.
- * @private
- */
- FileManager.prototype.onDragStart_ = function() {
- // On open file dialog, the preview panel is always shown.
- if (DialogType.isOpenDialog(this.dialogType))
- return;
- this.previewPanel_.visibilityType =
- PreviewPanel.VisibilityType.ALWAYS_HIDDEN;
- };
-
- /**
- * Invoked when the drag is ended on the list or the grid.
- * @private
- */
- FileManager.prototype.onDragEnd_ = function() {
- // On open file dialog, the preview panel is always shown.
- if (DialogType.isOpenDialog(this.dialogType))
- return;
- this.previewPanel_.visibilityType = PreviewPanel.VisibilityType.AUTO;
+ this.ui_.listContainer.currentView.updateListItemsMetadata(
+ event.metadataType, event.entries);
};
/**
var selectionEntry;
// Resolve the selectionURL to selectionEntry or to currentDirectoryEntry
- // in case of being a display root.
+ // in case of being a display root or a default directory to open files.
queue.run(function(callback) {
if (!this.initSelectionURL_) {
callback();
// opening it.
if (locationInfo.isRootEntry)
nextCurrentDirEntry = inEntry;
- else
+
+ // If this dialog attempts to open file(s) and the selection is a
+ // directory, the selection should be the current directory.
+ if (DialogType.isOpenFileDialog(this.dialogType) &&
+ inEntry.isDirectory) {
+ nextCurrentDirEntry = inEntry;
+ }
+
+ // By default, the selection should be selected entry and the
+ // parent directory of it should be the current directory.
+ if (!nextCurrentDirEntry)
selectionEntry = inEntry;
+
callback();
}.bind(this), callback);
}.bind(this));
}.bind(this));
}.bind(this));
+ // Check if the next current directory is not a virtual directory which is
+ // not available in UI. This may happen to shared on Drive.
+ queue.run(function(callback) {
+ if (!nextCurrentDirEntry) {
+ callback();
+ return;
+ }
+ var locationInfo = this.volumeManager_.getLocationInfo(
+ nextCurrentDirEntry);
+ // If we can't check, assume that the directory is illegal.
+ if (!locationInfo) {
+ nextCurrentDirEntry = null;
+ callback();
+ return;
+ }
+ // Having root directory of DRIVE_OTHER here should be only for shared
+ // with me files. Fallback to Drive root in such case.
+ if (locationInfo.isRootEntry && locationInfo.rootType ===
+ VolumeManagerCommon.RootType.DRIVE_OTHER) {
+ var volumeInfo = this.volumeManager_.getVolumeInfo(nextCurrentDirEntry);
+ if (!volumeInfo) {
+ nextCurrentDirEntry = null;
+ callback();
+ return;
+ }
+ volumeInfo.resolveDisplayRoot().then(
+ function(entry) {
+ nextCurrentDirEntry = entry;
+ callback();
+ }).catch(function(error) {
+ console.error(error.stack || error);
+ nextCurrentDirEntry = null;
+ callback();
+ });
+ } else {
+ callback();
+ }
+ }.bind(this));
+
// If the directory to be changed to is still not resolved, then fallback
// to the default display root.
queue.run(function(callback) {
}, function() {
// Failed to resolve as a file
nextCurrentDirEntry.getDirectory(
- this.initTargetName_,
- {},
- function(targetEntry) {
- selectionEntry = targetEntry;
- callback();
- }, function() {
- // Failed to resolve as either file or directory.
- callback();
- });
+ this.initTargetName_,
+ {},
+ function(targetEntry) {
+ selectionEntry = targetEntry;
+ callback();
+ }, function() {
+ // Failed to resolve as either file or directory.
+ callback();
+ });
}.bind(this));
}.bind(this));
-
// Finalize.
queue.run(function(callback) {
// Check directory change.
return;
var task = null;
- // Handle restoring after crash, or the gallery action.
- // TODO(mtomasz): Use the gallery action instead of just the gallery
- // field.
- if (this.params_.gallery ||
- this.params_.action === 'gallery' ||
- this.params_.action === 'gallery-video') {
- if (!opt_selectionEntry) {
- // Non-existent file or a directory.
- // Reloading while the Gallery is open with empty or multiple
- // selection. Open the Gallery when the directory is scanned.
- task = function() {
- new FileTasks(this, this.params_).openGallery([]);
- }.bind(this);
- } else {
- // The file or the directory exists.
- task = function() {
- new FileTasks(this, this.params_).openGallery([opt_selectionEntry]);
- }.bind(this);
- }
- } else {
- // TODO(mtomasz): Implement remounting archives after crash.
- // See: crbug.com/333139
- }
+
+ // TODO(mtomasz): Implement remounting archives after crash.
+ // See: crbug.com/333139
// If there is a task to be run, run it after the scan is completed.
if (task) {
var listener = function() {
if (!util.isSameEntry(this.directoryModel_.getCurrentDirEntry(),
- directoryEntry)) {
+ directoryEntry)) {
// Opened on a different URL. Probably fallbacked. Therefore,
// do not invoke a task.
return;
this.directoryModel_.addEventListener('scan-completed', listener);
}
} else if (this.dialogType === DialogType.SELECT_SAVEAS_FILE) {
- this.filenameInput_.value = opt_suggestedName || '';
+ this.ui_.dialogFooter.filenameInput.value = opt_suggestedName || '';
this.selectTargetNameInFilenameInput_();
}
};
var getEntries = (isFakeEntry ? [] : [directoryEntry]).concat(entries);
if (!isFakeEntry)
this.metadataCache_.clearRecursively(directoryEntry, '*');
- this.metadataCache_.get(getEntries, 'filesystem', null);
+ this.metadataCache_.get(getEntries, 'filesystem|external', null);
- if (this.isOnDrive())
- this.metadataCache_.get(getEntries, 'drive', null);
-
- var visibleItems = this.currentList_.items;
+ var visibleItems = this.ui.listContainer.currentList.items;
var visibleEntries = [];
for (var i = 0; i < visibleItems.length; i++) {
- var index = this.currentList_.getIndexOfListItem(visibleItems[i]);
+ var index = this.ui.listContainer.currentList.getIndexOfListItem(
+ visibleItems[i]);
var entry = this.directoryModel_.getFileList().item(index);
// The following check is a workaround for the bug in list: sometimes item
// does not have listIndex, and therefore is not found in the list.
if (entry) visibleEntries.push(entry);
}
- this.metadataCache_.get(visibleEntries, 'thumbnail', null);
+ // Refreshes the metadata.
+ this.metadataCache_.getLatest(visibleEntries, 'thumbnail', null);
};
/**
this.metadataCache_.get(
entries,
'filesystem',
- this.updateMetadataInUI_.bind(this, 'filesystem', entries));
+ function() {
+ this.ui_.listContainer.currentView.updateListItemsMetadata(
+ 'filesystem', entries);
+ }.bind(this));
setTimeout(this.dailyUpdateModificationTime_.bind(this),
MILLISECONDS_IN_DAY);
};
/**
- * @param {string} type Type of metadata changed.
- * @param {Array.<Entry>} entries Array of entries.
- * @param {Object.<string, Object>} props Map from entry URLs to metadata
- * props.
- * @private
- */
- FileManager.prototype.updateMetadataInUI_ = function(
- type, entries, properties) {
- if (this.listType_ == FileManager.ListType.DETAIL)
- this.table_.updateListItemsMetadata(type, properties);
- else
- this.grid_.updateListItemsMetadata(type, properties);
- // TODO: update bottom panel thumbnails.
- };
-
- /**
- * Restore the item which is being renamed while refreshing the file list. Do
- * nothing if no item is being renamed or such an item disappeared.
- *
- * While refreshing file list it gets repopulated with new file entries.
- * There is not a big difference whether DOM items stay the same or not.
- * Except for the item that the user is renaming.
- *
- * @private
- */
- FileManager.prototype.restoreItemBeingRenamed_ = function() {
- if (!this.isRenamingInProgress())
- return;
-
- var dm = this.directoryModel_;
- var leadIndex = dm.getFileListSelection().leadIndex;
- if (leadIndex < 0)
- return;
-
- var leadEntry = dm.getFileList().item(leadIndex);
- if (!util.isSameEntry(this.renameInput_.currentEntry, leadEntry))
- return;
-
- var leadListItem = this.findListItemForNode_(this.renameInput_);
- if (this.currentList_ == this.table_.list) {
- this.table_.updateFileMetadata(leadListItem, leadEntry);
- }
- this.currentList_.restoreLeadItem(leadListItem);
- };
-
- /**
* TODO(mtomasz): Move this to a utility function working on the root type.
* @return {boolean} True if the current directory content is from Google
* Drive.
*/
FileManager.prototype.isOnDrive = function() {
var rootType = this.directoryModel_.getCurrentRootType();
- return rootType === VolumeManagerCommon.RootType.DRIVE ||
- rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
- rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
- rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE;
+ return rootType != null &&
+ VolumeManagerCommon.getVolumeTypeFromRootType(rootType) ==
+ VolumeManagerCommon.VolumeType.DRIVE;
};
/**
* @return {boolean} True if those setting items should be shown.
*/
FileManager.prototype.shouldShowDriveSettings = function() {
- return this.isOnDrive() && this.isSecretGearMenuShown_;
+ return this.isOnDrive();
};
/**
return;
if (this.dialogType != DialogType.FULL_PAGE)
- this.onCancel_();
+ this.ui_.dialogFooter.cancelButton.click();
};
/**
selection.tasks.showTaskPicker(this.defaultTaskPicker,
loadTimeData.getString('CHANGE_DEFAULT_MENU_ITEM'),
strf('CHANGE_DEFAULT_CAPTION', format),
- this.onDefaultTaskDone_.bind(this));
+ this.onDefaultTaskDone_.bind(this),
+ true);
}
};
FileManager.prototype.onDefaultTaskDone_ = function(task) {
// TODO(dgozman): move this method closer to tasks.
var selection = this.getSelection();
- chrome.fileBrowserPrivate.setDefaultTask(
+ // TODO(mtomasz): Move conversion from entry to url to custom bindings.
+ // crbug.com/345527.
+ chrome.fileManagerPrivate.setDefaultTask(
task.taskId,
util.entriesToURLs(selection.entries),
selection.mimeTypes);
if (self.hostedButton.hasAttribute('checked') ===
prefs.hostedFilesDisabled && self.isOnDrive()) {
- self.directoryModel_.rescan();
+ self.directoryModel_.rescan(false);
}
if (!prefs.hostedFilesDisabled)
};
/**
- * @param {Event} Unmount event.
+ * @param {Event} event Unmount event.
* @private
*/
FileManager.prototype.onExternallyUnmounted_ = function(event) {
};
/**
- * Shows a modal-like file viewer/editor on top of the File Manager UI.
- *
- * @param {HTMLElement} popup Popup element.
- * @param {function()} closeCallback Function to call after the popup is
- * closed.
- */
- FileManager.prototype.openFilePopup = function(popup, closeCallback) {
- this.closeFilePopup();
- this.filePopup_ = popup;
- this.filePopupCloseCallback_ = closeCallback;
- this.dialogDom_.insertBefore(
- this.filePopup_, this.dialogDom_.querySelector('#iframe-drag-area'));
- this.filePopup_.focus();
- this.document_.body.setAttribute('overlay-visible', '');
- this.document_.querySelector('#iframe-drag-area').hidden = false;
- };
-
- /**
- * Closes the modal-like file viewer/editor popup.
- */
- FileManager.prototype.closeFilePopup = function() {
- if (this.filePopup_) {
- this.document_.body.removeAttribute('overlay-visible');
- this.document_.querySelector('#iframe-drag-area').hidden = true;
- // The window resize would not be processed properly while the relevant
- // divs had 'display:none', force resize after the layout fired.
- setTimeout(this.onResize_.bind(this), 0);
- if (this.filePopup_.contentWindow &&
- this.filePopup_.contentWindow.unload) {
- this.filePopup_.contentWindow.unload();
- }
-
- if (this.filePopupCloseCallback_) {
- this.filePopupCloseCallback_();
- this.filePopupCloseCallback_ = null;
- }
-
- // These operations have to be in the end, otherwise v8 crashes on an
- // assert. See: crbug.com/224174.
- this.dialogDom_.removeChild(this.filePopup_);
- this.filePopup_ = null;
- }
- };
-
- /**
- * Updates visibility of the draggable app region in the modal-like file
- * viewer/editor.
- *
- * @param {boolean} visible True for visible, false otherwise.
- */
- FileManager.prototype.onFilePopupAppRegionChanged = function(visible) {
- if (!this.filePopup_)
- return;
-
- this.document_.querySelector('#iframe-drag-area').hidden = !visible;
- };
-
- /**
* @return {Array.<Entry>} List of all entries in the current directory.
*/
FileManager.prototype.getAllEntriesInCurrentDirectory = function() {
return this.directoryModel_.getFileList().slice();
};
- FileManager.prototype.isRenamingInProgress = function() {
- return !!this.renameInput_.currentEntry;
- };
-
- /**
- * @private
- */
- FileManager.prototype.focusCurrentList_ = function() {
- if (this.listType_ == FileManager.ListType.DETAIL)
- this.table_.focus();
- else // this.listType_ == FileManager.ListType.THUMBNAIL)
- this.grid_.focus();
- };
-
/**
* Return DirectoryEntry of the current directory or null.
* @return {DirectoryEntry} DirectoryEntry of the current directory. Returns
};
/**
- * Deletes the selected file and directories recursively.
- */
- FileManager.prototype.deleteSelection = function() {
- // TODO(mtomasz): Remove this temporary dialog. crbug.com/167364
- var entries = this.getSelection().entries;
- var message = entries.length == 1 ?
- strf('GALLERY_CONFIRM_DELETE_ONE', entries[0].name) :
- strf('GALLERY_CONFIRM_DELETE_SOME', entries.length);
- this.confirm.show(message, function() {
- this.fileOperationManager_.deleteEntries(entries);
- }.bind(this));
- };
-
- /**
* Shows the share dialog for the selected file or directory.
*/
FileManager.prototype.shareSelection = function() {
for (var i = 0; i < selection.entries.length; i++) {
var selectedIndex = selection.indexes[i];
- var listItem = this.currentList_.getListItemByIndex(selectedIndex);
+ var listItem =
+ this.ui.listContainer.currentList.getListItemByIndex(selectedIndex);
if (listItem)
this.blinkListItem_(listItem);
}
* @private
*/
FileManager.prototype.selectTargetNameInFilenameInput_ = function() {
- var input = this.filenameInput_;
+ var input = this.ui_.dialogFooter.filenameInput;
input.focus();
var selectionEnd = input.value.lastIndexOf('.');
if (selectionEnd == -1) {
* @private
*/
FileManager.prototype.onDetailClick_ = function(event) {
- if (this.isRenamingInProgress()) {
+ if (this.namingController_.isRenamingInProgress()) {
// Don't pay attention to clicks during a rename.
return;
}
- var listItem = this.findListItemForEvent_(event);
+ var listItem = this.ui_.listContainer.findListItemForNode(
+ event.touchedElement || event.srcElement);
var selection = this.getSelection();
if (!listItem || !listItem.selected || selection.totalCount != 1) {
return;
tasks.executeDefault();
return true;
}
- if (!this.okButton_.disabled) {
- this.onOk_();
+ if (!this.ui_.dialogFooter.okButton.disabled) {
+ this.ui_.dialogFooter.okButton.click();
return true;
}
return false;
};
/**
+ * Handles activate event of action menu item.
+ *
+ * @private
+ */
+ FileManager.prototype.onActionMenuItemActivated_ = function() {
+ var tasks = this.getSelection().tasks;
+ if (tasks)
+ tasks.execute(this.actionMenuItem_.taskId);
+ };
+
+ /**
* Opens the suggest file dialog.
*
* @param {Entry} entry Entry of the file.
return;
}
- this.metadataCache_.get([entry], 'drive', function(props) {
- if (!props || !props[0] || !props[0].contentMimeType) {
+ this.metadataCache_.getOne(entry, 'external', function(prop) {
+ if (!prop || !prop.contentMimeType) {
onFailure();
return;
}
var splitted = util.splitExtension(basename);
var filename = splitted[0];
var extension = splitted[1];
- var mime = props[0].contentMimeType;
+ var mime = prop.contentMimeType;
// Returns with failure if the file has neither extension nor mime.
if (!extension || !mime) {
/**
* Called when a dialog is shown or hidden.
- * @param {boolean} flag True if a dialog is shown, false if hidden.
+ * @param {boolean} show True if a dialog is shown, false if hidden.
*/
FileManager.prototype.onDialogShownOrHidden = function(show) {
if (show) {
};
/**
- * Update menus that move the window to the other profile's desktop.
- * TODO(hirono): Add the GearMenu class and make it a member of the class.
- * TODO(hirono): Handle the case where a profile is added while the menu is
- * opened.
- * @private
- */
- FileManager.prototype.updateVisitDesktopMenus_ = function() {
- var gearMenu = this.document_.querySelector('#gear-menu');
- var separator =
- this.document_.querySelector('#multi-profile-separator');
-
- // Remove existing menu items.
- var oldItems =
- this.document_.querySelectorAll('#gear-menu .visit-desktop');
- for (var i = 0; i < oldItems.length; i++) {
- gearMenu.removeChild(oldItems[i]);
- }
- separator.hidden = true;
-
- if (this.dialogType !== DialogType.FULL_PAGE)
- return;
-
- // Obtain the profile information.
- chrome.fileBrowserPrivate.getProfiles(function(profiles,
- currentId,
- displayedId) {
- // Check if the menus are needed or not.
- var insertingPosition = separator.nextSibling;
- if (profiles.length === 1 && profiles[0].profileId === displayedId)
- return;
-
- separator.hidden = false;
- for (var i = 0; i < profiles.length; i++) {
- var profile = profiles[i];
- if (profile.profileId === displayedId)
- continue;
- var item = this.document_.createElement('menuitem');
- cr.ui.MenuItem.decorate(item);
- gearMenu.insertBefore(item, insertingPosition);
- item.className = 'visit-desktop';
- item.label = strf('VISIT_DESKTOP_OF_USER',
- profile.displayName,
- profile.profileId);
- item.addEventListener('activate', function(inProfile, event) {
- // Stop propagate and hide the menu manually, in order to prevent the
- // focus from being back to the button. (cf. http://crbug.com/248479)
- event.stopPropagation();
- this.gearButton_.hideMenu();
- this.gearButton_.blur();
- chrome.fileBrowserPrivate.visitDesktop(inProfile.profileId);
- }.bind(this, profile));
- }
- }.bind(this));
- };
-
- /**
* Refreshes space info of the current volume.
* @param {boolean} showLoadingCaption Whether show loading caption or not.
* @private
if (!this.currentVolumeInfo_)
return;
- var volumeSpaceInfoLabel =
- this.dialogDom_.querySelector('#volume-space-info-label');
- var volumeSpaceInnerBar =
- this.dialogDom_.querySelector('#volume-space-info-bar');
- var volumeSpaceOuterBar =
- this.dialogDom_.querySelector('#volume-space-info-bar').parentNode;
+ var volumeSpaceInfo = /** @type {!HTMLElement} */
+ (this.dialogDom_.querySelector('#volume-space-info'));
+ var volumeSpaceInfoSeparator = /** @type {!HTMLElement} */
+ (this.dialogDom_.querySelector('#volume-space-info-separator'));
+ var volumeSpaceInfoLabel = /** @type {!HTMLElement} */
+ (this.dialogDom_.querySelector('#volume-space-info-label'));
+ var volumeSpaceInnerBar = /** @type {!HTMLElement} */
+ (this.dialogDom_.querySelector('#volume-space-info-bar'));
+ var volumeSpaceOuterBar = /** @type {!HTMLElement} */
+ (this.dialogDom_.querySelector('#volume-space-info-bar').parentNode);
+
+ var currentVolumeInfo = this.currentVolumeInfo_;
+
+ // TODO(mtomasz): Add support for remaining space indication for provided
+ // file systems.
+ if (currentVolumeInfo.volumeType ==
+ VolumeManagerCommon.VolumeType.PROVIDED) {
+ volumeSpaceInfo.hidden = true;
+ volumeSpaceInfoSeparator.hidden = true;
+ return;
+ }
+ volumeSpaceInfo.hidden = false;
+ volumeSpaceInfoSeparator.hidden = false;
volumeSpaceInnerBar.setAttribute('pending', '');
if (showLoadingCaption) {
volumeSpaceInnerBar.style.width = '100%';
}
- var currentVolumeInfo = this.currentVolumeInfo_;
- chrome.fileBrowserPrivate.getSizeStats(
+ chrome.fileManagerPrivate.getSizeStats(
currentVolumeInfo.volumeId, function(result) {
var volumeInfo = this.volumeManager_.getVolumeInfo(
this.directoryModel_.getCurrentDirEntry());
* @private
*/
FileManager.prototype.onDirectoryChanged_ = function(event) {
- var newCurrentVolumeInfo = this.volumeManager_.getVolumeInfo(
+ var oldCurrentVolumeInfo = this.currentVolumeInfo_;
+
+ // Remember the current volume info.
+ this.currentVolumeInfo_ = this.volumeManager_.getVolumeInfo(
event.newDirEntry);
// If volume has changed, then update the gear menu.
- if (this.currentVolumeInfo_ !== newCurrentVolumeInfo) {
+ if (oldCurrentVolumeInfo !== this.currentVolumeInfo_) {
this.updateGearMenu_();
// If the volume has changed, and it was previously set, then do not
// close on unmount anymore.
- if (this.currentVolumeInfo_)
+ if (oldCurrentVolumeInfo)
this.closeOnUnmount_ = false;
}
- // Remember the current volume info.
- this.currentVolumeInfo_ = newCurrentVolumeInfo;
-
this.selectionHandler_.onFileSelectionChanged();
- this.ui_.searchBox.clear();
+ this.searchController_.clear();
// TODO(mtomasz): Consider remembering the selection.
util.updateAppState(
this.getCurrentDirectoryEntry() ?
this.updateTitle_();
var currentEntry = this.getCurrentDirectoryEntry();
- this.previewPanel_.currentEntry = util.isFakeEntry(currentEntry) ?
+ this.ui_.locationLine.show(currentEntry);
+ this.ui_.previewPanel.currentEntry = util.isFakeEntry(currentEntry) ?
null : currentEntry;
};
}
};
- FileManager.prototype.findListItemForEvent_ = function(event) {
- return this.findListItemForNode_(event.touchedElement || event.srcElement);
- };
-
- FileManager.prototype.findListItemForNode_ = function(node) {
- var item = this.currentList_.getListItemAncestor(node);
- // TODO(serya): list should check that.
- return item && this.currentList_.isItem(item) ? item : null;
- };
-
/**
* Unload handler for the page.
* @private
this.directoryModel_.dispose();
if (this.volumeManager_)
this.volumeManager_.dispose();
- if (this.filePopup_ &&
- this.filePopup_.contentWindow &&
- this.filePopup_.contentWindow.unload)
- this.filePopup_.contentWindow.unload(true /* exiting */);
- if (this.progressCenterPanel_)
+ if (this.fileTransferController_) {
+ for (var i = 0;
+ i < this.fileTransferController_.pendingTaskIds.length;
+ i++) {
+ var taskId = this.fileTransferController_.pendingTaskIds[i];
+ var item =
+ this.backgroundPage_.background.progressCenter.getItemById(taskId);
+ item.message = '';
+ item.state = ProgressItemState.CANCELED;
+ this.backgroundPage_.background.progressCenter.updateItem(item);
+ }
+ }
+ if (this.progressCenterPanel_) {
this.backgroundPage_.background.progressCenter.removePanel(
this.progressCenterPanel_);
+ }
if (this.fileOperationManager_) {
if (this.onCopyProgressBound_) {
this.fileOperationManager_.removeEventListener(
'copy-progress', this.onCopyProgressBound_);
}
- if (this.onEntryChangedBound_) {
+ if (this.onEntriesChangedBound_) {
this.fileOperationManager_.removeEventListener(
- 'entry-changed', this.onEntryChangedBound_);
+ 'entries-changed', this.onEntriesChangedBound_);
}
}
window.closing = true;
this.backgroundPage_.background.tryClose();
};
- FileManager.prototype.initiateRename = function() {
- var item = this.currentList_.ensureLeadItemExists();
- if (!item)
- return;
- var label = item.querySelector('.filename-label');
- var input = this.renameInput_;
-
- input.value = label.textContent;
- item.setAttribute('renaming', '');
- label.parentNode.appendChild(input);
- input.focus();
- var selectionEnd = input.value.lastIndexOf('.');
- if (selectionEnd == -1) {
- input.select();
- } else {
- input.selectionStart = 0;
- input.selectionEnd = selectionEnd;
- }
-
- // This has to be set late in the process so we don't handle spurious
- // blur events.
- input.currentEntry = this.currentList_.dataModel.item(item.listIndex);
- this.table_.startBatchUpdates();
- this.grid_.startBatchUpdates();
- };
-
/**
- * @type {Event} Key event.
- * @private
- */
- FileManager.prototype.onRenameInputKeyDown_ = function(event) {
- if (!this.isRenamingInProgress())
- return;
-
- // Do not move selection or lead item in list during rename.
- if (event.keyIdentifier == 'Up' || event.keyIdentifier == 'Down') {
- event.stopPropagation();
- }
-
- switch (util.getKeyModifiers(event) + event.keyIdentifier) {
- case 'U+001B': // Escape
- this.cancelRename_();
- event.preventDefault();
- break;
-
- case 'Enter':
- this.commitRename_();
- event.preventDefault();
- break;
- }
- };
-
- /**
- * @type {Event} Blur event.
- * @private
- */
- FileManager.prototype.onRenameInputBlur_ = function(event) {
- if (this.isRenamingInProgress() && !this.renameInput_.validation_)
- this.commitRename_();
- };
-
- /**
- * @private
- */
- FileManager.prototype.commitRename_ = function() {
- var input = this.renameInput_;
- var entry = input.currentEntry;
- var newName = input.value;
-
- if (newName == entry.name) {
- this.cancelRename_();
- return;
- }
-
- var renamedItemElement = this.findListItemForNode_(this.renameInput_);
- var nameNode = renamedItemElement.querySelector('.filename-label');
-
- input.validation_ = true;
- var validationDone = function(valid) {
- input.validation_ = false;
-
- if (!valid) {
- // Cancel rename if it fails to restore focus from alert dialog.
- // Otherwise, just cancel the commitment and continue to rename.
- if (this.document_.activeElement != input)
- this.cancelRename_();
- return;
- }
-
- // Validation succeeded. Do renaming.
- this.renameInput_.currentEntry = null;
- if (this.renameInput_.parentNode)
- this.renameInput_.parentNode.removeChild(this.renameInput_);
- renamedItemElement.setAttribute('renaming', 'provisional');
-
- // Optimistically apply new name immediately to avoid flickering in
- // case of success.
- nameNode.textContent = newName;
-
- util.rename(
- entry, newName,
- function(newEntry) {
- this.directoryModel_.onRenameEntry(entry, newEntry);
- renamedItemElement.removeAttribute('renaming');
- this.table_.endBatchUpdates();
- this.grid_.endBatchUpdates();
- }.bind(this),
- function(error) {
- // Write back to the old name.
- nameNode.textContent = entry.name;
- renamedItemElement.removeAttribute('renaming');
- this.table_.endBatchUpdates();
- this.grid_.endBatchUpdates();
-
- // Show error dialog.
- var message;
- if (error.name == util.FileError.PATH_EXISTS_ERR ||
- error.name == util.FileError.TYPE_MISMATCH_ERR) {
- // Check the existing entry is file or not.
- // 1) If the entry is a file:
- // a) If we get PATH_EXISTS_ERR, a file exists.
- // b) If we get TYPE_MISMATCH_ERR, a directory exists.
- // 2) If the entry is a directory:
- // a) If we get PATH_EXISTS_ERR, a directory exists.
- // b) If we get TYPE_MISMATCH_ERR, a file exists.
- message = strf(
- (entry.isFile && error.name ==
- util.FileError.PATH_EXISTS_ERR) ||
- (!entry.isFile && error.name ==
- util.FileError.TYPE_MISMATCH_ERR) ?
- 'FILE_ALREADY_EXISTS' :
- 'DIRECTORY_ALREADY_EXISTS',
- newName);
- } else {
- message = strf('ERROR_RENAMING', entry.name,
- util.getFileErrorString(error.name));
- }
-
- this.alert.show(message);
- }.bind(this));
- };
-
- // TODO(haruki): this.getCurrentDirectoryEntry() might not return the actual
- // parent if the directory content is a search result. Fix it to do proper
- // validation.
- this.validateFileName_(this.getCurrentDirectoryEntry(),
- newName,
- validationDone.bind(this));
- };
-
- /**
- * @private
- */
- FileManager.prototype.cancelRename_ = function() {
- this.renameInput_.currentEntry = null;
-
- var item = this.findListItemForNode_(this.renameInput_);
- if (item)
- item.removeAttribute('renaming');
-
- var parent = this.renameInput_.parentNode;
- if (parent)
- parent.removeChild(this.renameInput_);
-
- this.table_.endBatchUpdates();
- this.grid_.endBatchUpdates();
- };
-
- /**
- * @param {Event} Key event.
* @private
*/
FileManager.prototype.onFilenameInputInput_ = function() {
};
/**
- * @param {Event} Key event.
+ * @param {Event} event Key event.
* @private
*/
FileManager.prototype.onFilenameInputKeyDown_ = function(event) {
if ((util.getKeyModifiers(event) + event.keyCode) === '13' /* Enter */)
- this.okButton_.click();
+ this.ui_.dialogFooter.okButton.click();
};
/**
- * @param {Event} Focus event.
+ * @param {Event} event Focus event.
* @private
*/
FileManager.prototype.onFilenameInputFocus_ = function(event) {
- var input = this.filenameInput_;
+ var input = this.ui_.dialogFooter.filenameInput;
// On focus we want to select everything but the extension, but
// Chrome will select-all after the focus event completes. We
// schedule a timeout to alter the focus after that happens.
setTimeout(function() {
- var selectionEnd = input.value.lastIndexOf('.');
- if (selectionEnd == -1) {
- input.select();
- } else {
- input.selectionStart = 0;
- input.selectionEnd = selectionEnd;
- }
- }, 0);
- };
-
- /**
- * @private
- */
- FileManager.prototype.onScanStarted_ = function() {
- if (this.scanInProgress_) {
- this.table_.list.endBatchUpdates();
- this.grid_.endBatchUpdates();
- }
-
- if (this.commandHandler)
- this.commandHandler.updateAvailability();
- this.table_.list.startBatchUpdates();
- this.grid_.startBatchUpdates();
- this.scanInProgress_ = true;
-
- this.scanUpdatedAtLeastOnceOrCompleted_ = false;
- if (this.scanCompletedTimer_) {
- clearTimeout(this.scanCompletedTimer_);
- this.scanCompletedTimer_ = null;
- }
-
- if (this.scanUpdatedTimer_) {
- clearTimeout(this.scanUpdatedTimer_);
- this.scanUpdatedTimer_ = null;
- }
-
- if (this.spinner_.hidden) {
- this.cancelSpinnerTimeout_();
- this.showSpinnerTimeout_ =
- setTimeout(this.showSpinner_.bind(this, true), 500);
- }
- };
-
- /**
- * @private
- */
- FileManager.prototype.onScanCompleted_ = function() {
- if (!this.scanInProgress_) {
- console.error('Scan-completed event recieved. But scan is not started.');
- return;
- }
-
- if (this.commandHandler)
- this.commandHandler.updateAvailability();
- this.hideSpinnerLater_();
-
- if (this.scanUpdatedTimer_) {
- clearTimeout(this.scanUpdatedTimer_);
- this.scanUpdatedTimer_ = null;
- }
-
- // To avoid flickering postpone updating the ui by a small amount of time.
- // There is a high chance, that metadata will be received within 50 ms.
- this.scanCompletedTimer_ = setTimeout(function() {
- // Check if batch updates are already finished by onScanUpdated_().
- if (!this.scanUpdatedAtLeastOnceOrCompleted_) {
- this.scanUpdatedAtLeastOnceOrCompleted_ = true;
- this.updateMiddleBarVisibility_();
- }
-
- this.scanInProgress_ = false;
- this.table_.list.endBatchUpdates();
- this.grid_.endBatchUpdates();
- this.scanCompletedTimer_ = null;
- }.bind(this), 50);
- };
-
- /**
- * @private
- */
- FileManager.prototype.onScanUpdated_ = function() {
- if (!this.scanInProgress_) {
- console.error('Scan-updated event recieved. But scan is not started.');
- return;
- }
-
- if (this.scanUpdatedTimer_ || this.scanCompletedTimer_)
- return;
-
- // Show contents incrementally by finishing batch updated, but only after
- // 200ms elapsed, to avoid flickering when it is not necessary.
- this.scanUpdatedTimer_ = setTimeout(function() {
- // We need to hide the spinner only once.
- if (!this.scanUpdatedAtLeastOnceOrCompleted_) {
- this.scanUpdatedAtLeastOnceOrCompleted_ = true;
- this.hideSpinnerLater_();
- this.updateMiddleBarVisibility_();
- }
-
- // Update the UI.
- if (this.scanInProgress_) {
- this.table_.list.endBatchUpdates();
- this.grid_.endBatchUpdates();
- this.table_.list.startBatchUpdates();
- this.grid_.startBatchUpdates();
+ var selectionEnd = input.value.lastIndexOf('.');
+ if (selectionEnd == -1) {
+ input.select();
+ } else {
+ input.selectionStart = 0;
+ input.selectionEnd = selectionEnd;
}
- this.scanUpdatedTimer_ = null;
- }.bind(this), 200);
- };
-
- /**
- * @private
- */
- FileManager.prototype.onScanCancelled_ = function() {
- if (!this.scanInProgress_) {
- console.error('Scan-cancelled event recieved. But scan is not started.');
- return;
- }
-
- if (this.commandHandler)
- this.commandHandler.updateAvailability();
- this.hideSpinnerLater_();
- if (this.scanCompletedTimer_) {
- clearTimeout(this.scanCompletedTimer_);
- this.scanCompletedTimer_ = null;
- }
- if (this.scanUpdatedTimer_) {
- clearTimeout(this.scanUpdatedTimer_);
- this.scanUpdatedTimer_ = null;
- }
- // Finish unfinished batch updates.
- if (!this.scanUpdatedAtLeastOnceOrCompleted_) {
- this.scanUpdatedAtLeastOnceOrCompleted_ = true;
- this.updateMiddleBarVisibility_();
- }
-
- this.scanInProgress_ = false;
- this.table_.list.endBatchUpdates();
- this.grid_.endBatchUpdates();
- };
-
- /**
- * Handle the 'rescan-completed' from the DirectoryModel.
- * @private
- */
- FileManager.prototype.onRescanCompleted_ = function() {
- this.selectionHandler_.onFileSelectionChanged();
- };
-
- /**
- * @private
- */
- FileManager.prototype.cancelSpinnerTimeout_ = function() {
- if (this.showSpinnerTimeout_) {
- clearTimeout(this.showSpinnerTimeout_);
- this.showSpinnerTimeout_ = null;
- }
- };
-
- /**
- * @private
- */
- FileManager.prototype.hideSpinnerLater_ = function() {
- this.cancelSpinnerTimeout_();
- this.showSpinner_(false);
- };
-
- /**
- * @param {boolean} on True to show, false to hide.
- * @private
- */
- FileManager.prototype.showSpinner_ = function(on) {
- if (on && this.directoryModel_ && this.directoryModel_.isScanning())
- this.spinner_.hidden = false;
-
- if (!on && (!this.directoryModel_ ||
- !this.directoryModel_.isScanning() ||
- this.directoryModel_.getFileList().length != 0)) {
- this.spinner_.hidden = true;
- }
+ }, 0);
};
FileManager.prototype.createNewFolder = function() {
}
var self = this;
- var list = self.currentList_;
- var tryCreate = function() {
- };
+ var list = self.ui_.listContainer.currentList;
var onSuccess = function(entry) {
metrics.recordUserAction('CreateNewFolder');
list.selectedItem = entry;
- self.table_.list.endBatchUpdates();
- self.grid_.endBatchUpdates();
+ self.ui_.listContainer.endBatchUpdates();
- self.initiateRename();
+ self.namingController_.initiateRename();
};
var onError = function(error) {
- self.table_.list.endBatchUpdates();
- self.grid_.endBatchUpdates();
+ self.ui_.listContainer.endBatchUpdates();
self.alert.show(strf('ERROR_CREATING_FOLDER', current(),
util.getFileErrorString(error.name)));
};
var onAbort = function() {
- self.table_.list.endBatchUpdates();
- self.grid_.endBatchUpdates();
+ self.ui_.listContainer.endBatchUpdates();
};
- this.table_.list.startBatchUpdates();
- this.grid_.startBatchUpdates();
+ this.ui_.listContainer.startBatchUpdates();
this.directoryModel_.createDirectory(current(),
onSuccess,
onError,
};
/**
+ * Handles click event on the toggle-view button.
* @param {Event} event Click event.
* @private
*/
- FileManager.prototype.onDetailViewButtonClick_ = function(event) {
- // Stop propagate and hide the menu manually, in order to prevent the focus
- // from being back to the button. (cf. http://crbug.com/248479)
- event.stopPropagation();
- this.gearButton_.hideMenu();
- this.gearButton_.blur();
- this.setListType(FileManager.ListType.DETAIL);
- };
+ FileManager.prototype.onToggleViewButtonClick_ = function(event) {
+ if (this.ui_.listContainer.currentListType ===
+ ListContainer.ListType.DETAIL) {
+ this.setListType(ListContainer.ListType.THUMBNAIL);
+ } else {
+ this.setListType(ListContainer.ListType.DETAIL);
+ }
- /**
- * @param {Event} event Click event.
- * @private
- */
- FileManager.prototype.onThumbnailViewButtonClick_ = function(event) {
- // Stop propagate and hide the menu manually, in order to prevent the focus
- // from being back to the button. (cf. http://crbug.com/248479)
- event.stopPropagation();
- this.gearButton_.hideMenu();
- this.gearButton_.blur();
- this.setListType(FileManager.ListType.THUMBNAIL);
+ event.target.blur();
};
/**
if (event.keyCode === 17) // Ctrl
this.pressingCtrl_ = true;
- if (event.srcElement === this.renameInput_) {
+ if (event.srcElement === this.ui_.listContainer.renameInput) {
// Ignore keydown handler in the rename input box.
return;
}
- switch (util.getKeyModifiers(event) + event.keyCode) {
- case 'Ctrl-190': // Ctrl-. => Toggle filter files.
+ switch (util.getKeyModifiers(event) + event.keyIdentifier) {
+ case 'Ctrl-U+00BE': // Ctrl-. => Toggle filter files.
this.fileFilter_.setFilterHidden(
!this.fileFilter_.isFilterHiddenOn());
event.preventDefault();
return;
- case '27': // Escape => Cancel dialog.
+ case 'U+001B': // Escape => Cancel dialog.
if (this.dialogType != DialogType.FULL_PAGE) {
// If there is nothing else for ESC to do, then cancel the dialog.
event.preventDefault();
- this.cancelButton_.click();
+ this.ui_.dialogFooter.cancelButton.click();
}
break;
}
* @private
*/
FileManager.prototype.onListKeyDown_ = function(event) {
- if (event.srcElement.tagName == 'INPUT') {
- // Ignore keydown handler in the rename input box.
- return;
- }
-
- switch (util.getKeyModifiers(event) + event.keyCode) {
- case '8': // Backspace => Up one directory.
+ switch (util.getKeyModifiers(event) + event.keyIdentifier) {
+ case 'U+0008': // Backspace => Up one directory.
event.preventDefault();
// TODO(mtomasz): Use Entry.getParent() instead.
if (!this.getCurrentDirectoryEntry())
}
break;
- case '13': // Enter => Change directory or perform default action.
+ case 'Enter': // Enter => Change directory or perform default action.
// TODO(dgozman): move directory action to dispatchSelectionAction.
var selection = this.getSelection();
- if (selection.totalCount == 1 &&
+ if (selection.totalCount === 1 &&
selection.entries[0].isDirectory &&
!DialogType.isFolderDialog(this.dialogType)) {
- event.preventDefault();
- this.onDirectoryAction_(selection.entries[0]);
+ var item = this.ui.listContainer.currentList.getListItemByIndex(
+ selection.indexes[0]);
+ // If the item is in renaming process, we don't allow to change
+ // directory.
+ if (!item.hasAttribute('renaming')) {
+ event.preventDefault();
+ this.onDirectoryAction_(selection.entries[0]);
+ }
} else if (this.dispatchSelectionAction_()) {
event.preventDefault();
}
break;
}
-
- switch (event.keyIdentifier) {
- case 'Home':
- case 'End':
- case 'Up':
- case 'Down':
- case 'Left':
- case 'Right':
- // When navigating with keyboard we hide the distracting mouse hover
- // highlighting until the user moves the mouse again.
- this.setNoHover_(true);
- break;
- }
- };
-
- /**
- * Suppress/restore hover highlighting in the list container.
- * @param {boolean} on True to temporarity hide hover state.
- * @private
- */
- FileManager.prototype.setNoHover_ = function(on) {
- if (on) {
- this.listContainer_.classList.add('nohover');
- } else {
- this.listContainer_.classList.remove('nohover');
- }
- };
-
- /**
- * KeyPress event handler for the div#list-container element.
- * @param {Event} event Key event.
- * @private
- */
- FileManager.prototype.onListKeyPress_ = function(event) {
- if (event.srcElement.tagName == 'INPUT') {
- // Ignore keypress handler in the rename input box.
- return;
- }
-
- if (event.ctrlKey || event.metaKey || event.altKey)
- return;
-
- var now = new Date();
- var char = String.fromCharCode(event.charCode).toLowerCase();
- var text = now - this.textSearchState_.date > 1000 ? '' :
- this.textSearchState_.text;
- this.textSearchState_ = {text: text + char, date: now};
-
- this.doTextSearch_();
- };
-
- /**
- * Mousemove event handler for the div#list-container element.
- * @param {Event} event Mouse event.
- * @private
- */
- FileManager.prototype.onListMouseMove_ = function(event) {
- // The user grabbed the mouse, restore the hover highlighting.
- this.setNoHover_(false);
};
/**
* starting with entered text (case-insensitive).
* @private
*/
- FileManager.prototype.doTextSearch_ = function() {
- var text = this.textSearchState_.text;
- if (!text)
- return;
-
+ FileManager.prototype.onTextSearch_ = function() {
+ var text = this.ui_.listContainer.textSearchState.text;
var dm = this.directoryModel_.getFileList();
for (var index = 0; index < dm.length; ++index) {
var name = dm.item(index).name;
if (name.substring(0, text.length).toLowerCase() == text) {
- this.currentList_.selectionModel.selectedIndexes = [index];
+ this.ui.listContainer.currentList.selectionModel.selectedIndexes =
+ [index];
return;
}
}
- this.textSearchState_.text = '';
- };
-
- /**
- * Handle a click of the cancel button. Closes the window.
- * TODO(jamescook): Make unload handler work automatically, crbug.com/104811
- *
- * @param {Event} event The click event.
- * @private
- */
- FileManager.prototype.onCancel_ = function(event) {
- chrome.fileBrowserPrivate.cancelDialog();
- window.close();
- };
-
- /**
- * Resolves selected file urls returned from an Open dialog.
- *
- * For drive files this involves some special treatment.
- * Starts getting drive files if needed.
- *
- * @param {Array.<string>} fileUrls Drive URLs.
- * @param {function(Array.<string>)} callback To be called with fixed URLs.
- * @private
- */
- FileManager.prototype.resolveSelectResults_ = function(fileUrls, callback) {
- if (this.isOnDrive()) {
- chrome.fileBrowserPrivate.getDriveFiles(
- fileUrls,
- function(localPaths) {
- callback(fileUrls);
- });
- } else {
- callback(fileUrls);
- }
- };
-
- /**
- * Closes this modal dialog with some files selected.
- * TODO(jamescook): Make unload handler work automatically, crbug.com/104811
- * @param {Object} selection Contains urls, filterIndex and multiple fields.
- * @private
- */
- FileManager.prototype.callSelectFilesApiAndClose_ = function(selection) {
- var self = this;
- function callback() {
- window.close();
- }
- if (selection.multiple) {
- chrome.fileBrowserPrivate.selectFiles(
- selection.urls, this.params_.shouldReturnLocalPath, callback);
- } else {
- var forOpening = (this.dialogType != DialogType.SELECT_SAVEAS_FILE);
- chrome.fileBrowserPrivate.selectFile(
- selection.urls[0], selection.filterIndex, forOpening,
- this.params_.shouldReturnLocalPath, callback);
- }
- };
-
- /**
- * Tries to close this modal dialog with some files selected.
- * Performs preprocessing if needed (e.g. for Drive).
- * @param {Object} selection Contains urls, filterIndex and multiple fields.
- * @private
- */
- FileManager.prototype.selectFilesAndClose_ = function(selection) {
- if (!this.isOnDrive() ||
- this.dialogType == DialogType.SELECT_SAVEAS_FILE) {
- setTimeout(this.callSelectFilesApiAndClose_.bind(this, selection), 0);
- return;
- }
-
- var shade = this.document_.createElement('div');
- shade.className = 'shade';
- var footer = this.dialogDom_.querySelector('.button-panel');
- var progress = footer.querySelector('.progress-track');
- progress.style.width = '0%';
- var cancelled = false;
-
- var progressMap = {};
- var filesStarted = 0;
- var filesTotal = selection.urls.length;
- for (var index = 0; index < selection.urls.length; index++) {
- progressMap[selection.urls[index]] = -1;
- }
- var lastPercent = 0;
- var bytesTotal = 0;
- var bytesDone = 0;
-
- var onFileTransfersUpdated = function(statusList) {
- for (var index = 0; index < statusList.length; index++) {
- var status = statusList[index];
- var escaped = encodeURI(status.fileUrl);
- if (!(escaped in progressMap)) continue;
- if (status.total == -1) continue;
-
- var old = progressMap[escaped];
- if (old == -1) {
- // -1 means we don't know file size yet.
- bytesTotal += status.total;
- filesStarted++;
- old = 0;
- }
- bytesDone += status.processed - old;
- progressMap[escaped] = status.processed;
- }
-
- var percent = bytesTotal == 0 ? 0 : bytesDone / bytesTotal;
- // For files we don't have information about, assume the progress is zero.
- percent = percent * filesStarted / filesTotal * 100;
- // Do not decrease the progress. This may happen, if first downloaded
- // file is small, and the second one is large.
- lastPercent = Math.max(lastPercent, percent);
- progress.style.width = lastPercent + '%';
- }.bind(this);
-
- var setup = function() {
- this.document_.querySelector('.dialog-container').appendChild(shade);
- setTimeout(function() { shade.setAttribute('fadein', 'fadein') }, 100);
- footer.setAttribute('progress', 'progress');
- this.cancelButton_.removeEventListener('click', this.onCancelBound_);
- this.cancelButton_.addEventListener('click', onCancel);
- chrome.fileBrowserPrivate.onFileTransfersUpdated.addListener(
- onFileTransfersUpdated);
- }.bind(this);
-
- var cleanup = function() {
- shade.parentNode.removeChild(shade);
- footer.removeAttribute('progress');
- this.cancelButton_.removeEventListener('click', onCancel);
- this.cancelButton_.addEventListener('click', this.onCancelBound_);
- chrome.fileBrowserPrivate.onFileTransfersUpdated.removeListener(
- onFileTransfersUpdated);
- }.bind(this);
-
- var onCancel = function() {
- cancelled = true;
- // According to API cancel may fail, but there is no proper UI to reflect
- // this. So, we just silently assume that everything is cancelled.
- chrome.fileBrowserPrivate.cancelFileTransfers(
- selection.urls, function(response) {});
- cleanup();
- }.bind(this);
-
- var onResolved = function(resolvedUrls) {
- if (cancelled) return;
- cleanup();
- selection.urls = resolvedUrls;
- // Call next method on a timeout, as it's unsafe to
- // close a window from a callback.
- setTimeout(this.callSelectFilesApiAndClose_.bind(this, selection), 0);
- }.bind(this);
-
- var onProperties = function(properties) {
- for (var i = 0; i < properties.length; i++) {
- if (!properties[i] || properties[i].present) {
- // For files already in GCache, we don't get any transfer updates.
- filesTotal--;
- }
- }
- this.resolveSelectResults_(selection.urls, onResolved);
- }.bind(this);
-
- setup();
-
- // TODO(mtomasz): Use Entry instead of URLs, if possible.
- util.URLsToEntries(selection.urls, function(entries) {
- this.metadataCache_.get(entries, 'drive', onProperties);
- }.bind(this));
- };
-
- /**
- * Handle a click of the ok button.
- *
- * The ok button has different UI labels depending on the type of dialog, but
- * in code it's always referred to as 'ok'.
- *
- * @param {Event} event The click event.
- * @private
- */
- FileManager.prototype.onOk_ = function(event) {
- if (this.dialogType == DialogType.SELECT_SAVEAS_FILE) {
- // Save-as doesn't require a valid selection from the list, since
- // we're going to take the filename from the text input.
- var filename = this.filenameInput_.value;
- if (!filename)
- throw new Error('Missing filename!');
-
- var directory = this.getCurrentDirectoryEntry();
- this.validateFileName_(directory, filename, function(isValid) {
- if (!isValid)
- return;
-
- if (util.isFakeEntry(directory)) {
- // Can't save a file into a fake directory.
- return;
- }
-
- var selectFileAndClose = function() {
- // TODO(mtomasz): Clean this up by avoiding constructing a URL
- // via string concatenation.
- var currentDirUrl = directory.toURL();
- if (currentDirUrl.charAt(currentDirUrl.length - 1) != '/')
- currentDirUrl += '/';
- this.selectFilesAndClose_({
- urls: [currentDirUrl + encodeURIComponent(filename)],
- multiple: false,
- filterIndex: this.getSelectedFilterIndex_(filename)
- });
- }.bind(this);
-
- directory.getFile(
- filename, {create: false},
- function(entry) {
- // An existing file is found. Show confirmation dialog to
- // overwrite it. If the user select "OK" on the dialog, save it.
- this.confirm.show(strf('CONFIRM_OVERWRITE_FILE', filename),
- selectFileAndClose);
- }.bind(this),
- function(error) {
- if (error.name == util.FileError.NOT_FOUND_ERR) {
- // The file does not exist, so it should be ok to create a
- // new file.
- selectFileAndClose();
- return;
- }
- if (error.name == util.FileError.TYPE_MISMATCH_ERR) {
- // An directory is found.
- // Do not allow to overwrite directory.
- this.alert.show(strf('DIRECTORY_ALREADY_EXISTS', filename));
- return;
- }
-
- // Unexpected error.
- console.error('File save failed: ' + error.code);
- }.bind(this));
- }.bind(this));
- return;
- }
-
- var files = [];
- var selectedIndexes = this.currentList_.selectionModel.selectedIndexes;
-
- if (DialogType.isFolderDialog(this.dialogType) &&
- selectedIndexes.length == 0) {
- var url = this.getCurrentDirectoryEntry().toURL();
- var singleSelection = {
- urls: [url],
- multiple: false,
- filterIndex: this.getSelectedFilterIndex_()
- };
- this.selectFilesAndClose_(singleSelection);
- return;
- }
-
- // All other dialog types require at least one selected list item.
- // The logic to control whether or not the ok button is enabled should
- // prevent us from ever getting here, but we sanity check to be sure.
- if (!selectedIndexes.length)
- throw new Error('Nothing selected!');
-
- var dm = this.directoryModel_.getFileList();
- for (var i = 0; i < selectedIndexes.length; i++) {
- var entry = dm.item(selectedIndexes[i]);
- if (!entry) {
- console.error('Error locating selected file at index: ' + i);
- continue;
- }
-
- files.push(entry.toURL());
- }
-
- // Multi-file selection has no other restrictions.
- if (this.dialogType == DialogType.SELECT_OPEN_MULTI_FILE) {
- var multipleSelection = {
- urls: files,
- multiple: true
- };
- this.selectFilesAndClose_(multipleSelection);
- return;
- }
-
- // Everything else must have exactly one.
- if (files.length > 1)
- throw new Error('Too many files selected!');
-
- var selectedEntry = dm.item(selectedIndexes[0]);
-
- if (DialogType.isFolderDialog(this.dialogType)) {
- if (!selectedEntry.isDirectory)
- throw new Error('Selected entry is not a folder!');
- } else if (this.dialogType == DialogType.SELECT_OPEN_FILE) {
- if (!selectedEntry.isFile)
- throw new Error('Selected entry is not a file!');
- }
-
- var singleSelection = {
- urls: [files[0]],
- multiple: false,
- filterIndex: this.getSelectedFilterIndex_()
- };
- this.selectFilesAndClose_(singleSelection);
+ this.ui_.listContainer.textSearchState.text = '';
};
/**
* Verifies the user entered name for file or folder to be created or
- * renamed to. Name restrictions must correspond to File API restrictions
- * (see DOMFilePath::isValidPath). Curernt WebKit implementation is
- * out of date (spec is
- * http://dev.w3.org/2009/dap/file-system/file-dir-sys.html, 8.3) and going to
- * be fixed. Shows message box if the name is invalid.
- *
- * It also verifies if the name length is in the limit of the filesystem.
+ * renamed to. See also util.validateFileName.
*
* @param {DirectoryEntry} parentEntry The URL of the parent directory entry.
* @param {string} name New file or folder name.
- * @param {function} onDone Function to invoke when user closes the
+ * @param {function(boolean)} onDone Function to invoke when user closes the
* warning box or immediatelly if file name is correct. If the name was
* valid it is passed true, and false otherwise.
* @private
*/
FileManager.prototype.validateFileName_ = function(
parentEntry, name, onDone) {
- var msg;
- var testResult = /[\/\\\<\>\:\?\*\"\|]/.exec(name);
- if (testResult) {
- msg = strf('ERROR_INVALID_CHARACTER', testResult[0]);
- } else if (/^\s*$/i.test(name)) {
- msg = str('ERROR_WHITESPACE_NAME');
- } else if (/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(name)) {
- msg = str('ERROR_RESERVED_NAME');
- } else if (this.fileFilter_.isFilterHiddenOn() && name[0] == '.') {
- msg = str('ERROR_HIDDEN_NAME');
- }
-
- if (msg) {
- this.alert.show(msg, function() {
- onDone(false);
- });
- return;
- }
-
- var self = this;
- chrome.fileBrowserPrivate.validatePathNameLength(
- parentEntry.toURL(), name, function(valid) {
- if (!valid) {
- self.alert.show(str('ERROR_LONG_NAME'),
- function() { onDone(false); });
- } else {
- onDone(true);
- }
- });
+ var fileNameErrorPromise = util.validateFileName(
+ parentEntry,
+ name,
+ this.fileFilter_.isFilterHiddenOn());
+ fileNameErrorPromise.then(onDone.bind(null, true), function(message) {
+ this.alert.show(message, onDone.bind(null, false));
+ }.bind(this)).catch(function(error) {
+ console.error(error.stack || error);
+ });
};
/**
// If checked, the sync is disabled.
var nowCellularDisabled = this.syncButton.hasAttribute('checked');
var changeInfo = {cellularDisabled: !nowCellularDisabled};
- chrome.fileBrowserPrivate.setPreferences(changeInfo);
+ chrome.fileManagerPrivate.setPreferences(changeInfo);
};
/**
*/
var changeInfo = {};
changeInfo['hostedFilesDisabled'] = !nowHostedFilesDisabled;
- chrome.fileBrowserPrivate.setPreferences(changeInfo);
- };
-
- /**
- * Invoked when the search box is changed.
- *
- * @param {Event} event The changed event.
- * @private
- */
- FileManager.prototype.onSearchBoxUpdate_ = function(event) {
- var searchString = this.searchBox_.value;
-
- if (this.isOnDrive()) {
- // When the search text is changed, finishes the search and showes back
- // the last directory by passing an empty string to
- // {@code DirectoryModel.search()}.
- if (this.directoryModel_.isSearching() &&
- this.lastSearchQuery_ != searchString) {
- this.doSearch('');
- }
-
- // On drive, incremental search is not invoked since we have an auto-
- // complete suggestion instead.
- return;
- }
-
- this.search_(searchString);
- };
-
- /**
- * Handle the search clear button click.
- * @private
- */
- FileManager.prototype.onSearchClearButtonClick_ = function() {
- this.ui_.searchBox.clear();
- this.onSearchBoxUpdate_();
- };
-
- /**
- * Search files and update the list with the search result.
- *
- * @param {string} searchString String to be searched with.
- * @private
- */
- FileManager.prototype.search_ = function(searchString) {
- var noResultsDiv = this.document_.getElementById('no-search-results');
-
- var reportEmptySearchResults = function() {
- if (this.directoryModel_.getFileList().length === 0) {
- // The string 'SEARCH_NO_MATCHING_FILES_HTML' may contain HTML tags,
- // hence we escapes |searchString| here.
- var html = strf('SEARCH_NO_MATCHING_FILES_HTML',
- util.htmlEscape(searchString));
- noResultsDiv.innerHTML = html;
- noResultsDiv.setAttribute('show', 'true');
- } else {
- noResultsDiv.removeAttribute('show');
- }
- };
-
- var hideNoResultsDiv = function() {
- noResultsDiv.removeAttribute('show');
- };
-
- this.doSearch(searchString,
- reportEmptySearchResults.bind(this),
- hideNoResultsDiv.bind(this));
- };
-
- /**
- * Performs search and displays results.
- *
- * @param {string} query Query that will be searched for.
- * @param {function()=} opt_onSearchRescan Function that will be called when
- * the search directory is rescanned (i.e. search results are displayed).
- * @param {function()=} opt_onClearSearch Function to be called when search
- * state gets cleared.
- */
- FileManager.prototype.doSearch = function(
- searchString, opt_onSearchRescan, opt_onClearSearch) {
- var onSearchRescan = opt_onSearchRescan || function() {};
- var onClearSearch = opt_onClearSearch || function() {};
-
- this.lastSearchQuery_ = searchString;
- this.directoryModel_.search(searchString, onSearchRescan, onClearSearch);
- };
-
- /**
- * Requests autocomplete suggestions for files on Drive.
- * Once the suggestions are returned, the autocomplete popup will show up.
- *
- * @param {string} query The text to autocomplete from.
- * @private
- */
- FileManager.prototype.requestAutocompleteSuggestions_ = function(query) {
- query = query.trimLeft();
-
- // Only Drive supports auto-compelete
- if (!this.isOnDrive())
- return;
-
- // Remember the most recent query. If there is an other request in progress,
- // then it's result will be discarded and it will call a new request for
- // this query.
- this.lastAutocompleteQuery_ = query;
- if (this.autocompleteSuggestionsBusy_)
- return;
-
- // The autocomplete list should be resized and repositioned here as the
- // search box is resized when it's focused.
- this.autocompleteList_.syncWidthAndPositionToInput();
-
- if (!query) {
- this.autocompleteList_.suggestions = [];
- return;
- }
-
- var headerItem = {isHeaderItem: true, searchQuery: query};
- if (!this.autocompleteList_.dataModel ||
- this.autocompleteList_.dataModel.length == 0)
- this.autocompleteList_.suggestions = [headerItem];
- else
- // Updates only the head item to prevent a flickering on typing.
- this.autocompleteList_.dataModel.splice(0, 1, headerItem);
-
- this.autocompleteSuggestionsBusy_ = true;
-
- var searchParams = {
- 'query': query,
- 'types': 'ALL',
- 'maxResults': 4
- };
- chrome.fileBrowserPrivate.searchDriveMetadata(
- searchParams,
- function(suggestions) {
- this.autocompleteSuggestionsBusy_ = false;
-
- // Discard results for previous requests and fire a new search
- // for the most recent query.
- if (query != this.lastAutocompleteQuery_) {
- this.requestAutocompleteSuggestions_(this.lastAutocompleteQuery_);
- return;
- }
-
- // Keeps the items in the suggestion list.
- this.autocompleteList_.suggestions = [headerItem].concat(suggestions);
- }.bind(this));
- };
-
- /**
- * Opens the currently selected suggestion item.
- * @private
- */
- FileManager.prototype.openAutocompleteSuggestion_ = function() {
- var selectedItem = this.autocompleteList_.selectedItem;
-
- // If the entry is the search item or no entry is selected, just change to
- // the search result.
- if (!selectedItem || selectedItem.isHeaderItem) {
- var query = selectedItem ?
- selectedItem.searchQuery : this.searchBox_.value;
- this.search_(query);
- return;
- }
-
- var entry = selectedItem.entry;
- // If the entry is a directory, just change the directory.
- if (entry.isDirectory) {
- this.onDirectoryAction_(entry);
- return;
- }
-
- var entries = [entry];
- var self = this;
-
- // To open a file, first get the mime type.
- this.metadataCache_.get(entries, 'drive', function(props) {
- var mimeType = props[0].contentMimeType || '';
- var mimeTypes = [mimeType];
- var openIt = function() {
- if (self.dialogType == DialogType.FULL_PAGE) {
- var tasks = new FileTasks(self);
- tasks.init(entries, mimeTypes);
- tasks.executeDefault();
- } else {
- self.onOk_();
- }
- };
-
- // Change the current directory to the directory that contains the
- // selected file. Note that this is necessary for an image or a video,
- // which should be opened in the gallery mode, as the gallery mode
- // requires the entry to be in the current directory model. For
- // consistency, the current directory is always changed regardless of
- // the file type.
- entry.getParent(function(parentEntry) {
- var onDirectoryChanged = function(event) {
- self.directoryModel_.removeEventListener('scan-completed',
- onDirectoryChanged);
- self.directoryModel_.selectEntry(entry);
- openIt();
- };
- // changeDirectoryEntry() returns immediately. We should wait until the
- // directory scan is complete.
- self.directoryModel_.addEventListener('scan-completed',
- onDirectoryChanged);
- self.directoryModel_.changeDirectoryEntry(
- parentEntry,
- function() {
- // Remove the listner if the change directory failed.
- self.directoryModel_.removeEventListener('scan-completed',
- onDirectoryChanged);
- });
- });
- });
+ chrome.fileManagerPrivate.setPreferences(changeInfo);
};
FileManager.prototype.decorateSplitter = function(splitterElement) {
};
/**
- * Updates default action menu item to match passed taskItem (icon,
- * label and action).
+ * Updates action menu item to match passed task items.
*
- * @param {Object} defaultItem - taskItem to match.
- * @param {boolean} isMultiple - if multiple tasks available.
- */
- FileManager.prototype.updateContextMenuActionItems = function(defaultItem,
- isMultiple) {
- if (defaultItem) {
- if (defaultItem.iconType) {
- this.defaultActionMenuItem_.style.backgroundImage = '';
- this.defaultActionMenuItem_.setAttribute('file-type-icon',
- defaultItem.iconType);
- } else if (defaultItem.iconUrl) {
- this.defaultActionMenuItem_.style.backgroundImage =
- 'url(' + defaultItem.iconUrl + ')';
+ * @param {Array.<Object>=} opt_items List of items.
+ */
+ FileManager.prototype.updateContextMenuActionItems = function(opt_items) {
+ var items = opt_items || [];
+
+ // When only one task is available, show it as default item.
+ if (items.length === 1) {
+ var actionItem = items[0];
+
+ if (actionItem.iconType) {
+ this.actionMenuItem_.style.backgroundImage = '';
+ this.actionMenuItem_.setAttribute('file-type-icon',
+ actionItem.iconType);
+ } else if (actionItem.iconUrl) {
+ this.actionMenuItem_.style.backgroundImage =
+ 'url(' + actionItem.iconUrl + ')';
} else {
- this.defaultActionMenuItem_.style.backgroundImage = '';
+ this.actionMenuItem_.style.backgroundImage = '';
}
- this.defaultActionMenuItem_.label = defaultItem.title;
- this.defaultActionMenuItem_.disabled = !!defaultItem.disabled;
- this.defaultActionMenuItem_.taskId = defaultItem.taskId;
+ this.actionMenuItem_.label =
+ actionItem.taskId === FileTasks.ZIP_UNPACKER_TASK_ID ?
+ str('ACTION_OPEN') : actionItem.title;
+ this.actionMenuItem_.disabled = !!actionItem.disabled;
+ this.actionMenuItem_.taskId = actionItem.taskId;
}
- var defaultActionSeparator =
- this.dialogDom_.querySelector('#default-action-separator');
+ this.actionMenuItem_.hidden = items.length !== 1;
+ // When multiple tasks are available, show them in open with.
this.openWithCommand_.canExecuteChange();
- this.openWithCommand_.setHidden(!(defaultItem && isMultiple));
- this.openWithCommand_.disabled = defaultItem && !!defaultItem.disabled;
+ this.openWithCommand_.setHidden(items.length < 2);
+ this.openWithCommand_.disabled = items.length < 2;
- this.defaultActionMenuItem_.hidden = !defaultItem;
- defaultActionSeparator.hidden = !defaultItem;
+ // Hide default action separator when there does not exist available task.
+ var defaultActionSeparator =
+ this.dialogDom_.querySelector('#default-action-separator');
+ defaultActionSeparator.hidden = items.length === 0;
};
/**
};
/**
- * @return {ArrayDataModel} File list.
+ * @return {cr.ui.ArrayDataModel} File list.
*/
FileManager.prototype.getFileList = function() {
return this.directoryModel_.getFileList();
};
/**
- * @return {cr.ui.List} Current list object.
+ * @return {!cr.ui.List} Current list object.
*/
FileManager.prototype.getCurrentList = function() {
- return this.currentList_;
+ return this.ui.listContainer.currentList;
};
/**
* @private
*/
FileManager.prototype.getPreferences_ = function(callback, opt_update) {
- if (!opt_update && this.preferences_ !== undefined) {
+ if (!opt_update && this.preferences_ !== null) {
callback(this.preferences_);
return;
}
- chrome.fileBrowserPrivate.getPreferences(function(prefs) {
+ chrome.fileManagerPrivate.getPreferences(function(prefs) {
this.preferences_ = prefs;
callback(prefs);
}.bind(this));
};
+
+ /**
+ * @param {FileEntry} entry
+ * @private
+ */
+ FileManager.prototype.doEntryAction_ = function(entry) {
+ if (this.dialogType == DialogType.FULL_PAGE) {
+ this.metadataCache_.get([entry], 'external', function(props) {
+ var tasks = new FileTasks(this);
+ tasks.init([entry], [props[0].contentMimeType || '']);
+ tasks.executeDefault_();
+ }.bind(this));
+ } else {
+ var selection = this.getSelection();
+ if (selection.entries.length === 1 &&
+ util.isSameEntry(selection.entries[0], entry)) {
+ this.ui_.dialogFooter.okButton.click();
+ }
+ }
+ };
+
+ /**
+ * Outputs the current state for debugging.
+ */
+ FileManager.prototype.debugMe = function() {
+ var out = 'Debug information.\n' +
+ '1. fileManager.initializeQueue_.pendingTasks_\n';
+ var keys = Object.keys(this.initializeQueue_.pendingTasks);
+ out += 'Length: ' + keys.length + '\n';
+ keys.forEach(function(key) {
+ out += this.initializeQueue_.pendingTasks[key].toString() + '\n';
+ }.bind(this));
+
+ out += '2. VolumeManagerWrapper\n' +
+ this.volumeManager_.toString() + '\n';
+
+ out += 'End of debug information.';
+ console.log(out);
+ };
})();