Home | History | Annotate | Download | only in js
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 'use strict';
      6 
      7 var CommandUtil = {};
      8 
      9 /**
     10  * Extracts path on which command event was dispatched.
     11  *
     12  * @param {DirectoryTree|DirectoryItem|NavigationList|HTMLLIElement|cr.ui.List}
     13  *     element Directory to extract a path from.
     14  * @return {?string} Path of the found node.
     15  */
     16 CommandUtil.getCommandPath = function(element) {
     17   if (element instanceof NavigationList) {
     18     // element is a NavigationList.
     19     return element.selectedItem;
     20   } else if (element instanceof NavigationListItem) {
     21     // element is a subitem of NavigationList.
     22     var navigationList = element.parentElement;
     23     var index = navigationList.getIndexOfListItem(element);
     24     return (index != -1) ? navigationList.dataModel.item(index) : null;
     25   } else if (element instanceof DirectoryTree) {
     26     // element is a DirectoryTree.
     27     var item = element.selectedItem;
     28     return item && item.fullPath;
     29   } else if (element instanceof DirectoryItem) {
     30     // element is a sub item in DirectoryTree.
     31 
     32     // DirectoryItem.fullPath is set on initialization, but entry is lazily.
     33     // We may use fullPath just in case that the entry has not been set yet.
     34     return element.entry && element.entry.fullPath ||
     35            element.fullPath;
     36   } else if (cr.ui.List) {
     37     // element is a normal List (eg. the file list on the right panel).
     38     var entry = element.selectedItem;
     39     return entry && entry.fullPath;
     40   } else {
     41     console.warn('Unsupported element');
     42     return null;
     43   }
     44 };
     45 
     46 /**
     47  * @param {NavigationList} navigationList navigation list to extract root node.
     48  * @return {?RootType} Type of the found root.
     49  */
     50 CommandUtil.getCommandRootType = function(navigationList) {
     51   var root = CommandUtil.getCommandPath(navigationList);
     52   return root && PathUtil.isRootPath(root) && PathUtil.getRootType(root);
     53 };
     54 
     55 /**
     56  * Checks if command can be executed on drive.
     57  * @param {Event} event Command event to mark.
     58  * @param {FileManager} fileManager FileManager to use.
     59  */
     60 CommandUtil.canExecuteEnabledOnDriveOnly = function(event, fileManager) {
     61   event.canExecute = fileManager.isOnDrive();
     62 };
     63 
     64 /**
     65  * Checks if command should be visible on drive.
     66  * @param {Event} event Command event to mark.
     67  * @param {FileManager} fileManager FileManager to use.
     68  */
     69 CommandUtil.canExecuteVisibleOnDriveOnly = function(event, fileManager) {
     70   event.canExecute = fileManager.isOnDrive();
     71   event.command.setHidden(!fileManager.isOnDrive());
     72 };
     73 
     74 /**
     75  * Checks if command should be visible on drive with pressing ctrl key.
     76  * @param {Event} event Command event to mark.
     77  * @param {FileManager} fileManager FileManager to use.
     78  */
     79 CommandUtil.canExecuteVisibleOnDriveWithCtrlKeyOnly =
     80     function(event, fileManager) {
     81   event.canExecute = fileManager.isOnDrive() && fileManager.isCtrlKeyPressed();
     82   event.command.setHidden(!event.canExecute);
     83 };
     84 
     85 /**
     86  * Sets as the command as always enabled.
     87  * @param {Event} event Command event to mark.
     88  */
     89 CommandUtil.canExecuteAlways = function(event) {
     90   event.canExecute = true;
     91 };
     92 
     93 /**
     94  * Returns a single selected/passed entry or null.
     95  * @param {Event} event Command event.
     96  * @param {FileManager} fileManager FileManager to use.
     97  * @return {FileEntry} The entry or null.
     98  */
     99 CommandUtil.getSingleEntry = function(event, fileManager) {
    100   if (event.target.entry) {
    101     return event.target.entry;
    102   }
    103   var selection = fileManager.getSelection();
    104   if (selection.totalCount == 1) {
    105     return selection.entries[0];
    106   }
    107   return null;
    108 };
    109 
    110 /**
    111  * Registers handler on specific command on specific node.
    112  * @param {Node} node Node to register command handler on.
    113  * @param {string} commandId Command id to respond to.
    114  * @param {{execute:function, canExecute:function}} handler Handler to use.
    115  * @param {...Object} var_args Additional arguments to pass to handler.
    116  */
    117 CommandUtil.registerCommand = function(node, commandId, handler, var_args) {
    118   var args = Array.prototype.slice.call(arguments, 3);
    119 
    120   node.addEventListener('command', function(event) {
    121     if (event.command.id == commandId) {
    122       handler.execute.apply(handler, [event].concat(args));
    123       event.cancelBubble = true;
    124     }
    125   });
    126 
    127   node.addEventListener('canExecute', function(event) {
    128     if (event.command.id == commandId)
    129       handler.canExecute.apply(handler, [event].concat(args));
    130   });
    131 };
    132 
    133 /**
    134  * Sets Commands.defaultCommand for the commandId and prevents handling
    135  * the keydown events for this command. Not doing that breaks relationship
    136  * of original keyboard event and the command. WebKit would handle it
    137  * differently in some cases.
    138  * @param {Node} node to register command handler on.
    139  * @param {string} commandId Command id to respond to.
    140  */
    141 CommandUtil.forceDefaultHandler = function(node, commandId) {
    142   var doc = node.ownerDocument;
    143   var command = doc.querySelector('command[id="' + commandId + '"]');
    144   node.addEventListener('keydown', function(e) {
    145     if (command.matchesEvent(e)) {
    146       // Prevent cr.ui.CommandManager of handling it and leave it
    147       // for the default handler.
    148       e.stopPropagation();
    149     }
    150   });
    151   CommandUtil.registerCommand(node, commandId, Commands.defaultCommand, doc);
    152 };
    153 
    154 var Commands = {};
    155 
    156 /**
    157  * Forwards all command events to standard document handlers.
    158  */
    159 Commands.defaultCommand = {
    160   execute: function(event, document) {
    161     document.execCommand(event.command.id);
    162   },
    163   canExecute: function(event, document) {
    164     event.canExecute = document.queryCommandEnabled(event.command.id);
    165   }
    166 };
    167 
    168 /**
    169  * Unmounts external drive.
    170  */
    171 Commands.unmountCommand = {
    172   /**
    173    * @param {Event} event Command event.
    174    * @param {FileManager} fileManager The file manager instance.
    175    */
    176   execute: function(event, fileManager) {
    177     var root = CommandUtil.getCommandPath(event.target);
    178     if (root)
    179       fileManager.unmountVolume(PathUtil.getRootPath(root));
    180   },
    181   /**
    182    * @param {Event} event Command event.
    183    */
    184   canExecute: function(event) {
    185     var rootType = CommandUtil.getCommandRootType(event.target);
    186 
    187     event.canExecute = (rootType == RootType.ARCHIVE ||
    188                         rootType == RootType.REMOVABLE);
    189     event.command.setHidden(!event.canExecute);
    190     event.command.label = rootType == RootType.ARCHIVE ?
    191         str('CLOSE_ARCHIVE_BUTTON_LABEL') :
    192         str('UNMOUNT_DEVICE_BUTTON_LABEL');
    193   }
    194 };
    195 
    196 /**
    197  * Formats external drive.
    198  */
    199 Commands.formatCommand = {
    200   /**
    201    * @param {Event} event Command event.
    202    * @param {FileManager} fileManager The file manager instance.
    203    */
    204   execute: function(event, fileManager) {
    205     var root = CommandUtil.getCommandPath(event.target);
    206 
    207     if (root) {
    208       var url = util.makeFilesystemUrl(PathUtil.getRootPath(root));
    209       fileManager.confirm.show(
    210           loadTimeData.getString('FORMATTING_WARNING'),
    211           chrome.fileBrowserPrivate.formatDevice.bind(null, url));
    212     }
    213   },
    214   /**
    215    * @param {Event} event Command event.
    216    * @param {FileManager} fileManager The file manager instance.
    217    * @param {DirectoryModel} directoryModel The directory model instance.
    218    */
    219   canExecute: function(event, fileManager, directoryModel) {
    220     var root = CommandUtil.getCommandPath(event.target);
    221     var removable = root &&
    222                     PathUtil.getRootType(root) == RootType.REMOVABLE;
    223     var isReadOnly = root && directoryModel.isPathReadOnly(root);
    224     event.canExecute = removable && !isReadOnly;
    225     event.command.setHidden(!removable);
    226   }
    227 };
    228 
    229 /**
    230  * Imports photos from external drive
    231  */
    232 Commands.importCommand = {
    233   /**
    234    * @param {Event} event Command event.
    235    * @param {NavigationList} navigationList Target navigation list.
    236    */
    237   execute: function(event, navigationList) {
    238     var root = CommandUtil.getCommandPath(navigationList);
    239     if (!root)
    240       return;
    241 
    242     // TODO(mtomasz): Implement launching Photo Importer.
    243   },
    244   /**
    245    * @param {Event} event Command event.
    246    * @param {NavigationList} navigationList Target navigation list.
    247    */
    248   canExecute: function(event, navigationList) {
    249     var rootType = CommandUtil.getCommandRootType(navigationList);
    250     event.canExecute = (rootType != RootType.DRIVE);
    251   }
    252 };
    253 
    254 /**
    255  * Initiates new folder creation.
    256  */
    257 Commands.newFolderCommand = {
    258   execute: function(event, fileManager) {
    259     fileManager.createNewFolder();
    260   },
    261   canExecute: function(event, fileManager, directoryModel) {
    262     event.canExecute = !fileManager.isOnReadonlyDirectory() &&
    263                        !fileManager.isRenamingInProgress() &&
    264                        !directoryModel.isSearching() &&
    265                        !directoryModel.isScanning();
    266   }
    267 };
    268 
    269 /**
    270  * Initiates new window creation.
    271  */
    272 Commands.newWindowCommand = {
    273   execute: function(event, fileManager, directoryModel) {
    274     chrome.runtime.getBackgroundPage(function(background) {
    275       var appState = {
    276         defaultPath: directoryModel.getCurrentDirPath()
    277       };
    278       background.launchFileManager(appState);
    279     });
    280   },
    281   canExecute: function(event, fileManager) {
    282     event.canExecute = (fileManager.dialogType == DialogType.FULL_PAGE);
    283   }
    284 };
    285 
    286 /**
    287  * Changed the default app handling inserted media.
    288  */
    289 Commands.changeDefaultAppCommand = {
    290   execute: function(event, fileManager) {
    291     fileManager.showChangeDefaultAppPicker();
    292   },
    293   canExecute: CommandUtil.canExecuteAlways
    294 };
    295 
    296 /**
    297  * Deletes selected files.
    298  */
    299 Commands.deleteFileCommand = {
    300   execute: function(event, fileManager) {
    301     fileManager.deleteSelection();
    302   },
    303   canExecute: function(event, fileManager) {
    304     var selection = fileManager.getSelection();
    305     event.canExecute = !fileManager.isOnReadonlyDirectory() &&
    306                        selection &&
    307                        selection.totalCount > 0;
    308   }
    309 };
    310 
    311 /**
    312  * Pastes files from clipboard.
    313  */
    314 Commands.pasteFileCommand = {
    315   execute: Commands.defaultCommand.execute,
    316   canExecute: function(event, document, fileTransferController) {
    317     event.canExecute = (fileTransferController &&
    318         fileTransferController.queryPasteCommandEnabled());
    319   }
    320 };
    321 
    322 /**
    323  * Initiates file renaming.
    324  */
    325 Commands.renameFileCommand = {
    326   execute: function(event, fileManager) {
    327     fileManager.initiateRename();
    328   },
    329   canExecute: function(event, fileManager) {
    330     var selection = fileManager.getSelection();
    331     event.canExecute =
    332         !fileManager.isRenamingInProgress() &&
    333         !fileManager.isOnReadonlyDirectory() &&
    334         selection &&
    335         selection.totalCount == 1;
    336   }
    337 };
    338 
    339 /**
    340  * Opens drive help.
    341  */
    342 Commands.volumeHelpCommand = {
    343   execute: function() {
    344     if (fileManager.isOnDrive())
    345       chrome.windows.create({url: FileManager.GOOGLE_DRIVE_HELP});
    346     else
    347       chrome.windows.create({url: FileManager.FILES_APP_HELP});
    348   },
    349   canExecute: CommandUtil.canExecuteAlways
    350 };
    351 
    352 /**
    353  * Opens drive buy-more-space url.
    354  */
    355 Commands.driveBuySpaceCommand = {
    356   execute: function() {
    357     chrome.windows.create({url: FileManager.GOOGLE_DRIVE_BUY_STORAGE});
    358   },
    359   canExecute: CommandUtil.canExecuteVisibleOnDriveOnly
    360 };
    361 
    362 /**
    363  * Clears drive cache.
    364  */
    365 Commands.driveClearCacheCommand = {
    366   execute: function() {
    367     chrome.fileBrowserPrivate.clearDriveCache();
    368   },
    369   canExecute: CommandUtil.canExecuteVisibleOnDriveWithCtrlKeyOnly
    370 };
    371 
    372 /**
    373  * Opens drive.google.com.
    374  */
    375 Commands.driveGoToDriveCommand = {
    376   execute: function() {
    377     chrome.windows.create({url: FileManager.GOOGLE_DRIVE_ROOT});
    378   },
    379   canExecute: CommandUtil.canExecuteVisibleOnDriveOnly
    380 };
    381 
    382 /**
    383  * Displays open with dialog for current selection.
    384  */
    385 Commands.openWithCommand = {
    386   execute: function(event, fileManager) {
    387     var tasks = fileManager.getSelection().tasks;
    388     if (tasks) {
    389       tasks.showTaskPicker(fileManager.defaultTaskPicker,
    390           str('OPEN_WITH_BUTTON_LABEL'),
    391           null,
    392           function(task) {
    393             tasks.execute(task.taskId);
    394           });
    395     }
    396   },
    397   canExecute: function(event, fileManager) {
    398     var tasks = fileManager.getSelection().tasks;
    399     event.canExecute = tasks && tasks.size() > 1;
    400   }
    401 };
    402 
    403 /**
    404  * Focuses search input box.
    405  */
    406 Commands.searchCommand = {
    407   execute: function(event, fileManager, element) {
    408     element.focus();
    409     element.select();
    410   },
    411   canExecute: function(event, fileManager) {
    412     event.canExecute = !fileManager.isRenamingInProgress();
    413   }
    414 };
    415 
    416 /**
    417  * Activates the n-th volume.
    418  */
    419 Commands.volumeSwitchCommand = {
    420   execute: function(event, navigationList, index) {
    421     navigationList.selectByIndex(index - 1);
    422   },
    423   canExecute: function(event, navigationList, index) {
    424     event.canExecute = index > 0 && index <= navigationList.dataModel.length;
    425   }
    426 };
    427 
    428 /**
    429  * Flips 'available offline' flag on the file.
    430  */
    431 Commands.togglePinnedCommand = {
    432   execute: function(event, fileManager) {
    433     var pin = !event.command.checked;
    434     event.command.checked = pin;
    435     var entries = Commands.togglePinnedCommand.getTargetEntries_();
    436     var currentEntry;
    437     var error = false;
    438     var steps = {
    439       // Pick an entry and pin it.
    440       start: function() {
    441         // Check if all the entries are pinned or not.
    442         if (entries.length == 0)
    443           return;
    444         currentEntry = entries.shift();
    445         chrome.fileBrowserPrivate.pinDriveFile(
    446             currentEntry.toURL(),
    447             pin,
    448             steps.entryPinned);
    449       },
    450 
    451       // Check the result of pinning
    452       entryPinned: function() {
    453         // Convert to boolean.
    454         error = !!chrome.runtime.lastError;
    455         if (error && pin) {
    456           fileManager.metadataCache_.get(
    457               currentEntry, 'filesystem', steps.showError);
    458         }
    459         fileManager.metadataCache_.clear(currentEntry, 'drive');
    460         fileManager.metadataCache_.get(
    461             currentEntry, 'drive', steps.updateUI.bind(this));
    462       },
    463 
    464       // Update the user interface accoding to the cache state.
    465       updateUI: function(drive) {
    466         fileManager.updateMetadataInUI_(
    467             'drive', [currentEntry.toURL()], [drive]);
    468         if (!error)
    469           steps.start();
    470       },
    471 
    472       // Show the error
    473       showError: function(filesystem) {
    474         fileManager.alert.showHtml(str('DRIVE_OUT_OF_SPACE_HEADER'),
    475                                    strf('DRIVE_OUT_OF_SPACE_MESSAGE',
    476                                         unescape(currentEntry.name),
    477                                         util.bytesToString(filesystem.size)));
    478       }
    479     };
    480     steps.start();
    481   },
    482 
    483   canExecute: function(event, fileManager) {
    484     var entries = Commands.togglePinnedCommand.getTargetEntries_();
    485     var checked = true;
    486     for (var i = 0; i < entries.length; i++) {
    487       checked = checked && entries[i].pinned;
    488     }
    489     if (entries.length > 0) {
    490       event.canExecute = true;
    491       event.command.setHidden(false);
    492       event.command.checked = checked;
    493     } else {
    494       event.canExecute = false;
    495       event.command.setHidden(true);
    496     }
    497   },
    498 
    499   /**
    500    * Obtains target entries from the selection.
    501    * If directories are included in the selection, it just returns an empty
    502    * array to avoid confusing because pinning directory is not supported
    503    * currently.
    504    *
    505    * @return {Array.<Entry>} Target entries.
    506    * @private
    507    */
    508   getTargetEntries_: function() {
    509     var hasDirectory = false;
    510     var results = fileManager.getSelection().entries.filter(function(entry) {
    511       hasDirectory = hasDirectory || entry.isDirectory;
    512       if (!entry || hasDirectory)
    513         return false;
    514       var metadata = fileManager.metadataCache_.getCached(entry, 'drive');
    515         if (!metadata || metadata.hosted)
    516           return false;
    517       entry.pinned = metadata.pinned;
    518       return true;
    519     });
    520     return hasDirectory ? [] : results;
    521   }
    522 };
    523 
    524 /**
    525  * Creates zip file for current selection.
    526  */
    527 Commands.zipSelectionCommand = {
    528   execute: function(event, fileManager, directoryModel) {
    529     var dirEntry = directoryModel.getCurrentDirEntry();
    530     var selectionEntries = fileManager.getSelection().entries;
    531     fileManager.copyManager_.zipSelection(dirEntry, selectionEntries);
    532   },
    533   canExecute: function(event, fileManager) {
    534     var selection = fileManager.getSelection();
    535     event.canExecute = !fileManager.isOnReadonlyDirectory() &&
    536         !fileManager.isOnDrive() &&
    537         selection && selection.totalCount > 0;
    538   }
    539 };
    540 
    541 /**
    542  * Shows the share dialog for the current selection (single only).
    543  */
    544 Commands.shareCommand = {
    545   execute: function(event, fileManager) {
    546     fileManager.shareSelection();
    547   },
    548   canExecute: function(event, fileManager) {
    549     var selection = fileManager.getSelection();
    550     event.canExecute = fileManager.isOnDrive() &&
    551         !fileManager.isDriveOffline() &&
    552         selection && selection.totalCount == 1;
    553     event.command.setHidden(!fileManager.isOnDrive());
    554   }
    555 };
    556 
    557 /**
    558  * Creates a shortcut of the selected folder (single only).
    559  */
    560 Commands.createFolderShortcutCommand = {
    561   /**
    562    * @param {Event} event Command event.
    563    * @param {FileManager} fileManager The file manager instance.
    564    */
    565   execute: function(event, fileManager) {
    566     var path = CommandUtil.getCommandPath(event.target);
    567     if (path)
    568       fileManager.createFolderShortcut(path);
    569   },
    570 
    571   /**
    572    * @param {Event} event Command event.
    573    * @param {FileManager} fileManager The file manager instance.
    574    */
    575   canExecute: function(event, fileManager) {
    576     var target = event.target;
    577     // TODO(yoshiki): remove this after launching folder shortcuts feature.
    578     if (!fileManager.isFolderShortcutsEnabled() ||
    579         (!target instanceof NavigationListItem &&
    580          !target instanceof DirectoryItem)) {
    581       event.command.setHidden(true);
    582       return;
    583     }
    584 
    585     var path = CommandUtil.getCommandPath(event.target);
    586     var folderShortcutExists = path && fileManager.folderShortcutExists(path);
    587 
    588     var onlyOneFolderSelected = true;
    589     // Only on list, user can select multiple files. The command is enabled only
    590     // when a single file is selected.
    591     if (event.target instanceof cr.ui.List) {
    592       var items = event.target.selectedItems;
    593       onlyOneFolderSelected = (items.length == 1 && items[0].isDirectory);
    594     }
    595 
    596     var eligible = path && PathUtil.isEligibleForFolderShortcut(path);
    597     event.canExecute =
    598         eligible && onlyOneFolderSelected && !folderShortcutExists;
    599     event.command.setHidden(!eligible || !onlyOneFolderSelected);
    600   }
    601 };
    602 
    603 /**
    604  * Removes the folder shortcut.
    605  */
    606 Commands.removeFolderShortcutCommand = {
    607   /**
    608    * @param {Event} event Command event.
    609    * @param {FileManager} fileManager The file manager instance.
    610    */
    611   execute: function(event, fileManager) {
    612     var path = CommandUtil.getCommandPath(event.target);
    613     if (path)
    614       fileManager.removeFolderShortcut(path);
    615   },
    616 
    617   /**
    618    * @param {Event} event Command event.
    619    * @param {FileManager} fileManager The file manager instance.
    620    */
    621   canExecute: function(event, fileManager) {
    622     var target = event.target;
    623     // TODO(yoshiki): remove this after launching folder shortcut feature.
    624     if (!fileManager.isFolderShortcutsEnabled() ||
    625         (!target instanceof NavigationListItem &&
    626          !target instanceof DirectoryItem)) {
    627       event.command.setHidden(true);
    628       return;
    629     }
    630 
    631     var path = CommandUtil.getCommandPath(target);
    632     var eligible = path && PathUtil.isEligibleForFolderShortcut(path);
    633     var isShortcut = path && fileManager.folderShortcutExists(path);
    634     event.canExecute = isShortcut && eligible;
    635     event.command.setHidden(!event.canExecute);
    636   }
    637 };
    638 
    639 /**
    640  * Zoom in to the Files.app.
    641  */
    642 Commands.zoomInCommand = {
    643   execute: function(event) {
    644     chrome.fileBrowserPrivate.zoom('in');
    645   },
    646   canExecute: CommandUtil.canExecuteAlways
    647 };
    648 
    649 /**
    650  * Zoom out from the Files.app.
    651  */
    652 Commands.zoomOutCommand = {
    653   execute: function(event) {
    654     chrome.fileBrowserPrivate.zoom('out');
    655   },
    656   canExecute: CommandUtil.canExecuteAlways
    657 };
    658 
    659 /**
    660  * Reset the zoom factor.
    661  */
    662 Commands.zoomResetCommand = {
    663   execute: function(event) {
    664     chrome.fileBrowserPrivate.zoom('reset');
    665   },
    666   canExecute: CommandUtil.canExecuteAlways
    667 };
    668