diff options
13 files changed, 692 insertions, 140 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index fdfba12..839a0e5 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -12172,6 +12172,9 @@ Some features may be unavailable. Please check that the profile exists and you <message name="IDS_FILE_BROWSER_DRIVE_DIRECTORY_LABEL" desc="/drive directory label."> Google Drive </message> + <message name="IDS_FILE_BROWSER_DRIVE_MY_DRIVE_LABEL" desc="A label for the 'My Drive' collection of Google Drive."> + My Drive + </message> <message name="IDS_FILE_BROWSER_DRIVE_SHARED_WITH_ME_COLLECTION_LABEL" desc="A label for the 'shared with me' collection of Google Drive."> Shared with me </message> diff --git a/chrome/browser/chromeos/extensions/file_manager/file_browser_private_api.cc b/chrome/browser/chromeos/extensions/file_manager/file_browser_private_api.cc index f689691..2cf52f9 100644 --- a/chrome/browser/chromeos/extensions/file_manager/file_browser_private_api.cc +++ b/chrome/browser/chromeos/extensions/file_manager/file_browser_private_api.cc @@ -1855,6 +1855,7 @@ bool FileDialogStringsFunction::RunImpl() { SET_STRING("DOWNLOADS_DIRECTORY_LABEL", IDS_FILE_BROWSER_DOWNLOADS_DIRECTORY_LABEL); SET_STRING("DRIVE_DIRECTORY_LABEL", IDS_FILE_BROWSER_DRIVE_DIRECTORY_LABEL); + SET_STRING("DRIVE_MY_DRIVE_LABEL", IDS_FILE_BROWSER_DRIVE_MY_DRIVE_LABEL); SET_STRING("DRIVE_OFFLINE_COLLECTION_LABEL", IDS_FILE_BROWSER_DRIVE_OFFLINE_COLLECTION_LABEL); SET_STRING("DRIVE_SHARED_WITH_ME_COLLECTION_LABEL", diff --git a/chrome/browser/resources/file_manager/css/file_manager.css b/chrome/browser/resources/file_manager/css/file_manager.css index 62250d1..eaf8e0f 100644 --- a/chrome/browser/resources/file_manager/css/file_manager.css +++ b/chrome/browser/resources/file_manager/css/file_manager.css @@ -284,7 +284,7 @@ body[new-ui] .dialog-sidebar-footer { /* A vertical splitter between the roots list and the file list. It is actually a transparent area centered on the roots list right border.*/ -div.sidebar-splitter { +div.splitter { -webkit-box-flex: 0; cursor: col-resize; margin-left: -3px; @@ -294,6 +294,66 @@ div.sidebar-splitter { z-index: 100; } +body[new-ui] #volume-list { + -webkit-box-flex: 1; + -webkit-box-orient: vertical; + display: -webkit-box; +} + +body[new-ui] #volume-list > * { + background-color: rgb(240, 240, 240); + background-image: none; + border: none; + height: 40px; + margin: 0; + padding: 0 5px; +} + +body[new-ui] #volume-list > :hover { + background-color: rgba(0, 0, 0, 0.05); + border-color: rgba(0, 0, 0, 0.05); +} + +body[new-ui] #volume-list > .accepts, +body[new-ui] #volume-list > [lead][selected], +body[new-ui] #volume-list > [lead], +body[new-ui] #volume-list > [selected], +body[new-ui] #volume-list > [anchor] { + background-color: rgb(225, 225, 225); +} + +body[new-ui] #volume-list:focus > .accepts, +body[new-ui] #volume-list:focus > [lead][selected], +body[new-ui] #volume-list:focus > [lead], +body[new-ui] #volume-list:focus > [selected], +body[new-ui] #volume-list:focus > [anchor] { + background-color: rgb(77, 144, 254); + color: white; +} + +body[new-ui] #volume-list li.root-item { + -webkit-box-align: center; + display: -webkit-box; + line-height: 22px; /* To accomodate for icons. */ + padding-left: 11px; +} + +body[new-ui] #volume-list li.root-item > .root-label { + -webkit-box-flex: 1; + margin: 0 2px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +body[new-ui] #volume-list .volume-icon { + background-position: center 2px; + background-repeat: no-repeat; + height: 24px; + width: 24px; +} + + #directory-tree { bottom: 0; left: 0; @@ -304,6 +364,10 @@ div.sidebar-splitter { top: 0; } +body[new-ui] #directory-tree { + border-right: 1px solid rgb(212, 212, 212); +} + #directory-tree .tree-row { -webkit-border-radius: 0; background-image: none; @@ -316,6 +380,7 @@ div.sidebar-splitter { body[new-ui] #directory-tree .tree-row { cursor: pointer; + line-height: 29px; } /* For rows of subitems (non-top items) */ @@ -323,6 +388,10 @@ body[new-ui] #directory-tree .tree-row { line-height: 31px; } +body[new-ui] #directory-tree .tree-children .tree-row { + line-height: 29px; +} + #directory-tree .tree-row > .expand-icon { height: 37px; left: 3px; @@ -352,10 +421,6 @@ body[new-ui] #directory-tree .tree-row { background-color: rgba(0, 0, 0, 0.05); } -body[new-ui] #directory-tree .tree-row:hover { - background-color: transparent; -} - #directory-tree .tree-item.accepts > .tree-row, #directory-tree .tree-row[lead][selected], #directory-tree .tree-row[lead], @@ -502,6 +567,19 @@ body[maximized][type='full-page'] .dialog-header { position: relative; } +.main-panel { + -webkit-box-flex: 1; + display: -webkit-box; +} + +body[new-ui] .dialog-middlebar-contents { + display: -webkit-box; + max-width: 50%; + min-width: 45px; + position: relative; + width: 180px; +} + /* Container for the ok/cancel buttons. */ .dialog-footer { -webkit-box-align: center; diff --git a/chrome/browser/resources/file_manager/js/directory_model.js b/chrome/browser/resources/file_manager/js/directory_model.js index 52c1038..4ce915f 100644 --- a/chrome/browser/resources/file_manager/js/directory_model.js +++ b/chrome/browser/resources/file_manager/js/directory_model.js @@ -784,7 +784,8 @@ DirectoryModel.prototype.changeDirectory = function(path, opt_errorCallback) { }; /** - * Resolves absolute directory path. Handles Drive stub. + * Resolves absolute directory path. Handles Drive stub. If the drive is + * mounting, callbacks will be called after the mount is completed. * @param {string} path Path to the directory. * @param {function(DirectoryEntry} successCallback Success callback. * @param {function(FileError} errorCallback Error callback. @@ -792,7 +793,9 @@ DirectoryModel.prototype.changeDirectory = function(path, opt_errorCallback) { DirectoryModel.prototype.resolveDirectory = function(path, successCallback, errorCallback) { if (PathUtil.getRootType(path) == RootType.DRIVE) { - if (!this.isDriveMounted()) { + var driveStatus = this.volumeManager_.getDriveStatus(); + if (!this.isDriveMounted() && + driveStatus != VolumeManager.DriveStatus.MOUNTING) { if (path == DirectoryModel.fakeDriveEntry_.fullPath) successCallback(DirectoryModel.fakeDriveEntry_); else // Subdirectory. @@ -1059,19 +1062,26 @@ DirectoryModel.prototype.resolveRoots_ = function(callback) { drive: null, driveSpecialSearchRoots: null }; + if (util.platform.newUI()) { + groups = { + downloads: null, + archives: null, + removables: null, + drive: null + }; + } var self = this; metrics.startInterval('Load.Roots'); var done = function() { - for (var i in groups) + var roots = []; + for (var i in groups) { if (!groups[i]) return; + roots = roots.concat(groups[i]); + } - callback(groups.downloads. - concat(groups.drive). - concat(groups.driveSpecialSearchRoots). - concat(groups.archives). - concat(groups.removables)); + callback(roots); metrics.recordInterval('Load.Roots'); }; @@ -1105,17 +1115,16 @@ DirectoryModel.prototype.resolveRoots_ = function(callback) { append.bind(this, 'removables')); if (this.driveEnabled_) { - groups.driveSpecialSearchRoots = this.showSpecialSearchRoots_ ? - DirectoryModel.fakeDriveSpecialSearchEntries_ : []; - var fake = [DirectoryModel.fakeDriveEntry_]; - if (this.isDriveMounted()) { - readSingle(DirectoryModel.fakeDriveEntry_.fullPath, 'drive', fake); - } else { - groups.drive = fake; - done(); + if (!util.platform.newUI()) { + groups.driveSpecialSearchRoots = this.showSpecialSearchRoots_ ? + DirectoryModel.fakeDriveSpecialSearchEntries_ : []; } + // Use a fake instead to return a list as fast as possible. + groups.drive = [DirectoryModel.fakeDriveEntry_]; + done(); } else { - groups.driveSpecialSearchRoots = []; + if (!util.platform.newUI()) + groups.driveSpecialSearchRoots = []; groups.drive = []; done(); } @@ -1142,8 +1151,8 @@ DirectoryModel.prototype.updateRoots_ = function(opt_callback) { * @return {boolean} True if DRIVE is fully mounted. */ DirectoryModel.prototype.isDriveMounted = function() { - return this.volumeManager_.getDriveStatus() == - VolumeManager.DriveStatus.MOUNTED; + var driveStatus = this.volumeManager_.getDriveStatus(); + return driveStatus == VolumeManager.DriveStatus.MOUNTED; }; /** diff --git a/chrome/browser/resources/file_manager/js/file_manager.js b/chrome/browser/resources/file_manager/js/file_manager.js index 1e8c40e..715284e 100644 --- a/chrome/browser/resources/file_manager/js/file_manager.js +++ b/chrome/browser/resources/file_manager/js/file_manager.js @@ -342,7 +342,7 @@ DialogType.isModal = function(type) { metrics.startInterval('Load.FileSystem'); - var downcount = 3; + var downcount = 4; var viewOptions = {}; var done = function() { if (--downcount == 0) @@ -378,6 +378,7 @@ DialogType.isModal = function(type) { this.getPreferences_(function() { if (this.isDriveEnabled()) this.volumeManager_.mountDrive(function() {}, function() {}); + done(); }.bind(this)); }; @@ -543,7 +544,10 @@ DialogType.isModal = function(type) { this.dialogDom_.querySelector('#roots-context-menu'); cr.ui.Menu.decorate(this.rootsContextMenu_); - this.directoryTree_.setContextMenu(this.rootsContextMenu_); + if (util.platform.newUI()) + this.volumeList_.setContextMenu(this.rootsContextMenu_); + else + this.directoryTree_.setContextMenu(this.rootsContextMenu_); this.textContextMenu_ = this.dialogDom_.querySelector('#text-context-menu'); @@ -611,16 +615,24 @@ DialogType.isModal = function(type) { CommandUtil.registerCommand(doc, 'change-default-app', Commands.changeDefaultAppCommand, this); - CommandUtil.registerCommand(this.directoryTree_, 'unmount', - Commands.unmountCommand, this.directoryTree_, this); + if (!util.platform.newUI()) { + CommandUtil.registerCommand(this.directoryTree_, 'unmount', + Commands.unmountCommand, this.directoryTree_, this); + + CommandUtil.registerCommand(this.directoryTree_, 'import-photos', + Commands.importCommand, this.directoryTree_); + } else { + CommandUtil.registerCommand(this.volumeList_, 'unmount', + Commands.unmountCommand, this.volumeList_, this); + + CommandUtil.registerCommand(this.volumeList_, 'import-photos', + Commands.importCommand, this.volumeList_); + } CommandUtil.registerCommand(doc, 'format', Commands.formatCommand, this.directoryTree_, this, this.directoryModel_); - CommandUtil.registerCommand(this.directoryTree_, 'import-photos', - Commands.importCommand, this.directoryTree_); - CommandUtil.registerCommand(doc, 'delete', Commands.deleteFileCommand, this); @@ -786,8 +798,15 @@ DialogType.isModal = function(type) { this.onCancelBound_ = this.onCancel_.bind(this); this.cancelButton_.addEventListener('click', this.onCancelBound_); - this.decorateSplitter( - this.dialogDom_.querySelector('div.sidebar-splitter')); + if (util.platform.newUI()) { + this.decorateSplitter( + this.dialogDom_.querySelector('div#sidebar-splitter')); + this.decorateSplitter( + this.dialogDom_.querySelector('div#middlebar-splitter')); + } else { + this.decorateSplitter( + this.dialogDom_.querySelector('div.sidebar-splitter')); + } this.dialogContainer_ = this.dialogDom_.querySelector('.dialog-container'); @@ -1029,6 +1048,10 @@ DialogType.isModal = function(type) { FileManager.prototype.initSidebar_ = function() { this.directoryTree_ = this.dialogDom_.querySelector('#directory-tree'); DirectoryTree.decorate(this.directoryTree_, this.directoryModel_); + if (util.platform.newUI()) { + this.volumeList_ = this.dialogDom_.querySelector('#volume-list'); + VolumeList.decorate(this.volumeList_, this.directoryModel_); + } }; /** @@ -1839,7 +1862,8 @@ DialogType.isModal = function(type) { FileManager.prototype.onDriveConnectionChanged_ = function() { var connection = this.volumeManager_.getDriveConnectionState(); - this.dialogContainer_.setAttribute('connection', connection.type); + if (this.dialogContainer_) + this.dialogContainer_.setAttribute('connection', connection.type); }; /** @@ -2204,7 +2228,6 @@ DialogType.isModal = function(type) { this.closeOnUnmount_ = false; } - this.directoryTree_.selectPath(this.getCurrentDirectory()); this.updateUnformattedDriveStatus_(); this.updateTitle_(); this.updateGearMenu_(); diff --git a/chrome/browser/resources/file_manager/js/file_manager_commands.js b/chrome/browser/resources/file_manager/js/file_manager_commands.js index 88e9351..ef40f60 100644 --- a/chrome/browser/resources/file_manager/js/file_manager_commands.js +++ b/chrome/browser/resources/file_manager/js/file_manager_commands.js @@ -10,16 +10,24 @@ var CommandUtil = {}; * Extracts root on which command event was dispatched. * * @param {Event} event Command event for which to retrieve root to operate on. - * @param {DirectoryTree} directoryTree Directory tree to extract root node. + * @param {DirectoryTree|VolumeList} list Directory tree or volume list to + * extract root node. * @return {DirectoryEntry} Found root. */ -CommandUtil.getCommandRoot = function(event, directoryTree) { - var entry = directoryTree.selectedItem; - - if (entry && PathUtil.isRootPath(entry.fullPath)) - return entry; - else - return null; +CommandUtil.getCommandRoot = function(event, list) { + if (util.platform.newUI() && list instanceof VolumeList) { + var result = list.dataModel.item( + list.getIndexOfListItem(event.target)) || + list.selectedItem; + return result; + } else { + var entry = list.selectedItem; + + if (entry && PathUtil.isRootPath(entry.fullPath)) + return entry; + else + return null; + } }; /** @@ -29,7 +37,6 @@ CommandUtil.getCommandRoot = function(event, directoryTree) { */ CommandUtil.getCommandRootType = function(event, directoryTree) { var root = CommandUtil.getCommandRoot(event, directoryTree); - return root && PathUtil.getRootType(root.fullPath); }; diff --git a/chrome/browser/resources/file_manager/js/main_scripts.js b/chrome/browser/resources/file_manager/js/main_scripts.js index 4521379..06f0e0d 100644 --- a/chrome/browser/resources/file_manager/js/main_scripts.js +++ b/chrome/browser/resources/file_manager/js/main_scripts.js @@ -89,6 +89,7 @@ //<include src="file_type.js"/> //<include src="scrollbar.js"/> //<include src="sidebar.js"/> +//<include src="volume_list.js"/> //<include src="volume_manager.js"/> //<include src="media/media_util.js"/> //<include src="metadata/metadata_cache.js"/> diff --git a/chrome/browser/resources/file_manager/js/sidebar.js b/chrome/browser/resources/file_manager/js/sidebar.js index 2753b07..9904366 100644 --- a/chrome/browser/resources/file_manager/js/sidebar.js +++ b/chrome/browser/resources/file_manager/js/sidebar.js @@ -4,6 +4,11 @@ 'use strict'; +// TODO(yoshiki): rename this sidebar.js to directory_tree.js. + +//////////////////////////////////////////////////////////////////////////////// +// DirectoryTreeUtil + /** * Utility methods. They are intended for use only in this file. */ @@ -99,7 +104,7 @@ DirectoryTreeUtil.isDummyEntry = function(dirEntry) { DirectoryTreeUtil.searchAndSelectPath = function(items, path) { for (var i = 0; i < items.length; i++) { var item = items[i]; - if (PathUtil.isParentPath(item.fullPath, path)) { + if (PathUtil.isParentPath(item.entry.fullPath, path)) { item.selectPath(path); return true; } @@ -108,6 +113,123 @@ DirectoryTreeUtil.searchAndSelectPath = function(items, path) { }; /** + * Modifies a list of the directory entries to match the new UI sepc. + * + * TODO(yoshiki): remove this after the old UI is removed. + * + * @param {Array.<DirectoryEntry>} entries The list of entty. + * @return {Array.<DirectoryEntries>} Modified entries. + */ +DirectoryTreeUtil.addAndRemoveDriveSpecialDirs = function(entries) { + if (!util.platform.newUI()) { + console.error('This function should be used only in new ui.'); + return []; + } + var modifiedEntries = []; + for (var i in entries) { + // Removes '/drive/other'. + var entry = entries[i]; + if (entry.fullPath == + (RootDirectory.DRIVE + '/' + DriveSubRootDirectory.OTHER)) { + continue; + } + + // Changes the label of '/drive/root' to 'My Drive'. + if (entry.fullPath == DirectoryModel.fakeDriveEntry_.fullPath) { + entry.label = str('DRIVE_MY_DRIVE_LABEL'); + } + + modifiedEntries.push(entry); + } + + // Adds the special directories. + var specialDirs = [DirectoryModel.fakeDriveSharedWithMeEntry_, + DirectoryModel.fakeDriveRecentEntry_, + DirectoryModel.fakeDriveOfflineEntry_]; + for (var i in specialDirs) { + var dir = specialDirs[i]; + dir['label'] = PathUtil.getRootLabel(dir.fullPath); + modifiedEntries.push(dir); + } + return modifiedEntries; +}; + +/** + * Retrieves the file list with the latest information. + * + * @param {DirectoryTree|DirectoryItem} item Parent to be reloaded. + * @param {DirectoryModel} dm The directory model. + * @param {boolean} recursive True if the update is recursively. + * @param {function(Array.<Entry>)} successCallback Callback on success. + * @param {function()=} opt_errorCallback Callback on failure. + */ +DirectoryTreeUtil.updateSubDirectories = function( + item, dm, recursive, successCallback, opt_errorCallback) { + // Tries to retrieve new entry if the cached entry is dummy. + if (DirectoryTreeUtil.isDummyEntry(item.entry)) { + // Fake Drive root. + dm.resolveDirectory( + item.fullPath, + function(entry) { + item.dirEntry_ = entry; + + // If the retrieved entry is dummy again, returns with an error. + if (DirectoryTreeUtil.isDummyEntry(entry)) { + if (opt_errorCallback) + opt_errorCallback(); + return; + } + + DirectoryTreeUtil.updateSubDirectories( + item, dm, recursive, successCallback, opt_errorCallback); + }, + opt_errorCallback); + return; + } + + var reader = item.entry.createReader(); + var entries = []; + var readEntry = function() { + reader.readEntries(function(results) { + if (!results.length) { + if (item.entry.fullPath == RootDirectory.DRIVE) + successCallback( + DirectoryTreeUtil.addAndRemoveDriveSpecialDirs(entries)); + else + successCallback( + DirectoryTreeUtil.sortEntries(item.fileFilter_, entries)); + return; + } + + for (var i = 0; i < results.length; i++) { + var entry = results[i]; + if (entry.isDirectory) + entries.push(entry); + } + readEntry(); + }); + }; + readEntry(); +}; + +/** + * Sorts a list of entries. + * + * @param {FileFilter} fileFilter The file filter. + * @param {Array.<Entries>} entries Entries to be sorted. + * @return {Array.<Entries>} Sorted entries. + */ +DirectoryTreeUtil.sortEntries = function(fileFilter, entries) { + entries.sort(function(a, b) { + return (a.name.toLowerCase() > b.name.toLowerCase()) ? 1 : -1; + }); + return entries.filter(fileFilter.filter.bind(fileFilter)); +}; + +//////////////////////////////////////////////////////////////////////////////// +// DirectoryItem + +/** * A directory in the tree. Each element represents one directory. * * @param {DirectoryEntry} dirEntry DirectoryEntry of this item. @@ -166,8 +288,13 @@ DirectoryItem.prototype = { DirectoryItem.prototype.decorate = function( dirEntry, parentDirItem, directoryModel) { var path = dirEntry.fullPath; - var label = PathUtil.isRootPath(path) ? - PathUtil.getRootLabel(path) : dirEntry.name; + var label; + if (!util.platform.newUI()) { + label = PathUtil.isRootPath(path) ? + PathUtil.getRootLabel(path) : dirEntry.name; + } else { + label = dirEntry.label ? dirEntry.label : dirEntry.name; + } this.className = 'tree-item'; this.innerHTML = @@ -195,13 +322,22 @@ DirectoryItem.prototype.decorate = function( this.addEventListener('expand', this.onExpand_.bind(this), false); var volumeManager = VolumeManager.getInstance(); var icon = this.querySelector('.icon'); - if (PathUtil.isRootPath(path)) { - icon.classList.add('volume-icon'); - var iconType = PathUtil.getRootType(path); - icon.setAttribute('volume-type-icon', iconType); - - if (iconType == RootType.REMOVABLE) - icon.setAttribute('volume-subtype', volumeManager.getDeviceType(path)); + if (!util.platform.newUI()) { + if (PathUtil.isRootPath(path)) { + icon.classList.add('volume-icon'); + var iconType = PathUtil.getRootType(path); + icon.setAttribute('volume-type-icon', iconType); + + if (iconType == RootType.REMOVABLE) + icon.setAttribute('volume-subtype', volumeManager.getDeviceType(path)); + } + } else { + icon.classList.add('volume-icon'); + var iconType = PathUtil.getRootType(path); + if (iconType && PathUtil.isRootPath(path)) + icon.setAttribute('volume-type-icon', iconType); + else + icon.setAttribute('file-type-icon', 'folder'); } var eject = this.querySelector('.root-eject'); @@ -252,49 +388,16 @@ DirectoryItem.prototype.onExpand_ = function(e) { */ DirectoryItem.prototype.updateSubDirectories = function( recursive, opt_successCallback, opt_errorCallback) { - // Tries to retrieve new entry if the cached entry is dummy. - if (DirectoryTreeUtil.isDummyEntry(this.dirEntry_)) { - // Fake Drive root. - this.directoryModel_.resolveDirectory( - this.fullPath, - function(entry) { - this.dirEntry_ = entry; - - // If the retrieved entry is dummy again, returns with an error. - if (DirectoryTreeUtil.isDummyEntry(entry)) { - if (opt_errorCallback) - opt_errorCallback(); - return; - } - - this.updateSubDirectories( - recursive, opt_successCallback, opt_errorCallback); - }.bind(this), - opt_errorCallback); - return; - } - - var reader = this.dirEntry_.createReader(); - var entries = []; - - var readEntry = function() { - reader.readEntries(function(results) { - if (!results.length) { - this.entries_ = entries.sort(); + DirectoryTreeUtil.updateSubDirectories( + this, + this.directoryModel_, + recursive, + function(entries) { + this.entries_ = entries; this.redrawSubDirectoryList_(recursive); opt_successCallback && opt_successCallback(); - return; - } - - for (var i = 0; i < results.length; i++) { - var entry = results[i]; - if (entry.isDirectory) - entries.push(entry); - } - readEntry(); - }.bind(this)); - }.bind(this); - readEntry(); + }.bind(this), + opt_errorCallback); }; /** @@ -351,6 +454,9 @@ DirectoryItem.prototype.doAction = function() { this.directoryModel_.changeDirectory(this.fullPath); }; +//////////////////////////////////////////////////////////////////////////////// +// DirectoryTree + /** * Tree of directories on the sidebar. This element is also the root of items, * in other words, this is the parent of the top-level items. @@ -378,7 +484,17 @@ DirectoryTree.prototype = { /** * @param {boolean} value Not used. */ - set expanded(value) {} + set expanded(value) {}, + + /** + * The DirectoryEntry corresponding to this DirectoryItem. This may be + * a dummy DirectoryEntry. + * @type {DirectoryEntry|Object} + * @override + **/ + get entry() { + return this.dirEntry_; + } }; /** @@ -389,36 +505,53 @@ DirectoryTree.prototype.decorate = function(directoryModel) { cr.ui.Tree.prototype.decorate.call(this); this.directoryModel_ = directoryModel; + this.entries_ = []; this.fileFilter_ = this.directoryModel_.getFileFilter(); this.fileFilter_.addEventListener('changed', this.onFilterChanged_.bind(this)); + /** + * The path of the root directory. + * @type {string} + */ + this.fullPath = '/'; + this.dirEntry_ = null; + if (!util.platform.newUI()) { + this.rootsList_ = this.directoryModel_.getRootsList(); + this.rootsList_.addEventListener('change', + this.onRootsListChanged_.bind(this)); + this.rootsList_.addEventListener('permuted', + this.onRootsListChanged_.bind(this)); + } + + /** + * The path of the current directory. + * @type {string} + */ + this.currentPath_ = null; - this.rootsList_ = this.directoryModel_.getRootsList(); - this.rootsList_.addEventListener('change', - this.onRootsListChanged_.bind(this)); - this.rootsList_.addEventListener('permuted', - this.onRootsListChanged_.bind(this)); - this.onRootsListChanged_(); + this.directoryModel_.addEventListener('directory-changed', + this.onCurrentDirectoryChanged_.bind(this)); // Add a handler for directory change. this.addEventListener('change', function() { - if (this.selectedItem) { + if (this.selectedItem && this.currentPath_ != this.selectedItem.fullPath) { + this.currentPath_ = this.selectedItem; this.selectedItem.doAction(); return; } - - // Fallback to the default directory. - this.directoryModel_.changeDirectory( - this.directoryModel_.getDefaultDirectory()); }.bind(this)); - this.privateOnDirectoryChangedBound_ = this.onDirectoryChanged_.bind(this); + this.privateOnDirectoryChangedBound_ = + this.onDirectoryContentChanged_.bind(this); chrome.fileBrowserPrivate.onDirectoryChanged.addListener( this.privateOnDirectoryChangedBound_); if (util.platform.newUI()) ScrollBar.createVertical(this.parentNode, this); + + if (!util.platform.newUI()) + this.onRootsListChanged_(); }; /** @@ -428,8 +561,10 @@ DirectoryTree.prototype.decorate = function(directoryModel) { * @param {cr.ui.Menu} menu Context menu. */ DirectoryTree.prototype.setContextMenu = function(menu) { - this.contextMenu_ = menu; + if (util.platform.newUI()) + return; + this.contextMenu_ = menu; for (var i = 0; i < this.rootsList_.length; i++) { var item = this.rootsList_.item(i); var type = PathUtil.getRootType(item.fullPath); @@ -446,10 +581,82 @@ DirectoryTree.prototype.setContextMenu = function(menu) { * @param {string} path Path to be selected. */ DirectoryTree.prototype.selectPath = function(path) { - if (this.selectedItem && path == this.selectedItem.fullPath) + if ((this.entry && this.entry.fullPath == path) || this.currentPath_ == path) return; + this.currentPath_ = path; + this.selectPathInternal_(path); +}; - DirectoryTreeUtil.searchAndSelectPath(this.items, path); +/** + * Select the item corresponding to the given path. This method is used + * internally. + * @param {string} path Path to be selected. + * @private + */ +DirectoryTree.prototype.selectPathInternal_ = function(path) { + var rootDirPath = PathUtil.getRootPath(path); + + if (PathUtil.isSpecialSearchRoot(rootDirPath) || + PathUtil.getRootType(rootDirPath) == RootType.DRIVE) { + rootDirPath = RootDirectory.DRIVE; + } + + if (this.fullPath != rootDirPath) { + this.fullPath = rootDirPath; + + // Clears the list + this.dirEntry_ = []; + this.entries_ = []; + this.redraw(false); + + this.directoryModel_.resolveDirectory( + rootDirPath, + function(entry) { + if (this.fullPath != rootDirPath) + return; + + this.dirEntry_ = entry; + this.selectPathInternal_(path); + }.bind(this), + function() {}); + } else { + if (this.selectedItem && path == this.selectedItem.fullPath) + return; + + if (DirectoryTreeUtil.searchAndSelectPath(this.items, path)) + return; + + this.selectedItem = null; + this.updateSubDirectories( + false /* recursive */, + function() { + if (!DirectoryTreeUtil.searchAndSelectPath( + this.items, this.currentPath_)) + this.selectedItem = null; + }.bind(this)); + } +}; + +/** + * Retrieves the latest subdirectories and update them on the tree. + * @param {boolean} recursive True if the update is recursively. + * @param {function()=} opt_successCallback Callback called on success. + */ +DirectoryTree.prototype.updateSubDirectories = function( + recursive, opt_successCallback) { + if (!this.currentPath_) + return; + + DirectoryTreeUtil.updateSubDirectories( + this, + this.directoryModel_, + recursive, + function(entries) { + this.entries_ = entries; + this.redraw(recursive); + if (opt_successCallback) + opt_successCallback(); + }.bind(this)); }; /** @@ -458,8 +665,8 @@ DirectoryTree.prototype.selectPath = function(path) { * @private */ DirectoryTree.prototype.onRootsListChanged_ = function() { - this.redraw(false /* recursive */); - this.selectPath(this.directoryModel_.getCurrentDirPath()); + if (!util.platform.newUI()) + this.redraw(false /* recursive */); }; /** @@ -468,12 +675,21 @@ DirectoryTree.prototype.onRootsListChanged_ = function() { * only root items are updated. */ DirectoryTree.prototype.redraw = function(recursive) { - var rootsList = this.rootsList_; - DirectoryTreeUtil.updateSubElementsFromList(this, - rootsList.item.bind(rootsList), - this.directoryModel_, - recursive); - this.setContextMenu(this.contextMenu_); + if (!util.platform.newUI()) { + var rootsList = this.rootsList_; + DirectoryTreeUtil.updateSubElementsFromList( + this, + rootsList.item.bind(rootsList), + this.directoryModel_, + recursive); + } else { + DirectoryTreeUtil.updateSubElementsFromList( + this, + function(i) { return this.entries_[i]; }.bind(this), + this.directoryModel_, + recursive); + this.setContextMenu(this.contextMenu_); + } }; /** @@ -489,7 +705,7 @@ DirectoryTree.prototype.onFilterChanged_ = function() { * @param {!UIEvent} event Event. * @private */ -DirectoryTree.prototype.onDirectoryChanged_ = function(event) { +DirectoryTree.prototype.onDirectoryContentChanged_ = function(event) { if (event.eventType == 'changed') { var path = util.extractFilePath(event.directoryUrl); DirectoryTreeUtil.updateChangedDirectoryItem(path, this); @@ -497,6 +713,15 @@ DirectoryTree.prototype.onDirectoryChanged_ = function(event) { }; /** + * Invoked when the current directory is changed. + * @param {!UIEvent} event Event. + * @private + */ +DirectoryTree.prototype.onCurrentDirectoryChanged_ = function(event) { + this.selectPath(event.newDirEntry.fullPath); +}; + +/** * Returns the path of the selected item. * @return {string} The current path. */ diff --git a/chrome/browser/resources/file_manager/js/test_util.js b/chrome/browser/resources/file_manager/js/test_util.js index aa1ca01..c3a4b46 100644 --- a/chrome/browser/resources/file_manager/js/test_util.js +++ b/chrome/browser/resources/file_manager/js/test_util.js @@ -113,7 +113,9 @@ test.util.waitForFileListChange = function( var notReadyRows = files.filter(function(row) { return row.filter(function(cell) { return cell == '...'; }).length; }); - if (notReadyRows.length === 0 && files.length !== lengthBefore) + if (notReadyRows.length === 0 && + files.length !== lengthBefore && + files.length !== 0) callback(files); else window.setTimeout(helper, 50); diff --git a/chrome/browser/resources/file_manager/js/volume_list.js b/chrome/browser/resources/file_manager/js/volume_list.js new file mode 100644 index 0000000..5910fdf --- /dev/null +++ b/chrome/browser/resources/file_manager/js/volume_list.js @@ -0,0 +1,196 @@ +// 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'; + +/** + * A volume list. + */ +function VolumeList() { +} + +/** + * VolumeList inherits cr.ui.List. + */ +VolumeList.prototype.__proto__ = cr.ui.List.prototype; + +/** + * @param {HTMLElement} el Element to be DirectoryItem. + * @param {DirectoryModel} directoryModel Current DirectoryModel. + */ +VolumeList.decorate = function(el, directoryModel) { + el.__proto__ = VolumeList.prototype; + el.decorate(directoryModel); +}; + +/** + * @param {DirectoryModel} directoryModel Current DirectoryModel. + */ +VolumeList.prototype.decorate = function(directoryModel) { + cr.ui.List.decorate(this); + this.__proto__ = VolumeList.prototype; + + this.directoryModel_ = directoryModel; + this.volumeManager_ = VolumeManager.getInstance(); + this.selectionModel = new cr.ui.ListSingleSelectionModel(); + + this.directoryModel_.addEventListener('directory-changed', + this.onCurrentDirectoryChanged_.bind(this)); + this.selectionModel.addEventListener( + 'change', this.onSelectionChange_.bind(this)); + this.selectionModel.addEventListener( + 'beforeChange', this.onBeforeSelectionChange_.bind(this)); + this.currentVolume_ = null; + + // Overriding default role 'list' set by cr.ui.List.decorate() to 'listbox' + // role for better accessibility on ChromeOS. + this.setAttribute('role', 'listbox'); + + var self = this; + this.itemConstructor = function(entry) { + return self.renderRoot_(entry); + }; + + //this.rootsList_.selectionModel = + // this.directoryModel_.getRootsListSelectionModel(); + this.dataModel = this.directoryModel_.getRootsList(); +}; + +/** + * Creates an element of a volume. This method is called from cr.ui.List + * internally. + * @param {DirectoryEntry} entry Entry of the directory to be rendered. + * @return {HTMLElement} Rendered element. + * @private + */ +VolumeList.prototype.renderRoot_ = function(entry) { + var path = entry.fullPath; + var li = cr.doc.createElement('li'); + li.className = 'root-item'; + li.setAttribute('role', 'option'); + var dm = this.directoryModel_; + var handleClick = function() { + if (li.selected && path !== dm.getCurrentDirPath()) { + this.directoryModel_.changeDirectory(path); + } + }.bind(this); + li.addEventListener('click', handleClick); + li.addEventListener(cr.ui.TouchHandler.EventType.TOUCH_START, handleClick); + + var rootType = PathUtil.getRootType(path); + + var iconDiv = cr.doc.createElement('div'); + iconDiv.className = 'volume-icon'; + iconDiv.setAttribute('volume-type-icon', rootType); + if (rootType === RootType.REMOVABLE) { + iconDiv.setAttribute('volume-subtype', + this.volumeManager_.getDeviceType(path)); + } + li.appendChild(iconDiv); + + var div = cr.doc.createElement('div'); + div.className = 'root-label'; + + div.textContent = PathUtil.getRootLabel(path); + li.appendChild(div); + + if (rootType === RootType.ARCHIVE || rootType === RootType.REMOVABLE) { + var eject = cr.doc.createElement('div'); + eject.className = 'root-eject'; + eject.addEventListener('click', function(event) { + event.stopPropagation(); + var unmountCommand = cr.doc.querySelector('command#unmount'); + // Let's make sure 'canExecute' state of the command is properly set for + // the root before executing it. + unmountCommand.canExecuteChange(li); + unmountCommand.execute(li); + }.bind(this)); + // Block other mouse handlers. + eject.addEventListener('mouseup', function(e) { e.stopPropagation() }); + eject.addEventListener('mousedown', function(e) { e.stopPropagation() }); + li.appendChild(eject); + } + + if (this.contextMenu_ && + rootType != RootType.DRIVE && rootType != RootType.DOWNLOADS) + cr.ui.contextMenuHandler.setContextMenu(li, this.contextMenu_); + + cr.defineProperty(li, 'lead', cr.PropertyKind.BOOL_ATTR); + cr.defineProperty(li, 'selected', cr.PropertyKind.BOOL_ATTR); + + // If the current directory is already set. + if (this.currentVolume_ == path) { + setTimeout(function() { + this.selectedItem = entry; + }.bind(this), 0); + } + + return li; +}; + +/** + * Sets a context menu. Context menu is enabled only on archive and removable + * volumes as of now. + * + * @param {cr.ui.Menu} menu Context menu. + */ +VolumeList.prototype.setContextMenu = function(menu) { + this.contextMenu_ = menu; + + for (var i = 0; i < this.dataModel.length; i++) { + var item = this.rootsList_.item(i); + var type = PathUtil.getRootType(item.fullPath); + // Context menu is set only to archive and removable volumes. + if (type == RootType.ARCHIVE || type == RootType.REMOVABLE) { + cr.ui.contextMenuHandler.setContextMenu(this.getListItemByIndex(i), + this.contextMenu_); + } + } +}; + +/** + * Handler before root item change. + * @param {Event} event The event. + * @private + */ +VolumeList.prototype.onBeforeSelectionChange_ = function(event) { + if (event.changes.length == 1 && !event.changes[0].selected) + event.preventDefault(); +}; + +/** + * Handler for root item being clicked. + * @param {Event} event The event. + * @private + */ +VolumeList.prototype.onSelectionChange_ = function(event) { + var newRootDir = this.dataModel.item(this.selectionModel.selectedIndex); + if (newRootDir && this.currentVolume_ != newRootDir.fullPath) { + this.currentVolume_ = newRootDir.fullPath; + this.directoryModel_.changeDirectory(this.currentVolume_); + } +}; + +/** + * Invoked when the current directory is changed. + * @param {Event} event The event. + * @private + */ +VolumeList.prototype.onCurrentDirectoryChanged_ = function(event) { + var path = event.newDirEntry.fullPath || dm.getCurrentDirPath(); + var newRootPath = PathUtil.getRootPath(path); + + // Sets |this.currentVolume_| in advance to prevent |onSelectionChange_()| + // from calling |DirectoryModel.ChangeDirectory()| again. + this.currentVolume_ = newRootPath; + + for (var i = 0; i < this.dataModel.length; i++) { + var item = this.dataModel.item(i); + if (PathUtil.getRootPath(item.fullPath) == newRootPath) { + + this.selectionModel.selectedIndex = i; + return; + } + } +}; diff --git a/chrome/browser/resources/file_manager/js/volume_manager.js b/chrome/browser/resources/file_manager/js/volume_manager.js index c329d87..41d72fa 100644 --- a/chrome/browser/resources/file_manager/js/volume_manager.js +++ b/chrome/browser/resources/file_manager/js/volume_manager.js @@ -375,6 +375,7 @@ VolumeManager.prototype.mountDrive = function(successCallback, errorCallback) { if (this.getDriveStatus() == VolumeManager.DriveStatus.ERROR) { this.setDriveStatus_(VolumeManager.DriveStatus.UNMOUNTED); } + this.setDriveStatus_(VolumeManager.DriveStatus.MOUNTING); var self = this; this.mount_('', 'drive', function(mountPath) { this.waitDriveLoaded_(mountPath, function(success, error) { diff --git a/chrome/browser/resources/file_manager/main.html b/chrome/browser/resources/file_manager/main.html index 9afdc11..a0cb7c663 100644 --- a/chrome/browser/resources/file_manager/main.html +++ b/chrome/browser/resources/file_manager/main.html @@ -218,7 +218,7 @@ <div class=dialog-sidebar> <tree id="directory-tree" tabindex="8"></tree> </div> - <div class=sidebar-splitter></div> + <div class="sidebar-splitter splitter"></div> <div class=dialog-main> <div class=dialog-header> <div class="offline-icon"></div> diff --git a/chrome/browser/resources/file_manager/main_new_ui.html b/chrome/browser/resources/file_manager/main_new_ui.html index 3720302..ecc5d3e 100644 --- a/chrome/browser/resources/file_manager/main_new_ui.html +++ b/chrome/browser/resources/file_manager/main_new_ui.html @@ -103,6 +103,7 @@ <script src="js/scrollbar.js"></script> <script src="js/sidebar.js"></script> <script src="js/volume_manager.js"></script> + <script src="js/volume_list.js"></script> <script src="js/media/media_util.js"></script> <script src="js/metadata/metadata_cache.js"></script> <script src="js/default_action_dialog.js"></script> @@ -236,7 +237,7 @@ <span id="app-name"></span> </div> <div class=dialog-sidebar-contents> - <tree id="directory-tree" tabindex="8"></tree> + <list id="volume-list" tabindex="8"></list> </div> <div class=dialog-sidebar-footer> <div id="butter-bar-container"> @@ -252,7 +253,7 @@ </div> </div> </div> - <div class=sidebar-splitter></div> + <div class="splitter" id="sidebar-splitter"></div> <div class=dialog-main> <div class=dialog-header> <input id="search-box" type="search" tabindex="9" @@ -266,27 +267,32 @@ <button id="close-button" visibleif="full-page" tabindex="-1"> </button> </div> - - </div> - <div class="drive-welcome header"></div> - <div class="volume-warning" id="volume-space-warning" hidden></div> - <div class="volume-warning" id="drive-auth-failed-warning" hidden> - <div class="drive-icon"></div> - <div class="drive-text" id="drive-auth-failed-warning-text"></div> </div> <div class=dialog-body> - <div class=filelist-panel> - <div id="list-container"> - <div class=detail-table id="detail-table" tabindex=1 autofocus> + <div class="main-panel"> + <div class="dialog-middlebar-contents"> + <tree id="directory-tree" tabindex="8"></tree> + </div> + <div class="splitter" id="middlebar-splitter"></div> + <div class=filelist-panel> + <div class="drive-welcome header"></div> + <div class="volume-warning" id="volume-space-warning" hidden></div> + <div class="volume-warning" id="drive-auth-failed-warning" hidden> + <div class="drive-icon"></div> + <div class="drive-text" id="drive-auth-failed-warning-text"></div> </div> - <grid class=thumbnail-grid tabindex=1></grid> - <div id="spinner-container"> - <div id="spinner-with-text"></div> + <div id="list-container"> + <div class=detail-table id="detail-table" tabindex=1 autofocus> + </div> + <grid class=thumbnail-grid tabindex=1></grid> + <div id="spinner-container"> + <div id="spinner-with-text"></div> + </div> + <div class="drive-welcome page"></div> + <div id="no-search-results"></div> </div> - <div class="drive-welcome page"></div> - <div id="no-search-results"></div> + <div class=downloads-warning hidden></div> </div> - <div class=downloads-warning hidden></div> </div> <div class="preview-panel" visibility="hidden"> <div> |