| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // Setting the src of an img to an empty string can crash the browser, so we | 5 // Setting the src of an img to an empty string can crash the browser, so we |
| 6 // use an empty 1x1 gif instead. | 6 // use an empty 1x1 gif instead. |
| 7 const EMPTY_IMAGE_URI = 'data:image/gif;base64,' | 7 const EMPTY_IMAGE_URI = 'data:image/gif;base64,' |
| 8 + 'R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D'; | 8 + 'R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D'; |
| 9 | 9 |
| 10 var g_slideshow_data = null; | 10 var g_slideshow_data = null; |
| 11 | 11 |
| 12 const GALLERY_ENABLED = true; | 12 const GALLERY_ENABLED = true; |
| 13 | 13 |
| 14 // If directory files changes too often, don't rescan directory more than once | 14 // If directory files changes too often, don't rescan directory more than once |
| 15 // per specified interval | 15 // per specified interval |
| 16 const SIMULTANEOUS_RESCAN_INTERVAL = 1000; | 16 const SIMULTANEOUS_RESCAN_INTERVAL = 1000; |
| 17 | 17 |
| 18 /** | 18 /** |
| 19 * FileManager constructor. | 19 * FileManager constructor. |
| 20 * | 20 * |
| 21 * FileManager objects encapsulate the functionality of the file selector | 21 * FileManager objects encapsulate the functionality of the file selector |
| 22 * dialogs, as well as the full screen file manager application (though the | 22 * dialogs, as well as the full screen file manager application (though the |
| 23 * latter is not yet implemented). | 23 * latter is not yet implemented). |
| 24 * | 24 * |
| 25 * @param {HTMLElement} dialogDom The DOM node containing the prototypical | 25 * @param {HTMLElement} dialogDom The DOM node containing the prototypical |
| 26 * dialog UI. | 26 * dialog UI. |
| 27 * @param {DOMFileSystem} filesystem The HTML5 filesystem object representing | 27 * @param {DOMFileSystem} filesystem The HTML5 filesystem object representing |
| 28 * the root filesystem for the new FileManager. | 28 * the root filesystem for the new FileManager. |
| 29 * @param {Object} params A map of parameter names to values controlling the | |
| 30 * appearance of the FileManager. Names are: | |
| 31 * - type: A value from FileManager.DialogType defining what kind of | |
| 32 * dialog to present. Defaults to FULL_PAGE. | |
| 33 * - title: The title for the dialog. Defaults to a localized string based | |
| 34 * on the dialog type. | |
| 35 * - defaultPath: The default path for the dialog. The default path should | |
| 36 * end with a trailing slash if it represents a directory. | |
| 37 */ | 29 */ |
| 38 function FileManager(dialogDom, filesystem, rootEntries) { | 30 function FileManager(dialogDom) { |
| 39 console.log('Init FileManager: ' + dialogDom); | 31 console.log('Init FileManager: ' + dialogDom); |
| 40 | 32 |
| 41 this.dialogDom_ = dialogDom; | 33 this.dialogDom_ = dialogDom; |
| 42 this.rootEntries_ = rootEntries; | 34 this.rootEntries_ = null; |
| 43 this.filesystem_ = filesystem; | 35 this.filesystem_ = null; |
| 44 this.params_ = location.search ? | 36 this.params_ = location.search ? |
| 45 JSON.parse(decodeURIComponent(location.search.substr(1))) : | 37 JSON.parse(decodeURIComponent(location.search.substr(1))) : |
| 46 {}; | 38 {}; |
| 47 | 39 |
| 48 this.listType_ = null; | 40 this.listType_ = null; |
| 49 | 41 |
| 50 this.selection = null; | 42 this.selection = null; |
| 51 | 43 |
| 52 this.clipboard_ = null; // Current clipboard, or null if empty. | 44 this.clipboard_ = null; // Current clipboard, or null if empty. |
| 53 | 45 |
| (...skipping 11 matching lines...) Expand all Loading... |
| 65 this.document_ = dialogDom.ownerDocument; | 57 this.document_ = dialogDom.ownerDocument; |
| 66 this.dialogType_ = this.params_.type || FileManager.DialogType.FULL_PAGE; | 58 this.dialogType_ = this.params_.type || FileManager.DialogType.FULL_PAGE; |
| 67 | 59 |
| 68 metrics.recordEnum('Create', this.dialogType_, | 60 metrics.recordEnum('Create', this.dialogType_, |
| 69 [FileManager.DialogType.SELECT_FOLDER, | 61 [FileManager.DialogType.SELECT_FOLDER, |
| 70 FileManager.DialogType.SELECT_SAVEAS_FILE, | 62 FileManager.DialogType.SELECT_SAVEAS_FILE, |
| 71 FileManager.DialogType.SELECT_OPEN_FILE, | 63 FileManager.DialogType.SELECT_OPEN_FILE, |
| 72 FileManager.DialogType.SELECT_OPEN_MULTI_FILE, | 64 FileManager.DialogType.SELECT_OPEN_MULTI_FILE, |
| 73 FileManager.DialogType.FULL_PAGE]); | 65 FileManager.DialogType.FULL_PAGE]); |
| 74 | 66 |
| 75 this.initDialogs_(); | |
| 76 | |
| 77 // TODO(dgozman): This will be changed to LocaleInfo. | 67 // TODO(dgozman): This will be changed to LocaleInfo. |
| 78 this.locale_ = new v8Locale(navigator.language); | 68 this.locale_ = new v8Locale(navigator.language); |
| 79 | 69 |
| 80 // TODO(rginda): 6/22/11: Remove this test when createDateTimeFormat is | 70 this.resolveRoots_(); |
| 81 // available in all chrome trunk builds. | |
| 82 if ('createDateTimeFormat' in this.locale_) { | |
| 83 this.shortDateFormatter_ = | |
| 84 this.locale_.createDateTimeFormat({'dateType': 'medium'}); | |
| 85 } else { | |
| 86 this.shortDateFormatter_ = { | |
| 87 format: function(d) { | |
| 88 return (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear(); | |
| 89 } | |
| 90 }; | |
| 91 } | |
| 92 | |
| 93 // TODO(rginda): 6/22/11: Remove this test when createCollator is | |
| 94 // available in all chrome trunk builds. | |
| 95 if ('createCollator' in this.locale_) { | |
| 96 this.collator_ = this.locale_.createCollator({ | |
| 97 'numeric': true, 'ignoreCase': true, 'ignoreAccents': true}); | |
| 98 } else { | |
| 99 this.collator_ = { | |
| 100 compare: function(a, b) { | |
| 101 if (a > b) return 1; | |
| 102 if (a < b) return -1; | |
| 103 return 0; | |
| 104 } | |
| 105 }; | |
| 106 } | |
| 107 | |
| 108 // Optional list of file types. | |
| 109 this.fileTypes_ = this.params_.typeList; | |
| 110 | |
| 111 this.showCheckboxes_ = | |
| 112 (this.dialogType_ == FileManager.DialogType.FULL_PAGE || | |
| 113 this.dialogType_ == FileManager.DialogType.SELECT_OPEN_MULTI_FILE); | |
| 114 | |
| 115 // DirectoryEntry representing the current directory of the dialog. | |
| 116 this.currentDirEntry_ = null; | |
| 117 | |
| 118 this.copyManager_ = new FileCopyManager(); | |
| 119 this.copyManager_.addEventListener('copy-progress', | |
| 120 this.onCopyProgress_.bind(this)); | |
| 121 | |
| 122 window.addEventListener('popstate', this.onPopState_.bind(this)); | |
| 123 window.addEventListener('unload', this.onUnload_.bind(this)); | |
| 124 | |
| 125 this.addEventListener('directory-changed', | |
| 126 this.onDirectoryChanged_.bind(this)); | |
| 127 this.addEventListener('selection-summarized', | |
| 128 this.onSelectionSummarized_.bind(this)); | |
| 129 | |
| 130 // The list of archives requested to mount. We will show contents once | |
| 131 // archive is mounted, but only for mounts from within this filebrowser tab. | |
| 132 this.mountRequests_ = []; | |
| 133 chrome.fileBrowserPrivate.onMountCompleted.addListener( | |
| 134 this.onMountCompleted_.bind(this)); | |
| 135 | |
| 136 chrome.fileBrowserPrivate.onFileChanged.addListener( | |
| 137 this.onFileChanged_.bind(this)); | |
| 138 | |
| 139 var self = this; | |
| 140 | |
| 141 // The list of callbacks to be invoked during the directory rescan after | |
| 142 // all paste tasks are complete. | |
| 143 this.pasteSuccessCallbacks_ = []; | |
| 144 | |
| 145 // The list of active mount points to distinct them from other directories. | |
| 146 chrome.fileBrowserPrivate.getMountPoints(function(mountPoints) { | |
| 147 self.mountPoints_ = mountPoints; | |
| 148 }); | |
| 149 | |
| 150 this.initCommands_(); | |
| 151 this.initDom_(); | 71 this.initDom_(); |
| 152 this.initDialogType_(); | 72 this.initDialogType_(); |
| 153 this.setupCurrentDirectory_(); | 73 this.dialogDom_.style.opacity = '1'; |
| 154 | |
| 155 this.summarizeSelection_(); | |
| 156 | |
| 157 this.dataModel_.sort('cachedMtime_', 'desc'); | |
| 158 | |
| 159 this.refocus(); | |
| 160 | |
| 161 this.createMetadataProvider_(); | |
| 162 } | 74 } |
| 163 | 75 |
| 164 FileManager.prototype = { | 76 FileManager.prototype = { |
| 165 __proto__: cr.EventTarget.prototype | 77 __proto__: cr.EventTarget.prototype |
| 166 }; | 78 }; |
| 167 | 79 |
| 168 // Anonymous "namespace". | 80 // Anonymous "namespace". |
| 169 (function() { | 81 (function() { |
| 170 | 82 |
| 171 // Private variables and helper functions. | 83 // Private variables and helper functions. |
| (...skipping 378 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 550 chrome.fileBrowserPrivate.getStrings(function(strings) { | 462 chrome.fileBrowserPrivate.getStrings(function(strings) { |
| 551 localStrings = new LocalStrings(strings); | 463 localStrings = new LocalStrings(strings); |
| 552 if (callback) | 464 if (callback) |
| 553 callback(); | 465 callback(); |
| 554 }); | 466 }); |
| 555 }; | 467 }; |
| 556 | 468 |
| 557 // Instance methods. | 469 // Instance methods. |
| 558 | 470 |
| 559 /** | 471 /** |
| 472 * Request file system and get root entries asynchronously. Invokes init_ |
| 473 * when have finished. |
| 474 */ |
| 475 FileManager.prototype.resolveRoots_ = function(callback) { |
| 476 var rootPaths = ['Downloads', 'removable', 'archive']; |
| 477 |
| 478 metrics.startInterval('RequestLocalFileSystem'); |
| 479 var self = this; |
| 480 chrome.fileBrowserPrivate.requestLocalFileSystem(function(filesystem) { |
| 481 self.filesystem_ = filesystem; |
| 482 util.installFileErrorToString(); |
| 483 |
| 484 metrics.recordTime('RequestLocalFileSystem'); |
| 485 console.log('Found filesystem: ' + filesystem.name, filesystem); |
| 486 |
| 487 var rootEntries = []; |
| 488 |
| 489 function onAllRootsFound() { |
| 490 self.rootEntries_ = rootEntries; |
| 491 self.init_(); |
| 492 } |
| 493 |
| 494 function onPathError(path, err) { |
| 495 console.error('Error locating root path: ' + path + ': ' + err); |
| 496 } |
| 497 |
| 498 function onEntryFound(entry) { |
| 499 if (entry) { |
| 500 rootEntries.push(entry); |
| 501 } else { |
| 502 onAllRootsFound(); |
| 503 } |
| 504 } |
| 505 |
| 506 metrics.startInterval('EnumerateRoots'); |
| 507 if (filesystem.name.match(/^chrome-extension_\S+:external/i)) { |
| 508 // We've been handed the local filesystem, whose root directory |
| 509 // cannot be enumerated. |
| 510 util.getDirectories(filesystem.root, {create: false}, rootPaths, |
| 511 onEntryFound, onPathError); |
| 512 } else { |
| 513 util.forEachDirEntry(filesystem.root, onEntryFound); |
| 514 } |
| 515 }); |
| 516 }; |
| 517 |
| 518 /** |
| 519 * Continue initializing the file manager after resolving roots. |
| 520 */ |
| 521 FileManager.prototype.init_ = function() { |
| 522 metrics.startInterval('InitFileManager'); |
| 523 this.initFileList_(); |
| 524 this.initDialogs_(); |
| 525 |
| 526 // TODO(rginda): 6/22/11: Remove this test when createDateTimeFormat is |
| 527 // available in all chrome trunk builds. |
| 528 if ('createDateTimeFormat' in this.locale_) { |
| 529 this.shortDateFormatter_ = |
| 530 this.locale_.createDateTimeFormat({'dateType': 'medium'}); |
| 531 } else { |
| 532 this.shortDateFormatter_ = { |
| 533 format: function(d) { |
| 534 return (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear(); |
| 535 } |
| 536 }; |
| 537 } |
| 538 |
| 539 // TODO(rginda): 6/22/11: Remove this test when createCollator is |
| 540 // available in all chrome trunk builds. |
| 541 if ('createCollator' in this.locale_) { |
| 542 this.collator_ = this.locale_.createCollator({ |
| 543 'numeric': true, 'ignoreCase': true, 'ignoreAccents': true}); |
| 544 } else { |
| 545 this.collator_ = { |
| 546 compare: function(a, b) { |
| 547 if (a > b) return 1; |
| 548 if (a < b) return -1; |
| 549 return 0; |
| 550 } |
| 551 }; |
| 552 } |
| 553 |
| 554 // Optional list of file types. |
| 555 this.fileTypes_ = this.params_.typeList; |
| 556 |
| 557 this.showCheckboxes_ = |
| 558 (this.dialogType_ == FileManager.DialogType.FULL_PAGE || |
| 559 this.dialogType_ == FileManager.DialogType.SELECT_OPEN_MULTI_FILE); |
| 560 |
| 561 // DirectoryEntry representing the current directory of the dialog. |
| 562 this.currentDirEntry_ = null; |
| 563 |
| 564 this.copyManager_ = new FileCopyManager(); |
| 565 this.copyManager_.addEventListener('copy-progress', |
| 566 this.onCopyProgress_.bind(this)); |
| 567 |
| 568 window.addEventListener('popstate', this.onPopState_.bind(this)); |
| 569 window.addEventListener('unload', this.onUnload_.bind(this)); |
| 570 |
| 571 this.addEventListener('directory-changed', |
| 572 this.onDirectoryChanged_.bind(this)); |
| 573 this.addEventListener('selection-summarized', |
| 574 this.onSelectionSummarized_.bind(this)); |
| 575 |
| 576 // The list of archives requested to mount. We will show contents once |
| 577 // archive is mounted, but only for mounts from within this filebrowser tab. |
| 578 this.mountRequests_ = []; |
| 579 chrome.fileBrowserPrivate.onMountCompleted.addListener( |
| 580 this.onMountCompleted_.bind(this)); |
| 581 |
| 582 chrome.fileBrowserPrivate.onFileChanged.addListener( |
| 583 this.onFileChanged_.bind(this)); |
| 584 |
| 585 var self = this; |
| 586 |
| 587 // The list of callbacks to be invoked during the directory rescan after |
| 588 // all paste tasks are complete. |
| 589 this.pasteSuccessCallbacks_ = []; |
| 590 |
| 591 // The list of active mount points to distinct them from other directories. |
| 592 chrome.fileBrowserPrivate.getMountPoints(function(mountPoints) { |
| 593 self.mountPoints_ = mountPoints; |
| 594 }); |
| 595 |
| 596 this.initCommands_(); |
| 597 |
| 598 this.setupCurrentDirectory_(); |
| 599 |
| 600 this.summarizeSelection_(); |
| 601 |
| 602 this.dataModel_.sort('cachedMtime_', 'desc'); |
| 603 |
| 604 this.refocus(); |
| 605 |
| 606 this.createMetadataProvider_(); |
| 607 metrics.recordTime('InitFileManager'); |
| 608 metrics.recordTime('TotalLoad'); |
| 609 }; |
| 610 |
| 611 /** |
| 560 * One-time initialization of commands. | 612 * One-time initialization of commands. |
| 561 */ | 613 */ |
| 562 FileManager.prototype.initCommands_ = function() { | 614 FileManager.prototype.initCommands_ = function() { |
| 563 var commands = this.dialogDom_.querySelectorAll('command'); | 615 var commands = this.dialogDom_.querySelectorAll('command'); |
| 564 for (var i = 0; i < commands.length; i++) { | 616 for (var i = 0; i < commands.length; i++) { |
| 565 var command = commands[i]; | 617 var command = commands[i]; |
| 566 cr.ui.Command.decorate(command); | 618 cr.ui.Command.decorate(command); |
| 567 this.commands_[command.id] = command; | 619 this.commands_[command.id] = command; |
| 568 } | 620 } |
| 569 | 621 |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 645 | 697 |
| 646 var ary = this.dialogDom_.querySelectorAll('[visibleif]'); | 698 var ary = this.dialogDom_.querySelectorAll('[visibleif]'); |
| 647 for (var i = 0; i < ary.length; i++) { | 699 for (var i = 0; i < ary.length; i++) { |
| 648 var expr = ary[i].getAttribute('visibleif'); | 700 var expr = ary[i].getAttribute('visibleif'); |
| 649 if (!eval(expr)) | 701 if (!eval(expr)) |
| 650 ary[i].style.display = 'none'; | 702 ary[i].style.display = 'none'; |
| 651 } | 703 } |
| 652 | 704 |
| 653 // Populate the static localized strings. | 705 // Populate the static localized strings. |
| 654 i18nTemplate.process(this.document_, localStrings.templateData); | 706 i18nTemplate.process(this.document_, localStrings.templateData); |
| 707 }; |
| 655 | 708 |
| 709 /** |
| 710 * Constructs table and grid (heavy operation). |
| 711 **/ |
| 712 FileManager.prototype.initFileList_ = function() { |
| 656 // Always sharing the data model between the detail/thumb views confuses | 713 // Always sharing the data model between the detail/thumb views confuses |
| 657 // them. Instead we maintain this bogus data model, and hook it up to the | 714 // them. Instead we maintain this bogus data model, and hook it up to the |
| 658 // view that is not in use. | 715 // view that is not in use. |
| 659 this.emptyDataModel_ = new cr.ui.ArrayDataModel([]); | 716 this.emptyDataModel_ = new cr.ui.ArrayDataModel([]); |
| 660 | 717 |
| 661 this.dataModel_ = new cr.ui.ArrayDataModel([]); | 718 this.dataModel_ = new cr.ui.ArrayDataModel([]); |
| 662 var collator = this.collator_; | 719 var collator = this.collator_; |
| 663 this.dataModel_.setCompareFunction('name', function(a, b) { | 720 this.dataModel_.setCompareFunction('name', function(a, b) { |
| 664 return collator.compare(a.name, b.name); | 721 return collator.compare(a.name, b.name); |
| 665 }); | 722 }); |
| (...skipping 12 matching lines...) Expand all Loading... |
| 678 } else { | 735 } else { |
| 679 this.selectionModelClass_ = cr.ui.ListSelectionModel; | 736 this.selectionModelClass_ = cr.ui.ListSelectionModel; |
| 680 } | 737 } |
| 681 | 738 |
| 682 this.initTable_(); | 739 this.initTable_(); |
| 683 this.initGrid_(); | 740 this.initGrid_(); |
| 684 | 741 |
| 685 this.setListType(FileManager.ListType.DETAIL); | 742 this.setListType(FileManager.ListType.DETAIL); |
| 686 | 743 |
| 687 this.onResize_(); | 744 this.onResize_(); |
| 688 this.dialogDom_.style.opacity = '1'; | |
| 689 | 745 |
| 690 this.textSearchState_ = {text: '', date: new Date()}; | 746 this.textSearchState_ = {text: '', date: new Date()}; |
| 691 }; | 747 }; |
| 692 | 748 |
| 693 /** | 749 /** |
| 694 * Get the icon type for a given Entry. | 750 * Get the icon type for a given Entry. |
| 695 * | 751 * |
| 696 * @param {Entry} entry An Entry subclass (FileEntry or DirectoryEntry). | 752 * @param {Entry} entry An Entry subclass (FileEntry or DirectoryEntry). |
| 697 * @return {string} | 753 * @return {string} |
| 698 */ | 754 */ |
| (...skipping 516 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1215 this.deleteEntries(this.selection.entries); | 1271 this.deleteEntries(this.selection.entries); |
| 1216 return; | 1272 return; |
| 1217 } | 1273 } |
| 1218 }; | 1274 }; |
| 1219 | 1275 |
| 1220 /** | 1276 /** |
| 1221 * Respond to the back and forward buttons. | 1277 * Respond to the back and forward buttons. |
| 1222 */ | 1278 */ |
| 1223 FileManager.prototype.onPopState_ = function(event) { | 1279 FileManager.prototype.onPopState_ = function(event) { |
| 1224 // TODO(serya): We should restore selected items here. | 1280 // TODO(serya): We should restore selected items here. |
| 1225 this.setupCurrentDirectory_(); | 1281 if (this.rootEntries_) |
| 1282 this.setupCurrentDirectory_(); |
| 1226 }; | 1283 }; |
| 1227 | 1284 |
| 1228 FileManager.prototype.requestResize_ = function(timeout) { | 1285 FileManager.prototype.requestResize_ = function(timeout) { |
| 1229 var self = this; | 1286 var self = this; |
| 1230 setTimeout(function() { self.onResize_() }, timeout || 0); | 1287 setTimeout(function() { self.onResize_() }, timeout || 0); |
| 1231 }; | 1288 }; |
| 1232 | 1289 |
| 1233 /** | 1290 /** |
| 1234 * Resize details and thumb views to fit the new window size. | 1291 * Resize details and thumb views to fit the new window size. |
| 1235 */ | 1292 */ |
| (...skipping 2500 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 3736 }); | 3793 }); |
| 3737 }, onError); | 3794 }, onError); |
| 3738 | 3795 |
| 3739 function onError(err) { | 3796 function onError(err) { |
| 3740 console.log('Error while checking free space: ' + err); | 3797 console.log('Error while checking free space: ' + err); |
| 3741 setTimeout(doCheck, 1000 * 60); | 3798 setTimeout(doCheck, 1000 * 60); |
| 3742 } | 3799 } |
| 3743 } | 3800 } |
| 3744 } | 3801 } |
| 3745 })(); | 3802 })(); |
| OLD | NEW |