Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1372)

Unified Diff: ui/file_manager/file_manager/foreground/js/file_manager.js

Issue 645853013: Remove some platform specific stuff from views. (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/file_manager/file_manager/foreground/js/file_manager.js
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager.js b/ui/file_manager/file_manager/foreground/js/file_manager.js
deleted file mode 100644
index 4344c58cf4943d463b89628313a186afb675d4aa..0000000000000000000000000000000000000000
--- a/ui/file_manager/file_manager/foreground/js/file_manager.js
+++ /dev/null
@@ -1,3939 +0,0 @@
-// 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.
-
-'use strict';
-
-/**
- * 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
- */
-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;
-
- // --------------------------------------------------------------------------
- // Parameters determining the type of file manager.
-
- /**
- * Dialog type of this window.
- * @type {DialogType}
- */
- this.dialogType = DialogType.FULL_PAGE;
-
- /**
- * Current list type.
- * @type {FileManager.ListType}
- * @private
- */
- this.listType_ = null;
-
- /**
- * List of acceptable file types for open dialog.
- * @type {Array.<Object>}
- * @private
- */
- this.fileTypes_ = [];
-
- /**
- * Startup parameters for this application.
- * @type {Object}
- * @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;
-
- /**
- * Preview panel.
- * @type {PreviewPanel}
- * @private
- */
- this.previewPanel_ = null;
-
- /**
- * Progress center panel.
- * @type {ProgressCenterPanel}
- * @private
- */
- this.progressCenterPanel_ = null;
-
- /**
- * Directory tree.
- * @type {DirectoryTree}
- * @private
- */
- this.directoryTree_ = null;
-
- /**
- * Controller for search UI.
- * @type {SearchController}
- * @private
- */
- this.searchController_ = 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 files.
- * @type {HTMLMenuElement}
- * @private
- */
- this.fileContextMenu_ = null;
-
- /**
- * Context menu for volumes or shortcuts displayed on left pane.
- * @type {HTMLMenuElement}
- * @private
- */
- this.rootsContextMenu_ = null;
-
- /**
- * Context menu for directory tree items.
- * @type {HTMLMenuElement}
- * @private
- */
- this.directoryTreeContextMenu_ = null;
-
- /**
- * Context menu for texts.
- * @type {HTMLMenuElement}
- * @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 default action.
- * @type {HTMLMenuItemElement}
- * @private
- */
- this.defaultActionMenuItem_ = null;
-
- /**
- * The button to open gear menu.
- * @type {HTMLButtonElement}
- * @private
- */
- this.gearButton_ = null;
-
- /**
- * The OK button.
- * @type {HTMLButtonElement}
- * @private
- */
- this.okButton_ = null;
-
- /**
- * The cancel button.
- * @type {HTMLButtonElement}
- * @private
- */
- this.cancelButton_ = null;
-
- /**
- * The combo button to specify the task.
- * @type {HTMLButtonElement}
- * @private
- */
- this.taskItems_ = null;
-
- /**
- * The input element to rename entry.
- * @type {HTMLInputElement}
- * @private
- */
- this.renameInput_ = null;
-
- /**
- * The input element to specify file name.
- * @type {HTMLInputElement}
- * @private
- */
- this.filenameInput_ = null;
-
- /**
- * The file table.
- * @type {FileTable}
- * @private
- */
- this.table_ = null;
-
- /**
- * The file grid.
- * @type {FileGrid}
- * @private
- */
- this.grid_ = null;
-
- /**
- * Current file list.
- * @type {cr.ui.List}
- * @private
- */
- this.currentList_ = null;
-
- /**
- * Spinner on file list which is shown while loading.
- * @type {HTMLDivElement}
- * @private
- */
- this.spinner_ = null;
-
- /**
- * The container element of the dialog.
- * @type {HTMLDivElement}
- * @private
- */
- this.dialogContainer_ = null;
-
- /**
- * The container element of the file list.
- * @type {HTMLDivElement}
- * @private
- */
- this.listContainer_ = null;
-
- /**
- * The file type selector.
- * @type {HTMLSelectElement}
- * @private
- */
- this.fileTypeSelector_ = 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;
-
- /**
- * Bound function for onCancel_.
- * @type {?function(this:FileManager, Event)}
- * @private
- */
- this.onCancelBound_ = null;
-
- // --------------------------------------------------------------------------
- // Scan state.
-
- /**
- * Whether a scan is in progress.
- * @type {boolean}
- * @private
- */
- this.scanInProgress_ = false;
-
- /**
- * Whether a scan is updated at least once. If true, spinner should disappear.
- * @type {boolean}
- * @private
- */
- this.scanUpdatedAtLeastOnceOrCompleted_ = false;
-
- /**
- * Timer ID to delay UI refresh after a scan is completed.
- * @type {number}
- * @private
- */
- this.scanCompletedTimer_ = 0;
-
- /**
- * Timer ID to delay UI refresh after a scan is updated.
- * @type {number}
- * @private
- */
- this.scanUpdatedTimer_ = 0;
-
- /**
- * Timer ID to delay showing spinner after a scan starts.
- * @type {number}
- * @private
- */
- this.showSpinnerTimeout_ = 0;
-
- // --------------------------------------------------------------------------
- // Search states.
-
- /**
- * The last search query.
- * @type {string}
- * @private
- */
- this.lastSearchQuery_ = '';
-
- /**
- * The last auto-complete query.
- * @type {string}
- * @private
- */
- this.lastAutocompleteQuery_ = '';
-
- /**
- * Whether auto-complete suggestion is busy to respond previous request.
- * @type {boolean}
- * @private
- */
- this.autocompleteSuggestionsBusy_ = false;
-
- /**
- * State of text-search, which is triggerd by keyboard input on file list.
- * @type {Object}
- * @private
- */
- this.textSearchState_ = {text: '', date: new Date()};
-
- // --------------------------------------------------------------------------
- // 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_ = '';
-
- /**
- * Data model which is used as a placefolder in inactive file list.
- * @type {cr.ui.ArrayDataModel}
- * @private
- */
- this.emptyDataModel_ = null;
-
- /**
- * Selection model which is used as a placefolder in inactive file list.
- * @type {cr.ui.ListSelectionModel}
- * @private
- */
- this.emptySelectionModel_ = null;
-
- // Object.seal() has big performance/memory overhead for now, so we use
- // Object.preventExtensions() here. crbug.com/412239.
- Object.preventExtensions(this);
-}
-
-FileManager.prototype = {
- __proto__: cr.EventTarget.prototype,
- get directoryModel() {
- return this.directoryModel_;
- },
- get directoryTree() {
- return this.directoryTree_;
- },
- get document() {
- return this.document_;
- },
- get fileTransferController() {
- return this.fileTransferController_;
- },
- get fileOperationManager() {
- return this.fileOperationManager_;
- },
- get backgroundPage() {
- return this.backgroundPage_;
- },
- get volumeManager() {
- return this.volumeManager_;
- },
- 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');
- }
- };
-
- /**
- * @enum {string}
- * @const
- */
- FileManager.ListType = {
- DETAIL: 'detail',
- THUMBNAIL: 'thumb'
- };
-
- 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.table_.startBatchUpdates();
- this.grid_.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.currentList_.startBatchUpdates();
- // Remember the list which was used when updating files started, so
- // endBatchUpdates() is called on the same list.
- listBeingUpdated = self.currentList_;
- });
- dm.addEventListener('end-update-files', function() {
- self.restoreItemBeingRenamed_();
- listBeingUpdated.endBatchUpdates();
- listBeingUpdated = null;
- });
-
- dm.addEventListener('scan-started', this.onScanStarted_.bind(this));
- dm.addEventListener('scan-completed', this.onScanCompleted_.bind(this));
- dm.addEventListener('scan-failed', this.onScanCancelled_.bind(this));
- dm.addEventListener('scan-cancelled', this.onScanCancelled_.bind(this));
- dm.addEventListener('scan-updated', this.onScanUpdated_.bind(this));
- dm.addEventListener('rescan-completed',
- this.onRescanCompleted_.bind(this));
-
- this.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.initContextMenus_();
- this.initCommands_();
-
- this.updateFileTypeFilter_();
-
- this.selectionHandler_.onFileSelectionChanged();
-
- this.table_.endBatchUpdates();
- this.grid_.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.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.table_.list);
- controller.attachFileListDropTarget(this.table_.list);
- controller.attachDragSource(this.grid_);
- controller.attachFileListDropTarget(this.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() {
- this.fileContextMenu_ = this.dialogDom_.querySelector('#file-context-menu');
- cr.ui.Menu.decorate(this.fileContextMenu_);
-
- cr.ui.contextMenuHandler.setContextMenu(this.grid_, this.fileContextMenu_);
- cr.ui.contextMenuHandler.setContextMenu(this.table_.querySelector('.list'),
- this.fileContextMenu_);
- cr.ui.contextMenuHandler.setContextMenu(
- this.document_.querySelector('.drive-welcome.page'),
- this.fileContextMenu_);
-
- this.rootsContextMenu_ =
- this.dialogDom_.querySelector('#roots-context-menu');
- cr.ui.Menu.decorate(this.rootsContextMenu_);
- this.directoryTree_.contextMenuForRootItems = this.rootsContextMenu_;
-
- this.directoryTreeContextMenu_ =
- this.dialogDom_.querySelector('#directory-tree-context-menu');
- cr.ui.Menu.decorate(this.directoryTreeContextMenu_);
- this.directoryTree_.contextMenuForSubitems = this.directoryTreeContextMenu_;
-
- this.textContextMenu_ =
- this.dialogDom_.querySelector('#text-context-menu');
- cr.ui.Menu.decorate(this.textContextMenu_);
-
- this.gearButton_ = this.dialogDom_.querySelector('#gear-button');
- this.gearButton_.addEventListener('menushow',
- this.onShowGearMenu_.bind(this));
-
- this.dialogDom_.querySelector('#gear-menu').menuItemSelector =
- 'menuitem, hr';
- cr.ui.decorate(this.gearButton_, cr.ui.MenuButton);
-
- 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() {
- 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.renameInput_,
- this.textContextMenu_);
- this.registerInputCommands_(this.renameInput_);
- this.document_.addEventListener('command',
- this.setNoHover_.bind(this, true));
- };
-
- /**
- * 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.
- this.ui_ = new FileManagerUI(this.dialogDom_, this.dialogType);
- this.fileTypeSelector_ = this.ui_.fileTypeSelector;
- this.okButton_ = this.ui_.okButton;
- this.cancelButton_ = this.ui_.cancelButton;
-
- // Show the window as soon as the UI pre-initialization is done.
- if (this.dialogType == DialogType.FULL_PAGE && !util.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) {
- this.initDialogs_();
- this.ui_.initAdditionalUI();
-
- this.dialogDom_.addEventListener('drop', function(e) {
- // Prevent opening an URL by dropping it onto the page.
- e.preventDefault();
- });
-
- this.dialogDom_.addEventListener('click',
- this.onExternalLinkClick_.bind(this));
- // Cache nodes we'll be manipulating.
- var dom = this.dialogDom_;
-
- this.filenameInput_ = dom.querySelector('#filename-input-box input');
- this.taskItems_ = dom.querySelector('#tasks');
-
- this.table_ = dom.querySelector('.detail-table');
- this.grid_ = dom.querySelector('.thumbnail-grid');
- this.spinner_ = dom.querySelector('#list-container > .spinner-layer');
- this.showSpinner_(true);
-
- var fullPage = this.dialogType == DialogType.FULL_PAGE;
- FileTable.decorate(
- this.table_, this.metadataCache_, this.volumeManager_, fullPage);
- FileGrid.decorate(this.grid_, this.metadataCache_, this.volumeManager_);
-
- this.ui_.locationLine = new LocationLine(
- dom.querySelector('#location-breadcrumbs'),
- dom.querySelector('#location-volume-icon'),
- this.metadataCache_,
- this.volumeManager_);
- this.ui_.locationLine.addEventListener(
- 'pathclick', this.onBreadcrumbClick_.bind(this));
-
- this.previewPanel_ = new PreviewPanel(
- dom.querySelector('.preview-panel'),
- DialogType.isOpenDialog(this.dialogType) ?
- PreviewPanel.VisibilityType.ALWAYS_VISIBLE :
- PreviewPanel.VisibilityType.AUTO,
- this.metadataCache_,
- this.volumeManager_);
- this.previewPanel_.addEventListener(
- PreviewPanel.Event.VISIBILITY_CHANGE,
- this.onPreviewPanelVisibilityChange_.bind(this));
- this.previewPanel_.initialize();
-
- // Initialize progress center panel.
- this.progressCenterPanel_ = new ProgressCenterPanel(
- dom.querySelector('#progress-center'));
- this.backgroundPage_.background.progressCenter.addPanel(
- this.progressCenterPanel_);
-
- this.document_.addEventListener('keydown', this.onKeyDown_.bind(this));
- this.document_.addEventListener('keyup', this.onKeyUp_.bind(this));
-
- this.renameInput_ = /** @type {HTMLInputElement} */
- (this.document_.createElement('input'));
- this.renameInput_.className = 'rename entry-name';
-
- this.renameInput_.addEventListener(
- 'keydown', this.onRenameInputKeyDown_.bind(this));
- this.renameInput_.addEventListener(
- 'blur', this.onRenameInputBlur_.bind(this));
-
- // TODO(hirono): Rename the handler after creating the DialogFooter class.
- this.filenameInput_.addEventListener(
- 'input', this.onFilenameInputInput_.bind(this));
- this.filenameInput_.addEventListener(
- 'keydown', this.onFilenameInputKeyDown_.bind(this));
- this.filenameInput_.addEventListener(
- 'focus', this.onFilenameInputFocus_.bind(this));
-
- this.listContainer_ = /** @type {!HTMLDivElement} */
- (this.dialogDom_.querySelector('#list-container'));
- this.listContainer_.addEventListener(
- 'keydown', this.onListKeyDown_.bind(this));
- this.listContainer_.addEventListener(
- 'keypress', this.onListKeyPress_.bind(this));
- this.listContainer_.addEventListener(
- 'mousemove', this.onListMouseMove_.bind(this));
-
- this.okButton_.addEventListener('click', this.onOk_.bind(this));
- this.onCancelBound_ = this.onCancel_.bind(this);
- this.cancelButton_.addEventListener('click', this.onCancelBound_);
-
- this.decorateSplitter(
- this.dialogDom_.querySelector('#navigation-list-splitter'));
-
- this.dialogContainer_ = /** @type {!HTMLDivElement} */
- (this.dialogDom_.querySelector('.dialog-container'));
-
- this.syncButton = /** @type {!HTMLElement} */
- (this.dialogDom_.querySelector('#gear-menu-drive-sync-settings'));
- this.hostedButton = /** @type {!HTMLElement} */
- (this.dialogDom_.querySelector('#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.defaultActionMenuItem_ = /** @type {HTMLElement} */
- (this.dialogDom_.querySelector('#default-action'));
-
- this.openWithCommand_ = /** @type {cr.ui.Command} */
- (this.dialogDom_.querySelector('#open-with'));
-
- this.defaultActionMenuItem_.addEventListener('activate',
- this.dispatchSelectionAction_.bind(this));
-
- this.initFileTypeFilter_();
-
- util.addIsFocusedMethod();
-
- // Populate the static localized strings.
- i18nTemplate.process(this.document_, loadTimeData);
-
- // Arrange the file list.
- this.table_.normalizeColumns();
- this.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() {
- // Always sharing the data model between the detail/thumb views confuses
- // them. Instead we maintain this bogus data model, and hook it up to the
- // view that is not in use.
- this.emptyDataModel_ = new cr.ui.ArrayDataModel([]);
- this.emptySelectionModel_ = new cr.ui.ListSelectionModel();
-
- var singleSelection =
- this.dialogType == DialogType.SELECT_OPEN_FILE ||
- this.dialogType == DialogType.SELECT_FOLDER ||
- this.dialogType == DialogType.SELECT_UPLOAD_FOLDER ||
- this.dialogType == DialogType.SELECT_SAVEAS_FILE;
-
- 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_));
-
- this.initList_(this.grid_);
- this.initList_(this.table_.list);
-
- var fileListFocusBound = this.onFileListFocus_.bind(this);
- this.table_.list.addEventListener('focus', fileListFocusBound);
- this.grid_.addEventListener('focus', fileListFocusBound);
-
- var draggingBound = this.onDragging_.bind(this);
- var dragEndBound = this.onDragEnd_.bind(this);
-
- // Listen to drag events to hide preview panel while user is dragging files.
- // Files.app prevents default actions in 'dragstart' in some situations,
- // so we listen to 'drag' to know the list is actually being dragged.
- this.table_.list.addEventListener('drag', draggingBound);
- this.grid_.addEventListener('drag', draggingBound);
- this.table_.list.addEventListener('dragend', dragEndBound);
- this.grid_.addEventListener('dragend', dragEndBound);
-
- // Listen to dragselection events to hide preview panel while the user is
- // selecting files by drag operation.
- this.table_.list.addEventListener('dragselectionstart', draggingBound);
- this.grid_.addEventListener('dragselectionstart', draggingBound);
- this.table_.list.addEventListener('dragselectionend', dragEndBound);
- this.grid_.addEventListener('dragselectionend', dragEndBound);
-
- // TODO(mtomasz, yoshiki): Create navigation list earlier, and here just
- // attach the directory model.
- this.initDirectoryTree_();
-
- this.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.table_.columnModel;
- for (var i = 0; i < cm.totalSize; i++) {
- if (this.viewOptions_.columns[i] > 0)
- cm.setWidth(i, this.viewOptions_.columns[i]);
- }
- }
- this.setListType(this.viewOptions_.listType || FileManager.ListType.DETAIL);
-
- this.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: 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.onOk_();
- }
- }
- }.bind(this)
- });
-
- // 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.listType_
- };
- var cm = this.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.filenameInput_;
- else
- targetElement = this.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);
- }
- };
-
- /**
- * Index of selected item in the typeList of the dialog params.
- *
- * @return {number} 1-based index of selected type or 0 if no type selected.
- * @private
- */
- FileManager.prototype.getSelectedFilterIndex_ = function() {
- var index = Number(this.fileTypeSelector_.selectedIndex);
- if (index < 0) // Nothing selected.
- return 0;
- if (this.params_.includeAllFiles) // Already 1-based.
- return index;
- return index + 1; // Convert to 1-based;
- };
-
- FileManager.prototype.setListType = function(type) {
- if (type && type == this.listType_)
- return;
-
- this.table_.list.startBatchUpdates();
- this.grid_.startBatchUpdates();
-
- // TODO(dzvorygin): style.display and dataModel setting order shouldn't
- // cause any UI bugs. Currently, the only right way is first to set display
- // style and only then set dataModel.
-
- if (type == FileManager.ListType.DETAIL) {
- this.table_.dataModel = this.directoryModel_.getFileList();
- this.table_.selectionModel = this.directoryModel_.getFileListSelection();
- this.table_.hidden = false;
- this.grid_.hidden = true;
- this.grid_.selectionModel = this.emptySelectionModel_;
- this.grid_.dataModel = this.emptyDataModel_;
- this.table_.hidden = false;
- /** @type {cr.ui.List} */
- this.currentList_ = this.table_.list;
- this.ui_.toggleViewButton.classList.remove('table');
- this.ui_.toggleViewButton.classList.add('grid');
- } else if (type == FileManager.ListType.THUMBNAIL) {
- this.grid_.dataModel = this.directoryModel_.getFileList();
- this.grid_.selectionModel = this.directoryModel_.getFileListSelection();
- this.grid_.hidden = false;
- this.table_.hidden = true;
- this.table_.selectionModel = this.emptySelectionModel_;
- this.table_.dataModel = this.emptyDataModel_;
- this.grid_.hidden = false;
- /** @type {cr.ui.List} */
- this.currentList_ = this.grid_;
- this.ui_.toggleViewButton.classList.remove('grid');
- this.ui_.toggleViewButton.classList.add('table');
- } else {
- throw new Error('Unknown list type: ' + type);
- }
-
- this.listType_ = type;
- this.updateStartupPrefs_();
- this.onResize_();
-
- this.table_.list.endBatchUpdates();
- this.grid_.endBatchUpdates();
- };
-
- /**
- * Initialize the file list table or grid.
- *
- * @param {cr.ui.List} list The list.
- * @private
- */
- FileManager.prototype.initList_ = function(list) {
- // Overriding the default role 'list' to 'listbox' for better accessibility
- // on ChromeOS.
- list.setAttribute('role', 'listbox');
- list.addEventListener('click', this.onDetailClick_.bind(this));
- list.id = 'file-list';
- };
-
- /**
- * @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]);
- }
- };
-
- /**
- * Fills the file type list or hides it.
- * @private
- */
- FileManager.prototype.initFileTypeFilter_ = function() {
- if (this.params_.includeAllFiles) {
- var option = this.document_.createElement('option');
- option.innerText = str('ALL_FILES_FILTER');
- this.fileTypeSelector_.appendChild(option);
- option.value = 0;
- }
-
- for (var i = 0; i !== this.fileTypes_.length; i++) {
- var fileType = this.fileTypes_[i];
- var option = this.document_.createElement('option');
- var description = fileType.description;
- if (!description) {
- // See if all the extensions in the group have the same description.
- for (var j = 0; j !== fileType.extensions.length; j++) {
- var currentDescription = FileType.typeToString(
- FileType.getTypeForName('.' + fileType.extensions[j]));
- if (!description) // Set the first time.
- description = currentDescription;
- else if (description != currentDescription) {
- // No single description, fall through to the extension list.
- description = null;
- break;
- }
- }
-
- if (!description)
- // Convert ['jpg', 'png'] to '*.jpg, *.png'.
- description = fileType.extensions.map(function(s) {
- return '*.' + s;
- }).join(', ');
- }
- option.innerText = description;
-
- option.value = i + 1;
-
- if (fileType.selected)
- option.selected = true;
-
- this.fileTypeSelector_.appendChild(option);
- }
-
- var options = this.fileTypeSelector_.querySelectorAll('option');
- if (options.length >= 2) {
- // There is in fact no choice, show the selector.
- this.fileTypeSelector_.hidden = false;
-
- this.fileTypeSelector_.addEventListener('change',
- this.updateFileTypeFilter_.bind(this));
- }
- };
-
- /**
- * Filters file according to the selected file type.
- * @private
- */
- FileManager.prototype.updateFileTypeFilter_ = function() {
- this.fileFilter_.removeFilter('fileType');
- var selectedIndex = this.getSelectedFilterIndex_();
- 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.filenameInput_.value;
- var newExt = this.fileTypes_[selectedIndex - 1].extensions[0];
- if (newExt && !regexp.test(current)) {
- var i = current.lastIndexOf('.');
- if (i >= 0) {
- this.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() {
- if (this.listType_ == FileManager.ListType.THUMBNAIL)
- this.grid_.relayout();
- else
- this.table_.relayout();
-
- // May not be available during initialization.
- if (this.directoryTree_)
- this.directoryTree_.relayout();
-
- this.ui_.locationLine.truncate();
- };
-
- /**
- * Handles local metadata changes in the currect directory.
- * @param {Event} event Change event.
- * @private
- */
- FileManager.prototype.onWatcherMetadataChanged_ = function(event) {
- this.updateMetadataInUI_(
- event.metadataType, event.entries, event.properties);
- };
-
- /**
- * Resize details and thumb views to fit the new window size.
- * @private
- */
- FileManager.prototype.onPreviewPanelVisibilityChange_ = function() {
- // This method may be called on initialization. Some object may not be
- // initialized.
-
- var panelHeight = this.previewPanel_.visible ?
- this.previewPanel_.height : 0;
- if (this.grid_)
- this.grid_.setBottomMarginForPanel(panelHeight);
- if (this.table_)
- this.table_.setBottomMarginForPanel(panelHeight);
- };
-
- /**
- * Invoked while the drag is being performed on the list or the grid.
- * Note: this method may be called multiple times before onDragEnd_().
- * @private
- */
- FileManager.prototype.onDragging_ = function() {
- // On open file dialog, the preview panel is always shown.
- if (DialogType.isOpenDialog(this.dialogType))
- return;
- this.previewPanel_.visibilityType =
- PreviewPanel.VisibilityType.ALWAYS_HIDDEN;
- };
-
- /**
- * Invoked when the drag is ended on the list or the grid.
- * @private
- */
- FileManager.prototype.onDragEnd_ = function() {
- // On open file dialog, the preview panel is always shown.
- if (DialogType.isOpenDialog(this.dialogType))
- return;
- this.previewPanel_.visibilityType = PreviewPanel.VisibilityType.AUTO;
- };
-
- /**
- * 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.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.currentList_.items;
- var visibleEntries = [];
- for (var i = 0; i < visibleItems.length; i++) {
- var index = this.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.updateMetadataInUI_('filesystem', entries);
- }.bind(this));
-
- setTimeout(this.dailyUpdateModificationTime_.bind(this),
- MILLISECONDS_IN_DAY);
- };
-
- /**
- * @param {string} type Type of metadata changed.
- * @param {Array.<Entry>} entries Array of entries.
- * @private
- */
- FileManager.prototype.updateMetadataInUI_ = function(type, entries) {
- if (this.listType_ == FileManager.ListType.DETAIL)
- this.table_.updateListItemsMetadata(type, entries);
- else
- this.grid_.updateListItemsMetadata(type, entries);
- // TODO: update bottom panel thumbnails.
- };
-
- /**
- * Restore the item which is being renamed while refreshing the file list. Do
- * nothing if no item is being renamed or such an item disappeared.
- *
- * While refreshing file list it gets repopulated with new file entries.
- * There is not a big difference whether DOM items stay the same or not.
- * Except for the item that the user is renaming.
- *
- * @private
- */
- FileManager.prototype.restoreItemBeingRenamed_ = function() {
- if (!this.isRenamingInProgress())
- return;
-
- var dm = this.directoryModel_;
- var leadIndex = dm.getFileListSelection().leadIndex;
- if (leadIndex < 0)
- return;
-
- var leadEntry = /** @type {Entry} */ (dm.getFileList().item(leadIndex));
- if (!util.isSameEntry(this.renameInput_.currentEntry, leadEntry))
- return;
-
- var leadListItem = this.findListItemForNode_(this.renameInput_);
- if (this.currentList_ == this.table_.list) {
- this.table_.updateFileMetadata(leadListItem, leadEntry);
- }
- this.currentList_.restoreLeadItem(leadListItem);
- };
-
- /**
- * TODO(mtomasz): Move this to a utility function working on the root type.
- * @return {boolean} True if the current directory content is from Google
- * Drive.
- */
- FileManager.prototype.isOnDrive = function() {
- var rootType = this.directoryModel_.getCurrentRootType();
- return rootType === VolumeManagerCommon.RootType.DRIVE ||
- rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
- rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
- rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE;
- };
-
- /**
- * 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.onCancel_();
- };
-
- /**
- * 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));
- }
- };
-
- /**
- * 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();
- };
-
- FileManager.prototype.isRenamingInProgress = function() {
- return !!this.renameInput_.currentEntry;
- };
-
- /**
- * @private
- */
- FileManager.prototype.focusCurrentList_ = function() {
- if (this.listType_ == FileManager.ListType.DETAIL)
- this.table_.focus();
- else // this.listType_ == FileManager.ListType.THUMBNAIL)
- this.grid_.focus();
- };
-
- /**
- * Return DirectoryEntry of the current directory or null.
- * @return {DirectoryEntry} DirectoryEntry of the current directory. Returns
- * 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.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.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.isRenamingInProgress()) {
- // Don't pay attention to clicks during a rename.
- return;
- }
-
- var listItem = this.findListItemForEvent_(event);
- 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.okButton_.disabled) {
- this.onOk_();
- return true;
- }
- return false;
- };
-
- /**
- * 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.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');
- }
- };
-
- FileManager.prototype.findListItemForEvent_ = function(event) {
- return this.findListItemForNode_(event.touchedElement || event.srcElement);
- };
-
- FileManager.prototype.findListItemForNode_ = function(node) {
- var item = this.currentList_.getListItemAncestor(node);
- // TODO(serya): list should check that.
- return item && this.currentList_.isItem(item) ? item : null;
- };
-
- /**
- * Unload handler for the page.
- * @private
- */
- FileManager.prototype.onUnload_ = function() {
- if (this.directoryModel_)
- this.directoryModel_.dispose();
- if (this.volumeManager_)
- this.volumeManager_.dispose();
- 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();
- };
-
- FileManager.prototype.initiateRename = function() {
- var item = this.currentList_.ensureLeadItemExists();
- if (!item)
- return;
- var label = item.querySelector('.filename-label');
- var input = this.renameInput_;
- var currentEntry = this.currentList_.dataModel.item(item.listIndex);
-
- input.value = label.textContent;
- item.setAttribute('renaming', '');
- label.parentNode.appendChild(input);
- input.focus();
-
- var selectionEnd = input.value.lastIndexOf('.');
- if (currentEntry.isFile && selectionEnd !== -1) {
- input.selectionStart = 0;
- input.selectionEnd = selectionEnd;
- } else {
- input.select();
- }
-
- // This has to be set late in the process so we don't handle spurious
- // blur events.
- input.currentEntry = currentEntry;
- this.table_.startBatchUpdates();
- this.grid_.startBatchUpdates();
- };
-
- /**
- * @param {Event} event Key event.
- * @private
- */
- FileManager.prototype.onRenameInputKeyDown_ = function(event) {
- if (!this.isRenamingInProgress())
- return;
-
- // Do not move selection or lead item in list during rename.
- if (event.keyIdentifier == 'Up' || event.keyIdentifier == 'Down') {
- event.stopPropagation();
- }
-
- switch (util.getKeyModifiers(event) + event.keyIdentifier) {
- case 'U+001B': // Escape
- this.cancelRename_();
- event.preventDefault();
- break;
-
- case 'Enter':
- this.commitRename_();
- event.preventDefault();
- break;
- }
- };
-
- /**
- * @param {Event} event Blur event.
- * @private
- */
- FileManager.prototype.onRenameInputBlur_ = function(event) {
- if (this.isRenamingInProgress() && !this.renameInput_.validation_)
- this.commitRename_();
- };
-
- /**
- * @private
- */
- FileManager.prototype.commitRename_ = function() {
- var input = this.renameInput_;
- var entry = input.currentEntry;
- var newName = input.value;
-
- if (newName == entry.name) {
- this.cancelRename_();
- return;
- }
-
- var renamedItemElement = this.findListItemForNode_(this.renameInput_);
- var nameNode = renamedItemElement.querySelector('.filename-label');
-
- input.validation_ = true;
- var validationDone = function(valid) {
- input.validation_ = false;
-
- if (!valid) {
- // Cancel rename if it fails to restore focus from alert dialog.
- // Otherwise, just cancel the commitment and continue to rename.
- if (this.document_.activeElement != input)
- this.cancelRename_();
- return;
- }
-
- // Validation succeeded. Do renaming.
- this.renameInput_.currentEntry = null;
- if (this.renameInput_.parentNode)
- this.renameInput_.parentNode.removeChild(this.renameInput_);
- renamedItemElement.setAttribute('renaming', 'provisional');
-
- // Optimistically apply new name immediately to avoid flickering in
- // case of success.
- nameNode.textContent = newName;
-
- util.rename(
- entry, newName,
- function(newEntry) {
- this.directoryModel_.onRenameEntry(entry, newEntry);
- renamedItemElement.removeAttribute('renaming');
- this.table_.endBatchUpdates();
- this.grid_.endBatchUpdates();
- // Focus may go out of the list. Back it to the list.
- this.currentList_.focus();
- }.bind(this),
- function(error) {
- // Write back to the old name.
- nameNode.textContent = entry.name;
- renamedItemElement.removeAttribute('renaming');
- this.table_.endBatchUpdates();
- this.grid_.endBatchUpdates();
-
- // Show error dialog.
- var message;
- if (error.name == util.FileError.PATH_EXISTS_ERR ||
- error.name == util.FileError.TYPE_MISMATCH_ERR) {
- // Check the existing entry is file or not.
- // 1) If the entry is a file:
- // a) If we get PATH_EXISTS_ERR, a file exists.
- // b) If we get TYPE_MISMATCH_ERR, a directory exists.
- // 2) If the entry is a directory:
- // a) If we get PATH_EXISTS_ERR, a directory exists.
- // b) If we get TYPE_MISMATCH_ERR, a file exists.
- message = strf(
- (entry.isFile && error.name ==
- util.FileError.PATH_EXISTS_ERR) ||
- (!entry.isFile && error.name ==
- util.FileError.TYPE_MISMATCH_ERR) ?
- 'FILE_ALREADY_EXISTS' :
- 'DIRECTORY_ALREADY_EXISTS',
- newName);
- } else {
- message = strf('ERROR_RENAMING', entry.name,
- util.getFileErrorString(error.name));
- }
-
- this.alert.show(message);
- }.bind(this));
- };
-
- // TODO(haruki): this.getCurrentDirectoryEntry() might not return the actual
- // parent if the directory content is a search result. Fix it to do proper
- // validation.
- this.validateFileName_(this.getCurrentDirectoryEntry(),
- newName,
- validationDone.bind(this));
- };
-
- /**
- * @private
- */
- FileManager.prototype.cancelRename_ = function() {
- this.renameInput_.currentEntry = null;
-
- var item = this.findListItemForNode_(this.renameInput_);
- if (item)
- item.removeAttribute('renaming');
-
- var parent = this.renameInput_.parentNode;
- if (parent)
- parent.removeChild(this.renameInput_);
-
- this.table_.endBatchUpdates();
- this.grid_.endBatchUpdates();
-
- // Focus may go out of the list. Back it to the list.
- this.currentList_.focus();
- };
-
- /**
- * @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.okButton_.click();
- };
-
- /**
- * @param {Event} event Focus event.
- * @private
- */
- FileManager.prototype.onFilenameInputFocus_ = function(event) {
- var input = this.filenameInput_;
-
- // On focus we want to select everything but the extension, but
- // Chrome will select-all after the focus event completes. We
- // schedule a timeout to alter the focus after that happens.
- setTimeout(function() {
- var selectionEnd = input.value.lastIndexOf('.');
- if (selectionEnd == -1) {
- input.select();
- } else {
- input.selectionStart = 0;
- input.selectionEnd = selectionEnd;
- }
- }, 0);
- };
-
- /**
- * @private
- */
- FileManager.prototype.onScanStarted_ = function() {
- if (this.scanInProgress_) {
- this.table_.list.endBatchUpdates();
- this.grid_.endBatchUpdates();
- }
-
- if (this.commandHandler)
- this.commandHandler.updateAvailability();
- this.table_.list.startBatchUpdates();
- this.grid_.startBatchUpdates();
- this.scanInProgress_ = true;
-
- this.scanUpdatedAtLeastOnceOrCompleted_ = false;
- if (this.scanCompletedTimer_) {
- clearTimeout(this.scanCompletedTimer_);
- this.scanCompletedTimer_ = 0;
- }
-
- if (this.scanUpdatedTimer_) {
- clearTimeout(this.scanUpdatedTimer_);
- this.scanUpdatedTimer_ = 0;
- }
-
- if (this.spinner_.hidden) {
- this.cancelSpinnerTimeout_();
- this.showSpinnerTimeout_ =
- setTimeout(this.showSpinner_.bind(this, true), 500);
- }
- };
-
- /**
- * @private
- */
- FileManager.prototype.onScanCompleted_ = function() {
- if (!this.scanInProgress_) {
- console.error('Scan-completed event recieved. But scan is not started.');
- return;
- }
-
- if (this.commandHandler)
- this.commandHandler.updateAvailability();
- this.hideSpinnerLater_();
-
- if (this.scanUpdatedTimer_) {
- clearTimeout(this.scanUpdatedTimer_);
- this.scanUpdatedTimer_ = 0;
- }
-
- // To avoid flickering postpone updating the ui by a small amount of time.
- // There is a high chance, that metadata will be received within 50 ms.
- this.scanCompletedTimer_ = setTimeout(function() {
- // Check if batch updates are already finished by onScanUpdated_().
- if (!this.scanUpdatedAtLeastOnceOrCompleted_) {
- this.scanUpdatedAtLeastOnceOrCompleted_ = true;
- }
-
- this.scanInProgress_ = false;
- this.table_.list.endBatchUpdates();
- this.grid_.endBatchUpdates();
- this.scanCompletedTimer_ = 0;
- }.bind(this), 50);
- };
-
- /**
- * @private
- */
- FileManager.prototype.onScanUpdated_ = function() {
- if (!this.scanInProgress_) {
- console.error('Scan-updated event recieved. But scan is not started.');
- return;
- }
-
- if (this.scanUpdatedTimer_ || this.scanCompletedTimer_)
- return;
-
- // Show contents incrementally by finishing batch updated, but only after
- // 200ms elapsed, to avoid flickering when it is not necessary.
- this.scanUpdatedTimer_ = setTimeout(function() {
- // We need to hide the spinner only once.
- if (!this.scanUpdatedAtLeastOnceOrCompleted_) {
- this.scanUpdatedAtLeastOnceOrCompleted_ = true;
- this.hideSpinnerLater_();
- }
-
- // Update the UI.
- if (this.scanInProgress_) {
- this.table_.list.endBatchUpdates();
- this.grid_.endBatchUpdates();
- this.table_.list.startBatchUpdates();
- this.grid_.startBatchUpdates();
- }
- this.scanUpdatedTimer_ = 0;
- }.bind(this), 200);
- };
-
- /**
- * @private
- */
- FileManager.prototype.onScanCancelled_ = function() {
- if (!this.scanInProgress_) {
- console.error('Scan-cancelled event recieved. But scan is not started.');
- return;
- }
-
- if (this.commandHandler)
- this.commandHandler.updateAvailability();
- this.hideSpinnerLater_();
- if (this.scanCompletedTimer_) {
- clearTimeout(this.scanCompletedTimer_);
- this.scanCompletedTimer_ = 0;
- }
- if (this.scanUpdatedTimer_) {
- clearTimeout(this.scanUpdatedTimer_);
- this.scanUpdatedTimer_ = 0;
- }
- // Finish unfinished batch updates.
- if (!this.scanUpdatedAtLeastOnceOrCompleted_) {
- this.scanUpdatedAtLeastOnceOrCompleted_ = true;
- }
-
- this.scanInProgress_ = false;
- this.table_.list.endBatchUpdates();
- this.grid_.endBatchUpdates();
- };
-
- /**
- * Handle the 'rescan-completed' from the DirectoryModel.
- * @private
- */
- FileManager.prototype.onRescanCompleted_ = function() {
- this.selectionHandler_.onFileSelectionChanged();
- };
-
- /**
- * @private
- */
- FileManager.prototype.cancelSpinnerTimeout_ = function() {
- if (this.showSpinnerTimeout_) {
- clearTimeout(this.showSpinnerTimeout_);
- this.showSpinnerTimeout_ = 0;
- }
- };
-
- /**
- * @private
- */
- FileManager.prototype.hideSpinnerLater_ = function() {
- this.cancelSpinnerTimeout_();
- this.showSpinner_(false);
- };
-
- /**
- * @param {boolean} on True to show, false to hide.
- * @private
- */
- FileManager.prototype.showSpinner_ = function(on) {
- if (on && this.directoryModel_ && this.directoryModel_.isScanning())
- this.spinner_.hidden = false;
-
- if (!on && (!this.directoryModel_ ||
- !this.directoryModel_.isScanning() ||
- this.directoryModel_.getFileList().length != 0)) {
- this.spinner_.hidden = true;
- }
- };
-
- 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.currentList_;
- var tryCreate = function() {
- };
-
- var onSuccess = function(entry) {
- metrics.recordUserAction('CreateNewFolder');
- list.selectedItem = entry;
-
- self.table_.list.endBatchUpdates();
- self.grid_.endBatchUpdates();
-
- self.initiateRename();
- };
-
- var onError = function(error) {
- self.table_.list.endBatchUpdates();
- self.grid_.endBatchUpdates();
-
- self.alert.show(strf('ERROR_CREATING_FOLDER', current(),
- util.getFileErrorString(error.name)));
- };
-
- var onAbort = function() {
- self.table_.list.endBatchUpdates();
- self.grid_.endBatchUpdates();
- };
-
- this.table_.list.startBatchUpdates();
- this.grid_.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.listType_ === FileManager.ListType.DETAIL)
- this.setListType(FileManager.ListType.THUMBNAIL);
- else
- this.setListType(FileManager.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.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.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) {
- if (event.srcElement.tagName == 'INPUT') {
- // Ignore keydown handler in the rename input box.
- return;
- }
-
- switch (util.getKeyModifiers(event) + event.keyCode) {
- case '8': // Backspace => Up one directory.
- 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 '13': // 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)) {
- event.preventDefault();
- this.onDirectoryAction_(selection.entries[0]);
- } else if (this.dispatchSelectionAction_()) {
- event.preventDefault();
- }
- break;
- }
-
- switch (event.keyIdentifier) {
- case 'Home':
- case 'End':
- case 'Up':
- case 'Down':
- case 'Left':
- case 'Right':
- // When navigating with keyboard we hide the distracting mouse hover
- // highlighting until the user moves the mouse again.
- this.setNoHover_(true);
- break;
- }
- };
-
- /**
- * Suppress/restore hover highlighting in the list container.
- * @param {boolean} on True to temporarity hide hover state.
- * @private
- */
- FileManager.prototype.setNoHover_ = function(on) {
- if (on) {
- this.listContainer_.classList.add('nohover');
- } else {
- this.listContainer_.classList.remove('nohover');
- }
- };
-
- /**
- * KeyPress event handler for the div#list-container element.
- * @param {Event} event Key event.
- * @private
- */
- FileManager.prototype.onListKeyPress_ = function(event) {
- if (event.srcElement.tagName == 'INPUT') {
- // Ignore keypress handler in the rename input box.
- return;
- }
-
- if (event.ctrlKey || event.metaKey || event.altKey)
- return;
-
- var now = new Date();
- var char = String.fromCharCode(event.charCode).toLowerCase();
- var text = now - this.textSearchState_.date > 1000 ? '' :
- this.textSearchState_.text;
- this.textSearchState_ = {text: text + char, date: now};
-
- this.doTextSearch_();
- };
-
- /**
- * Mousemove event handler for the div#list-container element.
- * @param {Event} event Mouse event.
- * @private
- */
- FileManager.prototype.onListMouseMove_ = function(event) {
- // The user grabbed the mouse, restore the hover highlighting.
- this.setNoHover_(false);
- };
-
- /**
- * Performs a 'text search' - selects a first list entry with name
- * starting with entered text (case-insensitive).
- * @private
- */
- FileManager.prototype.doTextSearch_ = function() {
- var text = this.textSearchState_.text;
- if (!text)
- return;
-
- var dm = this.directoryModel_.getFileList();
- for (var index = 0; index < dm.length; ++index) {
- var name = dm.item(index).name;
- if (name.substring(0, text.length).toLowerCase() == text) {
- this.currentList_.selectionModel.selectedIndexes = [index];
- return;
- }
- }
-
- this.textSearchState_.text = '';
- };
-
- /**
- * Handle a click of the cancel button. Closes the window.
- * TODO(jamescook): Make unload handler work automatically, crbug.com/104811
- *
- * @private
- */
- FileManager.prototype.onCancel_ = function() {
- chrome.fileManagerPrivate.cancelDialog();
- window.close();
- };
-
- /**
- * Tries to close this modal dialog with some files selected.
- * Performs preprocessing if needed (e.g. for Drive).
- * @param {Object} selection Contains urls, filterIndex and multiple fields.
- * @private
- */
- FileManager.prototype.selectFilesAndClose_ = function(selection) {
- var callSelectFilesApiAndClose = function(callback) {
- var onFileSelected = function() {
- callback();
- if (!chrome.runtime.lastError) {
- // Call next method on a timeout, as it's unsafe to
- // close a window from a callback.
- setTimeout(window.close.bind(window), 0);
- }
- };
- if (selection.multiple) {
- chrome.fileManagerPrivate.selectFiles(
- selection.urls,
- this.params_.shouldReturnLocalPath,
- onFileSelected);
- } else {
- chrome.fileManagerPrivate.selectFile(
- selection.urls[0],
- selection.filterIndex,
- this.dialogType != DialogType.SELECT_SAVEAS_FILE /* for opening */,
- this.params_.shouldReturnLocalPath,
- onFileSelected);
- }
- }.bind(this);
-
- if (!this.isOnDrive() || this.dialogType == DialogType.SELECT_SAVEAS_FILE) {
- callSelectFilesApiAndClose(function() {});
- return;
- }
-
- var shade = this.document_.createElement('div');
- shade.className = 'shade';
- var footer = this.dialogDom_.querySelector('.button-panel');
- var progress = footer.querySelector('.progress-track');
- progress.style.width = '0%';
- var cancelled = false;
-
- var progressMap = {};
- var filesStarted = 0;
- var filesTotal = selection.urls.length;
- for (var index = 0; index < selection.urls.length; index++) {
- progressMap[selection.urls[index]] = -1;
- }
- var lastPercent = 0;
- var bytesTotal = 0;
- var bytesDone = 0;
-
- var onFileTransfersUpdated = function(status) {
- if (!(status.fileUrl in progressMap))
- return;
- if (status.total == -1)
- return;
-
- var old = progressMap[status.fileUrl];
- if (old == -1) {
- // -1 means we don't know file size yet.
- bytesTotal += status.total;
- filesStarted++;
- old = 0;
- }
- bytesDone += status.processed - old;
- progressMap[status.fileUrl] = status.processed;
-
- var percent = bytesTotal == 0 ? 0 : bytesDone / bytesTotal;
- // For files we don't have information about, assume the progress is zero.
- percent = percent * filesStarted / filesTotal * 100;
- // Do not decrease the progress. This may happen, if first downloaded
- // file is small, and the second one is large.
- lastPercent = Math.max(lastPercent, percent);
- progress.style.width = lastPercent + '%';
- }.bind(this);
-
- var setup = function() {
- this.document_.querySelector('.dialog-container').appendChild(shade);
- setTimeout(function() { shade.setAttribute('fadein', 'fadein'); }, 100);
- footer.setAttribute('progress', 'progress');
- this.cancelButton_.removeEventListener('click', this.onCancelBound_);
- this.cancelButton_.addEventListener('click', onCancel);
- chrome.fileManagerPrivate.onFileTransfersUpdated.addListener(
- onFileTransfersUpdated);
- }.bind(this);
-
- var cleanup = function() {
- shade.parentNode.removeChild(shade);
- footer.removeAttribute('progress');
- this.cancelButton_.removeEventListener('click', onCancel);
- this.cancelButton_.addEventListener('click', this.onCancelBound_);
- chrome.fileManagerPrivate.onFileTransfersUpdated.removeListener(
- onFileTransfersUpdated);
- }.bind(this);
-
- var onCancel = function() {
- // According to API cancel may fail, but there is no proper UI to reflect
- // this. So, we just silently assume that everything is cancelled.
- chrome.fileManagerPrivate.cancelFileTransfers(
- selection.urls, function(response) {});
- }.bind(this);
-
- var onProperties = function(properties) {
- for (var i = 0; i < properties.length; i++) {
- if (!properties[i] || properties[i].present) {
- // For files already in GCache, we don't get any transfer updates.
- filesTotal--;
- }
- }
- callSelectFilesApiAndClose(cleanup);
- }.bind(this);
-
- setup();
-
- // TODO(mtomasz): Use Entry instead of URLs, if possible.
- util.URLsToEntries(selection.urls, function(entries) {
- this.metadataCache_.get(entries, 'external', onProperties);
- }.bind(this));
- };
-
- /**
- * Handle a click of the ok button.
- *
- * The ok button has different UI labels depending on the type of dialog, but
- * in code it's always referred to as 'ok'.
- *
- * @private
- */
- FileManager.prototype.onOk_ = function() {
- if (this.dialogType == DialogType.SELECT_SAVEAS_FILE) {
- // Save-as doesn't require a valid selection from the list, since
- // we're going to take the filename from the text input.
- var filename = this.filenameInput_.value;
- if (!filename)
- throw new Error('Missing filename!');
-
- var directory = this.getCurrentDirectoryEntry();
- this.validateFileName_(directory, filename, function(isValid) {
- if (!isValid)
- return;
-
- if (util.isFakeEntry(directory)) {
- // Can't save a file into a fake directory.
- return;
- }
-
- var selectFileAndClose = function() {
- // TODO(mtomasz): Clean this up by avoiding constructing a URL
- // via string concatenation.
- var currentDirUrl = directory.toURL();
- if (currentDirUrl.charAt(currentDirUrl.length - 1) != '/')
- currentDirUrl += '/';
- this.selectFilesAndClose_({
- urls: [currentDirUrl + encodeURIComponent(filename)],
- multiple: false,
- filterIndex: this.getSelectedFilterIndex_(filename)
- });
- }.bind(this);
-
- directory.getFile(
- filename, {create: false},
- function(entry) {
- // An existing file is found. Show confirmation dialog to
- // overwrite it. If the user select "OK" on the dialog, save it.
- this.confirm.show(strf('CONFIRM_OVERWRITE_FILE', filename),
- selectFileAndClose);
- }.bind(this),
- function(error) {
- if (error.name == util.FileError.NOT_FOUND_ERR) {
- // The file does not exist, so it should be ok to create a
- // new file.
- selectFileAndClose();
- return;
- }
- if (error.name == util.FileError.TYPE_MISMATCH_ERR) {
- // An directory is found.
- // Do not allow to overwrite directory.
- this.alert.show(strf('DIRECTORY_ALREADY_EXISTS', filename));
- return;
- }
-
- // Unexpected error.
- console.error('File save failed: ' + error.code);
- }.bind(this));
- }.bind(this));
- return;
- }
-
- var files = [];
- var selectedIndexes = this.currentList_.selectionModel.selectedIndexes;
-
- if (DialogType.isFolderDialog(this.dialogType) &&
- selectedIndexes.length == 0) {
- var url = this.getCurrentDirectoryEntry().toURL();
- var singleSelection = {
- urls: [url],
- multiple: false,
- filterIndex: this.getSelectedFilterIndex_()
- };
- this.selectFilesAndClose_(singleSelection);
- return;
- }
-
- // All other dialog types require at least one selected list item.
- // The logic to control whether or not the ok button is enabled should
- // prevent us from ever getting here, but we sanity check to be sure.
- if (!selectedIndexes.length)
- throw new Error('Nothing selected!');
-
- var dm = this.directoryModel_.getFileList();
- for (var i = 0; i < selectedIndexes.length; i++) {
- var entry = dm.item(selectedIndexes[i]);
- if (!entry) {
- console.error('Error locating selected file at index: ' + i);
- continue;
- }
-
- files.push(entry.toURL());
- }
-
- // Multi-file selection has no other restrictions.
- if (this.dialogType == DialogType.SELECT_OPEN_MULTI_FILE) {
- var multipleSelection = {
- urls: files,
- multiple: true
- };
- this.selectFilesAndClose_(multipleSelection);
- return;
- }
-
- // Everything else must have exactly one.
- if (files.length > 1)
- throw new Error('Too many files selected!');
-
- var selectedEntry = dm.item(selectedIndexes[0]);
-
- if (DialogType.isFolderDialog(this.dialogType)) {
- if (!selectedEntry.isDirectory)
- throw new Error('Selected entry is not a folder!');
- } else if (this.dialogType == DialogType.SELECT_OPEN_FILE) {
- if (!selectedEntry.isFile)
- throw new Error('Selected entry is not a file!');
- }
-
- var singleSelection = {
- urls: [files[0]],
- multiple: false,
- filterIndex: this.getSelectedFilterIndex_()
- };
- this.selectFilesAndClose_(singleSelection);
- };
-
- /**
- * 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 default action menu item to match passed taskItem (icon,
- * label and action).
- *
- * @param {Object} defaultItem - taskItem to match.
- * @param {boolean} isMultiple - if multiple tasks available.
- */
- FileManager.prototype.updateContextMenuActionItems = function(defaultItem,
- isMultiple) {
- if (defaultItem) {
- if (defaultItem.iconType) {
- this.defaultActionMenuItem_.style.backgroundImage = '';
- this.defaultActionMenuItem_.setAttribute('file-type-icon',
- defaultItem.iconType);
- } else if (defaultItem.iconUrl) {
- this.defaultActionMenuItem_.style.backgroundImage =
- 'url(' + defaultItem.iconUrl + ')';
- } else {
- this.defaultActionMenuItem_.style.backgroundImage = '';
- }
-
- this.defaultActionMenuItem_.label = defaultItem.title;
- this.defaultActionMenuItem_.disabled = !!defaultItem.disabled;
- this.defaultActionMenuItem_.taskId = defaultItem.taskId;
- }
-
- var defaultActionSeparator =
- this.dialogDom_.querySelector('#default-action-separator');
-
- this.openWithCommand_.canExecuteChange();
- this.openWithCommand_.setHidden(!(defaultItem && isMultiple));
- this.openWithCommand_.disabled =
- defaultItem ? !!defaultItem.disabled : false;
-
- this.defaultActionMenuItem_.hidden = !defaultItem;
- defaultActionSeparator.hidden = !defaultItem;
- };
-
- /**
- * @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.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));
- };
-})();

Powered by Google App Engine
This is Rietveld 408576698