| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * FileManager constructor. |
| * |
| * FileManager objects encapsulate the functionality of the file selector |
| * dialogs, as well as the full screen file manager application (though the |
| * latter is not yet implemented). |
| * |
| * @constructor |
| * @struct |
| */ |
| function FileManager() { |
| // -------------------------------------------------------------------------- |
| // 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. |
| |
| /** |
| * Queue for ordering FileManager's initialization process. |
| * @type {AsyncUtil.Group} |
| * @private |
| */ |
| this.initializeQueue_ = new AsyncUtil.Group(); |
| |
| /** |
| * True while a user is pressing <Tab>. |
| * This is used for identifying the trigger causing the filelist to |
| * be focused. |
| * @type {boolean} |
| * @private |
| */ |
| this.pressingTab_ = false; |
| |
| /** |
| * True while a user is pressing <Ctrl>. |
| * |
| * TODO(fukino): This key is used only for controlling gear menu, so it |
| * should be moved to GearMenu class. crbug.com/366032. |
| * |
| * @type {boolean} |
| * @private |
| */ |
| this.pressingCtrl_ = false; |
| |
| /** |
| * True if shown gear menu is in secret mode. |
| * |
| * TODO(fukino): The state of gear menu should be moved to GearMenu class. |
| * crbug.com/366032. |
| * |
| * @type {boolean} |
| * @private |
| */ |
| this.isSecretGearMenuShown_ = false; |
| |
| /** |
| * The last clicked item in the file list. |
| * @type {HTMLLIElement} |
| * @private |
| */ |
| this.lastClickedItem_ = null; |
| |
| /** |
| * Count of the SourceNotFound error. |
| * @type {number} |
| * @private |
| */ |
| 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 = /** @struct */ { |
| __proto__: cr.EventTarget.prototype, |
| /** |
| * @return {DirectoryModel} |
| */ |
| get directoryModel() { |
| return this.directoryModel_; |
| }, |
| /** |
| * @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_; |
| } |
| }; |
| |
| /** |
| * List of dialog types. |
| * |
| * Keep this in sync with FileManagerDialog::GetDialogTypeAsString, except |
| * FULL_PAGE which is specific to this code. |
| * |
| * @enum {string} |
| * @const |
| */ |
| var DialogType = { |
| SELECT_FOLDER: 'folder', |
| SELECT_UPLOAD_FOLDER: 'upload-folder', |
| SELECT_SAVEAS_FILE: 'saveas-file', |
| SELECT_OPEN_FILE: 'open-file', |
| SELECT_OPEN_MULTI_FILE: 'open-multi-file', |
| FULL_PAGE: 'full-page' |
| }; |
| |
| /** |
| * @param {DialogType} type Dialog type. |
| * @return {boolean} Whether the type is modal. |
| */ |
| DialogType.isModal = function(type) { |
| return type == DialogType.SELECT_FOLDER || |
| type == DialogType.SELECT_UPLOAD_FOLDER || |
| type == DialogType.SELECT_SAVEAS_FILE || |
| type == DialogType.SELECT_OPEN_FILE || |
| type == DialogType.SELECT_OPEN_MULTI_FILE; |
| }; |
| |
| /** |
| * @param {DialogType} type Dialog type. |
| * @return {boolean} Whether the type is open dialog. |
| */ |
| DialogType.isOpenDialog = function(type) { |
| return type == DialogType.SELECT_OPEN_FILE || |
| type == DialogType.SELECT_OPEN_MULTI_FILE || |
| type == DialogType.SELECT_FOLDER || |
| type == DialogType.SELECT_UPLOAD_FOLDER; |
| }; |
| |
| /** |
| * @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) { |
| return type == DialogType.SELECT_FOLDER || |
| type == DialogType.SELECT_UPLOAD_FOLDER; |
| }; |
| |
| Object.freeze(DialogType); |
| |
| /** |
| * Bottom margin of the list and tree for transparent preview panel. |
| * @const |
| */ |
| var BOTTOM_MARGIN_FOR_PREVIEW_PANEL_PX = 52; |
| |
| // Anonymous "namespace". |
| (function() { |
| // Private variables and helper functions. |
| |
| /** |
| * Number of milliseconds in a day. |
| */ |
| var MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; |
| |
| /** |
| * Some UI elements react on a single click and standard double click handling |
| * leads to confusing results. We ignore a second click if it comes soon |
| * after the first. |
| */ |
| var DOUBLE_CLICK_TIMEOUT = 200; |
| |
| /** |
| * 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. |
| * @param {!Element} spaceInfoLabel Inline element to contain the message. |
| * @param {!Element} spaceOuterBar Block element around the percentage bar. |
| */ |
| var updateSpaceInfo = function( |
| sizeStatsResult, spaceInnerBar, spaceInfoLabel, spaceOuterBar) { |
| spaceInnerBar.removeAttribute('pending'); |
| if (sizeStatsResult) { |
| var sizeStr = util.bytesToString(sizeStatsResult.remainingSize); |
| spaceInfoLabel.textContent = strf('SPACE_AVAILABLE', sizeStr); |
| |
| var usedSpace = |
| sizeStatsResult.totalSize - sizeStatsResult.remainingSize; |
| spaceInnerBar.style.width = |
| (100 * usedSpace / sizeStatsResult.totalSize) + '%'; |
| |
| spaceOuterBar.hidden = false; |
| } else { |
| spaceOuterBar.hidden = true; |
| spaceInfoLabel.textContent = str('FAILED_SPACE_INFO'); |
| } |
| }; |
| |
| FileManager.prototype.initPreferences_ = function(callback) { |
| var group = new AsyncUtil.Group(); |
| |
| // DRIVE preferences should be initialized before creating DirectoryModel |
| // to rebuild the roots list. |
| group.add(this.getPreferences_.bind(this)); |
| |
| // Get startup preferences. |
| group.add(function(done) { |
| 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); |
| } catch (ignore) {} |
| // Override with window-specific options. |
| if (window.appState && window.appState.viewOptions) { |
| for (var key in window.appState.viewOptions) { |
| if (window.appState.viewOptions.hasOwnProperty(key)) |
| this.viewOptions_[key] = window.appState.viewOptions[key]; |
| } |
| } |
| done(); |
| }.bind(this)); |
| }.bind(this)); |
| |
| group.run(callback); |
| }; |
| |
| /** |
| * One time initialization for the file system and related things. |
| * |
| * @param {function()} callback Completion callback. |
| * @private |
| */ |
| FileManager.prototype.initFileSystemUI_ = function(callback) { |
| 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(); |
| }.bind(this)); |
| |
| this.initDateTimeFormatters_(); |
| |
| var self = this; |
| |
| // Get the 'allowRedeemOffers' preference before launching |
| // FileListBannerController. |
| this.getPreferences_(function(pref) { |
| /** @type {boolean} */ |
| var showOffers = !!pref['allowRedeemOffers']; |
| self.bannersController_ = new FileListBannerController( |
| self.directoryModel_, self.volumeManager_, self.document_, |
| showOffers); |
| self.bannersController_.addEventListener('relayout', |
| self.onResize_.bind(self)); |
| }); |
| |
| var dm = this.directoryModel_; |
| dm.addEventListener('directory-changed', |
| this.onDirectoryChanged_.bind(this)); |
| |
| var listBeingUpdated = null; |
| dm.addEventListener('begin-update-files', function() { |
| 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.namingController_.restoreItemBeingRenamed(); |
| listBeingUpdated.endBatchUpdates(); |
| listBeingUpdated = null; |
| }); |
| |
| 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_(); |
| }.bind(this)); |
| |
| var stateChangeHandler = |
| this.onPreferencesChanged_.bind(this); |
| chrome.fileManagerPrivate.onPreferencesChanged.addListener( |
| stateChangeHandler); |
| stateChangeHandler(); |
| |
| var driveConnectionChangedHandler = |
| this.onDriveConnectionChanged_.bind(this); |
| this.volumeManager_.addEventListener('drive-connection-changed', |
| driveConnectionChangedHandler); |
| driveConnectionChangedHandler(); |
| |
| // Set the initial focus. |
| this.refocus(); |
| // Set it as a fallback when there is no focus. |
| this.document_.addEventListener('focusout', function(e) { |
| setTimeout(function() { |
| // When there is no focus, the active element is the <body>. |
| if (this.document_.activeElement == this.document_.body) |
| this.refocus(); |
| }.bind(this), 0); |
| }.bind(this)); |
| |
| this.initDataTransferOperations_(); |
| |
| this.updateFileTypeFilter_(); |
| this.selectionHandler_.onFileSelectionChanged(); |
| this.ui_.listContainer.endBatchUpdates(); |
| |
| callback(); |
| }; |
| |
| /** |
| * If |item| in the directory tree is behind the preview panel, scrolls up the |
| * parent view and make the item visible. This should be called when: |
| * - the selected item is changed in the directory tree. |
| * - the visibility of the the preview panel is changed. |
| * |
| * @private |
| */ |
| FileManager.prototype.ensureDirectoryTreeItemNotBehindPreviewPanel_ = |
| function() { |
| var selectedSubTree = this.directoryTree_.selectedItem; |
| if (!selectedSubTree) |
| return; |
| var item = selectedSubTree.rowElement; |
| var parentView = this.directoryTree_; |
| |
| var itemRect = item.getBoundingClientRect(); |
| if (!itemRect) |
| return; |
| |
| var listRect = parentView.getBoundingClientRect(); |
| if (!listRect) |
| return; |
| |
| var previewPanel = this.dialogDom_.querySelector('.preview-panel'); |
| var previewPanelRect = previewPanel.getBoundingClientRect(); |
| var panelHeight = previewPanelRect ? previewPanelRect.height : 0; |
| |
| var itemBottom = itemRect.bottom; |
| var listBottom = listRect.bottom - panelHeight; |
| |
| if (itemBottom > listBottom) { |
| var scrollOffset = itemBottom - listBottom; |
| parentView.scrollTop += scrollOffset; |
| } |
| }; |
| |
| /** |
| * @private |
| */ |
| FileManager.prototype.initDateTimeFormatters_ = function() { |
| var use12hourClock = !this.preferences_['use24hourClock']; |
| this.ui_.listContainer.table.setDateTimeFormat(use12hourClock); |
| }; |
| |
| /** |
| * @private |
| */ |
| FileManager.prototype.initDataTransferOperations_ = function() { |
| this.fileOperationManager_ = |
| this.backgroundPage_.background.fileOperationManager; |
| |
| // CopyManager are required for 'Delete' operation in |
| // Open and Save dialogs. But drag-n-drop and copy-paste are not needed. |
| if (this.dialogType != DialogType.FULL_PAGE) return; |
| |
| // TODO(hidehiko): Extract FileOperationManager related code from |
| // FileManager to simplify it. |
| this.onCopyProgressBound_ = this.onCopyProgress_.bind(this); |
| this.fileOperationManager_.addEventListener( |
| 'copy-progress', this.onCopyProgressBound_); |
| |
| this.onEntriesChangedBound_ = this.onEntriesChanged_.bind(this); |
| this.fileOperationManager_.addEventListener( |
| 'entries-changed', this.onEntriesChangedBound_); |
| |
| var controller = this.fileTransferController_ = |
| 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.attachCopyPasteHandlers(); |
| controller.addEventListener('selection-copied', |
| this.blinkSelection.bind(this)); |
| controller.addEventListener('selection-cut', |
| this.blinkSelection.bind(this)); |
| controller.addEventListener('source-not-found', |
| this.onSourceNotFound_.bind(this)); |
| }; |
| |
| /** |
| * Handles an error that the source entry of file operation is not found. |
| * @private |
| */ |
| FileManager.prototype.onSourceNotFound_ = function(event) { |
| var item = new ProgressCenterItem(); |
| item.id = 'source-not-found-' + this.sourceNotFoundErrorCount_; |
| if (event.progressType === ProgressItemType.COPY) |
| item.message = strf('COPY_SOURCE_NOT_FOUND_ERROR', event.fileName); |
| else if (event.progressType === ProgressItemType.MOVE) |
| item.message = strf('MOVE_SOURCE_NOT_FOUND_ERROR', event.fileName); |
| item.state = ProgressItemState.ERROR; |
| this.backgroundPage_.background.progressCenter.updateItem(item); |
| this.sourceNotFoundErrorCount_++; |
| }; |
| |
| /** |
| * One-time initialization of context menus. |
| * @private |
| */ |
| FileManager.prototype.initContextMenus_ = function() { |
| assert(this.ui_.listContainer.grid); |
| assert(this.ui_.listContainer.table); |
| assert(this.document_); |
| assert(this.dialogDom_); |
| |
| // 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(gearButton, cr.ui.MenuButton); |
| this.gearButton_ = /** @type {!cr.ui.MenuButton} */ (gearButton); |
| |
| this.syncButton.checkable = true; |
| this.hostedButton.checkable = true; |
| |
| if (util.runningInBrowser()) { |
| // Suppresses the default context menu. |
| this.dialogDom_.addEventListener('contextmenu', function(e) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| }); |
| } |
| }; |
| |
| FileManager.prototype.onShowGearMenu_ = function() { |
| this.refreshRemainingSpace_(false); /* Without loading caption. */ |
| |
| // If the menu is opened while CTRL key pressed, secret menu itemscan be |
| // shown. |
| this.isSecretGearMenuShown_ = this.pressingCtrl_; |
| |
| // Update view of drive-related settings. |
| this.commandHandler.updateAvailability(); |
| this.document_.getElementById('drive-separator').hidden = |
| !this.shouldShowDriveSettings(); |
| |
| // Force to update the gear menu position. |
| // TODO(hirono): Remove the workaround for the crbug.com/374093 after fixing |
| // it. |
| var gearMenu = this.document_.querySelector('#gear-menu'); |
| gearMenu.style.left = ''; |
| gearMenu.style.right = ''; |
| gearMenu.style.top = ''; |
| gearMenu.style.bottom = ''; |
| }; |
| |
| /** |
| * One-time initialization of commands. |
| * @private |
| */ |
| FileManager.prototype.initCommands_ = function() { |
| assert(this.textContextMenu_); |
| |
| this.commandHandler = new CommandHandler(this); |
| |
| // TODO(hirono): Move the following block to the UI part. |
| var commandButtons = this.dialogDom_.querySelectorAll('button[command]'); |
| for (var j = 0; j < commandButtons.length; j++) |
| CommandButton.decorate(commandButtons[j]); |
| |
| var inputs = this.dialogDom_.querySelectorAll( |
| 'input[type=text], input[type=search], textarea'); |
| for (var i = 0; i < inputs.length; i++) { |
| cr.ui.contextMenuHandler.setContextMenu(inputs[i], this.textContextMenu_); |
| this.registerInputCommands_(inputs[i]); |
| } |
| |
| cr.ui.contextMenuHandler.setContextMenu(this.ui_.listContainer.renameInput, |
| this.textContextMenu_); |
| this.registerInputCommands_(this.ui_.listContainer.renameInput); |
| this.document_.addEventListener( |
| 'command', |
| this.ui_.listContainer.clearHover.bind(this.ui_.listContainer)); |
| }; |
| |
| /** |
| * Registers cut, copy, paste and delete commands on input element. |
| * |
| * @param {Node} node Text input element to register on. |
| * @private |
| */ |
| FileManager.prototype.registerInputCommands_ = function(node) { |
| CommandUtil.forceDefaultHandler(node, 'cut'); |
| CommandUtil.forceDefaultHandler(node, 'copy'); |
| CommandUtil.forceDefaultHandler(node, 'paste'); |
| CommandUtil.forceDefaultHandler(node, 'delete'); |
| node.addEventListener('keydown', function(e) { |
| var key = util.getKeyModifiers(e) + e.keyCode; |
| if (key === '190' /* '/' */ || key === '191' /* '.' */) { |
| // If this key event is propagated, this is handled search command, |
| // which calls 'preventDefault' method. |
| e.stopPropagation(); |
| } |
| }); |
| }; |
| |
| /** |
| * Entry point of the initialization. |
| * This method is called from main.js. |
| */ |
| FileManager.prototype.initializeCore = function() { |
| this.initializeQueue_.add(this.initGeneral_.bind(this), [], 'initGeneral'); |
| this.initializeQueue_.add(this.initBackgroundPage_.bind(this), |
| [], 'initBackgroundPage'); |
| this.initializeQueue_.add(this.initPreferences_.bind(this), |
| ['initGeneral'], 'initPreferences'); |
| this.initializeQueue_.add(this.initVolumeManager_.bind(this), |
| ['initGeneral', 'initBackgroundPage'], |
| 'initVolumeManager'); |
| |
| this.initializeQueue_.run(); |
| window.addEventListener('pagehide', this.onUnload_.bind(this)); |
| }; |
| |
| FileManager.prototype.initializeUI = function(dialogDom, callback) { |
| this.dialogDom_ = dialogDom; |
| this.document_ = this.dialogDom_.ownerDocument; |
| |
| this.initializeQueue_.add( |
| this.initEssentialUI_.bind(this), |
| ['initGeneral', 'initBackgroundPage'], |
| 'initEssentialUI'); |
| this.initializeQueue_.add(this.initAdditionalUI_.bind(this), |
| ['initEssentialUI'], 'initAdditionalUI'); |
| this.initializeQueue_.add( |
| this.initFileSystemUI_.bind(this), |
| ['initAdditionalUI', 'initPreferences'], 'initFileSystemUI'); |
| |
| // Run again just in case if all pending closures have completed and the |
| // queue has stopped and monitor the completion. |
| this.initializeQueue_.run(callback); |
| }; |
| |
| /** |
| * Initializes general purpose basic things, which are used by other |
| * initializing methods. |
| * |
| * @param {function()} callback Completion callback. |
| * @private |
| */ |
| FileManager.prototype.initGeneral_ = function(callback) { |
| // Initialize the application state. |
| // TODO(mtomasz): Unify window.appState with location.search format. |
| if (window.appState) { |
| this.params_ = window.appState.params || {}; |
| this.initCurrentDirectoryURL_ = window.appState.currentDirectoryURL; |
| this.initSelectionURL_ = window.appState.selectionURL; |
| this.initTargetName_ = window.appState.targetName; |
| } else { |
| // Used by the select dialog only. |
| this.params_ = location.search ? |
| JSON.parse(decodeURIComponent(location.search.substr(1))) : |
| {}; |
| this.initCurrentDirectoryURL_ = this.params_.currentDirectoryURL; |
| this.initSelectionURL_ = this.params_.selectionURL; |
| this.initTargetName_ = this.params_.targetName; |
| } |
| |
| // Initialize the member variables that depend this.params_. |
| this.dialogType = this.params_.type || DialogType.FULL_PAGE; |
| this.startupPrefName_ = 'file-manager-' + this.dialogType; |
| this.fileTypes_ = this.params_.typeList || []; |
| |
| callback(); |
| }; |
| |
| /** |
| * Initialize the background page. |
| * @param {function()} callback Completion callback. |
| * @private |
| */ |
| FileManager.prototype.initBackgroundPage_ = function(callback) { |
| chrome.runtime.getBackgroundPage(function(backgroundPage) { |
| 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)); |
| }; |
| |
| /** |
| * Initializes the VolumeManager instance. |
| * @param {function()} callback Completion callback. |
| * @private |
| */ |
| FileManager.prototype.initVolumeManager_ = function(callback) { |
| // Auto resolving to local path does not work for folders (e.g., dialog for |
| // loading unpacked extensions). |
| var noLocalPathResolution = DialogType.isFolderDialog(this.params_.type); |
| |
| // If this condition is false, VolumeManagerWrapper hides all drive |
| // related event and data, even if Drive is enabled on preference. |
| // In other words, even if Drive is disabled on preference but Files.app |
| // should show Drive when it is re-enabled, then the value should be set to |
| // true. |
| // Note that the Drive enabling preference change is listened by |
| // DriveIntegrationService, so here we don't need to take care about it. |
| var driveEnabled = |
| !noLocalPathResolution || !this.params_.shouldReturnLocalPath; |
| this.volumeManager_ = new VolumeManagerWrapper( |
| /** @type {VolumeManagerWrapper.DriveEnabledStatus} */ (driveEnabled), |
| this.backgroundPage_); |
| callback(); |
| }; |
| |
| /** |
| * One time initialization of the Files.app's essential UI elements. These |
| * elements will be shown to the user. Only visible elements should be |
| * initialized here. Any heavy operation should be avoided. Files.app's |
| * window is shown at the end of this routine. |
| * |
| * @param {function()} callback Completion callback. |
| * @private |
| */ |
| FileManager.prototype.initEssentialUI_ = function(callback) { |
| // Record stats of dialog types. New values must NOT be inserted into the |
| // array enumerating the types. It must be in sync with |
| // FileDialogType enum in tools/metrics/histograms/histogram.xml. |
| metrics.recordEnum('Create', this.dialogType, |
| [DialogType.SELECT_FOLDER, |
| DialogType.SELECT_UPLOAD_FOLDER, |
| DialogType.SELECT_SAVEAS_FILE, |
| DialogType.SELECT_OPEN_FILE, |
| DialogType.SELECT_OPEN_MULTI_FILE, |
| DialogType.FULL_PAGE]); |
| |
| // Create the metadata cache. |
| this.metadataCache_ = MetadataCache.createFull(this.volumeManager_); |
| |
| // Create the root view of FileManager. |
| assert(this.dialogDom_); |
| this.ui_ = new FileManagerUI(this.dialogDom_, this.dialogType); |
| |
| // Show the window as soon as the UI pre-initialization is done. |
| if (this.dialogType == DialogType.FULL_PAGE && !util.runningInBrowser()) { |
| chrome.app.window.current().show(); |
| setTimeout(callback, 100); // Wait until the animation is finished. |
| } else { |
| callback(); |
| } |
| }; |
| |
| /** |
| * One-time initialization of dialogs. |
| * @private |
| */ |
| FileManager.prototype.initDialogs_ = function() { |
| // Initialize the dialog. |
| this.ui_.initDialogs(); |
| FileManagerDialogBase.setFileManager(this); |
| |
| // Obtains the dialog instances from FileManagerUI. |
| // TODO(hirono): Remove the properties from the FileManager class. |
| this.error = this.ui_.errorDialog; |
| this.alert = this.ui_.alertDialog; |
| this.confirm = this.ui_.confirmDialog; |
| this.prompt = this.ui_.promptDialog; |
| this.shareDialog_ = this.ui_.shareDialog; |
| this.defaultTaskPicker = this.ui_.defaultTaskPicker; |
| this.suggestAppsDialog = this.ui_.suggestAppsDialog; |
| }; |
| |
| /** |
| * One-time initialization of various DOM nodes. Loads the additional DOM |
| * elements visible to the user. Initialize here elements, which are expensive |
| * or hidden in the beginning. |
| * |
| * @param {function()} callback Completion callback. |
| * @private |
| */ |
| FileManager.prototype.initAdditionalUI_ = function(callback) { |
| // Cache nodes we'll be manipulating. |
| var dom = this.dialogDom_; |
| assert(dom); |
| |
| this.initDialogs_(); |
| |
| 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)); |
| |
| |
| var taskItems = queryRequiredElement(dom, '#tasks'); |
| this.taskItems_ = /** @type {HTMLButtonElement} */ (taskItems); |
| |
| this.ui_.locationLine = new LocationLine( |
| queryRequiredElement(dom, '#location-breadcrumbs'), |
| queryRequiredElement(dom, '#location-volume-icon'), |
| this.metadataCache_, |
| this.volumeManager_); |
| this.ui_.locationLine.addEventListener( |
| 'pathclick', this.onBreadcrumbClick_.bind(this)); |
| |
| // Initialize progress center panel. |
| this.progressCenterPanel_ = new ProgressCenterPanel( |
| 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.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.ui_.dialogFooter.filenameInput.addEventListener( |
| 'input', this.onFilenameInputInput_.bind(this)); |
| this.ui_.dialogFooter.filenameInput.addEventListener( |
| 'keydown', this.onFilenameInputKeyDown_.bind(this)); |
| this.ui_.dialogFooter.filenameInput.addEventListener( |
| 'focus', this.onFilenameInputFocus_.bind(this)); |
| |
| this.decorateSplitter( |
| this.dialogDom_.querySelector('#navigation-list-splitter')); |
| |
| this.dialogContainer_ = /** @type {!HTMLDivElement} */ |
| (this.dialogDom_.querySelector('.dialog-container')); |
| |
| 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.ui_.toggleViewButton.addEventListener('click', |
| this.onToggleViewButtonClick_.bind(this)); |
| |
| cr.ui.ComboButton.decorate(this.taskItems_); |
| this.taskItems_.showMenu = function(shouldSetFocus) { |
| // Prevent the empty menu from opening. |
| if (!this.menu.length) |
| return; |
| cr.ui.ComboButton.prototype.showMenu.call(this, shouldSetFocus); |
| }; |
| this.taskItems_.addEventListener('select', |
| this.onTaskItemClicked_.bind(this)); |
| |
| this.dialogDom_.ownerDocument.defaultView.addEventListener( |
| 'resize', this.onResize_.bind(this)); |
| |
| this.actionMenuItem_ = /** @type {!HTMLMenuItemElement} */ |
| (queryRequiredElement(this.dialogDom_, '#default-action')); |
| |
| this.openWithCommand_ = /** @type {cr.ui.Command} */ |
| (this.dialogDom_.querySelector('#open-with')); |
| |
| this.actionMenuItem_.addEventListener('activate', |
| this.onActionMenuItemActivated_.bind(this)); |
| |
| this.ui_.dialogFooter.initFileTypeFilter( |
| this.fileTypes_, this.params_.includeAllFiles); |
| this.ui_.dialogFooter.fileTypeSelector.addEventListener( |
| 'change', this.updateFileTypeFilter_.bind(this)); |
| |
| util.addIsFocusedMethod(); |
| |
| // Populate the static localized strings. |
| i18nTemplate.process(this.document_, loadTimeData); |
| |
| // Arrange the file list. |
| this.ui_.listContainer.table.normalizeColumns(); |
| this.ui_.listContainer.table.redraw(); |
| |
| callback(); |
| }; |
| |
| /** |
| * @param {Event} event Click event. |
| * @private |
| */ |
| FileManager.prototype.onBreadcrumbClick_ = function(event) { |
| this.directoryModel_.changeDirectoryEntry(event.entry); |
| }; |
| |
| /** |
| * Constructs table and grid (heavy operation). |
| * @private |
| **/ |
| FileManager.prototype.initFileList_ = function() { |
| 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 and *.crdownload by default. */); |
| |
| this.fileWatcher_ = new FileWatcher(this.metadataCache_); |
| this.fileWatcher_.addEventListener( |
| 'watcher-metadata-changed', |
| this.onWatcherMetadataChanged_.bind(this)); |
| |
| this.directoryModel_ = new DirectoryModel( |
| singleSelection, |
| this.fileFilter_, |
| this.fileWatcher_, |
| this.metadataCache_, |
| this.volumeManager_); |
| |
| this.folderShortcutsModel_ = new FolderShortcutsDataModel( |
| this.volumeManager_); |
| |
| this.selectionHandler_ = new FileSelectionHandler(this); |
| |
| var dataModel = this.directoryModel_.getFileList(); |
| dataModel.addEventListener('permuted', |
| this.updateStartupPrefs_.bind(this)); |
| |
| this.directoryModel_.getFileListSelection().addEventListener('change', |
| this.selectionHandler_.onFileSelectionChanged.bind( |
| this.selectionHandler_)); |
| |
| 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.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.initDirectoryTree_(); |
| |
| this.ui_.listContainer.table.addEventListener('column-resize-end', |
| this.updateStartupPrefs_.bind(this)); |
| |
| // Restore preferences. |
| this.directoryModel_.getFileList().sort( |
| this.viewOptions_.sortField || 'modificationTime', |
| this.viewOptions_.sortDirection || 'desc'); |
| if (this.viewOptions_.columns) { |
| 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.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)); |
| } |
| |
| // 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(); |
| today.setHours(0); |
| today.setMinutes(0); |
| today.setSeconds(0); |
| today.setMilliseconds(0); |
| setTimeout(this.dailyUpdateModificationTime_.bind(this), |
| today.getTime() + MILLISECONDS_IN_DAY - Date.now() + 1000); |
| }; |
| |
| /** |
| * @private |
| */ |
| 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.metadataCache_, |
| fakeEntriesVisible); |
| this.directoryTree_.dataModel = new NavigationListModel( |
| this.volumeManager_, this.folderShortcutsModel_); |
| |
| // 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})); |
| }; |
| |
| /** |
| * @private |
| */ |
| FileManager.prototype.updateStartupPrefs_ = function() { |
| var sortStatus = this.directoryModel_.getFileList().sortStatus; |
| var prefs = { |
| sortField: sortStatus.field, |
| sortDirection: sortStatus.direction, |
| columns: [], |
| listType: this.ui_.listContainer.currentListType |
| }; |
| 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. |
| var items = {}; |
| items[this.startupPrefName_] = JSON.stringify(prefs); |
| chrome.storage.local.set(items); |
| |
| // Save the window-specific preference. |
| if (window.appState) { |
| window.appState.viewOptions = prefs; |
| util.saveAppState(); |
| } |
| }; |
| |
| FileManager.prototype.refocus = function() { |
| var targetElement; |
| if (this.dialogType == DialogType.SELECT_SAVEAS_FILE) |
| targetElement = this.ui_.dialogFooter.filenameInput; |
| else |
| 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. |
| if (!targetElement.hasAttribute('tabIndex') || targetElement.tabIndex == -1) |
| targetElement = document.querySelector('button:not([tabIndex="-1"])'); |
| |
| if (targetElement) |
| targetElement.focus(); |
| }; |
| |
| /** |
| * File list focus handler. Used to select the top most element on the list |
| * if nothing was selected. |
| * |
| * @private |
| */ |
| FileManager.prototype.onFileListFocus_ = function() { |
| // If the file list is focused by <Tab>, select the first item if no item |
| // is selected. |
| if (this.pressingTab_) { |
| if (this.getSelection() && this.getSelection().totalCount == 0) |
| this.directoryModel_.selectIndex(0); |
| } |
| }; |
| |
| /** |
| * Sets the current list type. |
| * @param {ListContainer.ListType} type New list type. |
| */ |
| FileManager.prototype.setListType = function(type) { |
| if ((type && type == this.ui_.listContainer.currentListType) || |
| !this.directoryModel_) { |
| return; |
| } |
| |
| this.ui_.setCurrentListType(type); |
| this.updateStartupPrefs_(); |
| this.onResize_(); |
| }; |
| |
| /** |
| * @private |
| */ |
| FileManager.prototype.onCopyProgress_ = function(event) { |
| if (event.reason == 'ERROR' && |
| event.error.code == util.FileOperationErrorType.FILESYSTEM_ERROR && |
| event.error.data.toDrive && |
| event.error.data.name == util.FileError.QUOTA_EXCEEDED_ERR) { |
| this.alert.showHtml( |
| strf('DRIVE_SERVER_OUT_OF_SPACE_HEADER'), |
| strf('DRIVE_SERVER_OUT_OF_SPACE_MESSAGE', |
| decodeURIComponent( |
| event.error.data.sourceFileUrl.split('/').pop()), |
| str('GOOGLE_DRIVE_BUY_STORAGE_URL'))); |
| } |
| }; |
| |
| /** |
| * Handler of file manager operations. Called when an entry has been |
| * changed. |
| * This updates directory model to reflect operation result immediately (not |
| * waiting for directory update event). Also, preloads thumbnails for the |
| * images of new entries. |
| * See also FileOperationManager.EventRouter. |
| * |
| * @param {Event} event An event for the entry change. |
| * @private |
| */ |
| FileManager.prototype.onEntriesChanged_ = function(event) { |
| var kind = event.kind; |
| var entries = event.entries; |
| this.directoryModel_.onEntriesChanged(kind, entries); |
| this.selectionHandler_.onFileSelectionChanged(); |
| |
| if (kind !== util.EntryChangedKind.CREATED) |
| return; |
| |
| var preloadThumbnail = function(entry) { |
| var locationInfo = this.volumeManager_.getLocationInfo(entry); |
| if (!locationInfo) |
| return; |
| 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); |
| |
| 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]); |
| } |
| }; |
| |
| /** |
| * Filters file according to the selected file type. |
| * @private |
| */ |
| FileManager.prototype.updateFileTypeFilter_ = function() { |
| this.fileFilter_.removeFilter('fileType'); |
| var selectedIndex = this.ui_.dialogFooter.selectedFilterIndex; |
| if (selectedIndex > 0) { // Specific filter selected. |
| 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_(); |
| } |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Resize details and thumb views to fit the new window size. |
| * @private |
| */ |
| FileManager.prototype.onResize_ = function() { |
| // May not be available during initialization. |
| if (this.directoryTree_) |
| this.directoryTree_.relayout(); |
| |
| 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.ui_.listContainer.currentView.updateListItemsMetadata( |
| event.metadataType, event.entries); |
| }; |
| |
| /** |
| * Sets up the current directory during initialization. |
| * @private |
| */ |
| FileManager.prototype.setupCurrentDirectory_ = function() { |
| var tracker = this.directoryModel_.createDirectoryChangeTracker(); |
| var queue = new AsyncUtil.Queue(); |
| |
| // Wait until the volume manager is initialized. |
| queue.run(function(callback) { |
| tracker.start(); |
| this.volumeManager_.ensureInitialized(callback); |
| }.bind(this)); |
| |
| var nextCurrentDirEntry; |
| var selectionEntry; |
| |
| // Resolve the selectionURL to selectionEntry or to currentDirectoryEntry |
| // in case of being a display root or a default directory to open files. |
| queue.run(function(callback) { |
| if (!this.initSelectionURL_) { |
| callback(); |
| return; |
| } |
| webkitResolveLocalFileSystemURL( |
| this.initSelectionURL_, |
| function(inEntry) { |
| var locationInfo = this.volumeManager_.getLocationInfo(inEntry); |
| // If location information is not available, then the volume is |
| // no longer (or never) available. |
| if (!locationInfo) { |
| callback(); |
| return; |
| } |
| // If the selection is root, then use it as a current directory |
| // instead. This is because, selecting a root entry is done as |
| // opening it. |
| if (locationInfo.isRootEntry) |
| nextCurrentDirEntry = inEntry; |
| |
| // 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)); |
| // Resolve the currentDirectoryURL to currentDirectoryEntry (if not done |
| // by the previous step). |
| queue.run(function(callback) { |
| if (nextCurrentDirEntry || !this.initCurrentDirectoryURL_) { |
| callback(); |
| return; |
| } |
| webkitResolveLocalFileSystemURL( |
| this.initCurrentDirectoryURL_, |
| function(inEntry) { |
| var locationInfo = this.volumeManager_.getLocationInfo(inEntry); |
| if (!locationInfo) { |
| callback(); |
| return; |
| } |
| nextCurrentDirEntry = inEntry; |
| callback(); |
| }.bind(this), callback); |
| // TODO(mtomasz): Implement reopening on special search, when fake |
| // entries are converted to directory providers. |
| }.bind(this)); |
| |
| // If the directory to be changed to is not available, then first fallback |
| // to the parent of the selection entry. |
| queue.run(function(callback) { |
| if (nextCurrentDirEntry || !selectionEntry) { |
| callback(); |
| return; |
| } |
| selectionEntry.getParent(function(inEntry) { |
| nextCurrentDirEntry = inEntry; |
| callback(); |
| }.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) { |
| if (nextCurrentDirEntry) { |
| callback(); |
| return; |
| } |
| this.volumeManager_.getDefaultDisplayRoot(function(displayRoot) { |
| nextCurrentDirEntry = displayRoot; |
| callback(); |
| }.bind(this)); |
| }.bind(this)); |
| |
| // If selection failed to be resolved (eg. didn't exist, in case of saving |
| // a file, or in case of a fallback of the current directory, then try to |
| // resolve again using the target name. |
| queue.run(function(callback) { |
| if (selectionEntry || !nextCurrentDirEntry || !this.initTargetName_) { |
| callback(); |
| return; |
| } |
| // Try to resolve as a file first. If it fails, then as a directory. |
| nextCurrentDirEntry.getFile( |
| this.initTargetName_, |
| {}, |
| function(targetEntry) { |
| selectionEntry = targetEntry; |
| 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(); |
| }); |
| }.bind(this)); |
| }.bind(this)); |
| |
| // Finalize. |
| queue.run(function(callback) { |
| // Check directory change. |
| tracker.stop(); |
| if (tracker.hasChanged) { |
| callback(); |
| return; |
| } |
| // Finish setup current directory. |
| this.finishSetupCurrentDirectory_( |
| nextCurrentDirEntry, |
| selectionEntry, |
| this.initTargetName_); |
| callback(); |
| }.bind(this)); |
| }; |
| |
| /** |
| * @param {DirectoryEntry} directoryEntry Directory to be opened. |
| * @param {Entry=} opt_selectionEntry Entry to be selected. |
| * @param {string=} opt_suggestedName Suggested name for a non-existing\ |
| * selection. |
| * @private |
| */ |
| FileManager.prototype.finishSetupCurrentDirectory_ = function( |
| directoryEntry, opt_selectionEntry, opt_suggestedName) { |
| // Open the directory, and select the selection (if passed). |
| if (util.isFakeEntry(directoryEntry)) { |
| this.directoryModel_.specialSearch(directoryEntry, ''); |
| } else { |
| this.directoryModel_.changeDirectoryEntry(directoryEntry, function() { |
| if (opt_selectionEntry) |
| this.directoryModel_.selectEntry(opt_selectionEntry); |
| }.bind(this)); |
| } |
| |
| if (this.dialogType === DialogType.FULL_PAGE) { |
| // In the FULL_PAGE mode if the restored URL points to a file we might |
| // have to invoke a task after selecting it. |
| if (this.params_.action === 'select') |
| return; |
| |
| var task = null; |
| |
| // 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)) { |
| // Opened on a different URL. Probably fallbacked. Therefore, |
| // do not invoke a task. |
| return; |
| } |
| this.directoryModel_.removeEventListener( |
| 'scan-completed', listener); |
| task(); |
| }.bind(this); |
| this.directoryModel_.addEventListener('scan-completed', listener); |
| } |
| } else if (this.dialogType === DialogType.SELECT_SAVEAS_FILE) { |
| this.ui_.dialogFooter.filenameInput.value = opt_suggestedName || ''; |
| this.selectTargetNameInFilenameInput_(); |
| } |
| }; |
| |
| /** |
| * @private |
| */ |
| FileManager.prototype.refreshCurrentDirectoryMetadata_ = function() { |
| var entries = this.directoryModel_.getFileList().slice(); |
| var directoryEntry = this.directoryModel_.getCurrentDirEntry(); |
| if (!directoryEntry) |
| return; |
| // We don't pass callback here. When new metadata arrives, we have an |
| // observer registered to update the UI. |
| |
| // TODO(dgozman): refresh content metadata only when modificationTime |
| // changed. |
| var isFakeEntry = util.isFakeEntry(directoryEntry); |
| var getEntries = (isFakeEntry ? [] : [directoryEntry]).concat(entries); |
| if (!isFakeEntry) |
| this.metadataCache_.clearRecursively(directoryEntry, '*'); |
| this.metadataCache_.get(getEntries, 'filesystem|external', null); |
| |
| var visibleItems = this.ui.listContainer.currentList.items; |
| var visibleEntries = []; |
| for (var i = 0; i < visibleItems.length; 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); |
| } |
| // Refreshes the metadata. |
| this.metadataCache_.getLatest(visibleEntries, 'thumbnail', null); |
| }; |
| |
| /** |
| * @private |
| */ |
| FileManager.prototype.dailyUpdateModificationTime_ = function() { |
| var entries = this.directoryModel_.getFileList().slice(); |
| this.metadataCache_.get( |
| entries, |
| 'filesystem', |
| function() { |
| this.ui_.listContainer.currentView.updateListItemsMetadata( |
| 'filesystem', entries); |
| }.bind(this)); |
| |
| setTimeout(this.dailyUpdateModificationTime_.bind(this), |
| MILLISECONDS_IN_DAY); |
| }; |
| |
| /** |
| * 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 != null && |
| VolumeManagerCommon.getVolumeTypeFromRootType(rootType) == |
| VolumeManagerCommon.VolumeType.DRIVE; |
| }; |
| |
| /** |
| * Check if the drive-related setting items should be shown on currently |
| * displayed gear menu. |
| * @return {boolean} True if those setting items should be shown. |
| */ |
| FileManager.prototype.shouldShowDriveSettings = function() { |
| return this.isOnDrive() && this.isSecretGearMenuShown_; |
| }; |
| |
| /** |
| * Overrides default handling for clicks on hyperlinks. |
| * In a packaged apps links with targer='_blank' open in a new tab by |
| * default, other links do not open at all. |
| * |
| * @param {Event} event Click event. |
| * @private |
| */ |
| FileManager.prototype.onExternalLinkClick_ = function(event) { |
| if (event.target.tagName != 'A' || !event.target.href) |
| return; |
| |
| if (this.dialogType != DialogType.FULL_PAGE) |
| this.ui_.dialogFooter.cancelButton.click(); |
| }; |
| |
| /** |
| * Task combobox handler. |
| * |
| * @param {Object} event Event containing task which was clicked. |
| * @private |
| */ |
| FileManager.prototype.onTaskItemClicked_ = function(event) { |
| var selection = this.getSelection(); |
| if (!selection.tasks) return; |
| |
| if (event.item.task) { |
| // Task field doesn't exist on change-default dropdown item. |
| selection.tasks.execute(event.item.task.taskId); |
| } else { |
| var extensions = []; |
| |
| for (var i = 0; i < selection.entries.length; i++) { |
| var match = /\.(\w+)$/g.exec(selection.entries[i].toURL()); |
| if (match) { |
| var ext = match[1].toUpperCase(); |
| if (extensions.indexOf(ext) == -1) { |
| extensions.push(ext); |
| } |
| } |
| } |
| |
| var format = ''; |
| |
| if (extensions.length == 1) { |
| format = extensions[0]; |
| } |
| |
| // Change default was clicked. We should open "change default" dialog. |
| selection.tasks.showTaskPicker(this.defaultTaskPicker, |
| loadTimeData.getString('CHANGE_DEFAULT_MENU_ITEM'), |
| strf('CHANGE_DEFAULT_CAPTION', format), |
| this.onDefaultTaskDone_.bind(this), |
| true); |
| } |
| }; |
| |
| /** |
| * Sets the given task as default, when this task is applicable. |
| * |
| * @param {Object} task Task to set as default. |
| * @private |
| */ |
| FileManager.prototype.onDefaultTaskDone_ = function(task) { |
| // TODO(dgozman): move this method closer to tasks. |
| var selection = this.getSelection(); |
| // 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); |
| selection.tasks = new FileTasks(this); |
| selection.tasks.init(selection.entries, selection.mimeTypes); |
| selection.tasks.display(this.taskItems_); |
| this.refreshCurrentDirectoryMetadata_(); |
| this.selectionHandler_.onFileSelectionChanged(); |
| }; |
| |
| /** |
| * @private |
| */ |
| FileManager.prototype.onPreferencesChanged_ = function() { |
| var self = this; |
| this.getPreferences_(function(prefs) { |
| self.initDateTimeFormatters_(); |
| self.refreshCurrentDirectoryMetadata_(); |
| |
| if (prefs.cellularDisabled) |
| self.syncButton.setAttribute('checked', ''); |
| else |
| self.syncButton.removeAttribute('checked'); |
| |
| if (self.hostedButton.hasAttribute('checked') === |
| prefs.hostedFilesDisabled && self.isOnDrive()) { |
| self.directoryModel_.rescan(false); |
| } |
| |
| if (!prefs.hostedFilesDisabled) |
| self.hostedButton.setAttribute('checked', ''); |
| else |
| self.hostedButton.removeAttribute('checked'); |
| }, |
| true /* refresh */); |
| }; |
| |
| FileManager.prototype.onDriveConnectionChanged_ = function() { |
| var connection = this.volumeManager_.getDriveConnectionState(); |
| if (this.commandHandler) |
| this.commandHandler.updateAvailability(); |
| if (this.dialogContainer_) |
| this.dialogContainer_.setAttribute('connection', connection.type); |
| this.shareDialog_.hideWithResult(ShareDialog.Result.NETWORK_ERROR); |
| this.suggestAppsDialog.onDriveConnectionChanged(connection.type); |
| }; |
| |
| /** |
| * Tells whether the current directory is read only. |
| * TODO(mtomasz): Remove and use EntryLocation directly. |
| * @return {boolean} True if read only, false otherwise. |
| */ |
| FileManager.prototype.isOnReadonlyDirectory = function() { |
| return this.directoryModel_.isReadOnly(); |
| }; |
| |
| /** |
| * @param {Event} event Unmount event. |
| * @private |
| */ |
| FileManager.prototype.onExternallyUnmounted_ = function(event) { |
| if (event.volumeInfo === this.currentVolumeInfo_) { |
| if (this.closeOnUnmount_) { |
| // If the file manager opened automatically when a usb drive inserted, |
| // user have never changed current volume (that implies the current |
| // directory is still on the device) then close this window. |
| window.close(); |
| } |
| } |
| }; |
| |
| /** |
| * @return {Array.<Entry>} List of all entries in the current directory. |
| */ |
| FileManager.prototype.getAllEntriesInCurrentDirectory = function() { |
| return this.directoryModel_.getFileList().slice(); |
| }; |
| |
| /** |
| * Return DirectoryEntry of the current directory or null. |
| * @return {DirectoryEntry} DirectoryEntry of the current directory. Returns |
| * null if the directory model is not ready or the current directory is |
| * not set. |
| */ |
| FileManager.prototype.getCurrentDirectoryEntry = function() { |
| return this.directoryModel_ && this.directoryModel_.getCurrentDirEntry(); |
| }; |
| |
| /** |
| * Shows the share dialog for the selected file or directory. |
| */ |
| FileManager.prototype.shareSelection = function() { |
| var entries = this.getSelection().entries; |
| if (entries.length != 1) { |
| console.warn('Unable to share multiple items at once.'); |
| return; |
| } |
| // Add the overlapped class to prevent the applicaiton window from |
| // captureing mouse events. |
| this.shareDialog_.show(entries[0], function(result) { |
| if (result == ShareDialog.Result.NETWORK_ERROR) |
| this.error.show(str('SHARE_ERROR')); |
| }.bind(this)); |
| }; |
| |
| /** |
| * Creates a folder shortcut. |
| * @param {Entry} entry A shortcut which refers to |entry| to be created. |
| */ |
| FileManager.prototype.createFolderShortcut = function(entry) { |
| // Duplicate entry. |
| if (this.folderShortcutExists(entry)) |
| return; |
| |
| this.folderShortcutsModel_.add(entry); |
| }; |
| |
| /** |
| * Checkes if the shortcut which refers to the given folder exists or not. |
| * @param {Entry} entry Entry of the folder to be checked. |
| */ |
| FileManager.prototype.folderShortcutExists = function(entry) { |
| return this.folderShortcutsModel_.exists(entry); |
| }; |
| |
| /** |
| * Removes the folder shortcut. |
| * @param {Entry} entry The shortcut which refers to |entry| is to be removed. |
| */ |
| FileManager.prototype.removeFolderShortcut = function(entry) { |
| this.folderShortcutsModel_.remove(entry); |
| }; |
| |
| /** |
| * Blinks the selection. Used to give feedback when copying or cutting the |
| * selection. |
| */ |
| FileManager.prototype.blinkSelection = function() { |
| var selection = this.getSelection(); |
| if (!selection || selection.totalCount == 0) |
| return; |
| |
| for (var i = 0; i < selection.entries.length; i++) { |
| var selectedIndex = selection.indexes[i]; |
| var listItem = |
| this.ui.listContainer.currentList.getListItemByIndex(selectedIndex); |
| if (listItem) |
| this.blinkListItem_(listItem); |
| } |
| }; |
| |
| /** |
| * @param {Element} listItem List item element. |
| * @private |
| */ |
| FileManager.prototype.blinkListItem_ = function(listItem) { |
| listItem.classList.add('blink'); |
| setTimeout(function() { |
| listItem.classList.remove('blink'); |
| }, 100); |
| }; |
| |
| /** |
| * @private |
| */ |
| FileManager.prototype.selectTargetNameInFilenameInput_ = function() { |
| var input = this.ui_.dialogFooter.filenameInput; |
| input.focus(); |
| var selectionEnd = input.value.lastIndexOf('.'); |
| if (selectionEnd == -1) { |
| input.select(); |
| } else { |
| input.selectionStart = 0; |
| input.selectionEnd = selectionEnd; |
| } |
| }; |
| |
| /** |
| * Handles mouse click or tap. |
| * |
| * @param {Event} event The click event. |
| * @private |
| */ |
| FileManager.prototype.onDetailClick_ = function(event) { |
| if (this.namingController_.isRenamingInProgress()) { |
| // Don't pay attention to clicks during a rename. |
| return; |
| } |
| |
| var listItem = this.ui_.listContainer.findListItemForNode( |
| event.touchedElement || event.srcElement); |
| var selection = this.getSelection(); |
| if (!listItem || !listItem.selected || selection.totalCount != 1) { |
| return; |
| } |
| |
| // React on double click, but only if both clicks hit the same item. |
| // TODO(mtomasz): Simplify it, and use a double click handler if possible. |
| var clickNumber = (this.lastClickedItem_ == listItem) ? 2 : undefined; |
| this.lastClickedItem_ = listItem; |
| |
| if (event.detail != clickNumber) |
| return; |
| |
| var entry = selection.entries[0]; |
| if (entry.isDirectory) { |
| this.onDirectoryAction_(entry); |
| } else { |
| this.dispatchSelectionAction_(); |
| } |
| }; |
| |
| /** |
| * @private |
| */ |
| FileManager.prototype.dispatchSelectionAction_ = function() { |
| if (this.dialogType == DialogType.FULL_PAGE) { |
| var selection = this.getSelection(); |
| var tasks = selection.tasks; |
| var urls = selection.urls; |
| var mimeTypes = selection.mimeTypes; |
| if (tasks) |
| tasks.executeDefault(); |
| return true; |
| } |
| 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. |
| * @param {function()} onSuccess Success callback. |
| * @param {function()} onCancelled User-cancelled callback. |
| * @param {function()} onFailure Failure callback. |
| * @private |
| */ |
| FileManager.prototype.openSuggestAppsDialog = |
| function(entry, onSuccess, onCancelled, onFailure) { |
| if (!url) { |
| onFailure(); |
| return; |
| } |
| |
| this.metadataCache_.getOne(entry, 'external', function(prop) { |
| if (!prop || !prop.contentMimeType) { |
| onFailure(); |
| return; |
| } |
| |
| var basename = entry.name; |
| var splitted = util.splitExtension(basename); |
| var filename = splitted[0]; |
| var extension = splitted[1]; |
| var mime = prop.contentMimeType; |
| |
| // Returns with failure if the file has neither extension nor mime. |
| if (!extension || !mime) { |
| onFailure(); |
| return; |
| } |
| |
| var onDialogClosed = function(result) { |
| switch (result) { |
| case SuggestAppsDialog.Result.INSTALL_SUCCESSFUL: |
| onSuccess(); |
| break; |
| case SuggestAppsDialog.Result.FAILED: |
| onFailure(); |
| break; |
| default: |
| onCancelled(); |
| } |
| }; |
| |
| if (FileTasks.EXECUTABLE_EXTENSIONS.indexOf(extension) !== -1) { |
| this.suggestAppsDialog.showByFilename(filename, onDialogClosed); |
| } else { |
| this.suggestAppsDialog.showByExtensionAndMime( |
| extension, mime, onDialogClosed); |
| } |
| }.bind(this)); |
| }; |
| |
| /** |
| * Called when a dialog is shown or hidden. |
| * @param {boolean} show True if a dialog is shown, false if hidden. |
| */ |
| FileManager.prototype.onDialogShownOrHidden = function(show) { |
| if (show) { |
| // If a dialog is shown, activate the window. |
| var appWindow = chrome.app.window.current(); |
| if (appWindow) |
| appWindow.focus(); |
| } |
| |
| // Set/unset a flag to disable dragging on the title area. |
| this.dialogContainer_.classList.toggle('disable-header-drag', show); |
| }; |
| |
| /** |
| * Executes directory action (i.e. changes directory). |
| * |
| * @param {DirectoryEntry} entry Directory entry to which directory should be |
| * changed. |
| * @private |
| */ |
| FileManager.prototype.onDirectoryAction_ = function(entry) { |
| return this.directoryModel_.changeDirectoryEntry(entry); |
| }; |
| |
| /** |
| * Update the window title. |
| * @private |
| */ |
| FileManager.prototype.updateTitle_ = function() { |
| if (this.dialogType != DialogType.FULL_PAGE) |
| return; |
| |
| if (!this.currentVolumeInfo_) |
| return; |
| |
| this.document_.title = this.currentVolumeInfo_.label; |
| }; |
| |
| /** |
| * Update the gear menu. |
| * @private |
| */ |
| FileManager.prototype.updateGearMenu_ = function() { |
| this.refreshRemainingSpace_(true); // Show loading caption. |
| }; |
| |
| /** |
| * Refreshes space info of the current volume. |
| * @param {boolean} showLoadingCaption Whether show loading caption or not. |
| * @private |
| */ |
| FileManager.prototype.refreshRemainingSpace_ = function(showLoadingCaption) { |
| if (!this.currentVolumeInfo_) |
| return; |
| |
| 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) { |
| volumeSpaceInfoLabel.innerText = str('WAITING_FOR_SPACE_INFO'); |
| volumeSpaceInnerBar.style.width = '100%'; |
| } |
| |
| chrome.fileManagerPrivate.getSizeStats( |
| currentVolumeInfo.volumeId, function(result) { |
| var volumeInfo = this.volumeManager_.getVolumeInfo( |
| this.directoryModel_.getCurrentDirEntry()); |
| if (currentVolumeInfo !== this.currentVolumeInfo_) |
| return; |
| updateSpaceInfo(result, |
| volumeSpaceInnerBar, |
| volumeSpaceInfoLabel, |
| volumeSpaceOuterBar); |
| }.bind(this)); |
| }; |
| |
| /** |
| * Update the UI when the current directory changes. |
| * |
| * @param {Event} event The directory-changed event. |
| * @private |
| */ |
| FileManager.prototype.onDirectoryChanged_ = function(event) { |
| 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 (oldCurrentVolumeInfo !== this.currentVolumeInfo_) { |
| this.updateGearMenu_(); |
| // If the volume has changed, and it was previously set, then do not |
| // close on unmount anymore. |
| if (oldCurrentVolumeInfo) |
| this.closeOnUnmount_ = false; |
| } |
| |
| this.selectionHandler_.onFileSelectionChanged(); |
| this.searchController_.clear(); |
| // TODO(mtomasz): Consider remembering the selection. |
| util.updateAppState( |
| this.getCurrentDirectoryEntry() ? |
| this.getCurrentDirectoryEntry().toURL() : '', |
| '' /* selectionURL */, |
| '' /* opt_param */); |
| |
| if (this.commandHandler) |
| this.commandHandler.updateAvailability(); |
| |
| this.updateUnformattedVolumeStatus_(); |
| this.updateTitle_(); |
| |
| var currentEntry = this.getCurrentDirectoryEntry(); |
| this.ui_.locationLine.show(currentEntry); |
| this.ui_.previewPanel.currentEntry = util.isFakeEntry(currentEntry) ? |
| null : currentEntry; |
| }; |
| |
| FileManager.prototype.updateUnformattedVolumeStatus_ = function() { |
| var volumeInfo = this.volumeManager_.getVolumeInfo( |
| this.directoryModel_.getCurrentDirEntry()); |
| |
| if (volumeInfo && volumeInfo.error) { |
| this.dialogDom_.setAttribute('unformatted', ''); |
| |
| var errorNode = this.dialogDom_.querySelector('#format-panel > .error'); |
| if (volumeInfo.error === |
| VolumeManagerCommon.VolumeError.UNSUPPORTED_FILESYSTEM) { |
| errorNode.textContent = str('UNSUPPORTED_FILESYSTEM_WARNING'); |
| } else { |
| errorNode.textContent = str('UNKNOWN_FILESYSTEM_WARNING'); |
| } |
| |
| // Update 'canExecute' for format command so the format button's disabled |
| // property is properly set. |
| if (this.commandHandler) |
| this.commandHandler.updateAvailability(); |
| } else { |
| this.dialogDom_.removeAttribute('unformatted'); |
| } |
| }; |
| |
| /** |
| * Unload handler for the page. |
| * @private |
| */ |
| FileManager.prototype.onUnload_ = function() { |
| if (this.directoryModel_) |
| this.directoryModel_.dispose(); |
| if (this.volumeManager_) |
| this.volumeManager_.dispose(); |
| 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.onEntriesChangedBound_) { |
| this.fileOperationManager_.removeEventListener( |
| 'entries-changed', this.onEntriesChangedBound_); |
| } |
| } |
| window.closing = true; |
| if (this.backgroundPage_) |
| this.backgroundPage_.background.tryClose(); |
| }; |
| |
| /** |
| * @private |
| */ |
| FileManager.prototype.onFilenameInputInput_ = function() { |
| this.selectionHandler_.updateOkButton(); |
| }; |
| |
| /** |
| * @param {Event} event Key event. |
| * @private |
| */ |
| FileManager.prototype.onFilenameInputKeyDown_ = function(event) { |
| if ((util.getKeyModifiers(event) + event.keyCode) === '13' /* Enter */) |
| this.ui_.dialogFooter.okButton.click(); |
| }; |
| |
| /** |
| * @param {Event} event Focus event. |
| * @private |
| */ |
| FileManager.prototype.onFilenameInputFocus_ = function(event) { |
| 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); |
| }; |
| |
| FileManager.prototype.createNewFolder = function() { |
| var defaultName = str('DEFAULT_NEW_FOLDER_NAME'); |
| |
| // Find a name that doesn't exist in the data model. |
| var files = this.directoryModel_.getFileList(); |
| var hash = {}; |
| for (var i = 0; i < files.length; i++) { |
| var name = files.item(i).name; |
| // Filtering names prevents from conflicts with prototype's names |
| // and '__proto__'. |
| if (name.substring(0, defaultName.length) == defaultName) |
| hash[name] = 1; |
| } |
| |
| var baseName = defaultName; |
| var separator = ''; |
| var suffix = ''; |
| var index = ''; |
| |
| var advance = function() { |
| separator = ' ('; |
| suffix = ')'; |
| index++; |
| }; |
| |
| var current = function() { |
| return baseName + separator + index + suffix; |
| }; |
| |
| // Accessing hasOwnProperty is safe since hash properties filtered. |
| while (hash.hasOwnProperty(current())) { |
| advance(); |
| } |
| |
| var self = this; |
| var list = self.ui_.listContainer.currentList; |
| |
| var onSuccess = function(entry) { |
| metrics.recordUserAction('CreateNewFolder'); |
| list.selectedItem = entry; |
| |
| self.ui_.listContainer.endBatchUpdates(); |
| |
| self.namingController_.initiateRename(); |
| }; |
| |
| var onError = function(error) { |
| self.ui_.listContainer.endBatchUpdates(); |
| |
| self.alert.show(strf('ERROR_CREATING_FOLDER', current(), |
| util.getFileErrorString(error.name))); |
| }; |
| |
| var onAbort = function() { |
| self.ui_.listContainer.endBatchUpdates(); |
| }; |
| |
| this.ui_.listContainer.startBatchUpdates(); |
| this.directoryModel_.createDirectory(current(), |
| onSuccess, |
| onError, |
| onAbort); |
| }; |
| |
| /** |
| * Handles click event on the toggle-view button. |
| * @param {Event} event Click event. |
| * @private |
| */ |
| FileManager.prototype.onToggleViewButtonClick_ = function(event) { |
| if (this.ui_.listContainer.currentListType === |
| ListContainer.ListType.DETAIL) { |
| this.setListType(ListContainer.ListType.THUMBNAIL); |
| } else { |
| this.setListType(ListContainer.ListType.DETAIL); |
| } |
| |
| event.target.blur(); |
| }; |
| |
| /** |
| * KeyDown event handler for the document. |
| * @param {Event} event Key event. |
| * @private |
| */ |
| FileManager.prototype.onKeyDown_ = function(event) { |
| if (event.keyCode === 9) // Tab |
| this.pressingTab_ = true; |
| if (event.keyCode === 17) // Ctrl |
| this.pressingCtrl_ = true; |
| |
| if (event.srcElement === this.ui_.listContainer.renameInput) { |
| // Ignore keydown handler in the rename input box. |
| return; |
| } |
| |
| switch (util.getKeyModifiers(event) + event.keyIdentifier) { |
| case 'Ctrl-U+00BE': // Ctrl-. => Toggle filter files. |
| this.fileFilter_.setFilterHidden( |
| !this.fileFilter_.isFilterHiddenOn()); |
| event.preventDefault(); |
| return; |
| |
| 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.ui_.dialogFooter.cancelButton.click(); |
| } |
| break; |
| } |
| }; |
| |
| /** |
| * KeyUp event handler for the document. |
| * @param {Event} event Key event. |
| * @private |
| */ |
| FileManager.prototype.onKeyUp_ = function(event) { |
| if (event.keyCode === 9) // Tab |
| this.pressingTab_ = false; |
| if (event.keyCode == 17) // Ctrl |
| this.pressingCtrl_ = false; |
| }; |
| |
| /** |
| * KeyDown event handler for the div#list-container element. |
| * @param {Event} event Key event. |
| * @private |
| */ |
| FileManager.prototype.onListKeyDown_ = function(event) { |
| 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; |
| var currentEntry = this.getCurrentDirectoryEntry(); |
| var locationInfo = this.volumeManager_.getLocationInfo(currentEntry); |
| // TODO(mtomasz): There may be a tiny race in here. |
| if (locationInfo && !locationInfo.isRootEntry && |
| !locationInfo.isSpecialSearchRoot) { |
| currentEntry.getParent(function(parentEntry) { |
| this.directoryModel_.changeDirectoryEntry(parentEntry); |
| }.bind(this), function() { /* Ignore errors. */}); |
| } |
| break; |
| |
| case 'Enter': // Enter => Change directory or perform default action. |
| // TODO(dgozman): move directory action to dispatchSelectionAction. |
| var selection = this.getSelection(); |
| if (selection.totalCount === 1 && |
| selection.entries[0].isDirectory && |
| !DialogType.isFolderDialog(this.dialogType)) { |
| 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; |
| } |
| }; |
| |
| /** |
| * Performs a 'text search' - selects a first list entry with name |
| * starting with entered text (case-insensitive). |
| * @private |
| */ |
| 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.ui.listContainer.currentList.selectionModel.selectedIndexes = |
| [index]; |
| return; |
| } |
| } |
| |
| this.ui_.listContainer.textSearchState.text = ''; |
| }; |
| |
| /** |
| * Verifies the user entered name for file or folder to be created or |
| * 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(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 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); |
| }); |
| }; |
| |
| /** |
| * Toggle whether mobile data is used for sync. |
| */ |
| FileManager.prototype.toggleDriveSyncSettings = function() { |
| // If checked, the sync is disabled. |
| var nowCellularDisabled = this.syncButton.hasAttribute('checked'); |
| var changeInfo = {cellularDisabled: !nowCellularDisabled}; |
| chrome.fileManagerPrivate.setPreferences(changeInfo); |
| }; |
| |
| /** |
| * Toggle whether Google Docs files are shown. |
| */ |
| FileManager.prototype.toggleDriveHostedSettings = function() { |
| // If checked, showing drive hosted files is enabled. |
| var nowHostedFilesEnabled = this.hostedButton.hasAttribute('checked'); |
| var nowHostedFilesDisabled = !nowHostedFilesEnabled; |
| /* |
| var changeInfo = {hostedFilesDisabled: !nowHostedFilesDisabled}; |
| */ |
| var changeInfo = {}; |
| changeInfo['hostedFilesDisabled'] = !nowHostedFilesDisabled; |
| chrome.fileManagerPrivate.setPreferences(changeInfo); |
| }; |
| |
| FileManager.prototype.decorateSplitter = function(splitterElement) { |
| var self = this; |
| |
| var Splitter = cr.ui.Splitter; |
| |
| var customSplitter = cr.ui.define('div'); |
| |
| customSplitter.prototype = { |
| __proto__: Splitter.prototype, |
| |
| handleSplitterDragStart: function(e) { |
| Splitter.prototype.handleSplitterDragStart.apply(this, arguments); |
| this.ownerDocument.documentElement.classList.add('col-resize'); |
| }, |
| |
| handleSplitterDragMove: function(deltaX) { |
| Splitter.prototype.handleSplitterDragMove.apply(this, arguments); |
| self.onResize_(); |
| }, |
| |
| handleSplitterDragEnd: function(e) { |
| Splitter.prototype.handleSplitterDragEnd.apply(this, arguments); |
| this.ownerDocument.documentElement.classList.remove('col-resize'); |
| } |
| }; |
| |
| customSplitter.decorate(splitterElement); |
| }; |
| |
| /** |
| * Updates action menu item to match passed task items. |
| * |
| * @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.actionMenuItem_.style.backgroundImage = ''; |
| } |
| |
| 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; |
| } |
| |
| this.actionMenuItem_.hidden = items.length !== 1; |
| |
| // When multiple tasks are available, show them in open with. |
| this.openWithCommand_.canExecuteChange(); |
| this.openWithCommand_.setHidden(items.length < 2); |
| this.openWithCommand_.disabled = items.length < 2; |
| |
| // 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 {FileSelection} Selection object. |
| */ |
| FileManager.prototype.getSelection = function() { |
| return this.selectionHandler_.selection; |
| }; |
| |
| /** |
| * @return {cr.ui.ArrayDataModel} File list. |
| */ |
| FileManager.prototype.getFileList = function() { |
| return this.directoryModel_.getFileList(); |
| }; |
| |
| /** |
| * @return {!cr.ui.List} Current list object. |
| */ |
| FileManager.prototype.getCurrentList = function() { |
| return this.ui.listContainer.currentList; |
| }; |
| |
| /** |
| * Retrieve the preferences of the files.app. This method caches the result |
| * and returns it unless opt_update is true. |
| * @param {function(Object.<string, *>)} callback Callback to get the |
| * preference. |
| * @param {boolean=} opt_update If is's true, don't use the cache and |
| * retrieve latest preference. Default is false. |
| * @private |
| */ |
| FileManager.prototype.getPreferences_ = function(callback, opt_update) { |
| if (!opt_update && this.preferences_ !== null) { |
| callback(this.preferences_); |
| return; |
| } |
| |
| 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); |
| }; |
| })(); |