summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrginda@chromium.org <rginda@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-04 17:47:11 +0000
committerrginda@chromium.org <rginda@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-04 17:47:11 +0000
commit2f0c9bd240b4ae5b76b60c7b8d5570e8ff4f3044 (patch)
tree476d5112bc6e7ebff244581f7974e3f0283d6dab
parent3d037ce75a4ae3dc07f062bad8afc5079a07d282 (diff)
downloadchromium_src-2f0c9bd240b4ae5b76b60c7b8d5570e8ff4f3044.zip
chromium_src-2f0c9bd240b4ae5b76b60c7b8d5570e8ff4f3044.tar.gz
chromium_src-2f0c9bd240b4ae5b76b60c7b8d5570e8ff4f3044.tar.bz2
Initial file manager dialog implementation.
Here's the initial file manager implementation and development harness. After applying this patch you should be able to visit file://path/to/chrome/src/chrome/browser/resources/file_manager/harness.html to see dialog in the harness. You'll have to start chrome with `chrome --allow-file-access-from-files --unlimited-quota-for-files` Then click on the 'Choose File' button at the bottom of the harness to add files to the filesystem used by the dialog. Watch the js console for status messagesa bout the import, and reload the page when the import is complete. The 'Choose File' dialog wonn't allow you to import a directory structure, so you may want to use bin/squashdir.py to translate a directory tree into something that can be reconstituted by the harness. This code only implements the 'select one file' mode at the moment, the other modes are coming soon. Thumbnails are currently displayed using a data: url for the entire image file. This will change when soon when we have a thumbnail data source, or a js thumbnailer. Icons and preview images are only placeholders until the real assets are available. BUG= TEST= Review URL: http://codereview.chromium.org/6723010 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80335 0039d316-1c4b-4281-b951-d872f2087c98
-rwxr-xr-xchrome/browser/resources/file_manager/bin/squashdir.py57
-rw-r--r--chrome/browser/resources/file_manager/css/file_manager.css230
-rw-r--r--chrome/browser/resources/file_manager/harness.html50
-rw-r--r--chrome/browser/resources/file_manager/images/icon-image.pngbin0 -> 486 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/icon-unknown.pngbin0 -> 315 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/preview-background.pngbin0 -> 214 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/preview-folder.pngbin0 -> 1473 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/preview-unknown.pngbin0 -> 382 bytes
-rw-r--r--chrome/browser/resources/file_manager/js/file_manager.js786
-rw-r--r--chrome/browser/resources/file_manager/js/harness.js185
-rw-r--r--chrome/browser/resources/file_manager/js/main.js25
-rw-r--r--chrome/browser/resources/file_manager/js/mock_chrome.js85
-rw-r--r--chrome/browser/resources/file_manager/js/util.js52
-rw-r--r--chrome/browser/resources/file_manager/main.html101
-rw-r--r--chrome/browser/resources/file_manager/manifest.json2
-rw-r--r--chrome/browser/resources/shared/css/table.css3
-rw-r--r--chrome/browser/resources/shared/js/cr/locale.js167
-rw-r--r--chrome/browser/resources/shared/js/local_strings.js8
18 files changed, 1722 insertions, 29 deletions
diff --git a/chrome/browser/resources/file_manager/bin/squashdir.py b/chrome/browser/resources/file_manager/bin/squashdir.py
new file mode 100755
index 0000000..e523a91
--- /dev/null
+++ b/chrome/browser/resources/file_manager/bin/squashdir.py
@@ -0,0 +1,57 @@
+#!/usr/bin/python
+# Copyright (c) 2011 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.
+
+import glob
+import os
+import shutil
+import stat
+import sys
+
+def usage():
+ print("""Usage: squashdir.py <dest-dir> <source-dir> ...
+
+Basic tool to copy a directory heirarchy into a flat space.
+
+This crawls an arbitrarily deep heirarchy of files and directories, and copies
+each file into the destination directory. The destination file name will
+include the relative path to the source file, with '^^' inserted between each
+directory name.
+
+The resulting directory can then be imported into the file manager test harness,
+which will reconstitute the directory structure.
+
+This is used to work around the fact that the FileList and File objects
+presented by <input type=file multiple> do not allow users to recurse a
+selected directory, nor do they provide information about directory structure.
+""")
+
+def status(msg):
+ sys.stderr.write(msg + '\n')
+
+def scan_path(dest, src, path):
+ abs_src = os.path.join(src, path)
+ statinfo = os.stat(abs_src)
+
+ basename = os.path.basename(path)
+
+ if not stat.S_ISDIR(statinfo.st_mode):
+ newname = os.path.join(dest, path.replace('/', '^^'))
+ status(newname)
+ shutil.copyfile(abs_src, newname)
+ else:
+ for child_path in glob.glob(abs_src + '/*'):
+ scan_path(dest, src, child_path[len(src) + 1:])
+
+if __name__ == '__main__':
+ if len(sys.argv) < 3 or sys.argv[1][0] == '-':
+ usage()
+ return
+
+ dest = sys.argv[1]
+ for src in sys.argv[2:]:
+ abs_src = os.path.abspath(src)
+ path = os.path.basename(abs_src)
+ abs_src = os.path.dirname(abs_src)
+ scan_path(dest, abs_src, path)
diff --git a/chrome/browser/resources/file_manager/css/file_manager.css b/chrome/browser/resources/file_manager/css/file_manager.css
index 0173e04..4b89896 100644
--- a/chrome/browser/resources/file_manager/css/file_manager.css
+++ b/chrome/browser/resources/file_manager/css/file_manager.css
@@ -1,8 +1,226 @@
/*
-Copyright (c) 2010 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.
-*/
-h1 {
- color: blue;
+ * Copyright (c) 2011 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.
+ */
+
+/* Outer frame of the dialog. */
+body {
+ -webkit-box-flex: 1;
+ -webkit-box-orient: vertical;
+ -webkit-user-select: none;
+ display: -webkit-box;
+ font-family: sans-serif;
+ font-size: 13px;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ position: absolute;
+ width: 100%;
+}
+
+/* The top title of the dialog. */
+.dialog-title {
+ -webkit-box-sizing: border-box;
+ -webkit-padding-start: 15px;
+ background-image: -webkit-linear-gradient(top, #fff,#f6f6f6);
+ border-bottom: 1px #d6d9e3 solid;
+ color: #42506c;
+ font-size: 15px;
+ font-weight: bold;
+ height: 32px;
+ padding-top: 8px;
+ padding-bottom: 8px;
+}
+
+/* Breadcrumbs and things under the title but above the list view. */
+.dialog-header {
+ -webkit-box-orient: horizontal;
+ display: -webkit-box;
+ margin: 15px;
+}
+
+/* Container for the detail and thumbnail (not implemented yet) list views. */
+.dialog-body {
+ -webkit-box-orient: horizontal;
+ -webkit-box-flex: 1;
+ border: 1px #aaa solid;
+ border-radius: 3px;
+ display: -webkit-box;
+ margin: 15px;
+ margin-top: 0;
+}
+
+/* Container for the ok/cancel buttons. */
+.dialog-footer {
+ -webkit-box-orient: horizontal;
+ display: -webkit-box;
+ margin: 15px;
+ margin-top: 0;
+}
+
+/* The container for breadcrumb elements. */
+.breadcrumbs {
+ -webkit-box-orient: horizontal;
+ -webkit-box-flex: 1;
+ display: -webkit-box;
+ font-size: 15px;
+ line-height: 15px;
+}
+
+/* A single directory name in the list of path breadcrumbs. */
+.breadcrumb-path {
+ color: #265692;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+/* The final breadcrumb, representing the current directory. */
+.breadcrumb-last {
+ color: #666;
+ cursor: inherit;
+ font-weight: bold;
+}
+
+/* The > arrow between breadcrumbs. */
+.breadcrumb-spacer {
+ -webkit-margin-start: 7px;
+ -webkit-margin-end: 4px;
+ color: #aaa;
+ font-size: 12px;
+}
+
+/* The cr.ui.Table representing the detailed file list. */
+.detail-table {
+ -webkit-box-orient: vertical;
+ -webkit-box-flex: 1;
+ display: -webkit-box;
+ border: 0;
+}
+
+/* The actual element containing the list items. */
+.detail-table > .list {
+ /* TODO(rginda): The list is not able to adjust to the available height,
+ * so we have to hardcode it for now.
+ */
+ height: 417px;
+}
+
+/* The right-column 'Preview' column container. */
+.preview-container {
+ -webkit-border-start: 1px #aaa solid;
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ width: 225px;
+}
+
+/* cr.ui.Table has a black focus border by default, which we don't want. */
+.detail-table:focus {
+ border: 0;
+}
+
+/* Table splitter element */
+.table-header-splitter {
+ -webkit-border-start: 1px #aaa solid;
+ background-color: inherit;
+ height: 20px;
+ margin-top: 4px;
+}
+
+/* Container for a table header. */
+.table-header {
+ -webkit-box-sizing: border-box;
+ border-bottom: 1px #aaa solid;
+ background-image: -webkit-linear-gradient(top, #f9f9f9, #e8e8e8);
+ height: 28px;
+}
+
+/* Text label in a table header. */
+.table-header-label {
+ margin-top: 6px;
+}
+
+/* The first child of a list cell. */
+.table-row-cell > * {
+ -webkit-margin-start: 5px;
+ -webkit-box-orient: horizontal;
+ -webkit-box-flex: 1;
+ display: -webkit-box;
+}
+
+/* Column text containers. */
+.detail-name, .detail-size, .detail-date {
+ padding-top: 2px;
+}
+
+/* The icon in the name column. */
+.detail-icon {
+ -webkit-margin-end: 3px;
+ background-image: url(../images/icon-unknown.png);
+ background-position: center;
+ background-repeat: no-repeat;
+ height: 24px;
+ width: 16px;
+}
+
+/* Icon for image files. */
+.detail-icon[iconType="image"] {
+ background-image: url(../images/icon-image.png);
+}
+
+/* The filename text in the preview pane. */
+.preview-filename {
+ -webkit-margin-start: 8px;
+ color: #666;
+ font-weight: bold;
+ margin-top: 10px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* The preview image. */
+.preview-img {
+ margin-top: 10px;
+ max-height: 300px;
+ max-width: 190px;
+}
+
+.preview-img[src=''] {
+ visibility: hidden;
+}
+
+/* Decoration when multiple images are selected. */
+.preview-img.multiple-selected {
+ -webkit-box-shadow: 5px 5px 0 #aaa;
+}
+
+/* Checkboard background to distinguish images with alpha channels. */
+.preview-img.transparent-background {
+ background-image: url(../images/preview-background.png);
+}
+
+/* The selection summary text at the bottom of the preview pane. */
+.preview-summary {
+ border-top: 1px #aaa solid;
+ color: #666;
+ font-weight: bold;
+ overflow: hidden;
+ padding: 5px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* A horizontal spring. */
+.horizontal-spacer {
+ -webkit-box-orient: horizontal;
+ -webkit-box-flex: 1;
+ display: -webkit-box;
+}
+
+/* A vertical spring. */
+.vertical-spacer {
+ -webkit-box-orient: vertical;
+ -webkit-box-flex: 1;
+ display: -webkit-box;
}
diff --git a/chrome/browser/resources/file_manager/harness.html b/chrome/browser/resources/file_manager/harness.html
new file mode 100644
index 0000000..a42758b
--- /dev/null
+++ b/chrome/browser/resources/file_manager/harness.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<!--
+ -- Copyright (c) 2011 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.
+ -->
+<html>
+ <head>
+ <style>
+ body {
+ font-family: sans-serif;
+ }
+
+ #screen {
+ position: relative;
+ width: 800px;
+ height: 620px;
+
+ background-image: -webkit-linear-gradient(top, #c6c6c6, #888);
+ }
+
+ #dialog {
+ position: absolute;
+ left: 40px;
+ top: 20px;
+ width: 720px;
+ height: 580px;
+ border-radius: 4px;
+ border: none;
+ -webkit-box-shadow: 0px 0px 20px rgba(0, 0, 0, 1);
+ background-color: white;
+ }
+ </style>
+
+ <script src='js/util.js'></script>
+ <script src='js/harness.js'></script>
+ </head>
+ <body>
+ <div id=screen>
+ <iframe id=dialog src='main.html'></iframe>
+ </div>
+ <div>
+ <button onclick='harness.onClearClick(event)'>Reset Filesystem</button>
+ | Add Files:
+ <input type='file' multiple onchange='harness.onFilesChange(event)'>
+ </div>
+
+ <script>harness.init()</script>
+ </body>
+</html>
diff --git a/chrome/browser/resources/file_manager/images/icon-image.png b/chrome/browser/resources/file_manager/images/icon-image.png
new file mode 100644
index 0000000..d67351b
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/icon-image.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/icon-unknown.png b/chrome/browser/resources/file_manager/images/icon-unknown.png
new file mode 100644
index 0000000..22696b6
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/icon-unknown.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/preview-background.png b/chrome/browser/resources/file_manager/images/preview-background.png
new file mode 100644
index 0000000..1c50c7b
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/preview-background.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/preview-folder.png b/chrome/browser/resources/file_manager/images/preview-folder.png
new file mode 100644
index 0000000..7b6a1d3
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/preview-folder.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/preview-unknown.png b/chrome/browser/resources/file_manager/images/preview-unknown.png
new file mode 100644
index 0000000..9fe515f
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/preview-unknown.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/js/file_manager.js b/chrome/browser/resources/file_manager/js/file_manager.js
index 6b1212a..470e66c 100644
--- a/chrome/browser/resources/file_manager/js/file_manager.js
+++ b/chrome/browser/resources/file_manager/js/file_manager.js
@@ -1,13 +1,783 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 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.
-function onLoad() {
- var args = '';
- var argstr = decodeURIComponent(document.location.search.substr(1));
- if (argstr)
- args = JSON.parse(argstr);
+/**
+ * FileManager constructor.
+ *
+ * FileManager objects encapsulate the functionality of the file selector
+ * dialogs, as well as the full screen file manager application (though the
+ * latter is not yet implemented).
+ *
+ * @param {HTMLElement} dialogDom The DOM node containing the prototypical
+ * dialog UI.
+ * @param {DOMFileSystem} filesystem The HTML5 filesystem object representing
+ * the root filesystem for the new FileManager.
+ * @param {Object} params A map of parameter names to values controlling the
+ * appearance of the FileManager. Names are:
+ * - type: A value from FileManager.DialogType defining what kind of
+ * dialog to present. Defaults to SELECT_OPEN_FILE.
+ * - title: The title for the dialog. Defaults to a localized string based
+ * on the dialog type.
+ */
+function FileManager(dialogDom, filesystem, params) {
+ console.log('Init FileManager: ' + dialogDom);
- document.body.appendChild(document.createTextNode(
- 'dialog arguments: ' + JSON.stringify(args)));
+ this.dialogDom_ = dialogDom;
+ this.filesystem_ = filesystem;
+ this.params_ = params || {type: FileManager.DialogType.SELECT_OPEN_FILE};
+
+ this.document_ = dialogDom.ownerDocument;
+ this.dialogType_ = this.params_.type;
+
+ // DirectoryEntry representing the current directory of the dialog.
+ this.currentDirEntry_ = null;
+
+ this.addEventListener('directory-changed',
+ this.onDirectoryChanged_.bind(this));
+ this.addEventListener('selection-summarized',
+ this.onSelectionSummarized_.bind(this));
+
+ this.initDom_();
+ this.initDialogType_();
+
+ this.changeDirectory('/');
+ this.summarizeSelection_();
+ this.updatePreview_();
}
+
+FileManager.prototype = {
+ __proto__: cr.EventTarget.prototype
+};
+
+// Anonymous "namespace".
+(function() {
+
+ // Private variables and helper functions.
+
+ /**
+ * Directory where we can find icons.
+ */
+ const ICON_PATH = 'images/';
+
+ /**
+ * Unicode codepoint for 'BLACK RIGHT-POINTING SMALL TRIANGLE'.
+ */
+ const RIGHT_TRIANGLE = '\u25b8';
+
+ /**
+ * Translated strings.
+ */
+ var localStrings;
+
+ /**
+ * Map of icon types to regular expressions.
+ *
+ * The first regexp to match the file name determines the icon type
+ * assigned to dom elements for a file. Order of evaluation is not
+ * defined, so don't depend on it.
+ */
+ const iconTypes = {
+ 'image': /\.(png|gif|jpe?g)$/i,
+ // TODO(rginda): Uncomment these rules when we get the graphic assets.
+ // 'document' : /\.(txt|doc|docx|wpd|odt|ps)$/i,
+ // 'pdf' : /\.(pdf)$/i,
+ // 'drawing': /\.(svg)$/i,
+ // 'spreadsheet': /\.(xls|xlsx)$/i,
+ // 'presentation': /\.(ppt|pptx)$/i
+ };
+
+ /**
+ * Return a translated string.
+ *
+ * Wrapper function to make dealing with translated strings more concise.
+ * Equivilant to localStrings.getString(id).
+ *
+ * @param {string} id The id of the string to return.
+ * @return {string} The translated string.
+ */
+ function str(id) {
+ return localStrings.getString(id);
+ }
+
+ /**
+ * Return a translated string with arguments replaced.
+ *
+ * Wrapper function to make dealing with translated strings more concise.
+ * Equivilant to localStrings.getStringF(id, ...).
+ *
+ * @param {string} id The id of the string to return.
+ * @param {...string} The values to replace into the string.
+ * @return {string} The translated string with replaced values.
+ */
+ function strf(id, var_args) {
+ return localStrings.getStringF.apply(localStrings, arguments);
+ }
+
+ /**
+ * Get the icon type for a given Entry.
+ *
+ * @param {Entry} entry An Entry subclass (FileEntry or DirectoryEntry).
+ * @return {string} One of the keys from FileManager.iconTypes, or
+ * 'unknown'.
+ */
+ function getIconType(entry) {
+ if (entry.isDirectory)
+ return 'folder';
+
+ for (var name in this.iconTypes) {
+ var value = this.iconTypes[name];
+
+ if (value instanceof RegExp) {
+ if (value.test(entry.name))
+ return name;
+ } else if (typeof value == 'function') {
+ try {
+ if (value(entry))
+ return name;
+ } catch (ex) {
+ console.error('Caught exception while evaluating iconType: ' +
+ name, ex);
+ }
+ } else {
+ console.log('Unexpected value in iconTypes[' + name + ']: ' + value);
+ }
+ }
+
+ return 'unknown';
+ }
+
+ /**
+ * 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.
+ *
+ * The Entry method to be invoked should take two callbacks as parameters
+ * (one for success and one for failure), and it should invoke those
+ * callbacks with a single parameter representing the result of the call.
+ * Example methods are Entry.getMetadata() and FileEntry.file().
+ *
+ * Warning: Because this method caches the first result, subsequent changes
+ * to the entry will not be visible to callers.
+ *
+ * Error results are never cached.
+ *
+ * @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
+ * succeeds. The result of the method will be the one parameter to this
+ * callback.
+ * @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.
+ */
+ function batchAsyncCall(entry, methodName, successCallback,
+ opt_errorCallback) {
+ var resultCache = methodName + '_resultCache_';
+
+ 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]);
+ return;
+ }
+
+ if (!opt_errorCallback) {
+ opt_errorCallback = util.ferr('Error calling ' + methodName + ' for: ' +
+ entry.fullPath);
+ }
+
+ var observerList = methodName + '_observers_';
+
+ if (entry[observerList]) {
+ // The observer list already exists, indicating we have a pending call
+ // out to this method. Add this caller to the list of observers and
+ // bail out.
+ entry[observerList].push([successCallback, opt_errorCallback]);
+ return;
+ }
+
+ entry[observerList] = [[successCallback, opt_errorCallback]];
+
+ function onComplete(success, result) {
+ if (success)
+ entry[resultCache] = result;
+
+ for (var i = 0; i < entry[observerList].length; i++) {
+ entry[observerList][i][success ? 0 : 1](result);
+ }
+
+ delete entry[observerList];
+ };
+
+ entry[methodName](function(rv) { onComplete(true, rv) },
+ function(rv) { onComplete(false, rv) });
+ }
+
+ /**
+ * Get the size of a file, caching the result.
+ *
+ * When this method completes, the fileEntry object will get a
+ * '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.
+ */
+ function cacheFileSize(fileEntry, successCallback) {
+ if ('cachedSize_' in fileEntry) {
+ if (successCallback)
+ successCallback(fileEntry);
+ return;
+ }
+
+ batchAsyncCall(fileEntry, 'file', function(file) {
+ fileEntry.cachedSize_ = file.size;
+ if (successCallback)
+ successCallback(fileEntry);
+ });
+ }
+
+ // Public statics.
+
+ /**
+ * List of dialog types.
+ *
+ * Keep this in sync with FileManagerDialog::GetDialogTypeAsString.
+ *
+ * @enum {string}
+ */
+ FileManager.DialogType = {
+ SELECT_FOLDER: 'folder',
+ SELECT_SAVEAS_FILE: 'saveas-file',
+ SELECT_OPEN_FILE: 'open-file',
+ SELECT_OPEN_MULTI_FILE: 'open-multi-file'
+ };
+
+ /**
+ * Load translated strings.
+ */
+ FileManager.initStrings = function(callback) {
+ chrome.fileBrowserPrivate.getStrings(function(strings) {
+ localStrings = new LocalStrings(strings);
+ cr.initLocale(strings);
+
+ if (callback)
+ callback();
+ });
+ };
+
+ // Instance methods.
+
+ /**
+ * One-time initialization of various DOM nodes.
+ */
+ FileManager.prototype.initDom_ = function() {
+ // Cache nodes we'll be manipulating.
+ this.previewImage_ = this.dialogDom_.querySelector('.preview-img');
+ this.previewFilename_ = this.dialogDom_.querySelector('.preview-filename');
+ this.previewSummary_ = this.dialogDom_.querySelector('.preview-summary');
+ this.okButton_ = this.dialogDom_.querySelector('.ok');
+ this.cancelButton_ = this.dialogDom_.querySelector('.cancel');
+
+ this.okButton_.addEventListener('click', this.onOk_.bind(this));
+ this.cancelButton_.addEventListener('click', this.onCancel_.bind(this));
+
+ this.dialogDom_.querySelector('.preview-label').textContent =
+ str('PREVIEW_COLUMN_LABEL');
+
+ // Set up the detail table.
+ var dataModel = new cr.ui.table.TableDataModel([]);
+ dataModel.idField = 'name';
+ dataModel.defaultSortField = 'name';
+
+ var columns = [
+ new cr.ui.table.TableColumn('name', str('NAME_COLUMN_LABEL'), 64),
+ new cr.ui.table.TableColumn('cachedSize_',
+ str('SIZE_COLUMN_LABEL'), 15),
+ 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);
+
+ this.table = this.dialogDom_.querySelector('.detail-table');
+ cr.ui.Table.decorate(this.table);
+ this.table.dataModel = dataModel;
+ this.table.columnModel = new cr.ui.table.TableColumnModel(columns);
+
+ this.table.addEventListener(
+ 'dblclick', this.onDetailDoubleClick_.bind(this));
+ this.table.selectionModel.addEventListener(
+ 'change', this.onDetailSelectionChanged_.bind(this));
+ };
+
+ /**
+ * Tweak the UI to become a particular kind of dialog, as determined by the
+ * dialog type parameter passed to the constructor.
+ */
+ FileManager.prototype.initDialogType_ = function() {
+ var defaultTitle;
+ var okLabel = str('OPEN_LABEL');
+ var cancelLabel = str('CANCEL_LABEL');
+
+ switch (this.dialogType_) {
+ case FileManager.DialogType.SELECT_FOLDER:
+ defaultTitle = str('SELECT_FOLDER_TITLE');
+ break;
+
+ case FileManager.DialogType.SELECT_OPEN_FILE:
+ defaultTitle = str('SELECT_OPEN_FILE_TITLE');
+ break;
+
+ case FileManager.DialogType.SELECT_OPEN_MULTI_FILE:
+ defaultTitle = str('SELECT_OPEN_MULTI_FILE_TITLE');
+ break;
+
+ case FileManager.DialogType.SELECT_SAVEAS_FILE:
+ defaultTitle = str('SELECT_SAVEAS_FILE_TITLE');
+ okLabel = str('SAVE_LABEL');
+ break;
+
+ default:
+ throw new Error('Unknown dialog type: ' + this.dialogType_);
+ }
+
+ this.okButton_.textContent = okLabel;
+ this.cancelButton_.textContent = cancelLabel;
+
+ dialogTitle = this.params_.title || defaultTitle;
+ this.dialogDom_.querySelector('.dialog-title').textContent = dialogTitle;
+ };
+
+ /**
+ * 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 container = this.document_.createElement('div');
+
+ var icon = this.document_.createElement('div');
+ icon.className = 'detail-icon'
+ icon.setAttribute('iconType', getIconType(entry));
+ container.appendChild(icon);
+
+ var label = this.document_.createElement('div');
+ label.className = 'detail-name';
+ label.textContent = entry.name;
+ container.appendChild(label);
+
+ return container;
+ };
+
+ /**
+ * Render the Size column of the detail table.
+ *
+ * @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.renderSize_ = function(entry, columnId, table) {
+ 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 = '';
+ }
+
+ return div;
+ };
+
+ /**
+ * Render the Date column of the detail table.
+ *
+ * @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.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'));
+ });
+ }
+
+ return div;
+ };
+
+ /**
+ * Compute summary information about the current selection.
+ *
+ * This method dispatches the 'selection-summarized' event when it completes.
+ * Depending on how many of the selected files already have known sizes, the
+ * dispatch may happen immediately, or after a number of async calls complete.
+ */
+ FileManager.prototype.summarizeSelection_ = function() {
+ var selection = this.selection = {
+ entries: [],
+ leadEntry: null,
+ totalCount: 0,
+ fileCount: 0,
+ directoryCount: 0,
+ bytes: 0,
+ iconType: null,
+ };
+
+ this.previewSummary_.textContent = str('COMPUTING_SELECTION');
+
+ var selectedIndexes = this.table.selectionModel.selectedIndexes;
+ if (!selectedIndexes.length) {
+ cr.dispatchSimpleEvent(this, 'selection-summarized');
+ return;
+ }
+
+ var fileCount = 0;
+ var byteCount = 0;
+ var pendingFiles = [];
+
+ for (var i = 0; i < selectedIndexes.length; i++) {
+ var entry = this.table.dataModel.item(selectedIndexes[i]);
+
+ selection.entries.push(entry);
+
+ if (selection.iconType == null) {
+ selection.iconType = getIconType(entry);
+ } else if (selection.iconType != 'unknown') {
+ var iconType = getIconType(entry);
+ if (selection.iconType != iconType)
+ selection.iconType = 'unknown';
+ }
+
+ selection.totalCount++;
+
+ if (entry.isFile) {
+ if (!('cachedSize_' in entry)) {
+ // Any file that hasn't been rendered may be missing its cachedSize_
+ // property. For example, visit a large file list, and press ctrl-a
+ // to select all. In this case, we need to asynchronously get the
+ // sizes for these files before telling the world the selection has
+ // been summarized. See the 'computeNextFile' logic below.
+ pendingFiles.push(entry);
+ continue;
+ } else {
+ selection.bytes += entry.cachedSize_;
+ }
+ selection.fileCount += 1;
+ } else {
+ selection.directoryCount += 1;
+ }
+ }
+
+ var leadIndex = this.table.selectionModel.leadIndex;
+ if (leadIndex > -1) {
+ selection.leadEntry = this.table.dataModel.item(leadIndex);
+ } else {
+ selection.leadEntry = selection.entries[0];
+ }
+
+ var self = this;
+
+ function computeNextFile(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
+ // began.
+ selection.bytes += fileEntry.cachedSize_;
+ }
+
+ if (pendingFiles.length) {
+ cacheFileSize(pendingFiles.pop(), computeNextFile);
+ } else {
+ self.dispatchEvent(new cr.Event('selection-summarized'));
+ }
+ };
+
+ computeNextFile();
+ };
+
+ /**
+ * Update the breadcrumb display to reflect the current directory.
+ */
+ FileManager.prototype.updateBreadcrumbs_ = function() {
+ var bc = this.dialogDom_.querySelector('.breadcrumbs');
+ bc.innerHTML = '';
+
+ var fullPath = this.currentDirEntry_.fullPath.replace(/\/$/, '');
+ var pathNames = fullPath.split('/');
+ var path = '';
+
+ for (var i = 0; i < pathNames.length; i++) {
+ var pathName = pathNames[i];
+ path += pathName + '/';
+
+ var div = this.document_.createElement('div');
+ div.className = 'breadcrumb-path';
+ div.textContent = pathNames[i] || str('ROOT_DIRECTORY_LABEL');
+ div.path = path;
+ div.addEventListener('click', this.onBreadcrumbClick_.bind(this));
+ bc.appendChild(div);
+
+ if (i == pathNames.length - 1) {
+ div.classList.add('breadcrumb-last');
+ } else {
+ var spacer = this.document_.createElement('div');
+ spacer.className = 'breadcrumb-spacer';
+ spacer.textContent = RIGHT_TRIANGLE;
+ bc.appendChild(spacer);
+ }
+ }
+ };
+
+ /**
+ * Update the preview panel to display a given entry.
+ *
+ * The selection summary line is handled by the onSelectionSummarized handler
+ * rather than this function, because summarization may not complete quickly.
+ */
+ FileManager.prototype.updatePreview_ = function() {
+ // Clear the preview image first, in case the thumbnail takes long to load.
+ this.previewImage_.src = '';
+ // The transparent-background class is used to display the checkerboard
+ // background for image thumbnails. We don't want to display it for
+ // non-thumbnail preview images.
+ this.previewImage_.classList.remove('transparent-background');
+ // The multiple-selected class indicates that more than one entry is
+ // selcted.
+ this.previewImage_.classList.remove('multiple-selected');
+
+ if (!this.selection.totalCount) {
+ this.previewFilename_.textContent = '';
+ return;
+ }
+
+ this.previewFilename_.textContent = this.selection.leadEntry.name;
+
+ var entry = this.selection.leadEntry;
+ var iconType = getIconType(entry);
+ if (iconType != 'image') {
+ // Not an image, display a canned clip-art graphic.
+ this.previewImage_.src = ICON_PATH + 'preview-' + iconType + '.png';
+ } else {
+ // File is an image, fetch the thumbnail.
+ var fileManager = this;
+
+ batchAsyncCall(entry, 'file', function(file) {
+ var reader = new FileReader();
+
+ reader.onerror = util.ferr('Error reading preview: ' + entry.fullPath);
+ reader.onloadend = function(e) {
+ fileManager.previewImage_.src = this.result;
+ fileManager.previewImage_.classList.add('transparent-background');
+ if (fileManager.selection.totalCount > 1)
+ fileManager.previewImage_.classList.add('multiple-selected');
+ };
+
+ reader.readAsDataURL(file);
+ });
+ }
+ };
+
+ /**
+ * Change the current directory.
+ *
+ * Dispatches the 'directory-changed' event when the directory is successfully
+ * changed.
+ *
+ * @param {string} path The absolute path to the new directory.
+ */
+ FileManager.prototype.changeDirectory = function(path) {
+ var self = this;
+
+ function onPathFound(dirEntry) {
+ if (self.currentDirEntry_ &&
+ self.currentDirEntry_.fullPath == dirEntry.fullPath) {
+ // Directory didn't actually change.
+ return;
+ }
+
+ var e = new cr.Event('directory-changed');
+ e.previousDirEntry = self.currentDirEntry_;
+ e.newDirEntry = dirEntry;
+ self.currentDirEntry_ = dirEntry;
+ self.dispatchEvent(e);
+ };
+
+ if (path == '/')
+ return onPathFound(this.filesystem_.root);
+
+ this.filesystem_.root.getDirectory(
+ path, {create: false}, onPathFound,
+ util.ferr('Error changing directory to: ' + path));
+ };
+
+ /**
+ * Update the selection summary UI when the selection summarization completes.
+ */
+ FileManager.prototype.onSelectionSummarized_ = function() {
+ if (this.selection.totalCount == 0) {
+ this.previewSummary_.textContent = str('NOTHING_SELECTED');
+
+ } else if (this.selection.totalCount == 1) {
+ this.previewSummary_.textContent =
+ strf('ONE_FILE_SELECTED', cr.locale.bytesToSi(this.selection.bytes));
+
+ } else {
+ this.previewSummary_.textContent =
+ strf('MANY_FILES_SELECTED', this.selection.totalCount,
+ cr.locale.bytesToSi(this.selection.bytes));
+ }
+ };
+
+ /**
+ * Handle a click event on a breadcrumb element.
+ *
+ * @param {Event} event The click event.
+ */
+ FileManager.prototype.onBreadcrumbClick_ = function(event) {
+ this.changeDirectory(event.srcElement.path);
+ };
+
+ /**
+ * Update the UI when the selection model changes.
+ *
+ * @param {cr.Event} event The change event.
+ */
+ FileManager.prototype.onDetailSelectionChanged_ = function(event) {
+ var selectable;
+
+ this.summarizeSelection_();
+
+ if (this.dialogType_ == FileManager.DialogType.SELECT_FOLDER) {
+ selectable = this.selection.directoryCount == 1 &&
+ this.selection.fileCount == 0;
+ } else if (this.dialogType_ == FileManager.DialogType.SELECT_OPEN_FILE) {
+ selectable = (this.selection.directoryCount == 0 &&
+ this.selection.fileCount == 1);
+ } else if (this.dialogType_ ==
+ FileManager.DialogType.SELECT_OPEN_MULTI_FILE) {
+ selectable = (this.selection.directoryCount == 0 &&
+ this.selection.fileCount >= 1);
+ } else if (this.dialogType_ == FileManager.DialogType.SELECT_SAVEAS_FILE) {
+ // TODO(rginda): save-as logic.
+ selectable = false;
+ } else {
+ throw new Error('Unknown dialog type');
+ }
+
+ this.okButton_.disabled = !selectable;
+ this.updatePreview_();
+ };
+
+ /**
+ * Handle a double-click event on an entry in the detail list.
+ *
+ * @param {Event} event The click event.
+ */
+ FileManager.prototype.onDetailDoubleClick_ = function(event) {
+ var i = this.table.selectionModel.leadIndex;
+ var entry = this.table.dataModel.item(i);
+
+ if (entry.isDirectory)
+ return this.changeDirectory(entry.fullPath);
+
+ };
+
+ /**
+ * Update the UI when the current directory changes.
+ *
+ * @param {cr.Event} event The directory-changed event.
+ */
+ FileManager.prototype.onDirectoryChanged_ = function(event) {
+ var self = this;
+ var reader;
+
+ function onReadSome(entries) {
+ if (entries.length == 0)
+ return;
+
+ // Splice takes the to-be-spliced-in array as individual parameters,
+ // rather than as an array, so we need to perform some acrobatics...
+ var spliceArgs = [].slice.call(entries);
+ spliceArgs.unshift(0, 0); // index, deleteCount
+ self.table.dataModel.splice.apply(self.table.dataModel, spliceArgs);
+
+ // Keep reading until entries.length is 0.
+ reader.readEntries(onReadSome);
+ };
+
+ // Clear the table first.
+ this.table.dataModel.splice(0, this.table.dataModel.length);
+
+ this.updateBreadcrumbs_();
+
+ reader = this.currentDirEntry_.createReader();
+ reader.readEntries(onReadSome);
+ };
+
+ /**
+ * Handle a click of the cancel button.
+ *
+ * @param {Event} event The click event.
+ */
+ FileManager.prototype.onCancel_ = function(event) {
+ chrome.fileBrowserPrivate.cancelDialog();
+ };
+
+ /**
+ * Handle a click of the ok button.
+ *
+ * The ok button has different UI labels depending on the type of dialog, but
+ * in code it's always referred to as 'ok'.
+ *
+ * @param {Event} event The click event.
+ */
+ FileManager.prototype.onOk_ = function(event) {
+ var ary = [];
+ var selectedIndexes = this.table.selectionModel.selectedIndexes;
+ if (!selectedIndexes.length)
+ return;
+
+ for (var i = 0; i < selectedIndexes.length; i++) {
+ var entry = this.table.dataModel.item(selectedIndexes[i]);
+ if (!entry) {
+ console.log('Error locating selected file at index: ' + i);
+ continue;
+ }
+
+ ary.push(entry);
+ }
+
+ chrome.fileBrowserPrivate.selectFiles(ary);
+ };
+
+})();
diff --git a/chrome/browser/resources/file_manager/js/harness.js b/chrome/browser/resources/file_manager/js/harness.js
new file mode 100644
index 0000000..00c662aa
--- /dev/null
+++ b/chrome/browser/resources/file_manager/js/harness.js
@@ -0,0 +1,185 @@
+// Copyright (c) 2011 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.
+
+var harness = {
+ /**
+ * Kick off the test harness.
+ *
+ * Called by harness.html after the dom has been parsed.
+ */
+ init: function() {
+ console.log('Initializing harness...');
+
+ util.installFileErrorToString();
+
+ var self = this;
+
+ function onFilesystem(filesystem) {
+ console.log('Filesystem found.');
+ self.filesystem = filesystem;
+ };
+
+ window.requestFileSystem(window.PERSISTENT, 16 * 1024 * 1024,
+ onFilesystem,
+ util.flog('Error initializing filesystem'));
+ },
+
+ /**
+ * Utility function to invoke callback once for each entry in dirEntry.
+ *
+ * @param {DirectoryEntry} dirEntry The directory entry to enumerate.
+ * @param {function(Entry)} callback The function to invoke for each entry in
+ * dirEntry.
+ */
+ forEachDirEntry: function(dirEntry, callback) {
+ var reader;
+
+ function onReadSome(results) {
+ if (results.length == 0)
+ return callback(null);
+
+ for (var i = 0; i < results.length; ++i)
+ callback(results[i]);
+
+ reader.readEntries(onReadSome);
+ };
+
+ reader = dirEntry.createReader();
+ reader.readEntries(onReadSome);
+ },
+
+ /**
+ * 'Reset Fileystem' button click handler.
+ */
+ onClearClick: function() {
+ this.forEachDirEntry(this.filesystem.root, function(dirEntry) {
+ if (!dirEntry)
+ return console.log('Filesystem reset.');
+
+ console.log('Remove: ' + dirEntry.name);
+
+ if (dirEntry.isDirectory) {
+ dirEntry.removeRecursively();
+ } else {
+ dirEntry.remove();
+ }
+ });
+ },
+
+ /**
+ * Change handler for the 'input type=file' element.
+ */
+ onFilesChange: function(event) {
+ this.importFiles([].slice.call(event.target.files));
+ },
+
+ /**
+ * The fileManager object under test.
+ *
+ * This is a getter rather than a normal property because the fileManager
+ * is initialized asynchronously, and we won't be sure when it'll be
+ * done. Since harness.fileManager is intended to be used for debugging
+ * from the JS console, we don't really need to be sure it's ready at any
+ * particular time.
+ */
+ get fileManager() {
+ return document.getElementById('dialog').contentWindow.fileManager;
+ },
+
+ /**
+ * Import a list of File objects into harness.filesystem.
+ */
+ importFiles: function(files) {
+ var currentSrc = null;
+ var currentDest = null;
+ var importCount = 0;
+
+ var self = this;
+
+ function onWriterCreated(writer) {
+ writer.onerror = util.flog('Error writing: ' + currentDest.fullPath);
+ writer.onwriteend = function() {
+ console.log('Wrote: ' + currentDest.fullPath);
+ //console.log(writer);
+ //console.log(currentDest);
+ ++importCount;
+ processNextFile();
+ };
+
+ writer.write(currentSrc);
+ }
+
+ function onFileFound(fileEntry) {
+ currentDest = fileEntry;
+ currentDest.createWriter(onWriterCreated,
+ util.flog('Error creating writer for: ' +
+ currentDest.fullPath));
+ }
+
+ function processNextFile() {
+ if (files.length == 0) {
+ console.log('Import complete: ' + importCount + ' file(s)');
+ return;
+ }
+
+ currentSrc = files.shift();
+ var destPath = currentSrc.name.replace(/\^\^/g, '/');
+ self.getOrCreateFile(destPath, onFileFound,
+ util.flog('Error finding path: ' + destPath));
+ }
+
+ console.log('Start import: ' + files.length + ' file(s)');
+ processNextFile();
+ },
+
+ /**
+ * Locate the file referred to by path, creating directories or the file
+ * itself if necessary.
+ */
+ getOrCreateFile: function(path, successCallback, errorCallback) {
+ var dirname = null;
+ var basename = null;
+
+ function onDirFound(dirEntry) {
+ dirEntry.getFile(basename, { create: true },
+ successCallback, errorCallback);
+ }
+
+ var i = path.lastIndexOf('/');
+ if (i > -1) {
+ dirname = path.substr(0, i);
+ basename = path.substr(i + 1);
+ } else {
+ basename = path;
+ }
+
+ if (!dirname)
+ return onDirFound(this.filesystem.root);
+
+ this.getOrCreatePath(dirname, onDirFound, errorCallback);
+ },
+
+ /**
+ * Locate the directory referred to by path, creating directories along the
+ * way.
+ */
+ getOrCreatePath: function(path, successCallback, errorCallback) {
+ var names = path.split('/');
+
+ function getOrCreateNextName(dir) {
+ if (!names.length)
+ return successCallback(dir);
+
+ var name;
+ do {
+ name = names.shift();
+ } while (!name || name == '.');
+
+ dir.getDirectory(name, { create: true }, getOrCreateNextName,
+ errorCallback);
+ }
+
+ getOrCreateNextName(this.filesystem.root);
+ }
+};
diff --git a/chrome/browser/resources/file_manager/js/main.js b/chrome/browser/resources/file_manager/js/main.js
new file mode 100644
index 0000000..cd2f978
--- /dev/null
+++ b/chrome/browser/resources/file_manager/js/main.js
@@ -0,0 +1,25 @@
+// Copyright (c) 2011 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.
+
+/**
+ * Global fileManager reference useful for poking at from the console.
+ */
+var fileManager;
+
+/**
+ * Kick off the file manager dialog.
+ *
+ * Called by main.html after the dom has been parsed.
+ */
+function init() {
+ function onFileSystemFound(filesystem) {
+ FileManager.initStrings(function () {
+ fileManager = new FileManager(document.body, filesystem);
+ });
+ };
+
+ util.installFileErrorToString();
+
+ chrome.fileBrowserPrivate.requestLocalFileSystem(onFileSystemFound);
+}
diff --git a/chrome/browser/resources/file_manager/js/mock_chrome.js b/chrome/browser/resources/file_manager/js/mock_chrome.js
new file mode 100644
index 0000000..31cd40b
--- /dev/null
+++ b/chrome/browser/resources/file_manager/js/mock_chrome.js
@@ -0,0 +1,85 @@
+// Copyright (c) 2011 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.
+
+/**
+ * Mock out the chrome.fileBrowserPrivate API for use in the harness.
+ */
+chrome.fileBrowserPrivate = {
+
+ /**
+ * Return a normal HTML5 filesystem api, rather than the real local
+ * filesystem.
+ *
+ * You must start chrome with --allow-file-access-from-files and
+ * --unlimited-quota-for-files in order for this to work.
+ */
+ requestLocalFileSystem: function(callback) {
+ window.requestFileSystem(window.PERSISTENT, 16 * 1024 * 1024,
+ callback,
+ util.ferr('Error requesting filesystem'));
+ },
+
+ /**
+ * Select multiple files.
+ */
+ selectFiles: function(selectedFiles) {
+ console.log('selectFiles called: ' + selectedFiles.length +
+ ' files selected');
+ console.log(selectedFiles);
+ },
+
+ /**
+ * Select a single file.
+ */
+ selectFile: function(selectedFile, index) {
+ console.log('selectFile called: ' + selectedFile + ', ' + index);
+ console.log(selectedFile);
+ },
+
+ /**
+ * Cancel the dialog without selecting anything.
+ */
+ cancelDialog: function() {
+ console.log('cancelDialog called');
+ },
+
+ /**
+ * Return localized strings.
+ */
+ getStrings: function(callback) {
+ // Keep this list in sync with the strings in generated_resources.grd and
+ // extension_file_manager_api.cc!
+ callback({
+ LOCALE_FMT_DATE_SHORT: '%b %d, %Y',
+ LOCALE_MONTHS_SHORT: 'Jan^Feb^Mar^Apr^May^Jun^Jul^Aug^Sep^Oct^Nov^Dec',
+ LOCALE_DAYS_SHORT: 'Sun^Mon^Tue^Wed^Thu^Fri^Sat',
+
+ SHORT_DATE_FORMAT: '%b %-d, %Y',
+ FILES_DISPLAYED_SUMMARY: '%1 Files Displayed',
+ FILES_SELECTED_SUMMARY: '%1 Files Selected',
+ FILE_IS_DIRECTORY: 'Folder',
+ PARENT_DIRECTORY: 'Parent Directory',
+
+ ROOT_DIRECTORY_LABEL: 'Files',
+ NAME_COLUMN_LABEL: 'Name',
+ SIZE_COLUMN_LABEL: 'Size',
+ DATE_COLUMN_LABEL: 'Date',
+ PREVIEW_COLUMN_LABEL: 'Preview',
+
+ CANCEL_LABEL: 'Cancel',
+ OPEN_LABEL: 'Open',
+ SAVE_LABEL: 'Save',
+
+ SELECT_FOLDER_TITLE: 'Select a folder to open',
+ SELECT_OPEN_FILE_TITLE: 'Select a file to open',
+ SELECT_OPEN_MULTI_FILE_TITLE: 'Select one or more files',
+ SELECT_SAVEAS_FILE: 'Select a file to save as',
+
+ COMPUTING_SELECTION: 'Computing selection...',
+ NOTHING_SELECTED: 'No files selected',
+ ONE_FILE_SELECTED: 'One file selected, $1',
+ MANY_FILES_SELECTED: '$1 files selected, $2',
+ });
+ }
+};
diff --git a/chrome/browser/resources/file_manager/js/util.js b/chrome/browser/resources/file_manager/js/util.js
new file mode 100644
index 0000000..92ce50674
--- /dev/null
+++ b/chrome/browser/resources/file_manager/js/util.js
@@ -0,0 +1,52 @@
+// Copyright (c) 2011 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.
+
+/**
+ * Namespace for utility functions.
+ */
+var util = {
+ /**
+ * Returns a function that console.log's its arguments, prefixed by |msg|.
+ *
+ * @param {string} msg The message prefix to use in the log.
+ */
+ flog: function(msg) {
+ return function() {
+ var ary = Array.apply(null, arguments);
+ console.log(msg + ': ' + ary.join(', '));
+ };
+ },
+
+ /**
+ * Returns a function that throws an exception that includes its arguments
+ * prefixed by |msg|.
+ *
+ * @param {string} msg The message prefix to use in the exception.
+ */
+ ferr: function(msg) {
+ return function() {
+ var ary = Array.apply(null, arguments);
+ throw new Error(msg + ': ' + ary.join(', '));
+ };
+ },
+
+ /**
+ * Install a sensible toString() on the FileError object.
+ *
+ * FileError.prototype.code is a numeric code describing the cause of the
+ * error. The FileError constructor has a named property for each possible
+ * error code, but provides no way to map the code to the named property.
+ * This toString() implementation fixes that.
+ */
+ 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 + ']';
+ }
+ },
+};
diff --git a/chrome/browser/resources/file_manager/main.html b/chrome/browser/resources/file_manager/main.html
index ef6d23b..baea97c 100644
--- a/chrome/browser/resources/file_manager/main.html
+++ b/chrome/browser/resources/file_manager/main.html
@@ -1,21 +1,100 @@
<!DOCTYPE HTML>
<!--
-
-Copyright (c) 2010 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.
-
--->
+ -- Copyright (c) 2011 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.
+ -->
<html>
<head>
- <title>Placeholder File Manager Main Page</title>
+ <script>
+ var css =
+ ['list.css',
+ 'table.css'
+ ];
+
+ var script =
+ ['local_strings.js',
+
+ 'cr.js',
+ 'cr/locale.js',
+ 'cr/ui.js',
+ 'cr/event_target.js',
+ 'cr/ui/array_data_model.js',
+ 'cr/ui/list_item.js',
+ 'cr/ui/list_selection_model.js',
+ 'cr/ui/list_single_selection_model.js',
+ 'cr/ui/list_selection_controller.js',
+ 'cr/ui/list.js',
+
+ 'cr/ui/splitter.js',
+ 'cr/ui/table/table_splitter.js',
+ 'cr/ui/table/table_selection_model.js',
+ 'cr/ui/table/table_single_selection_model.js',
+ 'cr/ui/table/table_data_model.js',
+
+ 'cr/ui/table/table_column.js',
+ 'cr/ui/table/table_column_model.js',
+ 'cr/ui/table/table_header.js',
+ 'cr/ui/table/table_list.js',
+ 'cr/ui/table.js',
+ ];
+
+ (function() {
+ // Switch to 'test harness' mode when loading from a file url.
+ var isHarness = document.location.protocol == 'file:';
- <link rel="stylesheet" href="css/file_manager.css">
+ // In test harness mode we load resources from relative dirs.
+ var prefix = isHarness ? '../shared/' : 'chrome://resources/';
+ for (var i = 0; i < css.length; ++i) {
+ document.write('<link href="' + prefix + 'css/' + css[i] +
+ '" rel="stylesheet"></link>');
+ }
+
+ for (var i = 0; i < script.length; ++i) {
+ document.write('<script src="' + prefix + 'js/' + script[i] +
+ '"><\57script>');
+ }
+
+ if (isHarness)
+ document.write('<script src="js/mock_chrome.js"><\57script>');
+ })();
+
+ </script>
+
+ <link rel="stylesheet" href="css/file_manager.css"></link>
+
+ <script src="js/util.js"></script>
<script src="js/file_manager.js"></script>
+ <script src="js/main.js"></script>
</head>
- <body onload="onLoad()">
- <h1>File Manager</h1>
- <p><img src="images/icon16.png"> Coming soon.
+ <body>
+ <div class=dialog-title>[TITLE]</div>
+ <div class=dialog-header>
+ <div class=breadcrumbs></div>
+ <!-- TODO(rginda): Detail/Thumbnail buttons. -->
+ </div>
+ <div class=dialog-body>
+ <div class=detail-table></div>
+ <div class=preview-container>
+ <div class=table-header style='width:100%'>
+ <div class=table-header-cell>
+ <div class='preview-label table-header-label'>[PREVIEW]</div>
+ </div>
+ </div>
+ <div class=preview-filename></div>
+ <center><img class=preview-img></center>
+ <div class=vertical-spacer></div>
+ <div class=preview-summary></div>
+ </div>
+ </div>
+ <div class=dialog-footer>
+ <!-- TODO(rginda): Text input for file name. -->
+ <div class=horizontal-spacer></div>
+ <button class=ok disabled>[OK]</button>
+ <button class=cancel>[CANCEL]</button>
+ </div>
+
+ <script>init();</script>
</body>
</html>
diff --git a/chrome/browser/resources/file_manager/manifest.json b/chrome/browser/resources/file_manager/manifest.json
index d3bd792..20df4a5 100644
--- a/chrome/browser/resources/file_manager/manifest.json
+++ b/chrome/browser/resources/file_manager/manifest.json
@@ -9,5 +9,7 @@
"16": "images/icon16.png"
},
"permissions": [
+ "fileBrowserPrivate",
+ "chrome://resources/"
]
}
diff --git a/chrome/browser/resources/shared/css/table.css b/chrome/browser/resources/shared/css/table.css
index 692f9dc..776b8c7 100644
--- a/chrome/browser/resources/shared/css/table.css
+++ b/chrome/browser/resources/shared/css/table.css
@@ -23,9 +23,6 @@ list > [selected]:hover {
border-right: none;
border-left: none;
}
-.list {
- height: 200px;
-}
.table:focus {
border: 1px solid;
diff --git a/chrome/browser/resources/shared/js/cr/locale.js b/chrome/browser/resources/shared/js/cr/locale.js
new file mode 100644
index 0000000..2c18735
--- /dev/null
+++ b/chrome/browser/resources/shared/js/cr/locale.js
@@ -0,0 +1,167 @@
+// Copyright (c) 2011 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.
+
+// TODO(rginda): Fill out formatTime, add testcases.
+
+cr.define('cr', function() {
+
+ /**
+ * Lookup tables used by bytesToSi.
+ */
+ var units = ['B', 'k', 'M', 'G', 'T', 'P'];
+ var scale = [1, 1e3, 1e6, 1e9, 1e12, 1e15];
+
+ /**
+ * Construct a new Locale object with a set of strings.
+ *
+ * The strings object maps symbolic string names to translated strings
+ * for the locale. Lists of translated strings are delimited with the caret
+ * ('^') character.
+ *
+ * LOCALE_DAYS_SHORT: List of abbreviated day names.
+ * LOCALE_MONTHS_SHORT: List of abbreviated month names.
+ * LOCALE_FMT_SHORT_DATE: A Locale.prototype.formatTime format specifier
+ * representing the short date format (e.g. Apr 1, 2011) for the
+ * locale.
+ */
+ function Locale(strings) {
+ this.dateStrings_ = {
+ dayShort: strings.LOCALE_DAYS_SHORT.split('^'),
+ monthShort: strings.LOCALE_MONTHS_SHORT.split('^'),
+ shortDateFormat: strings.LOCALE_FMT_DATE_SHORT
+ };
+ }
+
+ Locale.prototype = {
+ /**
+ * Convert a number of bytes into an appropriate International System of
+ * Units (SI) representation, using the correct number separators.
+ *
+ * The first time this function is called it computes a lookup table which
+ * is cached for subsequent calls.
+ *
+ * @param {number} bytes The number of bytes.
+ */
+ bytesToSi: function(bytes) {
+ function fmt(s, u) {
+ var rounded = Math.round(bytes / s * 10) / 10;
+ return rounded.toLocaleString() + u;
+ }
+
+ // This loop index is used outside the loop if it turns out |bytes|
+ // requires the largest unit.
+ var i;
+
+ for (i = 0; i < units.length - 1; i++) {
+ if (bytes < scale[i + 1])
+ return fmt(scale[i], units[i]);
+ }
+
+ return fmt(scale[i], units[i]);
+ },
+
+ /**
+ * Format a date as a string using the given format specifier.
+ *
+ * This function is similar to strftime() from the C standard library, with
+ * the GNU extensions for controlling padding.
+ *
+ * The following conversion specifiers are defined:
+ *
+ * %% - A literal '%'
+ * %a - The localized abbreviated weekday name.
+ * %b - The localized abbreviated month name.
+ * %d - The day of the month, zero padded (01-31).
+ * %Y - The four digit year.
+ *
+ * Between the '%' character and the conversion specifier character, an
+ * optional flag and field width may be specified.
+ *
+ * The following flag characters are permitted:
+ * _ (underscore) Pad a numeric result string with spaces.
+ * - (dash) Do not pad a numeric result string.
+ * ^ Convert alphabetic characters in result string to upper case.
+ *
+ * TODO(rginda): Implement more conversion specifiers.
+ *
+ * @param {Date} date The date to be formatted.
+ * @param {string} spec The format specification.
+ */
+ formatDate: function(date, spec) {
+ var self = this;
+ var strings = this.dateStrings_;
+
+ // Called back once for each conversion specifier.
+ function replaceSpecifier(m, flag, width, code) {
+
+ // Left pad utility.
+ function lpad(value, ch) {
+ value = String(value);
+
+ while (width && value.length < width) {
+ value = ch + value;
+ }
+
+ return value;
+ }
+
+ // Format a value according to the selected flag and field width.
+ function fmt(value, defaultWidth) {
+ if (flag == '-') // No padding.
+ return value;
+
+ if (flag == '^') // Convert to uppercase.
+ value = String(value).toUpperCase();
+
+ if (typeof width == 'undefined')
+ width = defaultWidth;
+
+ // If there is no width specifier, there's nothing to pad.
+ if (!width)
+ return value;
+
+ if (flag == '_') // Pad with spaces.
+ return lpad(value, ' ');
+
+ // Autodetect padding character.
+ if (typeof value == 'number')
+ return lpad(value, '0');
+
+ return lpad(value, ' ');
+ }
+
+ switch (code) {
+ 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 'Y': return date.getFullYear();
+ default:
+ console.log('Unknown format specifier: ' + code);
+ return m;
+ }
+ }
+
+ // Conversion specifiers start with a '%', optionally contain a
+ // flag and/or field width, followed by a single letter.
+ // e.g. %a, %-d, %2l.
+ return spec.replace(/%([\^\-_])?(\d+)?([%a-z])?/gi, replaceSpecifier);
+ }
+ };
+
+ /**
+ * Storage for the current cr.locale.
+ */
+ var locale = null;
+
+ return {
+ Locale: Locale,
+ get locale() {
+ return locale;
+ },
+ initLocale: function(strings) {
+ locale = new Locale(strings);
+ }
+ };
+});
diff --git a/chrome/browser/resources/shared/js/local_strings.js b/chrome/browser/resources/shared/js/local_strings.js
index d76d90c..0d03af5 100644
--- a/chrome/browser/resources/shared/js/local_strings.js
+++ b/chrome/browser/resources/shared/js/local_strings.js
@@ -8,9 +8,15 @@
* The local strings get injected into the page using a variable named
* {@code templateData}. This class provides a simpler interface to access those
* strings.
+ *
+ * @param {Object} opt_templateData Optional object containing translated
+ * strings. If this is not supplied during construction, it can be
+ * assigned to the templateData property after construction. If all else
+ * fails, the value of window.templateDate will be used.
* @constructor
*/
-function LocalStrings() {
+function LocalStrings(opt_templateData) {
+ this.templateData = opt_templateData;
}
// Start of anonymous namespace.