diff options
author | rginda@chromium.org <rginda@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-26 01:54:02 +0000 |
---|---|---|
committer | rginda@chromium.org <rginda@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-26 01:54:02 +0000 |
commit | 3e74c0652df00f546932a7f1894d45c84b2466e6 (patch) | |
tree | a16c230a3da479b3ff4930bb68aff5ef0ccf6020 | |
parent | 41b8a3594e276503c3233974cd7d4071c620e97f (diff) | |
download | chromium_src-3e74c0652df00f546932a7f1894d45c84b2466e6.zip chromium_src-3e74c0652df00f546932a7f1894d45c84b2466e6.tar.gz chromium_src-3e74c0652df00f546932a7f1894d45c84b2466e6.tar.bz2 |
File Manager: Create in-page modal dialogs
File manager is useing window.alert/confirm/prompt which are app modal, and a little janky for our purposes. Additionally, they began freezing the browser recently :/
This change adds js modal dialog replacements. They live in file_manager/ for now, but once stabalized they are intended to live in cr/ so that they can be used by other webui or component extensions.
BUG=chromium-os:17642
TEST=manually trigger all dialogs
Review URL: http://codereview.chromium.org/7495030
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@94020 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/app/generated_resources.grd | 24 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_file_browser_private_api.cc | 14 | ||||
-rw-r--r-- | chrome/browser/resources/file_manager/css/dialogs.css | 54 | ||||
-rw-r--r-- | chrome/browser/resources/file_manager/js/dialogs.js | 164 | ||||
-rw-r--r-- | chrome/browser/resources/file_manager/js/file_manager.js | 129 | ||||
-rw-r--r-- | chrome/browser/resources/file_manager/js/mock_chrome.js | 7 | ||||
-rw-r--r-- | chrome/browser/resources/file_manager/main.html | 2 |
7 files changed, 327 insertions, 67 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 6bbd201..68b726d 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -8862,11 +8862,8 @@ Keep your key file in a safe place. You will need it to create new versions of y <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_ERROR_INVALID_FILE_CHARACTER" desc="Error message displayed when the user enters an invalid character in a file name."> - Invalid character in file name: $1 + <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 </message> <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 @@ -8890,6 +8887,13 @@ Keep your key file in a safe place. You will need it to create new versions of y File name </message> + <message name="IDS_FILE_BROWSER_DIMENSIONS_LABEL" desc="Image dimensions label."> + Dimensions + </message> + <message name="IDS_FILE_BROWSER_DIMENSIONS_FORMAT" desc="Image dimensions format (width x height)."> + $1 x $2 + </message> + <message name="IDS_FILE_BROWSER_EJECT_BUTTON" desc="Button label."> Eject </message> @@ -9010,6 +9014,16 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_FILE_BROWSER_SAVE_LABEL" desc="Save label."> Save </message> + <message name="IDS_FILE_BROWSER_OK_LABEL" desc="Ok label."> + Ok + </message> + + <message name="IDS_FILE_BROWSER_CONFIRM_DELETE_ONE" desc="Asks the user if they are sure they want to delete a single file."> + Are you sure you want to delete "$1"? + </message> + <message name="IDS_FILE_BROWSER_CONFIRM_DELETE_SOME" desc="Asks the user if they are sure they want to delete multiple files/directories."> + Are you sure you want to delete $1 items? + </message> <message name="IDS_FILE_BROWSER_SELECT_FOLDER_TITLE" desc="Select folder title."> Select a folder to open diff --git a/chrome/browser/extensions/extension_file_browser_private_api.cc b/chrome/browser/extensions/extension_file_browser_private_api.cc index d9d148a..93fbb2e 100644 --- a/chrome/browser/extensions/extension_file_browser_private_api.cc +++ b/chrome/browser/extensions/extension_file_browser_private_api.cc @@ -1344,8 +1344,7 @@ bool FileDialogStringsFunction::RunImpl() { 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, ERROR_INVALID_FILE_CHARACTER); + SET_STRING(IDS_FILE_BROWSER, ERROR_INVALID_CHARACTER); SET_STRING(IDS_FILE_BROWSER, ERROR_RESERVED_NAME); SET_STRING(IDS_FILE_BROWSER, ERROR_HIDDEN_NAME); SET_STRING(IDS_FILE_BROWSER, ERROR_WHITESPACE_NAME); @@ -1354,6 +1353,9 @@ bool FileDialogStringsFunction::RunImpl() { SET_STRING(IDS_FILE_BROWSER, NEW_FOLDER_BUTTON_LABEL); SET_STRING(IDS_FILE_BROWSER, FILENAME_LABEL); + SET_STRING(IDS_FILE_BROWSER, DIMENSIONS_LABEL); + SET_STRING(IDS_FILE_BROWSER, DIMENSIONS_FORMAT); + SET_STRING(IDS_FILE_BROWSER, EJECT_BUTTON); SET_STRING(IDS_FILE_BROWSER, IMAGE_DIMENSIONS); SET_STRING(IDS_FILE_BROWSER, VOLUME_LABEL); @@ -1400,6 +1402,10 @@ bool FileDialogStringsFunction::RunImpl() { 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, OK_LABEL); + + SET_STRING(IDS_FILE_BROWSER, CONFIRM_DELETE_ONE); + SET_STRING(IDS_FILE_BROWSER, CONFIRM_DELETE_SOME); SET_STRING(IDS_FILE_BROWSER, SELECT_FOLDER_TITLE); SET_STRING(IDS_FILE_BROWSER, SELECT_OPEN_FILE_TITLE); @@ -1416,10 +1422,6 @@ bool FileDialogStringsFunction::RunImpl() { SET_STRING(IDS_FILE_BROWSER, PLAYBACK_ERROR); - // FILEBROWSER, without the underscore, is from the old school codebase. - // TODO(rginda): Move these into IDS_FILE_BROWSER post M12. - SET_STRING(IDS_FILEBROWSER, CONFIRM_DELETE); - SET_STRING(IDS_FILEBROWSER, ENQUEUE); #undef SET_STRING diff --git a/chrome/browser/resources/file_manager/css/dialogs.css b/chrome/browser/resources/file_manager/css/dialogs.css new file mode 100644 index 0000000..b66e608 --- /dev/null +++ b/chrome/browser/resources/file_manager/css/dialogs.css @@ -0,0 +1,54 @@ +/* + * 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. + */ + +.cr-dialog-container { + position: absolute; + -webkit-user-select: none; + top: 0; + left: 0; + height: 100%; + width: 100%; + + background-color: rgba(0,0,0,0.05); + -webkit-box-shadow: inset 0px 0px 100px #000; + opacity: 0; + -webkit-transition: opacity 0.25s linear; + + overflow: hidden; +} + +.cr-dialog-frame { + position: absolute; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-box-shadow: 5px 5px 5px rgba(100, 100, 100, 0.5); + + -webkit-transition-duration: 0.25s; + -webkit-transition-property: none; + + min-width: 25%; + max-width: 75%; + border: 1px #aaa solid; + border-radius: 2px; + background-color: white; + padding: 15px; +} + +.cr-dialog-text { + padding-bottom: 10px; +} + +.cr-dialog-frame input { + -webkit-box-sizing: border-box; + width: 100%; +} + +.cr-dialog-buttons { + display: -webkit-box; + -webkit-box-orient: horizontal; + padding-top: 10px; + -webkit-box-pack: end; +} diff --git a/chrome/browser/resources/file_manager/js/dialogs.js b/chrome/browser/resources/file_manager/js/dialogs.js new file mode 100644 index 0000000..3642aea --- /dev/null +++ b/chrome/browser/resources/file_manager/js/dialogs.js @@ -0,0 +1,164 @@ +// 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. + +cr.define('cr.ui.dialogs', function() { + + function BaseDialog(parentNode) { + this.parentNode_ = parentNode; + this.document_ = parentNode.ownerDocument; + this.initDom_(); + } + + /** + * Default text for Ok and Cancel buttons. + * + * Clients should override these with localized labels. + */ + BaseDialog.OK_LABEL = 'Ok'; + BaseDialog.CANCEL_LABEL = 'Cancel'; + + BaseDialog.prototype.initDom_ = function() { + var doc = this.document_; + this.container_ = doc.createElement('div'); + this.container_.className = 'cr-dialog-container'; + + this.frame_ = doc.createElement('div'); + this.frame_.className = 'cr-dialog-frame'; + this.container_.appendChild(this.frame_); + + this.text_ = doc.createElement('div'); + this.text_.className = 'cr-dialog-text'; + this.frame_.appendChild(this.text_); + + var buttons = doc.createElement('div'); + buttons.className = 'cr-dialog-buttons'; + this.frame_.appendChild(buttons); + + this.cancelButton_ = doc.createElement('button'); + this.cancelButton_.className = 'cr-dialog-cancel'; + this.cancelButton_.textContent = BaseDialog.CANCEL_LABEL; + this.cancelButton_.addEventListener('click', + this.onCancelClick_.bind(this)); + buttons.appendChild(this.cancelButton_); + + this.okButton_ = doc.createElement('button'); + this.okButton_.className = 'cr-dialog-ok'; + this.okButton_.textContent = BaseDialog.OK_LABEL; + this.okButton_.addEventListener('click', this.onOkClick_.bind(this)); + buttons.appendChild(this.okButton_); + }; + + BaseDialog.prototype.onOk_ = null; + BaseDialog.prototype.onCancel_ = null; + + BaseDialog.prototype.onOkClick_ = function(event) { + this.hide(); + if (this.onOk_) + this.onOk_(); + }; + + BaseDialog.prototype.onCancelClick_ = function(event) { + this.hide(); + if (this.onCancel_) + this.onCancel_(); + }; + + BaseDialog.prototype.setOkLabel = function(label) { + this.okButton_.textContent = label; + }; + + BaseDialog.prototype.setCancelLabel = function(label) { + this.cancelButton_.textContent = label; + }; + + BaseDialog.prototype.show = function(message, onOk, onCancel, onShow) { + this.parentNode_.appendChild(this.container_); + + this.onOk_ = onOk; + this.onCancel_ = onCancel; + + this.text_.textContent = message; + + var top = (this.document_.body.clientHeight - + this.frame_.clientHeight) / 2; + var left = (this.document_.body.clientWidth - + this.frame_.clientWidth) / 2; + + this.frame_.style.top = (top - 50) + 'px'; + this.frame_.style.left = (left + 10) + 'px'; + + var self = this; + setTimeout(function () { + self.frame_.style.top = top + 'px'; + self.frame_.style.left = left + 'px'; + self.frame_.style.webkitTransitionProperty = 'left, top'; + self.container_.style.opacity = '1'; + }, 0); + }; + + BaseDialog.prototype.hide = function(onHide) { + this.container_.style.opacity = '0'; + this.frame_.style.top = (parseInt(this.frame_.style.top) + 50) + 'px'; + this.frame_.style.left = (parseInt(this.frame_.style.left) - 10) + 'px'; + + var self = this; + setTimeout(function() { + // Wait until the transition is done before removing the dialog. + self.parentNode_.removeChild(self.container_); + self.frame_.style.webkitTransitionProperty = ''; + if (onHide) + onHide(); + }, 500); + }; + + function AlertDialog(parentNode) { + BaseDialog.apply(this, [parentNode]); + this.cancelButton_.style.display = 'none'; + } + + AlertDialog.prototype = {__proto__: BaseDialog.prototype}; + + AlertDialog.prototype.show = function(message, onOk, onShow) { + return BaseDialog.prototype.show.apply(this, [message, onOk, onOk, onShow]); + }; + + function ConfirmDialog(parentNode) { + BaseDialog.apply(this, [parentNode]); + } + + ConfirmDialog.prototype = {__proto__: BaseDialog.prototype}; + + function PromptDialog(parentNode) { + BaseDialog.apply(this, [parentNode]); + this.input_ = this.document_.createElement('input'); + this.input_.setAttribute('type', 'text'); + this.frame_.insertBefore(this.input_, this.text_.nextSibling); + } + + PromptDialog.prototype = {__proto__: BaseDialog.prototype}; + + PromptDialog.prototype.show = function(message, defaultValue, onOk, onCancel, + onShow) { + this.input_.value = defaultValue || ''; + return BaseDialog.prototype.show.apply(this, [message, onOk, onCancel, + onShow]); + }; + + PromptDialog.prototype.getValue = function() { + return this.input_.value; + }; + + PromptDialog.prototype.onOkClick_ = function(event) { + this.hide(); + if (this.onOk_) + this.onOk_(this.getValue()); + }; + + return { + BaseDialog: BaseDialog, + AlertDialog: AlertDialog, + ConfirmDialog: ConfirmDialog, + PromptDialog: PromptDialog + }; +}); diff --git a/chrome/browser/resources/file_manager/js/file_manager.js b/chrome/browser/resources/file_manager/js/file_manager.js index ffe1a78..25d9854 100644 --- a/chrome/browser/resources/file_manager/js/file_manager.js +++ b/chrome/browser/resources/file_manager/js/file_manager.js @@ -60,6 +60,10 @@ function FileManager(dialogDom, rootEntries, params) { this.defaultPath_ = this.params_.defaultPath || '/'; + this.alert = new cr.ui.dialogs.AlertDialog(this.dialogDom_); + this.confirm = new cr.ui.dialogs.ConfirmDialog(this.dialogDom_); + this.prompt = new cr.ui.dialogs.PromptDialog(this.dialogDom_); + // TODO(dgozman): This will be changed to LocaleInfo. this.locale_ = new v8Locale(navigator.language); @@ -1591,8 +1595,8 @@ FileManager.prototype = { if (event.mountType == 'file') { var fileName = event.sourceUrl.substr( event.sourceUrl.lastIndexOf('/') + 1); - window.alert(strf('ARCHIVE_MOUNT_FAILED', fileName, - event.status)); + self.alert.show(strf('ARCHIVE_MOUNT_FAILED', fileName, + event.status)); } } return; @@ -1942,9 +1946,20 @@ FileManager.prototype = { }); }; - FileManager.prototype.deleteEntries = function(entries) { - if (!window.confirm(str('CONFIRM_DELETE'))) + FileManager.prototype.deleteEntries = function(entries, force) { + if (!force) { + var self = this; + var msg; + if (entries.length == 1) { + msg = strf('CONFIRM_DELETE_ONE', entries[0].name); + } else { + msg = strf('CONFIRM_DELETE_SOME', entries.length); + } + + this.confirm.show(msg, + function() { self.deleteEntries(entries, true); }); return; + } var count = entries.length; @@ -2456,7 +2471,7 @@ FileManager.prototype = { FileManager.prototype.commitRename_ = function() { var entry = this.renameInput_.currentEntry; var newName = this.renameInput_.value; - if (!this.validateFileName_(newName, entry.isDirectory)) + if (!this.validateFileName_(newName)) return; this.renameInput_.currentEntry = null; @@ -2473,8 +2488,8 @@ FileManager.prototype = { } function onError(err) { - window.alert(strf('ERROR_RENAMING', entry.name, - util.getFileErrorMnemonic(err.code))); + self.alert.show(strf('ERROR_RENAMING', entry.name, + util.getFileErrorMnemonic(err.code))); } function resolveCallback(victim) { @@ -2484,7 +2499,7 @@ FileManager.prototype = { var message = victim.isFile ? 'FILE_ALREADY_EXISTS': 'DIRECTORY_ALREADY_EXISTS'; - window.alert(strf(message, newName)); + self.alert.show(strf(message, newName)); } } @@ -2526,21 +2541,26 @@ FileManager.prototype = { }; FileManager.prototype.onNewFolderButtonClick_ = function(event) { - var name = ''; + var self = this; - while (1) { - name = window.prompt(str('NEW_FOLDER_PROMPT'), name); - if (name == '') { - window.alert(str('ERROR_NEW_FOLDER_EMPTY_NAME')); - continue; - } else if (!name) { + function onNameSelected(name) { + if (!self.validateFileName_(name, promptForName)) { + // Validation failed. User will be prompted for a new name after they + // dismiss the validation error dialog. return; } - if (this.validateFileName_(name, true)) - break; + self.createNewFolder(name); + } + + function promptForName() { + self.prompt.show(str('NEW_FOLDER_PROMPT'), name, onNameSelected); } + promptForName(); + }; + + FileManager.prototype.createNewFolder = function(name) { var self = this; function onSuccess(dirEntry) { @@ -2548,8 +2568,8 @@ FileManager.prototype = { } function onError(err) { - window.alert(strf('ERROR_CREATING_FOLDER', name, - util.getFileErrorMnemonic(err.code))); + self.alert.show(strf('ERROR_CREATING_FOLDER', name, + util.getFileErrorMnemonic(err.code))); } this.currentDirEntry_.getDirectory(name, {create: true, exclusive: true}, @@ -2758,25 +2778,32 @@ FileManager.prototype = { var filename = this.filenameInput_.value; if (!filename) throw new Error('Missing filename!'); - if (!this.validateFileName_(filename, false)) + if (!this.validateFileName_(filename)) return; var self = this; function resolveCallback(victim) { - if (!(victim instanceof FileError)) { - if (victim.isDirectory) { - // Do not allow to overwrite directory. - window.alert(strf('DIRECTORY_ALREADY_EXISTS', filename)); - return; - } - if (!window.confirm(strf('CONFIRM_OVERWRITE_FILE', filename))) - return; + if (victim instanceof FileError) { + // File does not exist. (NB: selectFile Closes the window and does + // not return.) + chrome.fileBrowserPrivate.selectFile( + currentDirUrl + encodeURIComponent(filename), + self.getSelectedFilterIndex_(filename)); } - // Closes the window and does not return. - chrome.fileBrowserPrivate.selectFile( - currentDirUrl + encodeURIComponent(filename), - self.getSelectedFilterIndex_(filename)); + if (victim.isDirectory) { + // Do not allow to overwrite directory. + self.alert.show(strf('DIRECTORY_ALREADY_EXISTS', filename)); + } else { + self.confirm.show(strf('CONFIRM_OVERWRITE_FILE', filename), + function() { + // User selected Ok from the confirm dialog. + chrome.fileBrowserPrivate.selectFile( + currentDirUrl + encodeURIComponent(filename), + self.getSelectedFilterIndex_(filename)); + }); + } + return; } this.resolvePath(this.currentDirEntry_.fullPath + '/' + filename, @@ -2843,33 +2870,29 @@ FileManager.prototype = { * be fixed. Shows message box if the name is invalid. * * @param {name} name New file or folder name. - * @param {boolean} isFolder If true error message will be adjusted for - * folders. + * @param {function} onAccept Function to invoke when user accepts the + * message box. * @return {boolean} True if name is vaild. */ - FileManager.prototype.validateFileName_ = function(name, isFolder) { - if (name.length == 0) { - return false; - } + FileManager.prototype.validateFileName_ = function(name, onAccept) { + var msg; var testResult = /[\/\\\<\>\:\?\*\"\|]/.exec(name); if (testResult) { - var msgId = isFolder ? 'ERROR_INVALID_FOLDER_CHARACTER' : - 'ERROR_INVALID_FILE_CHARACTER'; - window.alert(strf(msgId, testResult[0])); - return false; - } - if (/^\s*$/i.test(name)) { - window.alert(str('ERROR_WHITESPACE_NAME')); - return false; - } - if (/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(name)) { - window.alert(str('ERROR_RESERVED_NAME')); - return false; - } - if (this.filterFiles_ && name[0] == '.') { - window.alert(str('ERROR_HIDDEN_NAME')); + msg = strf('ERROR_INVALID_CHARACTER', testResult[0]); + } else if (/^\s*$/i.test(name)) { + msg = str('ERROR_WHITESPACE_NAME'); + } else if (/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(name)) { + msg = str('ERROR_RESERVED_NAME'); + } else if (this.filterFiles_ && name[0] == '.') { + msg = str('ERROR_HIDDEN_NAME'); + } + + if (msg) { + console.log('no no no'); + this.alert.show(msg, onAccept); return false; } + return true; }; })(); diff --git a/chrome/browser/resources/file_manager/js/mock_chrome.js b/chrome/browser/resources/file_manager/js/mock_chrome.js index 9be96c3..d74ad44 100644 --- a/chrome/browser/resources/file_manager/js/mock_chrome.js +++ b/chrome/browser/resources/file_manager/js/mock_chrome.js @@ -198,8 +198,7 @@ chrome.fileBrowserPrivate = { PREVIEW_COLUMN_LABEL: 'Preview', ERROR_CREATING_FOLDER: 'Unable to create folder "$1": $2', - ERROR_INVALID_FOLDER_CHARACTER: 'Invalid character in folder name: $1', - ERROR_INVALID_FILE_CHARACTER: 'Invalid character in file name: $1', + ERROR_INVALID_CHARACTER: 'Invalid character: $1', ERROR_RESERVED_NAME: 'This name may not be used as a file of folder name', ERROR_WHITESPACE_NAME: 'Invalid name', NEW_FOLDER_PROMPT: 'Enter a name for the new folder', @@ -255,6 +254,7 @@ chrome.fileBrowserPrivate = { CANCEL_LABEL: 'Cancel', OPEN_LABEL: 'Open', SAVE_LABEL: 'Save', + OK_LABEL: 'Ok', SELECT_FOLDER_TITLE: 'Select a folder to open', SELECT_OPEN_FILE_TITLE: 'Select a file to open', @@ -269,7 +269,8 @@ chrome.fileBrowserPrivate = { MANY_DIRECTORIES_SELECTED: '$1 directories selected', MANY_ENTRIES_SELECTED: '$1 items selected, $2', - CONFIRM_DELETE: 'Are you sure?', + CONFIRM_DELETE_ONE: 'Are you sure you want to delete "$1"?', + CONFIRM_DELETE_SOME: 'Are you sure you want to delete $1 items?', }); } }; diff --git a/chrome/browser/resources/file_manager/main.html b/chrome/browser/resources/file_manager/main.html index 65c3d26..105d897 100644 --- a/chrome/browser/resources/file_manager/main.html +++ b/chrome/browser/resources/file_manager/main.html @@ -72,6 +72,7 @@ </script> <link rel="stylesheet" href="css/file_manager.css"></link> + <link rel="stylesheet" href="css/dialogs.css"></link> <link rel="stylesheet" href="js/image_editor/image_editor.css"></link> <script src="js/image_editor/image_util.js"></script> @@ -83,6 +84,7 @@ <script src="js/util.js"></script> <script src="js/file_copy_manager.js"></script> <script src="js/file_manager.js"></script> + <script src="js/dialogs.js"></script> <script src="js/main.js"></script> <!-- We have to set some default title, or chrome will use the page |