summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/app/generated_resources.grd18
-rw-r--r--chrome/browser/extensions/extension_file_browser_private_api.cc92
-rw-r--r--chrome/browser/resources/file_manager/css/file_manager.css16
-rw-r--r--chrome/browser/resources/file_manager/js/file_manager.js326
-rw-r--r--chrome/browser/resources/file_manager/js/mock_chrome.js6
-rw-r--r--chrome/browser/resources/file_manager/js/util.js16
-rw-r--r--chrome/browser/resources/file_manager/main.html10
-rw-r--r--chrome/browser/resources/shared/js/cr/locale.js2
8 files changed, 343 insertions, 143 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 0531073..f24a64ce 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -8181,12 +8181,6 @@ Keep your key file in a safe place. You will need it to create new versions of y
13px
</message>
- <message name="IDS_FILE_BROWSER_FILES_DISPLAYED_SUMMARY" desc="Files displayed summary.">
- %1 Files Displayed
- </message>
- <message name="IDS_FILE_BROWSER_FILES_SELECTED_SUMMARY" desc="Files selected summary.">
- %1 Files Selected
- </message>
<message name="IDS_FILE_BROWSER_ROOT_DIRECTORY_LABEL" desc="Root directory label.">
Files
</message>
@@ -8203,6 +8197,18 @@ Keep your key file in a safe place. You will need it to create new versions of y
Preview
</message>
+ <message name="IDS_FILE_BROWSER_ERROR_CREATING_FOLDER" desc="Message displayed when we can't create a folder.">
+ Unable to create folder "$1": $2
+ </message>
+ <message name="IDS_FILE_BROWSER_ERROR_INVALID_FOLDER_CHARACTER" desc="Error message displayed when the user enters an invalid character in a folder name.">
+ Invalid character in folder name: $1
+ </message>
+ <message name="IDS_FILE_BROWSER_NEW_FOLDER_PROMPT" desc="Prompt displayed in a message box when user trys to create a new folder.">
+ Enter a name for the new folder
+ </message>
+ <message name="IDS_FILE_BROWSER_NEW_FOLDER_BUTTON_LABEL" desc="Label on the 'New Folder' button.">
+ New Folder
+ </message>
<message name="IDS_FILE_BROWSER_FILENAME_LABEL" desc="Filename text area label.">
File name
</message>
diff --git a/chrome/browser/extensions/extension_file_browser_private_api.cc b/chrome/browser/extensions/extension_file_browser_private_api.cc
index ddba387..e578e12 100644
--- a/chrome/browser/extensions/extension_file_browser_private_api.cc
+++ b/chrome/browser/extensions/extension_file_browser_private_api.cc
@@ -255,61 +255,43 @@ bool FileDialogStringsFunction::RunImpl() {
result_.reset(new DictionaryValue());
DictionaryValue* dict = reinterpret_cast<DictionaryValue*>(result_.get());
- dict->SetString("LOCALE_DATE_SHORT",
- l10n_util::GetStringUTF16(IDS_LOCALE_DATE_SHORT));
- dict->SetString("LOCALE_MONTHS_SHORT",
- l10n_util::GetStringUTF16(IDS_LOCALE_MONTHS_SHORT));
- dict->SetString("LOCALE_DAYS_SHORT",
- l10n_util::GetStringUTF16(IDS_LOCALE_DAYS_SHORT));
- dict->SetString("FILES_DISPLAYED_SUMMARY",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_FILES_DISPLAYED_SUMMARY));
- dict->SetString("FILES_SELECTED_SUMMARY",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_FILES_SELECTED_SUMMARY));
-
- dict->SetString("BODY_FONT_FAMILY",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_BODY_FONT_FAMILY));
- dict->SetString("BODY_FONT_SIZE",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_BODY_FONT_SIZE));
-
- dict->SetString("ROOT_DIRECTORY_LABEL",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_ROOT_DIRECTORY_LABEL));
- dict->SetString("NAME_COLUMN_LABEL",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_NAME_COLUMN_LABEL));
- dict->SetString("SIZE_COLUMN_LABEL",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_SIZE_COLUMN_LABEL));
- dict->SetString("DATE_COLUMN_LABEL",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_DATE_COLUMN_LABEL));
- dict->SetString("PREVIEW_COLUMN_LABEL",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_PREVIEW_COLUMN_LABEL));
-
- dict->SetString("FILENAME_LABEL",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_FILENAME_LABEL));
-
- dict->SetString("CANCEL_LABEL",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_CANCEL_LABEL));
- dict->SetString("OPEN_LABEL",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_OPEN_LABEL));
- dict->SetString("SAVE_LABEL",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_SAVE_LABEL));
-
- dict->SetString("SELECT_FOLDER_TITLE",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_SELECT_FOLDER_TITLE));
- dict->SetString("SELECT_OPEN_FILE_TITLE",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_SELECT_OPEN_FILE_TITLE));
- dict->SetString("SELECT_OPEN_MULTI_FILE_TITLE",
- l10n_util::GetStringUTF16(
- IDS_FILE_BROWSER_SELECT_OPEN_MULTI_FILE_TITLE));
- dict->SetString("SELECT_SAVEAS_FILE_TITLE",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_SELECT_SAVEAS_FILE_TITLE));
-
- dict->SetString("COMPUTING_SELECTION",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_COMPUTING_SELECTION));
- dict->SetString("NOTHING_SELECTED",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_NOTHING_SELECTED));
- dict->SetString("ONE_FILE_SELECTED",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_ONE_FILE_SELECTED));
- dict->SetString("MANY_FILES_SELECTED",
- l10n_util::GetStringUTF16(IDS_FILE_BROWSER_MANY_FILES_SELECTED));
+#define SET_STRING(ns, id) \
+ dict->SetString(#id, l10n_util::GetStringUTF16(ns##_##id))
+
+ SET_STRING(IDS, LOCALE_DATE_SHORT);
+ SET_STRING(IDS, LOCALE_MONTHS_SHORT);
+ SET_STRING(IDS, LOCALE_DAYS_SHORT);
+
+ SET_STRING(IDS_FILE_BROWSER, BODY_FONT_FAMILY);
+ SET_STRING(IDS_FILE_BROWSER, BODY_FONT_SIZE);
+
+ SET_STRING(IDS_FILE_BROWSER, ROOT_DIRECTORY_LABEL);
+ SET_STRING(IDS_FILE_BROWSER, NAME_COLUMN_LABEL);
+ SET_STRING(IDS_FILE_BROWSER, SIZE_COLUMN_LABEL);
+ SET_STRING(IDS_FILE_BROWSER, DATE_COLUMN_LABEL);
+ SET_STRING(IDS_FILE_BROWSER, PREVIEW_COLUMN_LABEL);
+
+ SET_STRING(IDS_FILE_BROWSER, ERROR_CREATING_FOLDER);
+ SET_STRING(IDS_FILE_BROWSER, ERROR_INVALID_FOLDER_CHARACTER);
+ SET_STRING(IDS_FILE_BROWSER, NEW_FOLDER_PROMPT);
+ SET_STRING(IDS_FILE_BROWSER, NEW_FOLDER_BUTTON_LABEL);
+ SET_STRING(IDS_FILE_BROWSER, FILENAME_LABEL);
+
+ SET_STRING(IDS_FILE_BROWSER, CANCEL_LABEL);
+ SET_STRING(IDS_FILE_BROWSER, OPEN_LABEL);
+ SET_STRING(IDS_FILE_BROWSER, SAVE_LABEL);
+
+ SET_STRING(IDS_FILE_BROWSER, SELECT_FOLDER_TITLE);
+ SET_STRING(IDS_FILE_BROWSER, SELECT_OPEN_FILE_TITLE);
+ SET_STRING(IDS_FILE_BROWSER, SELECT_OPEN_MULTI_FILE_TITLE);
+ SET_STRING(IDS_FILE_BROWSER, SELECT_SAVEAS_FILE_TITLE);
+
+ SET_STRING(IDS_FILE_BROWSER, COMPUTING_SELECTION);
+ SET_STRING(IDS_FILE_BROWSER, NOTHING_SELECTED);
+ SET_STRING(IDS_FILE_BROWSER, ONE_FILE_SELECTED);
+ SET_STRING(IDS_FILE_BROWSER, MANY_FILES_SELECTED);
+
+#undef SET_STRING
SendResponse(true);
return true;
diff --git a/chrome/browser/resources/file_manager/css/file_manager.css b/chrome/browser/resources/file_manager/css/file_manager.css
index a80e51f..923cbee 100644
--- a/chrome/browser/resources/file_manager/css/file_manager.css
+++ b/chrome/browser/resources/file_manager/css/file_manager.css
@@ -38,6 +38,7 @@ body {
-webkit-box-orient: horizontal;
display: -webkit-box;
margin: 15px;
+ margin-bottom: 4px;
}
/* Container for the detail and thumbnail (not implemented yet) list views. */
@@ -140,6 +141,14 @@ body {
margin-top: 6px;
}
+/* First column has no label, so we want the sort indicator to take up the
+ * whole space.
+ */
+.table-header-cell:first-child .table-header-sort-image-desc:after,
+.table-header-cell:first-child .table-header-sort-image-asc:after {
+ -webkit-padding-start: 0;
+}
+
/* The first child of a list cell. */
.table-row-cell > * {
-webkit-margin-start: 5px;
@@ -156,6 +165,7 @@ body {
/* The icon in the name column. */
.detail-icon {
-webkit-margin-end: 3px;
+ -webkit-margin-start: 3px;
/* ../images/icon-unknown.png */
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sCCxUsLubmldcAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAlklEQVQ4y2P8////fwYKAAsDAwPD4nWnGA6cuMXw9dtPnAqtjJUZ8hIdMMSZGBgYGPafuIlXMwMDA4OirDDDpPkHsBvw7dsvopyLzRAmUv2MbggLsRqXbjiFwoeFBxMDhWDUAGoZwM/HSZImLi42VANUFcSINoSLi41BS0US1QALI0UGXm4OogyQkxJisDBShPMZKc3OAAB3Jftfqk4+AAAAAElFTkSuQmCC);
background-position: center;
@@ -164,6 +174,12 @@ body {
width: 16px;
}
+/* Icon for folders. */
+.detail-icon[iconType="folder"] {
+ /* ../images/icon-image.png */
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sECBQhEXkZ9t4AAADnSURBVCjPnZI/agJBFMZ/b2KuYJqAeAAbsbUUUqZII3qNtRNtRLTzCnqBHCJl8AYhLIGIq4X/FlnUGYtZ3RSB3dkfDAwM73vf994ICf3626BnMKQhCFqfOe7XCGSo+Idm94PdyqcA0JndNQwgKe0Zt4VzdASwAqJAX27PKcS9TlEIgHoq1xqjljjZ92aG98mrFVh+f/46DyB2IUqhgC9yIvKAAiIAY0yObWhUoibiWh5ul4lAHg6bhV1bsVR9Dvz5jzfVBrI5KTzCsCnWQeDPw1uQzOntv7lH2DmPT8eXcuVF/mzX+VwB3O5RWcIfa4kAAAAASUVORK5CYII=);
+}
+
/* Icon for image files. */
.detail-icon[iconType="image"] {
/* ../images/icon-image.png */
diff --git a/chrome/browser/resources/file_manager/js/file_manager.js b/chrome/browser/resources/file_manager/js/file_manager.js
index 2407d40..04bd423 100644
--- a/chrome/browser/resources/file_manager/js/file_manager.js
+++ b/chrome/browser/resources/file_manager/js/file_manager.js
@@ -170,8 +170,7 @@ FileManager.prototype = {
* Call an asynchronous method on dirEntry, batching multiple callers.
*
* This batches multiple callers into a single invocation, calling all
- * interested parties back when the async call completes. If the async call
- * has already been made this will invoke the successCallback synchronously.
+ * interested parties back when the async call completes.
*
* The Entry method to be invoked should take two callbacks as parameters
* (one for success and one for failure), and it should invoke those
@@ -186,10 +185,10 @@ FileManager.prototype = {
* @param {DirectoryEntry} dirEntry The DirectoryEntry to apply the method
* to.
* @param {string} methodName The name of the method to dispatch.
- * @param {Function(*)} successCallback The function to invoke if the method
+ * @param {function(*)} successCallback The function to invoke if the method
* succeeds. The result of the method will be the one parameter to this
* callback.
- * @param {Function(*)} opt_errorCallback The function to invoke if the
+ * @param {function(*)} opt_errorCallback The function to invoke if the
* method fails. The result of the method will be the one parameter to
* this callback. If not provided, the default errorCallback will throw
* an exception.
@@ -201,7 +200,9 @@ FileManager.prototype = {
if (entry[resultCache]) {
// The result cache for this method already exists. Just invoke the
// successCallback with the result of the previuos call.
- successCallback(entry[resultCache]);
+ // Callback via a setTimeout so the sync/async semantics don't change
+ // based on whether or not the value is cached.
+ setTimeout(function() { successCallback(entry[resultCache]) }, 0);
return;
}
@@ -244,23 +245,89 @@ FileManager.prototype = {
* 'cachedSize_' property (if it doesn't already have one) containing the
* size of the file in bytes.
*
- * Note that if the size of the file is already known, the successCallback
- * will be invoked synchronously.
+ * @param {Entry} entry An HTML5 Entry object.
+ * @param {function(Entry)} successCallback The function to invoke once the
+ * file size is known.
*/
- function cacheFileSize(fileEntry, successCallback) {
- if ('cachedSize_' in fileEntry) {
- if (successCallback)
- successCallback(fileEntry);
+ function cacheEntrySize(entry, successCallback) {
+ if (entry.isDirectory) {
+ // No size for a directory, -1 ensures it's sorted before 0 length files.
+ entry.cachedSize_ = -1;
+ }
+
+ if ('cachedSize_' in entry) {
+ if (successCallback) {
+ // Callback via a setTimeout so the sync/async semantics don't change
+ // based on whether or not the value is cached.
+ setTimeout(function() { successCallback(entry) }, 0);
+ }
return;
}
- batchAsyncCall(fileEntry, 'file', function(file) {
- fileEntry.cachedSize_ = file.size;
+ batchAsyncCall(entry, 'file', function(file) {
+ entry.cachedSize_ = file.size;
if (successCallback)
- successCallback(fileEntry);
+ successCallback(entry);
});
}
+ /**
+ * Get the mtime of a file, caching the result.
+ *
+ * When this method completes, the fileEntry object will get a
+ * 'cachedMtime_' property (if it doesn't already have one) containing the
+ * last modified time of the file as a Date object.
+ *
+ * @param {Entry} entry An HTML5 Entry object.
+ * @param {function(Entry)} successCallback The function to invoke once the
+ * mtime is known.
+ */
+ function cacheEntryDate(entry, successCallback) {
+ if ('cachedMtime_' in entry) {
+ if (successCallback) {
+ // Callback via a setTimeout so the sync/async semantics don't change
+ // based on whether or not the value is cached.
+ setTimeout(function() { successCallback(entry) }, 0);
+ }
+ return;
+ }
+
+ if (entry.isFile) {
+ batchAsyncCall(entry, 'file', function(file) {
+ entry.cachedMtime_ = file.lastModifiedDate;
+ if (successCallback)
+ successCallback(entry);
+ });
+ } else {
+ batchAsyncCall(entry, 'getMetadata', function(metadata) {
+ entry.cachedMtime_ = metadata.modificationTime;
+ if (successCallback)
+ successCallback(entry);
+ });
+ }
+ }
+
+ /**
+ * Get the icon type of a file, caching the result.
+ *
+ * When this method completes, the fileEntry object will get a
+ * 'cachedIconType_' property (if it doesn't already have one) containing the
+ * icon type of the file as a string.
+ *
+ * The successCallback is always invoked synchronously, since this does not
+ * actually require an async call. You should not depend on this, as it may
+ * change if we were to start reading magic numbers (for example).
+ *
+ * @param {Entry} entry An HTML5 Entry object.
+ * @param {function(Entry)} successCallback The function to invoke once the
+ * icon type is known.
+ */
+ function cacheEntryIconType(entry, successCallback) {
+ entry.cachedIconType_ = getIconType(entry);
+ if (successCallback)
+ setTimeout(function() { successCallback(entry) }, 0);
+ }
+
// Public statics.
/**
@@ -304,39 +371,47 @@ FileManager.prototype = {
this.okButton_ = this.dialogDom_.querySelector('.ok');
this.cancelButton_ = this.dialogDom_.querySelector('.cancel');
- if (this.dialogType_ == FileManager.DialogType.SELECT_SAVEAS_FILE) {
- this.filenameInput_.addEventListener(
- 'keyup', this.onFilenameInputKeyUp_.bind(this));
- this.filenameInput_.addEventListener(
- 'focus', this.onFilenameInputFocus_.bind(this));
- } else {
- var label = this.dialogDom_.querySelector('.filename-label');
- label.style.visibility = 'hidden';
- this.filenameInput_.style.visibility = 'hidden';
- }
+ this.filenameInput_.addEventListener(
+ 'keyup', this.onFilenameInputKeyUp_.bind(this));
+ this.filenameInput_.addEventListener(
+ 'focus', this.onFilenameInputFocus_.bind(this));
this.okButton_.addEventListener('click', this.onOk_.bind(this));
this.cancelButton_.addEventListener('click', this.onCancel_.bind(this));
+ this.dialogDom_.querySelector('button.new-folder').addEventListener(
+ 'click', this.onNewFolderButtonClick_.bind(this));
+
+ var ary = this.dialogDom_.querySelectorAll('[visibleif]');
+ for (var i = 0; i < ary.length; i++) {
+ var expr = ary[i].getAttribute('visibleif');
+ if (!eval(expr))
+ ary[i].style.visibility = 'hidden';
+ }
+
// Populate the static localized strings.
i18nTemplate.process(this.document_, localStrings.templateData);
// Set up the detail table.
var dataModel = new cr.ui.table.TableDataModel([]);
- dataModel.idField = 'name';
- dataModel.defaultSortField = 'name';
+ dataModel.sort('name');
+ dataModel.addEventListener('sorted',
+ this.onDataModelSorted_.bind(this));
+ dataModel.prepareSort = this.prepareSort_.bind(this);
var columns = [
+ new cr.ui.table.TableColumn('cachedIconType_', '', 5.4),
new cr.ui.table.TableColumn('name', str('NAME_COLUMN_LABEL'), 64),
new cr.ui.table.TableColumn('cachedSize_',
- str('SIZE_COLUMN_LABEL'), 15),
+ str('SIZE_COLUMN_LABEL'), 15.5),
new cr.ui.table.TableColumn('cachedMtime_',
str('DATE_COLUMN_LABEL'), 21)
];
- columns[0].renderFunction = this.renderName_.bind(this);
- columns[1].renderFunction = this.renderSize_.bind(this);
- columns[2].renderFunction = this.renderDate_.bind(this);
+ columns[0].renderFunction = this.renderIconType_.bind(this);
+ columns[1].renderFunction = this.renderName_.bind(this);
+ columns[2].renderFunction = this.renderSize_.bind(this);
+ columns[3].renderFunction = this.renderDate_.bind(this);
this.table = this.dialogDom_.querySelector('.detail-table');
cr.ui.Table.decorate(this.table);
@@ -392,7 +467,53 @@ FileManager.prototype = {
};
/**
- * Render the Name column of the detail table.
+ * Cache necessary data before a sort happens.
+ *
+ * This is called by the table code before a sort happens, so that we can
+ * go fetch data for the sort field that we may not have yet.
+ */
+ FileManager.prototype.prepareSort_ = function(field, callback) {
+ var cacheFunction;
+
+ if (field == 'cachedMtime_') {
+ cacheFunction = cacheEntryDate;
+ } else if (field == 'cachedSize_') {
+ cacheFunction = cacheEntrySize;
+ } else if (field == 'cachedIconType_') {
+ cacheFunction = cacheEntryIconType;
+ } else {
+ callback();
+ return;
+ }
+
+ function checkCount() {
+ if (uncachedCount == 0) {
+ // Callback via a setTimeout so the sync/async semantics don't change
+ // based on whether or not the value is cached.
+ setTimeout(callback, 0);
+ }
+ }
+
+ var dataModel = this.table.dataModel;
+ var uncachedCount = dataModel.length;
+
+ for (var i = uncachedCount - 1; i >= 0 ; i--) {
+ var entry = dataModel.item(i);
+ if (field in entry) {
+ uncachedCount--;
+ } else {
+ cacheFunction(entry, function() {
+ uncachedCount--;
+ checkCount();
+ });
+ }
+ }
+
+ checkCount();
+ }
+
+ /**
+ * Render the type column of the detail table.
*
* Invoked by cr.ui.Table when a file needs to be rendered.
*
@@ -400,20 +521,28 @@ FileManager.prototype = {
* @param {string} columnId The id of the column to be rendered.
* @param {cr.ui.Table} table The table doing the rendering.
*/
- FileManager.prototype.renderName_ = function(entry, columnId, table) {
- var container = this.document_.createElement('div');
-
+ FileManager.prototype.renderIconType_ = function(entry, columnId, table) {
var icon = this.document_.createElement('div');
- icon.className = 'detail-icon'
- icon.setAttribute('iconType', getIconType(entry));
- container.appendChild(icon);
+ icon.className = 'detail-icon';
+ entry.cachedIconType_ = getIconType(entry);
+ icon.setAttribute('iconType', entry.cachedIconType_);
+ return icon;
+ }
+ /**
+ * Render the Name column of the detail table.
+ *
+ * Invoked by cr.ui.Table when a file needs to be rendered.
+ *
+ * @param {Entry} entry The Entry object to render.
+ * @param {string} columnId The id of the column to be rendered.
+ * @param {cr.ui.Table} table The table doing the rendering.
+ */
+ FileManager.prototype.renderName_ = function(entry, columnId, table) {
var label = this.document_.createElement('div');
label.className = 'detail-name';
label.textContent = entry.name;
- container.appendChild(label);
-
- return container;
+ return label;
};
/**
@@ -427,16 +556,14 @@ FileManager.prototype = {
var div = this.document_.createElement('div');
div.className = 'detail-size';
- if (entry.isFile) {
- div.textContent = '...';
- cacheFileSize(entry, function(fileEntry) {
- div.textContent = cr.locale.bytesToSi(fileEntry.cachedSize_);
- });
- } else {
- // No size for a directory, -1 ensures it's sorted before 0 length files.
- entry.cachedSize_ = -1;
- div.textContent = '';
- }
+ div.textContent = '...';
+ cacheEntrySize(entry, function(entry) {
+ if (entry.cachedSize_ == -1) {
+ div.textContent = '';
+ } else {
+ div.textContent = cr.locale.bytesToSi(entry.cachedSize_);
+ }
+ });
return div;
};
@@ -451,21 +578,13 @@ FileManager.prototype = {
FileManager.prototype.renderDate_ = function(entry, columnId, table) {
var div = this.document_.createElement('div');
div.className = 'detail-date';
+
div.textContent = '...';
- if (entry.isFile) {
- batchAsyncCall(entry, 'file', function(file) {
- entry.cachedMtime_ = file.lastModifiedDate.getTime();
- div.textContent = cr.locale.formatDate(file.lastModifiedDate,
- str('SHORT_DATE_FORMAT'));
- });
- } else {
- batchAsyncCall(entry, 'getMetadata', function(metadata) {
- entry.cachedMtime_ = metadata.modificationTime.getTime();
- div.textContent = cr.locale.formatDate(metadata.modificationTime,
- str('SHORT_DATE_FORMAT'));
- });
- }
+ cacheEntryDate(entry, function(entry) {
+ div.textContent = cr.locale.formatDate(entry.cachedMtime_,
+ str('LOCALE_FMT_DATE_SHORT'));
+ });
return div;
};
@@ -542,7 +661,7 @@ FileManager.prototype = {
var self = this;
- function computeNextFile(fileEntry) {
+ function cacheNextFile(fileEntry) {
if (fileEntry) {
// We're careful to modify the 'selection', rather than 'self.selection'
// here, just in case the selection has changed since this summarization
@@ -551,13 +670,13 @@ FileManager.prototype = {
}
if (pendingFiles.length) {
- cacheFileSize(pendingFiles.pop(), computeNextFile);
+ cacheEntrySize(pendingFiles.pop(), cacheNextFile);
} else {
self.dispatchEvent(new cr.Event('selection-summarized'));
}
};
- computeNextFile();
+ cacheNextFile();
};
/**
@@ -677,6 +796,16 @@ FileManager.prototype = {
};
/**
+ * Invoked by the table dataModel after a sort completes.
+ *
+ * We use this hook to make sure selected files stay visible after a sort.
+ */
+ FileManager.prototype.onDataModelSorted_ = function() {
+ var i = this.table.selectionModel.leadIndex;
+ this.table.scrollIntoView(i);
+ }
+
+ /**
* Update the selection summary UI when the selection summarization completes.
*/
FileManager.prototype.onSelectionSummarized_ = function() {
@@ -758,12 +887,28 @@ FileManager.prototype = {
* @param {cr.Event} event The directory-changed event.
*/
FileManager.prototype.onDirectoryChanged_ = function(event) {
+ this.rescanDirectory_();
+ }
+
+ /**
+ * Rescan the current directory, refreshing the list.
+ *
+ * @param {function()} opt_callback Optional function to invoke when the
+ * rescan is complete.
+ */
+ FileManager.prototype.rescanDirectory_ = function(opt_callback) {
var self = this;
var reader;
function onReadSome(entries) {
- if (entries.length == 0)
+ if (entries.length == 0) {
+ if (self.table.dataModel.sortStatus.field != 'name')
+ self.table.dataModel.updateIndex(0);
+
+ if (opt_callback)
+ opt_callback();
return;
+ }
// Splice takes the to-be-spliced-in array as individual parameters,
// rather than as an array, so we need to perform some acrobatics...
@@ -773,7 +918,7 @@ FileManager.prototype = {
// TODO(rginda): User should be able to override this. Support for other
// commonly hidden patterns might be nice too.
spliceArgs = spliceArgs.filter(function(e) {
- return e.name.substr(0, 1) != '.';
+ return e.name.substr(0, 1) != '.';
});
spliceArgs.unshift(0, 0); // index, deleteCount
@@ -813,6 +958,44 @@ FileManager.prototype = {
}, 0);
};
+ FileManager.prototype.onNewFolderButtonClick_ = function(event) {
+ var name = '';
+
+ while (1) {
+ name = window.prompt(str('NEW_FOLDER_PROMPT'), name);
+ if (!name)
+ return;
+
+ if (name.indexOf('/') == -1)
+ break;
+
+ alert(strf('ERROR_INVALID_FOLDER_CHARACTER', '/'));
+ }
+
+ var self = this;
+
+ function onSuccess(dirEntry) {
+ self.rescanDirectory_(function () {
+ for (var i = 0; i < self.table.dataModel.length; i++) {
+ if (self.table.dataModel.item(i).name == dirEntry.name) {
+ self.table.selectionModel.selectedIndex = i;
+ self.table.scrollIndexIntoView(i);
+ self.table.focus();
+ return;
+ }
+ };
+ });
+ }
+
+ function onError(err) {
+ window.alert(strf('ERROR_CREATING_FOLDER', name,
+ util.getFileErrorMnemonic(err.code)));
+ }
+
+ this.currentDirEntry_.getDirectory(name, {create: true, exclusive: true},
+ onSuccess, onError);
+ };
+
/**
* Handle a click of the cancel button.
*
@@ -832,6 +1015,8 @@ FileManager.prototype = {
* @param {Event} event The click event.
*/
FileManager.prototype.onOk_ = function(event) {
+ var currentPath = this.currentDirEntry_.fullPath.substr(1);
+
if (this.dialogType_ == FileManager.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.
@@ -839,8 +1024,7 @@ FileManager.prototype = {
if (!filename)
throw new Error('Missing filename!');
- chrome.fileBrowserPrivate.selectFile(this.currentDirEntry_.fullPath +
- '/' + filename, 0);
+ chrome.fileBrowserPrivate.selectFile(currentPath + '/' + filename, 0);
window.close();
return;
}
@@ -861,7 +1045,7 @@ FileManager.prototype = {
continue;
}
- ary.push(entry.fullPath);
+ ary.push(currentPath + '/' + entry.name);
}
// Multi-file selection has no other restrictions.
diff --git a/chrome/browser/resources/file_manager/js/mock_chrome.js b/chrome/browser/resources/file_manager/js/mock_chrome.js
index 6060d4a..0eedfe1 100644
--- a/chrome/browser/resources/file_manager/js/mock_chrome.js
+++ b/chrome/browser/resources/file_manager/js/mock_chrome.js
@@ -56,8 +56,6 @@ chrome.fileBrowserPrivate = {
BODY_FONT_FAMILY: 'sans-serif',
BODY_FONT_SIZE: '13px',
- FILES_DISPLAYED_SUMMARY: '%1 Files Displayed',
- FILES_SELECTED_SUMMARY: '%1 Files Selected',
FILE_IS_DIRECTORY: 'Folder',
PARENT_DIRECTORY: 'Parent Directory',
@@ -67,6 +65,10 @@ chrome.fileBrowserPrivate = {
DATE_COLUMN_LABEL: 'Date',
PREVIEW_COLUMN_LABEL: 'Preview',
+ ERROR_CREATING_FOLDER: 'Unable to create folder "$1": $2',
+ ERROR_INVALID_FOLDER_CHARACTER: 'Invalid character in folder name: $1',
+ NEW_FOLDER_PROMPT: 'Enter a name for the new folder',
+ NEW_FOLDER_BUTTON_LABEL: 'New Folder',
FILENAME_LABEL: 'File Name',
CANCEL_LABEL: 'Cancel',
diff --git a/chrome/browser/resources/file_manager/js/util.js b/chrome/browser/resources/file_manager/js/util.js
index 92ce50674..ad11c92 100644
--- a/chrome/browser/resources/file_manager/js/util.js
+++ b/chrome/browser/resources/file_manager/js/util.js
@@ -41,12 +41,16 @@ var util = {
*/
installFileErrorToString: function() {
FileError.prototype.toString = function() {
- for (var key in FileError) {
- if (key.search(/_ERR$/) != -1 && FileError[key] == this.code)
- return '[object FileError: ' + key + ']';
- }
-
- return '[object FileError: ' + this.code + ']';
+ return '[object FileError: ' + util.getFileErrorMnemonic(this.code) + ']';
}
},
+
+ getFileErrorMnemonic: function(code) {
+ for (var key in FileError) {
+ if (key.search(/_ERR$/) != -1 && FileError[key] == code)
+ return key;
+ }
+
+ return code;
+ }
};
diff --git a/chrome/browser/resources/file_manager/main.html b/chrome/browser/resources/file_manager/main.html
index 25abc42..4c47ed7 100644
--- a/chrome/browser/resources/file_manager/main.html
+++ b/chrome/browser/resources/file_manager/main.html
@@ -75,6 +75,9 @@
<div class=dialog-header>
<div class=breadcrumbs></div>
<!-- TODO(rginda): Detail/Thumbnail buttons. -->
+ <button i18n-content=NEW_FOLDER_BUTTON_LABEL class='new-folder'
+ visibleif='this.dialogType_ == "saveas-file"'
+ ></button>
</div>
<div class=dialog-body>
<div class=detail-table></div>
@@ -92,8 +95,11 @@
</div>
</div>
<div class=dialog-footer>
- <div class=filename-label i18n-content=FILENAME_LABEL>[FILENAME]</div>
- <input type=text class=filename-input spellcheck=false>
+ <div class=filename-label i18n-content=FILENAME_LABEL
+ visibleif='this.dialogType_ == "saveas-file"'
+ >[FILENAME]</div>
+ <input type=text class=filename-input spellcheck=false
+ visibleif='this.dialogType_ == "saveas-file"'>
<div class=horizontal-spacer></div>
<button class=ok disabled>[OK]</button>
<button class=cancel i18n-content=CANCEL_LABEL>[CANCEL]</button>
diff --git a/chrome/browser/resources/shared/js/cr/locale.js b/chrome/browser/resources/shared/js/cr/locale.js
index 2c18735..f6dfab5 100644
--- a/chrome/browser/resources/shared/js/cr/locale.js
+++ b/chrome/browser/resources/shared/js/cr/locale.js
@@ -135,7 +135,7 @@ cr.define('cr', function() {
case '%': return '%';
case 'a': return fmt(strings.dayShort[date.getDay()]);
case 'b': return fmt(strings.monthShort[date.getMonth()]);
- case 'd': return fmt(date.getDate() + 1, 2);
+ case 'd': return fmt(date.getDate(), 2);
case 'Y': return date.getFullYear();
default:
console.log('Unknown format specifier: ' + code);