| // 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. |
| * |
| * @implements {CommandHandlerDeps} |
| */ |
| class FileManager extends cr.EventTarget { |
| constructor() { |
| super(); |
| |
| // ------------------------------------------------------------------------ |
| // Services FileManager depends on. |
| |
| /** |
| * Volume manager. |
| * @private {?FilteredVolumeManager} |
| */ |
| this.volumeManager_ = null; |
| |
| /** @private {?importer.HistoryLoader} */ |
| this.historyLoader_ = null; |
| |
| /** @private {?Crostini} */ |
| this.crostini_ = null; |
| |
| /** |
| * ImportHistory. Non-null only once history observer is added in |
| * {@code addHistoryObserver}. |
| * @private {?importer.ImportHistory} |
| */ |
| this.importHistory_ = null; |
| |
| /** |
| * Bound observer for use with {@code importer.ImportHistory.Observer}. |
| * The instance is bound once here as {@code ImportHistory.removeObserver} |
| * uses object equivilency to remove observers. |
| * |
| * @private @const {function(!importer.ImportHistory.ChangedEvent)} |
| */ |
| this.onHistoryChangedBound_ = this.onHistoryChanged_.bind(this); |
| |
| /** @private {?importer.MediaScanner} */ |
| this.mediaScanner_ = null; |
| |
| /** @private {?importer.ImportController} */ |
| this.importController_ = null; |
| |
| /** @private {?importer.ImportRunner} */ |
| this.mediaImportHandler_ = null; |
| |
| /** @private {?MetadataModel} */ |
| this.metadataModel_ = null; |
| |
| /** @private @const {!FileMetadataFormatter} */ |
| this.fileMetadataFormatter_ = new FileMetadataFormatter(); |
| |
| /** @private {?ThumbnailModel} */ |
| this.thumbnailModel_ = null; |
| |
| /** |
| * File operation manager. |
| * @private {?FileOperationManager} |
| */ |
| this.fileOperationManager_ = null; |
| |
| /** |
| * File filter. |
| * @private {?FileFilter} |
| */ |
| this.fileFilter_ = null; |
| |
| /** |
| * Model of current directory. |
| * @private {?DirectoryModel} |
| */ |
| this.directoryModel_ = null; |
| |
| /** |
| * Model of folder shortcuts. |
| * @private {?FolderShortcutsDataModel} |
| */ |
| this.folderShortcutsModel_ = null; |
| |
| /** |
| * Model of Android apps. |
| * @private {?AndroidAppListModel} |
| */ |
| this.androidAppListModel_ = null; |
| |
| /** |
| * Model for providers (providing extensions). |
| * @private {?ProvidersModel} |
| */ |
| this.providersModel_ = null; |
| |
| /** |
| * Model for quick view. |
| * @private {?QuickViewModel} |
| */ |
| this.quickViewModel_ = null; |
| |
| /** |
| * Controller for actions for current selection. |
| * @private {ActionsController} |
| */ |
| this.actionsController_ = null; |
| |
| /** |
| * Handler for command events. |
| * @private {CommandHandler} |
| */ |
| this.commandHandler_ = null; |
| |
| /** |
| * Handler for the change of file selection. |
| * @private {?FileSelectionHandler} |
| */ |
| this.selectionHandler_ = null; |
| |
| /** |
| * UI management class of file manager. |
| * @private {?FileManagerUI} |
| */ |
| this.ui_ = null; |
| |
| // ------------------------------------------------------------------------ |
| // Parameters determining the type of file manager. |
| |
| /** |
| * Dialog type of this window. |
| * @public {DialogType} |
| */ |
| this.dialogType = DialogType.FULL_PAGE; |
| |
| /** |
| * Startup parameters for this application. |
| * @private {?LaunchParam} |
| */ |
| this.launchParams_ = null; |
| |
| // ------------------------------------------------------------------------ |
| // Controllers. |
| |
| /** |
| * File transfer controller. |
| * @private {?FileTransferController} |
| */ |
| this.fileTransferController_ = null; |
| |
| /** |
| * Naming controller. |
| * @private {?NamingController} |
| */ |
| this.namingController_ = null; |
| |
| /** |
| * Directory tree naming controller. |
| * @private {DirectoryTreeNamingController} |
| */ |
| this.directoryTreeNamingController_ = null; |
| |
| /** |
| * Controller for search UI. |
| * @private {?SearchController} |
| */ |
| this.searchController_ = null; |
| |
| /** |
| * Controller for directory scan. |
| * @private {?ScanController} |
| */ |
| this.scanController_ = null; |
| |
| /** |
| * Controller for spinner. |
| * @private {?SpinnerController} |
| */ |
| this.spinnerController_ = null; |
| |
| /** |
| * Sort menu controller. |
| * @private {?SortMenuController} |
| */ |
| this.sortMenuController_ = null; |
| |
| /** |
| * Gear menu controller. |
| * @private {?GearMenuController} |
| */ |
| this.gearMenuController_ = null; |
| |
| /** |
| * Controller for the context menu opened by the action bar button in the |
| * check-select mode. |
| * @private {?SelectionMenuController} |
| */ |
| this.selectionMenuController_ = null; |
| |
| /** |
| * Toolbar controller. |
| * @private {?ToolbarController} |
| */ |
| this.toolbarController_ = null; |
| |
| /** |
| * Empty folder controller. |
| * @private {EmptyFolderController} |
| */ |
| this.emptyFolderController_ = null; |
| |
| /** |
| * App state controller. |
| * @private {?AppStateController} |
| */ |
| this.appStateController_ = null; |
| |
| /** |
| * Dialog action controller. |
| * @private {?DialogActionController} |
| */ |
| this.dialogActionController_ = null; |
| |
| /** |
| * List update controller. |
| * @private {?MetadataUpdateController} |
| */ |
| this.metadataUpdateController_ = null; |
| |
| /** |
| * Last modified controller. |
| * @private {LastModifiedController} |
| */ |
| this.lastModifiedController_ = null; |
| |
| /** |
| * Component for main window and its misc UI parts. |
| * @private {?MainWindowComponent} |
| */ |
| this.mainWindowComponent_ = null; |
| |
| /** @private {?TaskController} */ |
| this.taskController_ = null; |
| |
| /** @private {ColumnVisibilityController} */ |
| this.columnVisibilityController_ = null; |
| |
| /** @private {?QuickViewUma} */ |
| this.quickViewUma_ = null; |
| |
| /** @private {?QuickViewController} */ |
| this.quickViewController_ = null; |
| |
| /** |
| * Records histograms of directory-changed event. |
| * @private {?NavigationUma} |
| */ |
| this.navigationUma_ = null; |
| |
| // ------------------------------------------------------------------------ |
| // DOM elements. |
| |
| /** |
| * Background page. |
| * @private {?BackgroundWindow} |
| */ |
| this.backgroundPage_ = null; |
| |
| /** |
| * @private {?FileBrowserBackgroundFull} |
| */ |
| this.fileBrowserBackground_ = null; |
| |
| /** |
| * The root DOM element of this app. |
| * @private {?HTMLBodyElement} |
| */ |
| this.dialogDom_ = null; |
| |
| /** |
| * The document object of this app. |
| * @private {?Document} |
| */ |
| this.document_ = null; |
| |
| // ------------------------------------------------------------------------ |
| // Miscellaneous FileManager's states. |
| |
| /** |
| * Promise object which is fulfilled when initialization for app state |
| * controller is done. |
| * @private {?Promise<void>} |
| */ |
| this.initSettingsPromise_ = null; |
| |
| /** |
| * Promise object which is fulfilled when initialization related to the |
| * background page is done. |
| * @private {?Promise<void>} |
| */ |
| this.initBackgroundPagePromise_ = null; |
| |
| /** |
| * Flags async retrieved once at startup and can be used to switch behaviour |
| * on sync functions. |
| * @dict |
| * @private |
| */ |
| this.commandLineFlags_ = {}; |
| |
| /** |
| * Whether Drive is enabled. Retrieved from user preferences. |
| * @private {?boolean} |
| */ |
| this.driveEnabled_ = false; |
| |
| /** |
| * A fake Drive placeholder item. |
| * @private {?NavigationModelFakeItem} |
| */ |
| this.fakeDriveItem_ = null; |
| } |
| |
| /** |
| * @return {DirectoryModel} |
| */ |
| get directoryModel() { |
| return this.directoryModel_; |
| } |
| |
| /** |
| * @return {DirectoryTreeNamingController} |
| */ |
| get directoryTreeNamingController() { |
| return this.directoryTreeNamingController_; |
| } |
| |
| /** |
| * @return {FileFilter} |
| */ |
| get fileFilter() { |
| return this.fileFilter_; |
| } |
| |
| /** |
| * @return {FolderShortcutsDataModel} |
| */ |
| get folderShortcutsModel() { |
| return this.folderShortcutsModel_; |
| } |
| |
| /** |
| * @return {ActionsController} |
| */ |
| get actionsController() { |
| return this.actionsController_; |
| } |
| |
| /** |
| * @return {CommandHandler} |
| */ |
| get commandHandler() { |
| return this.commandHandler_; |
| } |
| |
| /** |
| * @return {ProvidersModel} |
| */ |
| get providersModel() { |
| return this.providersModel_; |
| } |
| |
| /** |
| * @return {MetadataModel} |
| */ |
| get metadataModel() { |
| return this.metadataModel_; |
| } |
| |
| /** |
| * @return {FileSelectionHandler} |
| */ |
| get selectionHandler() { |
| return this.selectionHandler_; |
| } |
| |
| /** |
| * @return {DirectoryTree} |
| */ |
| get directoryTree() { |
| return this.ui_.directoryTree; |
| } |
| /** |
| * @return {Document} |
| */ |
| get document() { |
| return this.document_; |
| } |
| |
| /** |
| * @return {FileTransferController} |
| */ |
| get fileTransferController() { |
| return this.fileTransferController_; |
| } |
| |
| /** |
| * @return {NamingController} |
| */ |
| get namingController() { |
| return this.namingController_; |
| } |
| |
| /** |
| * @return {TaskController} |
| */ |
| get taskController() { |
| return this.taskController_; |
| } |
| |
| /** |
| * @return {SpinnerController} |
| */ |
| get spinnerController() { |
| return this.spinnerController_; |
| } |
| |
| /** |
| * @return {FileOperationManager} |
| */ |
| get fileOperationManager() { |
| return this.fileOperationManager_; |
| } |
| |
| /** |
| * @return {BackgroundWindow} |
| */ |
| get backgroundPage() { |
| return this.backgroundPage_; |
| } |
| |
| /** |
| * @return {FilteredVolumeManager} |
| */ |
| get volumeManager() { |
| return this.volumeManager_; |
| } |
| |
| /** |
| * @return {importer.ImportController} |
| */ |
| get importController() { |
| return this.importController_; |
| } |
| |
| /** |
| * @return {importer.HistoryLoader} |
| */ |
| get historyLoader() { |
| return this.historyLoader_; |
| } |
| |
| /** |
| * @return {Crostini} |
| */ |
| get crostini() { |
| return this.crostini_; |
| } |
| |
| /** |
| * @return {importer.ImportRunner} |
| */ |
| get mediaImportHandler() { |
| return this.mediaImportHandler_; |
| } |
| |
| /** |
| * @return {FileManagerUI} |
| */ |
| get ui() { |
| return this.ui_; |
| } |
| |
| /** |
| * One time initialization for app state controller to load view option from |
| * local storage. |
| * @return {!Promise<void>} |
| * @private |
| */ |
| async startInitSettings_() { |
| metrics.startInterval('Load.InitSettings'); |
| this.appStateController_ = new AppStateController(this.dialogType); |
| await this.appStateController_.loadInitialViewOptions(); |
| metrics.recordInterval('Load.InitSettings'); |
| } |
| |
| /** |
| * One time initialization for the file system and related things. |
| * @private |
| */ |
| initFileSystemUI_() { |
| this.ui_.listContainer.startBatchUpdates(); |
| |
| this.initFileList_(); |
| this.setupCurrentDirectory_(); |
| |
| const self = this; |
| |
| let listBeingUpdated = null; |
| this.directoryModel_.addEventListener('begin-update-files', () => { |
| 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; |
| }); |
| this.directoryModel_.addEventListener('end-update-files', () => { |
| self.namingController_.restoreItemBeingRenamed(); |
| listBeingUpdated.endBatchUpdates(); |
| listBeingUpdated = null; |
| }); |
| this.volumeManager_.addEventListener( |
| VolumeManagerCommon.ARCHIVE_OPENED_EVENT_TYPE, event => { |
| assert(event.detail.mountPoint); |
| if (window.isFocused()) { |
| this.directoryModel_.changeDirectoryEntry(event.detail.mountPoint); |
| } |
| }); |
| |
| this.directoryModel_.addEventListener( |
| 'directory-changed', |
| /** @param {!Event} event */ |
| event => { |
| this.navigationUma_.onDirectoryChanged(event.newDirEntry); |
| if (event.volumeChanged) { |
| this.showArcStorageToast_( |
| this.volumeManager_.getVolumeInfo(event.newDirEntry)); |
| } |
| }); |
| |
| this.initCommands_(); |
| |
| assert(this.directoryModel_); |
| assert(this.spinnerController_); |
| assert(this.commandHandler_); |
| assert(this.selectionHandler_); |
| assert(this.launchParams_); |
| assert(this.volumeManager_); |
| assert(this.dialogDom_); |
| assert(this.fileFilter_); |
| |
| this.scanController_ = new ScanController( |
| this.directoryModel_, this.ui_.listContainer, this.spinnerController_, |
| this.commandHandler_, this.selectionHandler_); |
| this.sortMenuController_ = new SortMenuController( |
| this.ui_.sortButton, this.ui_.sortButtonToggleRipple, |
| assert(this.directoryModel_.getFileList())); |
| this.gearMenuController_ = new GearMenuController( |
| this.ui_.gearButton, this.ui_.gearButtonToggleRipple, this.ui_.gearMenu, |
| this.ui_.providersMenu, this.directoryModel_, this.commandHandler_, |
| assert(this.providersModel_)); |
| this.selectionMenuController_ = new SelectionMenuController( |
| this.ui_.selectionMenuButton, |
| util.queryDecoratedElement('#file-context-menu', cr.ui.Menu)); |
| this.toolbarController_ = new ToolbarController( |
| this.ui_.toolbar, this.ui_.dialogNavigationList, this.ui_.listContainer, |
| assert(this.ui_.locationLine), this.selectionHandler_, |
| this.directoryModel_, this.volumeManager_); |
| this.emptyFolderController_ = new EmptyFolderController( |
| this.ui_.emptyFolder, this.directoryModel_, this.ui_.alertDialog); |
| this.actionsController_ = new ActionsController( |
| this.volumeManager_, assert(this.metadataModel_), this.directoryModel_, |
| assert(this.folderShortcutsModel_), |
| this.fileBrowserBackground_.driveSyncHandler, this.selectionHandler_, |
| assert(this.ui_)); |
| this.lastModifiedController_ = new LastModifiedController( |
| this.ui_.listContainer.table, this.directoryModel_); |
| |
| this.quickViewModel_ = new QuickViewModel(); |
| const fileListSelectionModel = /** @type {!cr.ui.ListSelectionModel} */ ( |
| this.directoryModel_.getFileListSelection()); |
| this.quickViewUma_ = |
| new QuickViewUma(assert(this.volumeManager_), assert(this.dialogType)); |
| const metadataBoxController = new MetadataBoxController( |
| this.metadataModel_, this.quickViewModel_, this.fileMetadataFormatter_); |
| this.quickViewController_ = new QuickViewController( |
| assert(this.metadataModel_), assert(this.selectionHandler_), |
| assert(this.ui_.listContainer), assert(this.ui_.selectionMenuButton), |
| assert(this.quickViewModel_), assert(this.taskController_), |
| fileListSelectionModel, assert(this.quickViewUma_), |
| metadataBoxController, this.dialogType, assert(this.volumeManager_)); |
| |
| if (this.dialogType === DialogType.FULL_PAGE) { |
| this.importController_ = new importer.ImportController( |
| new importer.RuntimeControllerEnvironment( |
| this, assert(this.selectionHandler_)), |
| assert(this.mediaScanner_), assert(this.mediaImportHandler_), |
| new importer.RuntimeCommandWidget()); |
| } |
| |
| assert(this.fileFilter_); |
| assert(this.namingController_); |
| assert(this.appStateController_); |
| assert(this.taskController_); |
| this.mainWindowComponent_ = new MainWindowComponent( |
| this.dialogType, this.ui_, this.volumeManager_, this.directoryModel_, |
| this.fileFilter_, this.selectionHandler_, this.namingController_, |
| this.appStateController_, this.taskController_); |
| |
| this.initDataTransferOperations_(); |
| |
| this.selectionHandler_.onFileSelectionChanged(); |
| this.ui_.listContainer.endBatchUpdates(); |
| |
| this.ui_.initBanners(new Banners( |
| this.directoryModel_, this.volumeManager_, this.document_, |
| // Whether to show any welcome banner. |
| this.dialogType === DialogType.FULL_PAGE)); |
| |
| this.ui_.attachFilesTooltip(); |
| |
| this.ui_.decorateFilesMenuItems(); |
| |
| this.ui_.selectionMenuButton.hidden = false; |
| |
| console.warn('Files app sync startup finished'); |
| } |
| |
| /** |
| * @private |
| */ |
| initDataTransferOperations_() { |
| // 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; |
| } |
| |
| this.fileTransferController_ = new FileTransferController( |
| assert(this.document_), assert(this.ui_.listContainer), |
| assert(this.ui_.directoryTree), this.ui_.multiProfileShareDialog, |
| this.ui_.showConfirmationDialog.bind(this.ui_), |
| assert(this.fileBrowserBackground_.progressCenter), |
| assert(this.fileOperationManager_), assert(this.metadataModel_), |
| assert(this.thumbnailModel_), assert(this.directoryModel_), |
| assert(this.volumeManager_), assert(this.selectionHandler_)); |
| } |
| |
| /** |
| * One-time initialization of commands. |
| * @private |
| */ |
| initCommands_() { |
| assert(this.ui_.textContextMenu); |
| |
| this.commandHandler_ = |
| new CommandHandler(this, assert(this.selectionHandler_)); |
| |
| // TODO(hirono): Move the following block to the UI part. |
| const commandButtons = this.dialogDom_.querySelectorAll('button[command]'); |
| for (let j = 0; j < commandButtons.length; j++) { |
| CommandButton.decorate(commandButtons[j]); |
| } |
| |
| const inputs = this.getDomInputs_(); |
| |
| for (let input of inputs) { |
| this.setContextMenuForInput_(input); |
| } |
| |
| this.setContextMenuForInput_(this.ui_.listContainer.renameInput); |
| this.setContextMenuForInput_( |
| this.directoryTreeNamingController_.getInputElement()); |
| |
| this.document_.addEventListener( |
| 'command', |
| this.ui_.listContainer.clearHover.bind(this.ui_.listContainer)); |
| } |
| |
| /** |
| * Get input elements from root DOM element of this app. |
| * @private |
| */ |
| getDomInputs_() { |
| return this.dialogDom_.querySelectorAll( |
| 'input[type=text], input[type=search], textarea, cr-input'); |
| } |
| |
| /** |
| * Set context menu and handlers for an input element. |
| * @private |
| */ |
| setContextMenuForInput_(input) { |
| let touchInduced = false; |
| |
| // stop contextmenu propagation for touch-induced events. |
| input.addEventListener('touchstart', (e) => { |
| touchInduced = true; |
| }); |
| input.addEventListener('contextmenu', (e) => { |
| if (touchInduced) { |
| e.stopImmediatePropagation(); |
| } |
| touchInduced = false; |
| }); |
| input.addEventListener('click', (e) => { |
| touchInduced = false; |
| }); |
| |
| cr.ui.contextMenuHandler.setContextMenu(input, this.ui_.textContextMenu); |
| this.registerInputCommands_(input); |
| } |
| |
| /** |
| * Registers cut, copy, paste and delete commands on input element. |
| * |
| * @param {Node} node Text input element to register on. |
| * @private |
| */ |
| registerInputCommands_(node) { |
| CommandUtil.forceDefaultHandler(node, 'cut'); |
| CommandUtil.forceDefaultHandler(node, 'copy'); |
| CommandUtil.forceDefaultHandler(node, 'paste'); |
| CommandUtil.forceDefaultHandler(node, 'delete'); |
| node.addEventListener('keydown', e => { |
| const 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. |
| */ |
| initializeCore() { |
| this.initGeneral_(); |
| this.initSettingsPromise_ = this.startInitSettings_(); |
| this.initBackgroundPagePromise_ = this.startInitBackgroundPage_(); |
| this.initBackgroundPagePromise_.then(() => this.initVolumeManager_()); |
| |
| window.addEventListener('pagehide', this.onUnload_.bind(this)); |
| } |
| |
| /** |
| * @return {!Promise<void>} |
| */ |
| async initializeUI(dialogDom) { |
| this.dialogDom_ = dialogDom; |
| this.document_ = this.dialogDom_.ownerDocument; |
| |
| metrics.startInterval('Load.InitDocuments'); |
| await Promise.all( |
| [this.initBackgroundPagePromise_, window.importElementsPromise]); |
| metrics.recordInterval('Load.InitDocuments'); |
| |
| metrics.startInterval('Load.InitUI'); |
| this.initEssentialUI_(); |
| this.initAdditionalUI_(); |
| await this.initSettingsPromise_; |
| this.initFileSystemUI_(); |
| this.initUIFocus_(); |
| metrics.recordInterval('Load.InitUI'); |
| } |
| |
| /** |
| * Initializes general purpose basic things, which are used by other |
| * initializing methods. |
| * @private |
| */ |
| initGeneral_() { |
| // Initialize the application state. |
| // TODO(mtomasz): Unify window.appState with location.search format. |
| console.warn('Files app starting up'); |
| if (window.appState) { |
| const params = {}; |
| for (let name in window.appState) { |
| params[name] = window.appState[name]; |
| } |
| for (let name in window.appState.params) { |
| params[name] = window.appState.params[name]; |
| } |
| this.launchParams_ = new LaunchParam(params); |
| } else { |
| // Used by the select dialog only. |
| const json = location.search ? |
| JSON.parse(decodeURIComponent(location.search.substr(1))) : |
| {}; |
| this.launchParams_ = new LaunchParam(json instanceof Object ? json : {}); |
| } |
| |
| // Initialize the member variables that depend this.launchParams_. |
| this.dialogType = this.launchParams_.type; |
| } |
| |
| /** |
| * Initializes the background page. |
| * @return {!Promise<void>} |
| * @private |
| */ |
| async startInitBackgroundPage_() { |
| metrics.startInterval('Load.InitBackgroundPage'); |
| |
| /** @type {!Window} */ |
| const backgroundPage = |
| await new Promise(resolve => chrome.runtime.getBackgroundPage(resolve)); |
| assert(backgroundPage); |
| this.backgroundPage_ = |
| /** @type {!BackgroundWindow} */ (backgroundPage); |
| this.fileBrowserBackground_ = |
| /** @type {!FileBrowserBackgroundFull} */ ( |
| this.backgroundPage_.background); |
| |
| await new Promise(resolve => this.fileBrowserBackground_.ready(resolve)); |
| loadTimeData.data = this.fileBrowserBackground_.stringData; |
| if (util.runningInBrowser()) { |
| this.backgroundPage_.registerDialog(window); |
| } |
| this.fileOperationManager_ = |
| this.fileBrowserBackground_.fileOperationManager; |
| this.mediaImportHandler_ = this.fileBrowserBackground_.mediaImportHandler; |
| this.mediaScanner_ = this.fileBrowserBackground_.mediaScanner; |
| this.historyLoader_ = this.fileBrowserBackground_.historyLoader; |
| this.crostini_ = this.fileBrowserBackground_.crostini; |
| metrics.recordInterval('Load.InitBackgroundPage'); |
| } |
| |
| /** |
| * Initializes the VolumeManager instance. |
| * @private |
| */ |
| initVolumeManager_() { |
| const allowedPaths = this.getAllowedPaths_(); |
| const writableOnly = |
| this.launchParams_.type === DialogType.SELECT_SAVEAS_FILE; |
| |
| // FilteredVolumeManager hides virtual file system related event and data |
| // even depends on the value of |supportVirtualPath|. If it is |
| // VirtualPathSupport.NO_VIRTUAL_PATH, it hides Drive even if Drive is |
| // enabled on preference. |
| // In other words, even if Drive is disabled on preference but the 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. |
| this.volumeManager_ = new FilteredVolumeManager( |
| allowedPaths, writableOnly, this.backgroundPage_); |
| } |
| |
| /** |
| * One time initialization of the essential UI elements in the Files app. |
| * These elements will be shown to the user. Only visible elements should be |
| * initialized here. Any heavy operation should be avoided. The Files app's |
| * window is shown at the end of this routine. |
| * @private |
| */ |
| initEssentialUI_() { |
| // 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. |
| assert(this.volumeManager_); |
| this.metadataModel_ = MetadataModel.create(this.volumeManager_); |
| this.thumbnailModel_ = new ThumbnailModel(this.metadataModel_); |
| this.providersModel_ = new ProvidersModel(this.volumeManager_); |
| this.fileFilter_ = new FileFilter(this.metadataModel_); |
| |
| // Create the root view of FileManager. |
| assert(this.dialogDom_); |
| assert(this.launchParams_); |
| this.ui_ = new FileManagerUI( |
| assert(this.providersModel_), this.dialogDom_, this.launchParams_); |
| } |
| |
| /** |
| * 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. |
| * @private |
| */ |
| initAdditionalUI_() { |
| assert(this.metadataModel_); |
| assert(this.volumeManager_); |
| assert(this.historyLoader_); |
| assert(this.dialogDom_); |
| |
| // Cache nodes we'll be manipulating. |
| const dom = this.dialogDom_; |
| assert(dom); |
| |
| const table = queryRequiredElement('.detail-table', dom); |
| FileTable.decorate( |
| table, this.metadataModel_, this.volumeManager_, this.historyLoader_, |
| this.dialogType == DialogType.FULL_PAGE); |
| const grid = queryRequiredElement('.thumbnail-grid', dom); |
| FileGrid.decorate( |
| grid, this.metadataModel_, this.volumeManager_, this.historyLoader_); |
| |
| this.addHistoryObserver_(); |
| |
| this.ui_.initAdditionalUI( |
| assertInstanceof(table, FileTable), assertInstanceof(grid, FileGrid), |
| new LocationLine( |
| queryRequiredElement('#location-breadcrumbs', dom), |
| this.volumeManager_)); |
| |
| // Handle UI events. |
| this.fileBrowserBackground_.progressCenter.addPanel( |
| this.ui_.progressCenterPanel); |
| |
| util.addIsFocusedMethod(); |
| |
| // The cwd is not known at this point. Hide the import status column before |
| // redrawing, to avoid ugly flashing in the UI, caused when the first redraw |
| // has a visible status column, and then the cwd is later discovered to be |
| // not an import-eligible location. |
| this.ui_.listContainer.table.setImportStatusVisible(false); |
| |
| // Arrange the file list. |
| this.ui_.listContainer.table.normalizeColumns(); |
| this.ui_.listContainer.table.redraw(); |
| } |
| |
| /** |
| * One-time initialization of focus. This should run at the last of UI |
| * initialization. |
| * @private |
| */ |
| initUIFocus_() { |
| this.ui_.initUIFocus(); |
| } |
| |
| /** |
| * One-time initialization of import history observer. Provides |
| * the glue that updates the UI when history changes. |
| * |
| * @private |
| */ |
| addHistoryObserver_() { |
| // If, and only if history is ever fully loaded (it may not be), |
| // we want to update grid/list view when it changes. |
| this.historyLoader_.addHistoryLoadedListener( |
| /** |
| * @param {!importer.ImportHistory} history |
| * @this {FileManager} |
| */ |
| history => { |
| this.importHistory_ = history; |
| history.addObserver(this.onHistoryChangedBound_); |
| }); |
| } |
| |
| /** |
| * Handles events when import history changed. |
| * |
| * @param {!importer.ImportHistory.ChangedEvent} event |
| * @private |
| */ |
| onHistoryChanged_(event) { |
| // Ignore any entry that isn't an immediate child of the |
| // current directory. |
| util.isChildEntry(event.entry, this.getCurrentDirectoryEntry()) |
| .then( |
| /** |
| * @param {boolean} isChild |
| */ |
| isChild => { |
| if (isChild) { |
| this.ui_.listContainer.grid.updateListItemsMetadata( |
| 'import-history', [event.entry]); |
| this.ui_.listContainer.table.updateListItemsMetadata( |
| 'import-history', [event.entry]); |
| } |
| }); |
| } |
| |
| /** |
| * Constructs table and grid (heavy operation). |
| * @private |
| */ |
| initFileList_() { |
| const 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.volumeManager_); |
| assert(this.fileOperationManager_); |
| assert(this.metadataModel_); |
| this.directoryModel_ = new DirectoryModel( |
| singleSelection, this.fileFilter_, this.metadataModel_, |
| this.volumeManager_, this.fileOperationManager_); |
| |
| this.folderShortcutsModel_ = |
| new FolderShortcutsDataModel(this.volumeManager_); |
| |
| this.androidAppListModel_ = new AndroidAppListModel( |
| this.launchParams_.showAndroidPickerApps, |
| this.launchParams_.includeAllFiles, this.launchParams_.typeList); |
| |
| assert(this.launchParams_); |
| this.selectionHandler_ = new FileSelectionHandler( |
| assert(this.directoryModel_), assert(this.fileOperationManager_), |
| assert(this.ui_.listContainer), assert(this.metadataModel_), |
| assert(this.volumeManager_), this.launchParams_.allowedPaths); |
| |
| this.directoryModel_.getFileListSelection().addEventListener( |
| 'change', |
| this.selectionHandler_.onFileSelectionChanged.bind( |
| this.selectionHandler_)); |
| |
| // TODO(mtomasz, yoshiki): Create navigation list earlier, and here just |
| // attach the directory model. |
| this.initDirectoryTree_(); |
| |
| this.ui_.listContainer.listThumbnailLoader = new ListThumbnailLoader( |
| this.directoryModel_, assert(this.thumbnailModel_), |
| this.volumeManager_); |
| this.ui_.listContainer.dataModel = this.directoryModel_.getFileList(); |
| this.ui_.listContainer.emptyDataModel = |
| this.directoryModel_.getEmptyFileList(); |
| this.ui_.listContainer.selectionModel = |
| this.directoryModel_.getFileListSelection(); |
| |
| this.appStateController_.initialize(this.ui_, this.directoryModel_); |
| |
| if (this.dialogType === DialogType.FULL_PAGE) { |
| this.columnVisibilityController_ = new ColumnVisibilityController( |
| this.ui_, this.directoryModel_, this.volumeManager_); |
| } |
| |
| // Create metadata update controller. |
| this.metadataUpdateController_ = new MetadataUpdateController( |
| this.ui_.listContainer, this.directoryModel_, this.metadataModel_, |
| this.fileMetadataFormatter_); |
| |
| // Create naming controller. |
| this.namingController_ = new NamingController( |
| this.ui_.listContainer, assert(this.ui_.alertDialog), |
| assert(this.ui_.confirmDialog), this.directoryModel_, |
| assert(this.fileFilter_), this.selectionHandler_); |
| |
| // Create task controller. |
| this.taskController_ = new TaskController( |
| this.dialogType, this.volumeManager_, this.ui_, this.metadataModel_, |
| this.directoryModel_, this.selectionHandler_, |
| this.metadataUpdateController_, this.namingController_, |
| assert(this.crostini_)); |
| |
| // Create search controller. |
| this.searchController_ = new SearchController( |
| this.ui_.searchBox, assert(this.ui_.locationLine), this.directoryModel_, |
| this.volumeManager_, assert(this.taskController_)); |
| |
| // Create directory tree naming controller. |
| this.directoryTreeNamingController_ = new DirectoryTreeNamingController( |
| this.directoryModel_, assert(this.ui_.directoryTree), |
| this.ui_.alertDialog); |
| |
| // Create spinner controller. |
| this.spinnerController_ = |
| new SpinnerController(this.ui_.listContainer.spinner); |
| this.spinnerController_.blink(); |
| |
| // Create dialog action controller. |
| this.dialogActionController_ = new DialogActionController( |
| this.dialogType, this.ui_.dialogFooter, this.directoryModel_, |
| this.metadataModel_, this.volumeManager_, this.fileFilter_, |
| this.namingController_, this.selectionHandler_, this.launchParams_); |
| } |
| |
| /** |
| * @private |
| */ |
| initDirectoryTree_() { |
| const directoryTree = /** @type {DirectoryTree} */ |
| (this.dialogDom_.querySelector('#directory-tree')); |
| const fakeEntriesVisible = |
| this.dialogType !== DialogType.SELECT_SAVEAS_FILE; |
| this.navigationUma_ = new NavigationUma(assert(this.volumeManager_)); |
| DirectoryTree.decorate( |
| directoryTree, assert(this.directoryModel_), |
| assert(this.volumeManager_), assert(this.metadataModel_), |
| assert(this.fileOperationManager_), fakeEntriesVisible); |
| directoryTree.dataModel = new NavigationListModel( |
| assert(this.volumeManager_), assert(this.folderShortcutsModel_), |
| fakeEntriesVisible && |
| !DialogType.isFolderDialog(this.launchParams_.type) ? |
| new NavigationModelFakeItem( |
| str('RECENT_ROOT_LABEL'), NavigationModelItemType.RECENT, |
| new FakeEntry( |
| str('RECENT_ROOT_LABEL'), |
| VolumeManagerCommon.RootType.RECENT, |
| this.getSourceRestriction_())) : |
| null, |
| assert(this.directoryModel_), assert(this.androidAppListModel_)); |
| |
| this.ui_.initDirectoryTree(directoryTree); |
| this.crostini_.setEnabled( |
| constants.DEFAULT_CROSTINI_VM, |
| loadTimeData.getBoolean('CROSTINI_ENABLED')); |
| this.crostini_.setEnabled( |
| constants.PLUGIN_VM, loadTimeData.getBoolean('PLUGIN_VM_ENABLED')); |
| this.setupCrostini_(); |
| chrome.fileManagerPrivate.onCrostiniChanged.addListener( |
| this.onCrostiniChanged_.bind(this)); |
| |
| chrome.fileManagerPrivate.onPreferencesChanged.addListener(() => { |
| this.onPreferencesChanged_(); |
| }); |
| this.onPreferencesChanged_(); |
| } |
| |
| /** |
| * Setup crostini 'Linux files'. |
| * @private |
| */ |
| async setupCrostini_() { |
| // Setup Linux files fake root. |
| this.directoryTree.dataModel.linuxFilesItem = |
| this.crostini_.isEnabled(constants.DEFAULT_CROSTINI_VM) ? |
| new NavigationModelFakeItem( |
| str('LINUX_FILES_ROOT_LABEL'), NavigationModelItemType.CROSTINI, |
| new FakeEntry( |
| str('LINUX_FILES_ROOT_LABEL'), |
| VolumeManagerCommon.RootType.CROSTINI)) : |
| null; |
| // Redraw the tree to ensure 'Linux files' is added/removed. |
| this.directoryTree.redraw(false); |
| |
| // Load any existing shared paths. |
| // Only observe firstForSession when using full-page FilesApp. |
| // I.e., don't show toast in a dialog. |
| let showToast = false; |
| const getSharedPaths = (vmName) => { |
| return new Promise(resolve => { |
| if (!this.crostini_.isEnabled(vmName)) { |
| return resolve(0); |
| } |
| chrome.fileManagerPrivate.getCrostiniSharedPaths( |
| this.dialogType === DialogType.FULL_PAGE, vmName, |
| (entries, firstForSession) => { |
| showToast = showToast || firstForSession; |
| for (let i = 0; i < entries.length; i++) { |
| this.crostini_.registerSharedPath(vmName, entries[i]); |
| } |
| resolve(entries.length); |
| }); |
| }); |
| }; |
| |
| const toast = (count, msgSingle, msgPlural, action, subPage, umaItem) => { |
| if (!showToast || count == 0) { |
| return; |
| } |
| this.ui_.toast.show( |
| count == 1 ? str(msgSingle) : strf(msgPlural, count), { |
| text: str(action), |
| callback: () => { |
| chrome.fileManagerPrivate.openSettingsSubpage(subPage); |
| CommandHandler.recordMenuItemSelected(umaItem); |
| } |
| }); |
| }; |
| |
| const [crostiniShareCount, pluginVmShareCount] = await Promise.all([ |
| getSharedPaths(constants.DEFAULT_CROSTINI_VM), |
| getSharedPaths(constants.PLUGIN_VM) |
| ]); |
| |
| toast( |
| crostiniShareCount, 'FOLDER_SHARED_WITH_CROSTINI', |
| 'FOLDER_SHARED_WITH_CROSTINI_PLURAL', |
| 'MANAGE_LINUX_SHARING_BUTTON_LABEL', 'crostini/sharedPaths', |
| CommandHandler.MenuCommandsForUMA.MANAGE_LINUX_SHARING_TOAST_STARTUP); |
| // TODO(crbug.com/949356): UX to provide guidance for what to do |
| // when we have shared paths with both Linux and Plugin VM. |
| toast( |
| pluginVmShareCount, 'FOLDER_SHARED_WITH_PLUGIN_VM', |
| 'FOLDER_SHARED_WITH_PLUGIN_VM_PLURAL', |
| 'MANAGE_PLUGIN_VM_SHARING_BUTTON_LABEL', 'pluginVm/sharedPaths', |
| CommandHandler.MenuCommandsForUMA |
| .MANAGE_PLUGIN_VM_SHARING_TOAST_STARTUP); |
| } |
| |
| /** |
| * @param {chrome.fileManagerPrivate.CrostiniEvent} event |
| * @private |
| */ |
| onCrostiniChanged_(event) { |
| if (event.eventType === 'enable') { |
| this.crostini_.setEnabled(event.vmName, true); |
| this.setupCrostini_(); |
| } else if (event.eventType === 'disable') { |
| this.crostini_.setEnabled(event.vmName, false); |
| this.setupCrostini_(); |
| } |
| } |
| |
| /** |
| * Sets up the current directory during initialization. |
| * @private |
| */ |
| async setupCurrentDirectory_() { |
| const tracker = this.directoryModel_.createDirectoryChangeTracker(); |
| tracker.start(); |
| |
| // Wait until the volume manager is initialized. |
| await new Promise( |
| resolve => this.volumeManager_.ensureInitialized(resolve)); |
| |
| let nextCurrentDirEntry; |
| let selectionEntry; |
| |
| // Resolve the selectionURL to selectionEntry or to currentDirectoryEntry in |
| // case of being a display root or a default directory to open files. |
| if (this.launchParams_.selectionURL) { |
| try { |
| const inEntry = await new Promise((resolve, reject) => { |
| window.webkitResolveLocalFileSystemURL( |
| this.launchParams_.selectionURL, resolve, reject); |
| }); |
| const locationInfo = this.volumeManager_.getLocationInfo(inEntry); |
| // If location information is not available, then the volume is no |
| // longer (or never) available. |
| if (locationInfo) { |
| // 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; |
| } |
| } |
| } catch (error) { |
| console.warn(error.stack || error); |
| } |
| } |
| |
| // Resolve the currentDirectoryURL to currentDirectoryEntry (if not done by |
| // the previous step). |
| if (!nextCurrentDirEntry && this.launchParams_.currentDirectoryURL) { |
| try { |
| const inEntry = await new Promise((resolve, reject) => { |
| window.webkitResolveLocalFileSystemURL( |
| this.launchParams_.currentDirectoryURL, resolve, reject); |
| }); |
| const locationInfo = this.volumeManager_.getLocationInfo(inEntry); |
| if (locationInfo) { |
| nextCurrentDirEntry = inEntry; |
| } |
| } catch (error) { |
| console.warn(error.stack || error); |
| } |
| } |
| |
| // If the directory to be changed to is not available, then first fallback |
| // to the parent of the selection entry. |
| if (!nextCurrentDirEntry && selectionEntry) { |
| nextCurrentDirEntry = await new Promise(resolve => { |
| selectionEntry.getParent(resolve); |
| }); |
| } |
| |
| // Check if the next current directory is not a virtual directory which is |
| // not available in UI. This may happen to shared on Drive. |
| if (nextCurrentDirEntry) { |
| const locationInfo = |
| this.volumeManager_.getLocationInfo(nextCurrentDirEntry); |
| // If we can't check, assume that the directory is illegal. |
| if (!locationInfo) { |
| nextCurrentDirEntry = null; |
| } else { |
| // 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) { |
| const volumeInfo = |
| this.volumeManager_.getVolumeInfo(nextCurrentDirEntry); |
| if (!volumeInfo) { |
| nextCurrentDirEntry = null; |
| } else { |
| try { |
| nextCurrentDirEntry = await volumeInfo.resolveDisplayRoot(); |
| } catch (error) { |
| console.error(error.stack || error); |
| nextCurrentDirEntry = null; |
| } |
| } |
| } |
| } |
| } |
| |
| // If the directory to be changed to is still not resolved, then fallback to |
| // the default display root. |
| if (!nextCurrentDirEntry) { |
| nextCurrentDirEntry = await new Promise(resolve => { |
| this.volumeManager_.getDefaultDisplayRoot(resolve); |
| }); |
| } |
| |
| // 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. |
| if (!selectionEntry && nextCurrentDirEntry && |
| this.launchParams_.targetName) { |
| // Try to resolve as a file first. If it fails, then as a directory. |
| try { |
| selectionEntry = await new Promise((resolve, reject) => { |
| nextCurrentDirEntry.getFile( |
| this.launchParams_.targetName, {}, resolve, reject); |
| }); |
| } catch (error1) { |
| // Failed to resolve as a file. Try to resolve as a directory. |
| try { |
| selectionEntry = await new Promise((resolve, reject) => { |
| nextCurrentDirEntry.getDirectory( |
| this.launchParams_.targetName, {}, resolve, reject); |
| }); |
| } catch (error2) { |
| // Failed to resolve as either file or directory. |
| console.error(error1.stack || error1); |
| console.error(error2.stack || error2); |
| } |
| } |
| } |
| |
| // If there is no target select MyFiles by default. |
| if (!nextCurrentDirEntry && this.directoryTree.dataModel.myFilesModel_) { |
| nextCurrentDirEntry = this.directoryTree.dataModel.myFilesModel_.entry; |
| } |
| |
| // Check directory change. |
| tracker.stop(); |
| if (!tracker.hasChanged) { |
| // Finish setup current directory. |
| this.finishSetupCurrentDirectory_( |
| nextCurrentDirEntry, selectionEntry, this.launchParams_.targetName); |
| } |
| } |
| |
| /** |
| * @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 |
| */ |
| finishSetupCurrentDirectory_( |
| directoryEntry, opt_selectionEntry, opt_suggestedName) { |
| // Open the directory, and select the selection (if passed). |
| if (directoryEntry) { |
| const entryDescription = util.entryDebugString(directoryEntry); |
| console.warn( |
| `Files app start up: Changing to directory: ${entryDescription}`); |
| this.directoryModel_.changeDirectoryEntry(directoryEntry, () => { |
| if (opt_selectionEntry) { |
| this.directoryModel_.selectEntry(opt_selectionEntry); |
| } |
| console.warn( |
| `Files app start up: Changed to directory: ${entryDescription}`); |
| this.ui_.addLoadedAttribute(); |
| }); |
| } else { |
| console.warn('No entry for finishSetupCurrentDirectory_'); |
| this.ui_.addLoadedAttribute(); |
| } |
| |
| if (this.dialogType === DialogType.SELECT_SAVEAS_FILE) { |
| this.ui_.dialogFooter.filenameInput.value = opt_suggestedName || ''; |
| this.ui_.dialogFooter.selectTargetNameInFilenameInput(); |
| } |
| } |
| |
| /** |
| * Return DirectoryEntry of the current directory or null. |
| * @return {DirectoryEntry|FakeEntry|FilesAppDirEntry} DirectoryEntry of the |
| * current directory. |
| * Returns null if the directory model is not ready or the current |
| * directory is not set. |
| */ |
| getCurrentDirectoryEntry() { |
| return this.directoryModel_ && this.directoryModel_.getCurrentDirEntry(); |
| } |
| |
| /** |
| * Unload handler for the page. |
| * @private |
| */ |
| onUnload_() { |
| if (this.importHistory_) { |
| this.importHistory_.removeObserver(this.onHistoryChangedBound_); |
| } |
| if (this.directoryModel_) { |
| this.directoryModel_.dispose(); |
| } |
| if (this.volumeManager_) { |
| this.volumeManager_.dispose(); |
| } |
| if (this.fileTransferController_) { |
| for (let i = 0; i < this.fileTransferController_.pendingTaskIds.length; |
| i++) { |
| const taskId = this.fileTransferController_.pendingTaskIds[i]; |
| const item = |
| this.fileBrowserBackground_.progressCenter.getItemById(taskId); |
| item.message = ''; |
| item.state = ProgressItemState.CANCELED; |
| this.fileBrowserBackground_.progressCenter.updateItem(item); |
| } |
| } |
| if (this.ui_ && this.ui_.progressCenterPanel) { |
| this.fileBrowserBackground_.progressCenter.removePanel( |
| this.ui_.progressCenterPanel); |
| } |
| } |
| |
| /** |
| * Returns allowed path for the dialog by considering: |
| * 1) The launch parameter which specifies generic category of valid files |
| * paths. |
| * 2) Files app's unique capabilities and restrictions. |
| * @returns {AllowedPaths} |
| */ |
| getAllowedPaths_() { |
| let allowedPaths = this.launchParams_.allowedPaths; |
| // The native implementation of the Files app creates snapshot files for |
| // non-native files. But it does not work for folders (e.g., dialog for |
| // loading unpacked extensions). |
| if ((allowedPaths === AllowedPaths.NATIVE_PATH || |
| allowedPaths === AllowedPaths.NATIVE_OR_DRIVE_PATH) && |
| !DialogType.isFolderDialog(this.launchParams_.type)) { |
| if (this.launchParams_.type == DialogType.SELECT_SAVEAS_FILE) { |
| // Only drive can create snapshot files for saving. |
| allowedPaths = AllowedPaths.NATIVE_OR_DRIVE_PATH; |
| } else { |
| allowedPaths = AllowedPaths.ANY_PATH; |
| } |
| } |
| return allowedPaths; |
| } |
| |
| /** |
| * Returns SourceRestriction which is used to communicate restrictions about |
| * sources to chrome.fileManagerPrivate.getRecentFiles API. |
| * @returns {chrome.fileManagerPrivate.SourceRestriction} |
| */ |
| getSourceRestriction_() { |
| const allowedPaths = this.getAllowedPaths_(); |
| if (allowedPaths == AllowedPaths.NATIVE_PATH) { |
| return chrome.fileManagerPrivate.SourceRestriction.NATIVE_SOURCE; |
| } |
| if (allowedPaths == AllowedPaths.NATIVE_OR_DRIVE_PATH) { |
| return chrome.fileManagerPrivate.SourceRestriction.NATIVE_OR_DRIVE_SOURCE; |
| } |
| return chrome.fileManagerPrivate.SourceRestriction.ANY_SOURCE; |
| } |
| |
| /** |
| * @return {FileSelection} Selection object. |
| */ |
| getSelection() { |
| return this.selectionHandler_.selection; |
| } |
| |
| /** |
| * @return {cr.ui.ArrayDataModel} File list. |
| */ |
| getFileList() { |
| return this.directoryModel_.getFileList(); |
| } |
| |
| /** |
| * @return {!cr.ui.List} Current list object. |
| */ |
| getCurrentList() { |
| return this.ui.listContainer.currentList; |
| } |
| |
| /** |
| * Refreshes Drive prefs when they change. If Drive has been enabled or |
| * disabled, add or remove, respectively, the fake Drive item, creating it if |
| * necessary. |
| */ |
| onPreferencesChanged_() { |
| chrome.fileManagerPrivate.getPreferences( |
| (/** chrome.fileManagerPrivate.Preferences|undefined */ prefs) => { |
| if (chrome.runtime.lastError || |
| this.driveEnabled_ === prefs.driveEnabled) { |
| return; |
| } |
| this.driveEnabled_ = prefs.driveEnabled; |
| if (prefs.driveEnabled) { |
| if (!this.fakeDriveItem_) { |
| this.fakeDriveItem_ = new NavigationModelFakeItem( |
| str('DRIVE_DIRECTORY_LABEL'), NavigationModelItemType.DRIVE, |
| new FakeEntry( |
| str('DRIVE_DIRECTORY_LABEL'), |
| VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT)); |
| } |
| this.directoryTree.dataModel.fakeDriveItem = this.fakeDriveItem_; |
| } else { |
| this.directoryTree.dataModel.fakeDriveItem = null; |
| // The fake Drive item is being hidden so navigate away if it's the |
| // current directory. |
| if (this.directoryModel_.getCurrentDirEntry() === |
| this.fakeDriveItem_.entry) { |
| this.volumeManager_.getDefaultDisplayRoot((displayRoot) => { |
| if (this.directoryModel_.getCurrentDirEntry() === |
| this.fakeDriveItem_.entry && |
| displayRoot) { |
| this.directoryModel_.changeDirectoryEntry(displayRoot); |
| } |
| }); |
| } |
| } |
| this.directoryTree.redraw(false); |
| }); |
| } |
| |
| /** |
| * Shows a toast for ARC storage when needed. |
| * @param {VolumeInfo} volumeInfo Volume information currently selected. |
| */ |
| showArcStorageToast_(volumeInfo) { |
| if (!volumeInfo || |
| volumeInfo.volumeType !== VolumeManagerCommon.VolumeType.REMOVABLE) { |
| // The toast is for removable volumes. |
| return; |
| } |
| chrome.fileManagerPrivate.getPreferences(pref => { |
| if (!pref.arcEnabled || pref.arcRemovableMediaAccessEnabled) { |
| // We don't show the toast when ARC is disabled or the setting has |
| // already been enabled. |
| return; |
| } |
| chrome.fileManagerPrivate.setArcStorageToastShownFlag(originalFlag => { |
| if (originalFlag) { |
| // We show the toast only once. |
| return; |
| } |
| this.ui.toast.show(str('FILE_BROWSER_PLAY_STORE_ACCESS_LABEL'), { |
| text: str('FILE_BROWSER_OPEN_PLAY_STORE_SETTINGS_LABEL'), |
| callback: () => { |
| chrome.fileManagerPrivate.openSettingsSubpage( |
| 'storage/externalStoragePreferences'); |
| } |
| }); |
| }); |
| }); |
| } |
| } |