Commit ed2387b8 authored by Klemen Kozjek's avatar Klemen Kozjek Committed by Commit Bot

Files App: Prepare UI for rename external USB/SD drive

This CL prepares UI for rename external USB/SD drive in Files App. Because backend for this issue has not been finished yet I have added command line argument '--enable-external-drive-rename' which is used to enable UI part of the feature.

Bug: 274041
Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: Ida64372e68108d975cc8b17b3cab99bcf80fc850
Reviewed-on: https://chromium-review.googlesource.com/604611Reviewed-by: default avatarNaoki Fukino <fukino@chromium.org>
Reviewed-by: default avatarTomasz Mikolajewski <mtomasz@chromium.org>
Reviewed-by: default avatarMichael Giuffrida <michaelpg@chromium.org>
Reviewed-by: default avatarIlya Sherman <isherman@chromium.org>
Commit-Queue: Klemen Kozjek <klemenko@google.com>
Cr-Commit-Position: refs/heads/master@{#496562}
parent a021e41c
...@@ -226,6 +226,9 @@ Press any key to continue exploring. ...@@ -226,6 +226,9 @@ Press any key to continue exploring.
<message name="IDS_FILE_BROWSER_ERROR_INVALID_CHARACTER" desc="Error message displayed when the user enters an invalid character in a file or directory name."> <message name="IDS_FILE_BROWSER_ERROR_INVALID_CHARACTER" desc="Error message displayed when the user enters an invalid character in a file or directory name.">
Invalid character: $1 Invalid character: $1
</message> </message>
<message name="IDS_FILE_BROWSER_ERROR_EXTERNAL_DRIVE_INVALID_CHARACTER" desc="Error message displayed when the user enters an invalid character in an external drive's name.">
Invalid character: $1
</message>
<message name="IDS_FILE_BROWSER_ERROR_RESERVED_NAME" desc="Error message displayed when the user enters a file name which is reserved."> <message name="IDS_FILE_BROWSER_ERROR_RESERVED_NAME" desc="Error message displayed when the user enters a file name which is reserved.">
This name may not be used as a file of folder name This name may not be used as a file of folder name
</message> </message>
...@@ -238,6 +241,9 @@ Press any key to continue exploring. ...@@ -238,6 +241,9 @@ Press any key to continue exploring.
<message name="IDS_FILE_BROWSER_ERROR_LONG_NAME" desc="Error message displayed when user trys to enter too long name for a file or a folder."> <message name="IDS_FILE_BROWSER_ERROR_LONG_NAME" desc="Error message displayed when user trys to enter too long name for a file or a folder.">
This name is too long This name is too long
</message> </message>
<message name="IDS_FILE_BROWSER_ERROR_EXTERNAL_DRIVE_LONG_NAME" desc="Error message displayed when user trys to enter too long name for an external drive.">
This name is too long and exceeds $1 character length limit.
</message>
<message name="IDS_FILE_BROWSER_EMPTY_FOLDER" desc="Label shown in an empty folder."> <message name="IDS_FILE_BROWSER_EMPTY_FOLDER" desc="Label shown in an empty folder.">
Nothing to see here... Nothing to see here...
</message> </message>
......
...@@ -609,6 +609,30 @@ bool FileManagerPrivateFormatVolumeFunction::RunAsync() { ...@@ -609,6 +609,30 @@ bool FileManagerPrivateFormatVolumeFunction::RunAsync() {
return true; return true;
} }
bool FileManagerPrivateRenameVolumeFunction::RunAsync() {
using extensions::api::file_manager_private::RenameVolume::Params;
const std::unique_ptr<Params> params(Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params);
using file_manager::VolumeManager;
using file_manager::Volume;
VolumeManager* const volume_manager = VolumeManager::Get(GetProfile());
if (!volume_manager)
return false;
base::WeakPtr<Volume> volume =
volume_manager->FindVolumeById(params->volume_id);
if (!volume)
return false;
// TODO(klemenko): Uncomment the code below when RenameMountedDevice is
// implemented
/*DiskMountManager::GetInstance()->RenameMountedDevice(
volume->mount_path().AsUTF8Unsafe(), params->new_name);*/
SendResponse(true);
return true;
}
// Obtains file size of URL. // Obtains file size of URL.
void GetFileMetadataOnIOThread( void GetFileMetadataOnIOThread(
scoped_refptr<storage::FileSystemContext> file_system_context, scoped_refptr<storage::FileSystemContext> file_system_context,
......
...@@ -224,6 +224,21 @@ class FileManagerPrivateFormatVolumeFunction ...@@ -224,6 +224,21 @@ class FileManagerPrivateFormatVolumeFunction
bool RunAsync() override; bool RunAsync() override;
}; };
// Implements the chrome.fileManagerPrivate.renameVolume method.
// Renames Volume given its mount path and new Volume name.
class FileManagerPrivateRenameVolumeFunction
: public LoggedAsyncExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("fileManagerPrivate.renameVolume",
FILEMANAGERPRIVATE_RENAMEVOLUME)
protected:
~FileManagerPrivateRenameVolumeFunction() override {}
// AsyncExtensionFunction overrides.
bool RunAsync() override;
};
// Implements the chrome.fileManagerPrivate.startCopy method. // Implements the chrome.fileManagerPrivate.startCopy method.
class FileManagerPrivateInternalStartCopyFunction class FileManagerPrivateInternalStartCopyFunction
: public LoggedAsyncExtensionFunction { : public LoggedAsyncExtensionFunction {
......
...@@ -470,9 +470,13 @@ ExtensionFunction::ResponseAction FileManagerPrivateGetStringsFunction::Run() { ...@@ -470,9 +470,13 @@ ExtensionFunction::ResponseAction FileManagerPrivateGetStringsFunction::Run() {
SET_STRING("ERROR_HIDDEN_NAME", IDS_FILE_BROWSER_ERROR_HIDDEN_NAME); SET_STRING("ERROR_HIDDEN_NAME", IDS_FILE_BROWSER_ERROR_HIDDEN_NAME);
SET_STRING("ERROR_INVALID_CHARACTER", SET_STRING("ERROR_INVALID_CHARACTER",
IDS_FILE_BROWSER_ERROR_INVALID_CHARACTER); IDS_FILE_BROWSER_ERROR_INVALID_CHARACTER);
SET_STRING("ERROR_EXTERNAL_DRIVE_INVALID_CHARACTER",
IDS_FILE_BROWSER_ERROR_EXTERNAL_DRIVE_INVALID_CHARACTER);
SET_STRING("ERROR_INVALID_WALLPAPER", SET_STRING("ERROR_INVALID_WALLPAPER",
IDS_WALLPAPER_MANAGER_INVALID_WALLPAPER); IDS_WALLPAPER_MANAGER_INVALID_WALLPAPER);
SET_STRING("ERROR_LONG_NAME", IDS_FILE_BROWSER_ERROR_LONG_NAME); SET_STRING("ERROR_LONG_NAME", IDS_FILE_BROWSER_ERROR_LONG_NAME);
SET_STRING("ERROR_EXTERNAL_DRIVE_LONG_NAME",
IDS_FILE_BROWSER_ERROR_EXTERNAL_DRIVE_LONG_NAME);
SET_STRING("ERROR_PROGRESS_SUMMARY", IDS_FILE_BROWSER_ERROR_PROGRESS_SUMMARY); SET_STRING("ERROR_PROGRESS_SUMMARY", IDS_FILE_BROWSER_ERROR_PROGRESS_SUMMARY);
SET_STRING("ERROR_PROGRESS_SUMMARY_PLURAL", SET_STRING("ERROR_PROGRESS_SUMMARY_PLURAL",
IDS_FILE_BROWSER_ERROR_PROGRESS_SUMMARY_PLURAL); IDS_FILE_BROWSER_ERROR_PROGRESS_SUMMARY_PLURAL);
......
...@@ -839,6 +839,11 @@ interface Functions { ...@@ -839,6 +839,11 @@ interface Functions {
// |volumeId| ID of the volume to be formatted. // |volumeId| ID of the volume to be formatted.
static void formatVolume(DOMString volumeId); static void formatVolume(DOMString volumeId);
// Renames a mounted volume.
// |volumeId| ID of the volume to be renamed.
// |newName| New name of the target volume.
static void renameVolume(DOMString volumeId, DOMString newName);
// Retrieves file manager preferences. // Retrieves file manager preferences.
// |callback| // |callback|
static void getPreferences(GetPreferencesCallback callback); static void getPreferences(GetPreferencesCallback callback);
......
...@@ -521,6 +521,9 @@ const char kEnterpriseEnableLicenseTypeSelection[] = ...@@ -521,6 +521,9 @@ const char kEnterpriseEnableLicenseTypeSelection[] =
// Disables per-user timezone. // Disables per-user timezone.
const char kDisablePerUserTimezone[] = "disable-per-user-timezone"; const char kDisablePerUserTimezone[] = "disable-per-user-timezone";
// Enables a rename action for external drive such as USB and SD.
const char kEnableExternalDriveRename[] = "enable-external-drive-rename";
bool WakeOnWifiEnabled() { bool WakeOnWifiEnabled() {
return !base::CommandLine::ForCurrentProcess()->HasSwitch(kDisableWakeOnWifi); return !base::CommandLine::ForCurrentProcess()->HasSwitch(kDisableWakeOnWifi);
} }
......
...@@ -87,6 +87,7 @@ CHROMEOS_EXPORT extern const char kEnableDataSaverPrompt[]; ...@@ -87,6 +87,7 @@ CHROMEOS_EXPORT extern const char kEnableDataSaverPrompt[];
CHROMEOS_EXPORT extern const char kEnableEncryptionMigration[]; CHROMEOS_EXPORT extern const char kEnableEncryptionMigration[];
CHROMEOS_EXPORT extern const char kEnableExperimentalAccessibilityFeatures[]; CHROMEOS_EXPORT extern const char kEnableExperimentalAccessibilityFeatures[];
CHROMEOS_EXPORT extern const char kEnableExtensionAssetsSharing[]; CHROMEOS_EXPORT extern const char kEnableExtensionAssetsSharing[];
CHROMEOS_EXPORT extern const char kEnableExternalDriveRename[];
CHROMEOS_EXPORT extern const char kEnableFirstRunUITransitions[]; CHROMEOS_EXPORT extern const char kEnableFirstRunUITransitions[];
CHROMEOS_EXPORT extern const char kEnableLockScreenApps[]; CHROMEOS_EXPORT extern const char kEnableLockScreenApps[];
CHROMEOS_EXPORT extern const char kTetherStub[]; CHROMEOS_EXPORT extern const char kTetherStub[];
......
...@@ -1250,6 +1250,7 @@ enum HistogramValue { ...@@ -1250,6 +1250,7 @@ enum HistogramValue {
WEBVIEWINTERNAL_ISAUDIOMUTED, WEBVIEWINTERNAL_ISAUDIOMUTED,
WEBVIEWINTERNAL_GETAUDIOSTATE, WEBVIEWINTERNAL_GETAUDIOSTATE,
FILEMANAGERPRIVATE_GETRECENTFILES, FILEMANAGERPRIVATE_GETRECENTFILES,
FILEMANAGERPRIVATE_RENAMEVOLUME,
// Last entry: Add new entries above, then run: // Last entry: Add new entries above, then run:
// python tools/metrics/histograms/update_extension_histograms.py // python tools/metrics/histograms/update_extension_histograms.py
ENUM_BOUNDARY ENUM_BOUNDARY
......
...@@ -74,7 +74,8 @@ var ProfileInfo; ...@@ -74,7 +74,8 @@ var ProfileInfo;
* configurable: boolean, * configurable: boolean,
* watchable: boolean, * watchable: boolean,
* mountCondition: (string|undefined), * mountCondition: (string|undefined),
* mountContext: (string|undefined) * mountContext: (string|undefined),
* diskFileSystemType: (string|undefined)
* }} * }}
*/ */
var VolumeMetadata; var VolumeMetadata;
...@@ -438,6 +439,14 @@ chrome.fileManagerPrivate.getSizeStats = function(volumeId, callback) {}; ...@@ -438,6 +439,14 @@ chrome.fileManagerPrivate.getSizeStats = function(volumeId, callback) {};
*/ */
chrome.fileManagerPrivate.formatVolume = function(volumeId) {}; chrome.fileManagerPrivate.formatVolume = function(volumeId) {};
/**
* Renames a mounted volume. |volumeId| ID of the volume to be renamed to
* |newName|.
* @param {string} volumeId
* @param {string} newName
*/
chrome.fileManagerPrivate.renameVolume = function(volumeId, newName) {};
/** /**
* Retrieves file manager preferences. |callback| * Retrieves file manager preferences. |callback|
* @param {function((!Preferences|undefined))} callback * @param {function((!Preferences|undefined))} callback
......
...@@ -13320,6 +13320,7 @@ uploading your change for review. These are checked by presubmit scripts. ...@@ -13320,6 +13320,7 @@ uploading your change for review. These are checked by presubmit scripts.
<int value="1187" label="WEBVIEWINTERNAL_ISAUDIOMUTED"/> <int value="1187" label="WEBVIEWINTERNAL_ISAUDIOMUTED"/>
<int value="1188" label="WEBVIEWINTERNAL_GETAUDIOSTATE"/> <int value="1188" label="WEBVIEWINTERNAL_GETAUDIOSTATE"/>
<int value="1189" label="FILEMANAGERPRIVATE_GETRECENTFILES"/> <int value="1189" label="FILEMANAGERPRIVATE_GETRECENTFILES"/>
<int value="1190" label="FILEMANAGERPRIVATE_RENAMEVOLUME"/>
</enum> </enum>
<enum name="ExtensionIconState"> <enum name="ExtensionIconState">
...@@ -101,6 +101,9 @@ VolumeInfo.prototype.watchable; ...@@ -101,6 +101,9 @@ VolumeInfo.prototype.watchable;
/** @type {VolumeManagerCommon.Source} */ /** @type {VolumeManagerCommon.Source} */
VolumeInfo.prototype.source; VolumeInfo.prototype.source;
/** @type {VolumeManagerCommon.FileSystemType} */
VolumeInfo.prototype.diskFileSystemType;
/** /**
* Starts resolving the display root and obtains it. It may take long time for * Starts resolving the display root and obtains it. It may take long time for
* Drive. Once resolved, it is cached. * Drive. Once resolved, it is cached.
......
...@@ -31,23 +31,13 @@ ...@@ -31,23 +31,13 @@
* as containing media such as photos or videos. * as containing media such as photos or videos.
* @param {boolean} configurable When true, then the volume can be configured. * @param {boolean} configurable When true, then the volume can be configured.
* @param {VolumeManagerCommon.Source} source Source of the volume's data. * @param {VolumeManagerCommon.Source} source Source of the volume's data.
* @param {VolumeManagerCommon.FileSystemType} diskFileSystemType File system
* type indentifier.
*/ */
function VolumeInfoImpl( function VolumeInfoImpl(
volumeType, volumeType, volumeId, fileSystem, error, deviceType, devicePath, isReadOnly,
volumeId, isReadOnlyRemovableDevice, profile, label, extensionId, hasMedia,
fileSystem, configurable, watchable, source, diskFileSystemType) {
error,
deviceType,
devicePath,
isReadOnly,
isReadOnlyRemovableDevice,
profile,
label,
extensionId,
hasMedia,
configurable,
watchable,
source) {
this.volumeType_ = volumeType; this.volumeType_ = volumeType;
this.volumeId_ = volumeId; this.volumeId_ = volumeId;
this.fileSystem_ = fileSystem; this.fileSystem_ = fileSystem;
...@@ -101,6 +91,7 @@ function VolumeInfoImpl( ...@@ -101,6 +91,7 @@ function VolumeInfoImpl(
this.configurable_ = configurable; this.configurable_ = configurable;
this.watchable_ = watchable; this.watchable_ = watchable;
this.source_ = source; this.source_ = source;
this.diskFileSystemType_ = diskFileSystemType;
} }
VolumeInfoImpl.prototype = /** @struct */ { VolumeInfoImpl.prototype = /** @struct */ {
...@@ -214,6 +205,12 @@ VolumeInfoImpl.prototype = /** @struct */ { ...@@ -214,6 +205,12 @@ VolumeInfoImpl.prototype = /** @struct */ {
*/ */
get source() { get source() {
return this.source_; return this.source_;
},
/**
* @return {VolumeManagerCommon.FileSystemType} File system type identifier.
*/
get diskFileSystemType() {
return this.diskFileSystemType_;
} }
}; };
......
...@@ -151,22 +151,16 @@ volumeManagerUtil.createVolumeInfo = function(volumeMetadata) { ...@@ -151,22 +151,16 @@ volumeManagerUtil.createVolumeInfo = function(volumeMetadata) {
} }
return new VolumeInfoImpl( return new VolumeInfoImpl(
/** @type {VolumeManagerCommon.VolumeType} */ /** @type {VolumeManagerCommon.VolumeType} */
(volumeMetadata.volumeType), (volumeMetadata.volumeType), volumeMetadata.volumeId, fileSystem,
volumeMetadata.volumeId, volumeMetadata.mountCondition, volumeMetadata.deviceType,
fileSystem, volumeMetadata.devicePath, volumeMetadata.isReadOnly,
volumeMetadata.mountCondition, volumeMetadata.isReadOnlyRemovableDevice, volumeMetadata.profile,
volumeMetadata.deviceType, localizedLabel, volumeMetadata.extensionId, volumeMetadata.hasMedia,
volumeMetadata.devicePath, volumeMetadata.configurable, volumeMetadata.watchable,
volumeMetadata.isReadOnly,
volumeMetadata.isReadOnlyRemovableDevice,
volumeMetadata.profile,
localizedLabel,
volumeMetadata.extensionId,
volumeMetadata.hasMedia,
volumeMetadata.configurable,
volumeMetadata.watchable,
/** @type {VolumeManagerCommon.Source} */ /** @type {VolumeManagerCommon.Source} */
(volumeMetadata.source)); (volumeMetadata.source),
/** @type {VolumeManagerCommon.FileSystemType} */
(volumeMetadata.diskFileSystemType));
}) })
.catch( .catch(
/** /**
...@@ -180,22 +174,17 @@ volumeManagerUtil.createVolumeInfo = function(volumeMetadata) { ...@@ -180,22 +174,17 @@ volumeManagerUtil.createVolumeInfo = function(volumeMetadata) {
return new VolumeInfoImpl( return new VolumeInfoImpl(
/** @type {VolumeManagerCommon.VolumeType} */ /** @type {VolumeManagerCommon.VolumeType} */
(volumeMetadata.volumeType), (volumeMetadata.volumeType), volumeMetadata.volumeId,
volumeMetadata.volumeId,
null, // File system is not found. null, // File system is not found.
volumeMetadata.mountCondition, volumeMetadata.mountCondition, volumeMetadata.deviceType,
volumeMetadata.deviceType, volumeMetadata.devicePath, volumeMetadata.isReadOnly,
volumeMetadata.devicePath, volumeMetadata.isReadOnlyRemovableDevice, volumeMetadata.profile,
volumeMetadata.isReadOnly, localizedLabel, volumeMetadata.extensionId, volumeMetadata.hasMedia,
volumeMetadata.isReadOnlyRemovableDevice, volumeMetadata.configurable, volumeMetadata.watchable,
volumeMetadata.profile,
localizedLabel,
volumeMetadata.extensionId,
volumeMetadata.hasMedia,
volumeMetadata.configurable,
volumeMetadata.watchable,
/** @type {VolumeManagerCommon.Source} */ /** @type {VolumeManagerCommon.Source} */
(volumeMetadata.source)); (volumeMetadata.source),
/** @type {VolumeManagerCommon.FileSystemType} */
(volumeMetadata.diskFileSystemType));
}); });
}; };
......
...@@ -1126,6 +1126,60 @@ util.validateFileName = function(parentEntry, name, filterHiddenOn) { ...@@ -1126,6 +1126,60 @@ util.validateFileName = function(parentEntry, name, filterHiddenOn) {
}); });
}; };
/**
* Verifies the user entered name for external drive to be
* renamed to. Name restrictions must correspond to the target filesystem
* restrictions.
*
* It also verifies that name length is in the limits of the filesystem.
*
* @param {string} name New external drive name.
* @param {!VolumeInfo} volumeInfo
* @return {Promise} Promise fulfilled on success, or rejected with the error
* message.
*/
util.validateExternalDriveName = function(name, volumeInfo) {
// Verify if entered name for external drive respects restrictions provided by
// the target filesystem
var fileSystem = volumeInfo.diskFileSystemType;
var nameLength = name.length;
// Verify length for the target file system type
if (fileSystem == VolumeManagerCommon.FileSystemType.VFAT &&
nameLength >
VolumeManagerCommon.FileSystemTypeVolumeNameLengthLimit.VFAT) {
return Promise.reject(strf(
'ERROR_EXTERNAL_DRIVE_LONG_NAME',
VolumeManagerCommon.FileSystemTypeVolumeNameLengthLimit.VFAT));
} else if (
fileSystem == VolumeManagerCommon.FileSystemType.EXFAT &&
nameLength >
VolumeManagerCommon.FileSystemTypeVolumeNameLengthLimit.EXFAT) {
return Promise.reject(strf(
'ERROR_EXTERNAL_DRIVE_LONG_NAME',
VolumeManagerCommon.FileSystemTypeVolumeNameLengthLimit.EXFAT));
}
// Only printable ASCII (from ' ' to '~')
var containsNonPrintableAscii = /[\x00-\x1F\x7F-\x7F]/.exec(name);
if (containsNonPrintableAscii) {
return Promise.reject(strf(
'ERROR_EXTERNAL_DRIVE_INVALID_CHARACTER',
containsNonPrintableAscii[0]));
}
var containsForbiddenCharacters =
/[\*\?\.\,\;\:\/\\\|\+\=\<\>\[\]\"\'\t]/.exec(name);
if (containsForbiddenCharacters) {
return Promise.reject(strf(
'ERROR_EXTERNAL_DRIVE_INVALID_CHARACTER',
containsForbiddenCharacters[0]));
}
return Promise.resolve();
};
/** /**
* Adds a foregorund listener to the background page components. * Adds a foregorund listener to the background page components.
* The lisner will be removed when the foreground window is closed. * The lisner will be removed when the foreground window is closed.
......
...@@ -19,6 +19,34 @@ var AllowedPaths = { ...@@ -19,6 +19,34 @@ var AllowedPaths = {
ANY_PATH: 'anyPath' ANY_PATH: 'anyPath'
}; };
/**
* Type of a file system.
* @enum {string}
* @const
*/
VolumeManagerCommon.FileSystemType = {
UNKNOWN: '',
VFAT: 'vfat',
EXFAT: 'exfat',
NTFS: 'ntfs',
HFSPLUS: 'hfsplus',
EXT2: 'ext2',
EXT3: 'ext3',
EXT4: 'ext4',
ISO9660: 'iso9660',
UDF: 'udf',
};
/**
* Volume name length limits by file system type
* @enum {number}
* @const
*/
VolumeManagerCommon.FileSystemTypeVolumeNameLengthLimit = {
VFAT: 11,
EXFAT: 15,
};
/** /**
* Type of a root directory. * Type of a root directory.
* @enum {string} * @enum {string}
......
...@@ -168,6 +168,17 @@ div.splitter { ...@@ -168,6 +168,17 @@ div.splitter {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
#directory-tree .tree-row .rename-placeholder {
display: block;
flex: auto;
font-weight: 500;
margin: 0 6px;
}
#directory-tree .tree-row .rename-placeholder > input {
width: 100%;
}
#directory-tree [renaming] > .tree-row > .label { #directory-tree [renaming] > .tree-row > .label {
display: none; display: none;
} }
...@@ -1421,6 +1432,7 @@ html[dir='rtl'] .badge { ...@@ -1421,6 +1432,7 @@ html[dir='rtl'] .badge {
} }
input.rename:focus, input.rename:focus,
#directory-tree .tree-row .rename-placeholder > input:focus,
#directory-tree .tree-row > input:focus { #directory-tree .tree-row > input:focus {
outline-color: rgb(77, 144, 254); outline-color: rgb(77, 144, 254);
} }
...@@ -2229,7 +2241,7 @@ li#progress-center-close-view.error.single button.dismiss, ...@@ -2229,7 +2241,7 @@ li#progress-center-close-view.error.single button.dismiss,
color: white; color: white;
display: flex; display: flex;
flex: 0 0 14px; flex: 0 0 14px;
font-size; 14px; font-size: 14px;
padding: 20px 15px; padding: 20px 15px;
} }
......
...@@ -40,6 +40,16 @@ function DirectoryTreeNamingController( ...@@ -40,6 +40,16 @@ function DirectoryTreeNamingController(
*/ */
this.editting_ = false; this.editting_ = false;
/**
* @private {boolean}
*/
this.isRemovableRoot_ = false;
/**
* @private {VolumeInfo}
*/
this.volumeInfo_ = null;
/** /**
* @private {!HTMLInputElement} * @private {!HTMLInputElement}
* @const * @const
...@@ -67,16 +77,36 @@ DirectoryTreeNamingController.prototype.getInputElement = function() { ...@@ -67,16 +77,36 @@ DirectoryTreeNamingController.prototype.getInputElement = function() {
/** /**
* Attaches naming controller to specified directory item and start rename. * Attaches naming controller to specified directory item and start rename.
* @param {!DirectoryItem} directoryItem * @param {!DirectoryItem} directoryItem An html element of a node of the
* target.
* @param {boolean} isRemovableRoot Indicates whether the target is removable
* node or not.
* @param {VolumeInfo} volumeInfo A volume information about the target node.
* |volumeInfo| can be null if method is invoked on a folder that is in the
* tree view and is not root of an external drive.
*/ */
DirectoryTreeNamingController.prototype.attachAndStart = function( DirectoryTreeNamingController.prototype.attachAndStart = function(
directoryItem) { directoryItem, isRemovableRoot, volumeInfo) {
this.isRemovableRoot_ = isRemovableRoot;
this.volumeInfo_ = this.isRemovableRoot_ ? assert(volumeInfo) : null;
if (!!this.currentDirectoryItem_) if (!!this.currentDirectoryItem_)
return; return;
this.currentDirectoryItem_ = directoryItem; this.currentDirectoryItem_ = directoryItem;
this.currentDirectoryItem_.setAttribute('renaming', true); this.currentDirectoryItem_.setAttribute('renaming', true);
this.currentDirectoryItem_.firstElementChild.appendChild(this.inputElement_);
var renameInputElementPlaceholder =
this.currentDirectoryItem_.firstElementChild.getElementsByClassName(
'rename-placeholder');
if (this.isRemovableRoot_ && renameInputElementPlaceholder.length === 1) {
renameInputElementPlaceholder[0].appendChild(this.inputElement_);
} else {
this.currentDirectoryItem_.firstElementChild.appendChild(
this.inputElement_);
}
this.inputElement_.value = this.currentDirectoryItem_.label; this.inputElement_.value = this.currentDirectoryItem_.label;
this.inputElement_.select(); this.inputElement_.select();
...@@ -103,15 +133,32 @@ DirectoryTreeNamingController.prototype.commitRename_ = function() { ...@@ -103,15 +133,32 @@ DirectoryTreeNamingController.prototype.commitRename_ = function() {
return; return;
} }
// Validate new name. if (this.isRemovableRoot_) {
new Promise(entry.getParent.bind(entry)).then(function(parentEntry) { // Validate new name.
return util.validateFileName(parentEntry, newName, new Promise(entry.getParent.bind(entry))
this.directoryModel_.getFileFilter().isFilterHiddenOn()); .then(function(parentEntry) {
}.bind(this)).then( return util.validateExternalDriveName(
this.performRename_.bind(this, entry, newName), newName, assert(this.volumeInfo_));
function(errorMessage) { }.bind(this))
this.alertDialog_.show(errorMessage, this.detach_.bind(this)); .then(
}.bind(this)); this.performExternalDriveRename_.bind(this, entry, newName),
function(errorMessage) {
this.alertDialog_.show(errorMessage, this.detach_.bind(this));
}.bind(this));
} else {
// Validate new name.
new Promise(entry.getParent.bind(entry))
.then(function(parentEntry) {
return util.validateFileName(
parentEntry, newName,
this.directoryModel_.getFileFilter().isFilterHiddenOn());
}.bind(this))
.then(
this.performRename_.bind(this, entry, newName),
function(errorMessage) {
this.alertDialog_.show(errorMessage, this.detach_.bind(this));
}.bind(this));
}
}; };
/** /**
...@@ -127,32 +174,57 @@ DirectoryTreeNamingController.prototype.performRename_ = function( ...@@ -127,32 +174,57 @@ DirectoryTreeNamingController.prototype.performRename_ = function(
if (renamingCurrentDirectory) if (renamingCurrentDirectory)
this.directoryModel_.setIgnoringCurrentDirectoryDeletion(true /* ignore */); this.directoryModel_.setIgnoringCurrentDirectoryDeletion(true /* ignore */);
// TODO(yawano): Rename might take time on some volumes. Optimiscally show new // TODO(yawano): Rename might take time on some volumes. Optimistically show
// name in the UI before actual rename is completed. // new name in the UI before actual rename is completed.
new Promise(util.rename.bind(null, entry, newName)).then(function(newEntry) { new Promise(util.rename.bind(null, entry, newName))
// Show new name before detacching input element to prevent showing old .then(
// name. function(newEntry) {
var label = // Show new name before detaching input element to prevent showing
this.currentDirectoryItem_.firstElementChild.querySelector('.label'); // old name.
label.textContent = newName; var label =
this.currentDirectoryItem_.firstElementChild.querySelector(
'.label');
label.textContent = newName;
this.currentDirectoryItem_.entry = newEntry; this.currentDirectoryItem_.entry = newEntry;
this.detach_(); this.detach_();
// If renamed directory was current directory, change it to new one. // If renamed directory was current directory, change it to new one.
if (renamingCurrentDirectory) { if (renamingCurrentDirectory) {
this.directoryModel_.changeDirectoryEntry(newEntry, this.directoryModel_.changeDirectoryEntry(
this.directoryModel_.setIgnoringCurrentDirectoryDeletion.bind( newEntry,
this.directoryModel_, false /* not ignore */)); this.directoryModel_.setIgnoringCurrentDirectoryDeletion.bind(
} this.directoryModel_, false /* not ignore */));
}.bind(this), function(error) { }
this.directoryModel_.setIgnoringCurrentDirectoryDeletion( }.bind(this),
false /* not ignore*/); function(error) {
this.detach_(); this.directoryModel_.setIgnoringCurrentDirectoryDeletion(
false /* not ignore*/);
this.detach_();
this.alertDialog_.show(
util.getRenameErrorMessage(error, entry, newName));
}.bind(this));
};
/**
* Performs external drive rename operation.
* @param {!DirectoryEntry} entry
* @param {string} newName Validated name.
* @private
*/
DirectoryTreeNamingController.prototype.performExternalDriveRename_ = function(
entry, newName) {
// Invoke external drive rename
chrome.fileManagerPrivate.renameVolume(this.volumeInfo_.volumeId, newName);
// Show new name before detaching input element to prevent showing old
// name.
var label =
this.currentDirectoryItem_.firstElementChild.querySelector('.label');
label.textContent = newName;
this.alertDialog_.show(util.getRenameErrorMessage(error, entry, newName)); this.detach_();
}.bind(this));
}; };
/** /**
...@@ -174,7 +246,17 @@ DirectoryTreeNamingController.prototype.cancelRename_ = function() { ...@@ -174,7 +246,17 @@ DirectoryTreeNamingController.prototype.cancelRename_ = function() {
DirectoryTreeNamingController.prototype.detach_ = function() { DirectoryTreeNamingController.prototype.detach_ = function() {
assert(!!this.currentDirectoryItem_); assert(!!this.currentDirectoryItem_);
this.currentDirectoryItem_.firstElementChild.removeChild(this.inputElement_); var renameInputElementPlaceholder =
this.currentDirectoryItem_.firstElementChild.getElementsByClassName(
'rename-placeholder');
if (this.isRemovableRoot_ && renameInputElementPlaceholder.length === 1) {
renameInputElementPlaceholder[0].removeChild(this.inputElement_);
} else {
this.currentDirectoryItem_.firstElementChild.removeChild(
this.inputElement_);
}
this.currentDirectoryItem_.removeAttribute('renaming'); this.currentDirectoryItem_.removeAttribute('renaming');
this.currentDirectoryItem_ = null; this.currentDirectoryItem_ = null;
......
...@@ -363,8 +363,31 @@ var CommandHandler = function(fileManager, selectionHandler) { ...@@ -363,8 +363,31 @@ var CommandHandler = function(fileManager, selectionHandler) {
selectionHandler.addEventListener( selectionHandler.addEventListener(
FileSelectionHandler.EventType.CHANGE_THROTTLED, FileSelectionHandler.EventType.CHANGE_THROTTLED,
this.updateAvailability.bind(this)); this.updateAvailability.bind(this));
chrome.commandLinePrivate.hasSwitch(
'enable-external-drive-rename', function(enabled) {
CommandHandler.IS_EXTERNAL_DISK_RENAME_ENABLED_ = enabled;
}.bind(this));
}; };
/**
* Supported disk file system types for renaming.
* @type {boolean}
* @private
*/
CommandHandler.IS_EXTERNAL_DISK_RENAME_ENABLED_ = false;
/**
* Supported disk file system types for renaming.
* @type {!Array<!VolumeManagerCommon.FileSystemType>}
* @const
* @private
*/
CommandHandler.RENAME_DISK_FILE_SYSYTEM_SUPPORT_ = [
VolumeManagerCommon.FileSystemType.EXFAT,
VolumeManagerCommon.FileSystemType.VFAT
];
/** /**
* Updates the availability of all commands. * Updates the availability of all commands.
*/ */
...@@ -581,7 +604,8 @@ CommandHandler.COMMANDS_['new-folder'] = (function() { ...@@ -581,7 +604,8 @@ CommandHandler.COMMANDS_['new-folder'] = (function() {
directoryTree.updateAndSelectNewDirectory( directoryTree.updateAndSelectNewDirectory(
targetDirectory, newDirectory); targetDirectory, newDirectory);
fileManager.directoryTreeNamingController.attachAndStart( fileManager.directoryTreeNamingController.attachAndStart(
assert(fileManager.ui.directoryTree.selectedItem)); assert(fileManager.ui.directoryTree.selectedItem), false,
null);
} else { } else {
directoryModel.updateAndSelectNewDirectory( directoryModel.updateAndSelectNewDirectory(
newDirectory).then(function() { newDirectory).then(function() {
...@@ -924,14 +948,31 @@ CommandHandler.COMMANDS_['rename'] = /** @type {Command} */ ({ ...@@ -924,14 +948,31 @@ CommandHandler.COMMANDS_['rename'] = /** @type {Command} */ ({
* @param {!CommandHandlerDeps} fileManager CommandHandlerDeps to use. * @param {!CommandHandlerDeps} fileManager CommandHandlerDeps to use.
*/ */
execute: function(event, fileManager) { execute: function(event, fileManager) {
if (event.target instanceof DirectoryTree) { if (event.target instanceof DirectoryTree ||
var directoryTree = event.target; event.target instanceof DirectoryItem) {
assert(fileManager.directoryTreeNamingController) var isRemovableRoot = false;
.attachAndStart(assert(directoryTree.selectedItem)); var entry = CommandUtil.getCommandEntry(event.target);
} else if (event.target instanceof DirectoryItem) { if (entry) {
var directoryItem = event.target; var volumeInfo = fileManager.volumeManager.getVolumeInfo(entry);
assert(fileManager.directoryTreeNamingController) // Checks whether the target is actually external drive or just a folder
.attachAndStart(directoryItem); // inside the drive.
if (volumeInfo &&
CommandUtil.isRootEntry(fileManager.volumeManager, entry)) {
isRemovableRoot = true;
}
}
if (event.target instanceof DirectoryTree) {
var directoryTree = event.target;
assert(fileManager.directoryTreeNamingController)
.attachAndStart(
assert(directoryTree.selectedItem), isRemovableRoot,
volumeInfo);
} else if (event.target instanceof DirectoryItem) {
var directoryItem = event.target;
assert(fileManager.directoryTreeNamingController)
.attachAndStart(directoryItem, isRemovableRoot, volumeInfo);
}
} else { } else {
fileManager.namingController.initiateRename(); fileManager.namingController.initiateRename();
} }
...@@ -941,6 +982,29 @@ CommandHandler.COMMANDS_['rename'] = /** @type {Command} */ ({ ...@@ -941,6 +982,29 @@ CommandHandler.COMMANDS_['rename'] = /** @type {Command} */ ({
* @param {!CommandHandlerDeps} fileManager CommandHandlerDeps to use. * @param {!CommandHandlerDeps} fileManager CommandHandlerDeps to use.
*/ */
canExecute: function(event, fileManager) { canExecute: function(event, fileManager) {
// TODO(klemenko): Remove flag below when 274041 gets implemented.
if (CommandHandler.IS_EXTERNAL_DISK_RENAME_ENABLED_) {
// Check if it is removable drive
var root = CommandUtil.getCommandEntry(event.target);
// |root| is null for unrecognized volumes. Do not enable rename command
// for such volumes because they need to be formatted prior to rename.
if (root != null) {
var location = root && fileManager.volumeManager.getLocationInfo(root);
var volumeInfo = fileManager.volumeManager.getVolumeInfo(root);
var writable = location && !location.isReadOnly;
var removable = writable &&
location.rootType === VolumeManagerCommon.RootType.REMOVABLE;
var canExecute = removable && writable && volumeInfo &&
CommandHandler.RENAME_DISK_FILE_SYSYTEM_SUPPORT_.indexOf(
volumeInfo.diskFileSystemType) > -1;
event.canExecute = canExecute;
event.command.setHidden(!removable);
if (removable)
return;
}
}
// Check if it is file or folder
var renameTarget = CommandUtil.isFromSelectionMenu(event) ? var renameTarget = CommandUtil.isFromSelectionMenu(event) ?
fileManager.ui.listContainer.currentList : fileManager.ui.listContainer.currentList :
event.target; event.target;
......
...@@ -542,11 +542,15 @@ function VolumeItem(modelItem, tree) { ...@@ -542,11 +542,15 @@ function VolumeItem(modelItem, tree) {
item.setupIcon_(item.querySelector('.icon'), item.volumeInfo_); item.setupIcon_(item.querySelector('.icon'), item.volumeInfo_);
// Attach the "eject" icon if the volume is ejectable. // Attach a placeholder for rename input text box and the eject icon if the
// volume is ejectable
if ((modelItem.volumeInfo_.source === VolumeManagerCommon.Source.DEVICE && if ((modelItem.volumeInfo_.source === VolumeManagerCommon.Source.DEVICE &&
modelItem.volumeInfo_.volumeType !== modelItem.volumeInfo_.volumeType !==
VolumeManagerCommon.VolumeType.MTP) || VolumeManagerCommon.VolumeType.MTP) ||
modelItem.volumeInfo_.source === VolumeManagerCommon.Source.FILE) { modelItem.volumeInfo_.source === VolumeManagerCommon.Source.FILE) {
// This placeholder is added to allow to put textbox before eject button
// while executing renaming action on external drive.
item.setupRenamePlaceholder_(item.rowElement);
item.setupEjectButton_(item.rowElement); item.setupEjectButton_(item.rowElement);
} }
...@@ -682,6 +686,16 @@ VolumeItem.prototype.setupEjectButton_ = function(rowElement) { ...@@ -682,6 +686,16 @@ VolumeItem.prototype.setupEjectButton_ = function(rowElement) {
ejectButton.appendChild(ripple); ejectButton.appendChild(ripple);
}; };
/**
* Set up rename input textbox placeholder if needed.
* @param {HTMLElement} rowElement The parent element for placeholder.
* @private
*/
VolumeItem.prototype.setupRenamePlaceholder_ = function(rowElement) {
var placeholder = cr.doc.createElement('span');
placeholder.className = 'rename-placeholder';
rowElement.appendChild(placeholder);
};
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// DriveVolumeItem // DriveVolumeItem
......
...@@ -202,6 +202,7 @@ ...@@ -202,6 +202,7 @@
<cr-menu-item command="#configure"></cr-menu-item> <cr-menu-item command="#configure"></cr-menu-item>
<cr-menu-item command="#unmount"></cr-menu-item> <cr-menu-item command="#unmount"></cr-menu-item>
<cr-menu-item command="#format"></cr-menu-item> <cr-menu-item command="#format"></cr-menu-item>
<cr-menu-item command="#rename"></cr-menu-item>
<cr-menu-item command="#remove-folder-shortcut"></cr-menu-item> <cr-menu-item command="#remove-folder-shortcut"></cr-menu-item>
</cr-menu> </cr-menu>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment