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/**
8 * TODO(dzvorygin): Here we use this hack, since 'hidden' is standard
9 * attribute and we can't use it's setter as usual.
10 * @param {boolean} value New value of hidden property.
11 */
12cr.ui.Command.prototype.setHidden = function(value) {
13  this.__lookupSetter__('hidden').call(this, value);
14};
15
16/**
17 * A command.
18 * @interface
19 */
20var Command = function() {};
21
22/**
23 * Handles the execute event.
24 * @param {Event} event Command event.
25 * @param {FileManager} fileManager FileManager.
26 */
27Command.prototype.execute = function(event, fileManager) {};
28
29/**
30 * Handles the can execute event.
31 * @param {Event} event Can execute event.
32 * @param {FileManager} fileManager FileManager.
33 */
34Command.prototype.canExecute = function(event, fileManager) {};
35
36/**
37 * Utility for commands.
38 */
39var CommandUtil = {};
40
41/**
42 * Extracts entry on which command event was dispatched.
43 *
44 * @param {DirectoryTree|DirectoryItem|HTMLLIElement|cr.ui.List}
45 *     element Directory to extract a path from.
46 * @return {Entry} Entry of the found node.
47 */
48CommandUtil.getCommandEntry = function(element) {
49  if (element instanceof DirectoryTree) {
50    // element is a DirectoryTree.
51    return element.selectedItem ? element.selectedItem.entry : null;
52  } else if (element instanceof DirectoryItem ||
53             element instanceof VolumeItem ||
54             element instanceof ShortcutItem) {
55    // element are sub items in DirectoryTree.
56    return element.entry;
57  } else if (element instanceof cr.ui.List) {
58    // element is a normal List (eg. the file list on the right panel).
59    var entry = element.selectedItem;
60    // Check if it is Entry or not by checking for toURL().
61    return entry && 'toURL' in entry ? entry : null;
62  } else {
63    return null;
64  }
65};
66
67/**
68 * Obtains an entry from the give navigation model item.
69 * @param {NavigationModelItem} item Navigation model item.
70 * @return {Entry} Related entry.
71 * @private
72 */
73CommandUtil.getEntryFromNavigationModelItem_ = function(item) {
74  if (item.isVolume)
75    return item.volumeInfo.displayRoot;
76  if (item.isShortcut)
77    return item.entry;
78  return null;
79};
80
81/**
82 * Checks if command can be executed on drive.
83 * @param {Event} event Command event to mark.
84 * @param {FileManager} fileManager FileManager to use.
85 */
86CommandUtil.canExecuteEnabledOnDriveOnly = function(event, fileManager) {
87  event.canExecute = fileManager.isOnDrive();
88};
89
90/**
91 * Sets the command as visible only when the current volume is drive and it's
92 * running as a normal app, not as a modal dialog.
93 * @param {Event} event Command event to mark.
94 * @param {FileManager} fileManager FileManager to use.
95 */
96CommandUtil.canExecuteVisibleOnDriveInNormalAppModeOnly =
97    function(event, fileManager) {
98  var enabled = fileManager.isOnDrive() &&
99      !DialogType.isModal(fileManager.dialogType);
100  event.canExecute = enabled;
101  event.command.setHidden(!enabled);
102};
103
104/**
105 * Sets as the command as always enabled.
106 * @param {Event} event Command event to mark.
107 */
108CommandUtil.canExecuteAlways = function(event) {
109  event.canExecute = true;
110};
111
112/**
113 * Returns a single selected/passed entry or null.
114 * @param {Event} event Command event.
115 * @param {FileManager} fileManager FileManager to use.
116 * @return {FileEntry} The entry or null.
117 */
118CommandUtil.getSingleEntry = function(event, fileManager) {
119  if (event.target.entry) {
120    return event.target.entry;
121  }
122  var selection = fileManager.getSelection();
123  if (selection.totalCount == 1) {
124    return selection.entries[0];
125  }
126  return null;
127};
128
129/**
130 * Obtains target entries that can be pinned from the selection.
131 * If directories are included in the selection, it just returns an empty
132 * array to avoid confusing because pinning directory is not supported
133 * currently.
134 *
135 * @return {Array.<Entry>} Target entries.
136 */
137CommandUtil.getPinTargetEntries = function() {
138  var hasDirectory = false;
139  var results = fileManager.getSelection().entries.filter(function(entry) {
140    hasDirectory = hasDirectory || entry.isDirectory;
141    if (!entry || hasDirectory)
142      return false;
143    var metadata = fileManager.metadataCache_.getCached(entry, 'external');
144    if (!metadata || metadata.hosted)
145      return false;
146    entry.pinned = metadata.pinned;
147    return true;
148  });
149  return hasDirectory ? [] : results;
150};
151
152/**
153 * Sets the default handler for the commandId and prevents handling
154 * the keydown events for this command. Not doing that breaks relationship
155 * of original keyboard event and the command. WebKit would handle it
156 * differently in some cases.
157 * @param {Node} node to register command handler on.
158 * @param {string} commandId Command id to respond to.
159 */
160CommandUtil.forceDefaultHandler = function(node, commandId) {
161  var doc = node.ownerDocument;
162  var command = doc.querySelector('command[id="' + commandId + '"]');
163  node.addEventListener('keydown', function(e) {
164    if (command.matchesEvent(e)) {
165      // Prevent cr.ui.CommandManager of handling it and leave it
166      // for the default handler.
167      e.stopPropagation();
168    }
169  });
170  node.addEventListener('command', function(event) {
171    if (event.command.id !== commandId)
172      return;
173    document.execCommand(event.command.id);
174    event.cancelBubble = true;
175  });
176  node.addEventListener('canExecute', function(event) {
177    if (event.command.id === commandId)
178      event.canExecute = document.queryCommandEnabled(event.command.id);
179  });
180};
181
182/**
183 * Default command.
184 * @type {Command}
185 */
186CommandUtil.defaultCommand = {
187  execute: function(event, fileManager) {
188    fileManager.document.execCommand(event.command.id);
189  },
190  canExecute: function(event, fileManager) {
191    event.canExecute = fileManager.document.queryCommandEnabled(
192        event.command.id);
193  }
194};
195
196/**
197 * Creates the volume switch command with index.
198 * @param {number} index Volume index from 1 to 9.
199 * @return {Command} Volume switch command.
200 */
201CommandUtil.createVolumeSwitchCommand = function(index) {
202  return {
203    execute: function(event, fileManager) {
204      fileManager.directoryTree.selectByIndex(index - 1);
205    },
206    canExecute: function(event, fileManager) {
207      event.canExecute = index > 0 &&
208          index <= fileManager.directoryTree.items.length;
209    }
210  };
211};
212
213/**
214 * Returns a directory entry when only one entry is selected and it is
215 * directory. Otherwise, returns null.
216 * @param {FileSelection} selection Instance of FileSelection.
217 * @return {?DirectoryEntry} Directory entry which is selected alone.
218 */
219CommandUtil.getOnlyOneSelectedDirectory = function(selection) {
220  if (!selection)
221    return null;
222  if (selection.totalCount !== 1)
223    return null;
224  if (!selection.entries[0].isDirectory)
225    return null;
226  return selection.entries[0];
227};
228
229/**
230 * Handle of the command events.
231 * @param {FileManager} fileManager FileManager.
232 * @constructor
233 */
234var CommandHandler = function(fileManager) {
235  /**
236   * FileManager.
237   * @type {FileManager}
238   * @private
239   */
240  this.fileManager_ = fileManager;
241
242  /**
243   * Command elements.
244   * @type {Object.<string, cr.ui.Command>}
245   * @private
246   */
247  this.commands_ = {};
248
249  Object.seal(this);
250
251  // Decorate command tags in the document.
252  var commands = fileManager.document.querySelectorAll('command');
253  for (var i = 0; i < commands.length; i++) {
254    cr.ui.Command.decorate(commands[i]);
255    this.commands_[commands[i].id] = commands[i];
256  }
257
258  // Register events.
259  fileManager.document.addEventListener('command', this.onCommand_.bind(this));
260  fileManager.document.addEventListener('canExecute',
261                                        this.onCanExecute_.bind(this));
262};
263
264/**
265 * Updates the availability of all commands.
266 */
267CommandHandler.prototype.updateAvailability = function() {
268  for (var id in this.commands_) {
269    this.commands_[id].canExecuteChange();
270  }
271};
272
273/**
274 * Checks if the handler should ignore the current event, eg. since there is
275 * a popup dialog currently opened.
276 *
277 * @return {boolean} True if the event should be ignored, false otherwise.
278 * @private
279 */
280CommandHandler.prototype.shouldIgnoreEvents_ = function() {
281  // Do not handle commands, when a dialog is shown.
282  if (this.fileManager_.document.querySelector('.cr-dialog-container.shown'))
283    return true;
284
285  return false;  // Do not ignore.
286};
287
288/**
289 * Handles command events.
290 * @param {Event} event Command event.
291 * @private
292 */
293CommandHandler.prototype.onCommand_ = function(event) {
294  if (this.shouldIgnoreEvents_())
295    return;
296  var handler = CommandHandler.COMMANDS_[event.command.id];
297  handler.execute.call(this, event, this.fileManager_);
298};
299
300/**
301 * Handles canExecute events.
302 * @param {Event} event Can execute event.
303 * @private
304 */
305CommandHandler.prototype.onCanExecute_ = function(event) {
306  if (this.shouldIgnoreEvents_())
307    return;
308  var handler = CommandHandler.COMMANDS_[event.command.id];
309  handler.canExecute.call(this, event, this.fileManager_);
310};
311
312/**
313 * Commands.
314 * @type {Object.<string, Command>}
315 * @const
316 * @private
317 */
318CommandHandler.COMMANDS_ = {};
319
320/**
321 * Unmounts external drive.
322 * @type {Command}
323 */
324CommandHandler.COMMANDS_['unmount'] = {
325  /**
326   * @param {Event} event Command event.
327   * @param {FileManager} fileManager The file manager instance.
328   */
329  execute: function(event, fileManager) {
330    var root = CommandUtil.getCommandEntry(event.target);
331    if (!root) {
332      console.warn('unmount command executed on an element which does not ' +
333                   'have corresponding entry.');
334      return;
335    }
336    var errorCallback = function() {
337      fileManager.alert.showHtml('', str('UNMOUNT_FAILED'));
338    };
339    var volumeInfo = fileManager.volumeManager.getVolumeInfo(root);
340    if (!volumeInfo) {
341      errorCallback();
342      return;
343    }
344    fileManager.volumeManager_.unmount(
345        volumeInfo,
346        function() {},
347        errorCallback);
348  },
349  /**
350   * @param {Event} event Command event.
351   */
352  canExecute: function(event, fileManager) {
353    var root = CommandUtil.getCommandEntry(event.target);
354    if (!root)
355      return;
356    var locationInfo = this.fileManager_.volumeManager.getLocationInfo(root);
357    var rootType =
358        locationInfo && locationInfo.isRootEntry && locationInfo.rootType;
359
360    event.canExecute = (rootType == VolumeManagerCommon.RootType.ARCHIVE ||
361                        rootType == VolumeManagerCommon.RootType.REMOVABLE ||
362                        rootType == VolumeManagerCommon.RootType.PROVIDED);
363    event.command.setHidden(!event.canExecute);
364
365    switch (rootType) {
366      case VolumeManagerCommon.RootType.ARCHIVE:
367      case VolumeManagerCommon.RootType.PROVIDED:
368        event.command.label = str('CLOSE_VOLUME_BUTTON_LABEL');
369        break;
370      case VolumeManagerCommon.RootType.REMOVABLE:
371        event.command.label = str('UNMOUNT_DEVICE_BUTTON_LABEL');
372        break;
373    }
374  }
375};
376
377/**
378 * Formats external drive.
379 * @type {Command}
380 */
381CommandHandler.COMMANDS_['format'] = {
382  /**
383   * @param {Event} event Command event.
384   * @param {FileManager} fileManager The file manager instance.
385   */
386  execute: function(event, fileManager) {
387    var directoryModel = fileManager.directoryModel;
388    var root = CommandUtil.getCommandEntry(event.target);
389    // If an entry is not found from the event target, use the current
390    // directory. This can happen for the format button for unsupported and
391    // unrecognized volumes.
392    if (!root)
393      root = directoryModel.getCurrentDirEntry();
394
395    var volumeInfo = fileManager.volumeManager.getVolumeInfo(root);
396    if (volumeInfo) {
397      fileManager.confirm.show(
398          loadTimeData.getString('FORMATTING_WARNING'),
399          chrome.fileManagerPrivate.formatVolume.bind(null,
400                                                      volumeInfo.volumeId));
401    }
402  },
403  /**
404   * @param {Event} event Command event.
405   * @param {FileManager} fileManager The file manager instance.
406   */
407  canExecute: function(event, fileManager) {
408    var directoryModel = fileManager.directoryModel;
409    var root = CommandUtil.getCommandEntry(event.target);
410    // |root| is null for unrecognized volumes. Regard such volumes as writable
411    // so that the format command is enabled.
412    var isReadOnly = root && fileManager.isOnReadonlyDirectory();
413    // See the comment in execute() for why doing this.
414    if (!root)
415      root = directoryModel.getCurrentDirEntry();
416    var location = root && fileManager.volumeManager.getLocationInfo(root);
417    var removable = location && location.rootType ===
418        VolumeManagerCommon.RootType.REMOVABLE;
419    event.canExecute = removable && !isReadOnly;
420    event.command.setHidden(!removable);
421  }
422};
423
424/**
425 * Initiates new folder creation.
426 * @type {Command}
427 */
428CommandHandler.COMMANDS_['new-folder'] = {
429  execute: function(event, fileManager) {
430    fileManager.createNewFolder();
431  },
432  canExecute: function(event, fileManager) {
433    var directoryModel = fileManager.directoryModel;
434    event.canExecute = !fileManager.isOnReadonlyDirectory() &&
435                       !fileManager.isRenamingInProgress() &&
436                       !directoryModel.isSearching() &&
437                       !directoryModel.isScanning();
438  }
439};
440
441/**
442 * Initiates new window creation.
443 * @type {Command}
444 */
445CommandHandler.COMMANDS_['new-window'] = {
446  execute: function(event, fileManager) {
447    chrome.fileManagerPrivate.getProfiles(
448        function(profiles, currentId, displayedId) {
449          fileManager.backgroundPage.launchFileManager({
450            currentDirectoryURL: fileManager.getCurrentDirectoryEntry() &&
451                fileManager.getCurrentDirectoryEntry().toURL(),
452            displayedId: currentId !== displayedId ? displayedId : undefined
453          });
454        });
455  },
456  canExecute: function(event, fileManager) {
457    event.canExecute =
458        fileManager.getCurrentDirectoryEntry() &&
459        (fileManager.dialogType === DialogType.FULL_PAGE);
460  }
461};
462
463/**
464 * Toggles drive sync settings.
465 * @type {Command}
466 */
467CommandHandler.COMMANDS_['drive-sync-settings'] = {
468  execute: function(event, fileManager) {
469    fileManager.toggleDriveSyncSettings();
470  },
471  canExecute: function(event, fileManager) {
472    event.canExecute = fileManager.shouldShowDriveSettings();
473    event.command.setHidden(!event.canExecute);
474  }
475};
476
477/**
478 * Toggles drive hosted settings.
479 * @type {Command}
480 */
481CommandHandler.COMMANDS_['drive-hosted-settings'] = {
482  execute: function(event, fileManager) {
483    fileManager.toggleDriveHostedSettings();
484  },
485  canExecute: function(event, fileManager) {
486    event.canExecute = fileManager.shouldShowDriveSettings();
487    event.command.setHidden(!event.canExecute);
488  }
489};
490
491/**
492 * Deletes selected files.
493 * @type {Command}
494 */
495CommandHandler.COMMANDS_['delete'] = {
496  execute: function(event, fileManager) {
497    var entries = fileManager.getSelection().entries;
498    var message = entries.length == 1 ?
499        strf('GALLERY_CONFIRM_DELETE_ONE', entries[0].name) :
500        strf('GALLERY_CONFIRM_DELETE_SOME', entries.length);
501    fileManager.ui.deleteConfirmDialog.show(message, function() {
502      fileManager.fileOperationManager.deleteEntries(entries);
503    });
504  },
505  canExecute: function(event, fileManager) {
506    var selection = fileManager.getSelection();
507    event.canExecute = !fileManager.isOnReadonlyDirectory() &&
508                       selection &&
509                       selection.totalCount > 0;
510  }
511};
512
513/**
514 * Pastes files from clipboard.
515 * @type {Command}
516 */
517CommandHandler.COMMANDS_['paste'] = {
518  execute: function(event, fileManager) {
519    fileManager.document.execCommand(event.command.id);
520  },
521  canExecute: function(event, fileManager) {
522    var fileTransferController = fileManager.fileTransferController;
523    event.canExecute = (fileTransferController &&
524        fileTransferController.queryPasteCommandEnabled());
525    // Hide this command if only one folder is selected.
526    event.command.setHidden(!!CommandUtil.getOnlyOneSelectedDirectory(
527        fileManager.getSelection()));
528  }
529};
530
531/**
532 * Pastes files from clipboard into the selected folder.
533 * @type {Command}
534 */
535CommandHandler.COMMANDS_['paste-into-folder'] = {
536  execute: function(event, fileManager) {
537    var selection = fileManager.getSelection();
538    var dest = CommandUtil.getOnlyOneSelectedDirectory(selection);
539    if (!dest) return;
540
541    // This handler tweaks the Event object for 'paste' event so that
542    // the FileTransferController can distinguish this 'paste-into-folder'
543    // command and know the destination directory.
544    var handler = function(inEvent) {
545      inEvent.destDirectory = dest;
546    };
547    fileManager.document.addEventListener('paste', handler, true);
548    fileManager.document.execCommand('paste');
549    fileManager.document.removeEventListener('paste', handler, true);
550  },
551  canExecute: function(event, fileManager) {
552    var fileTransferController = fileManager.fileTransferController;
553    event.canExecute = (fileTransferController &&
554        fileTransferController.queryPasteCommandEnabled());
555    // Hide this command unless only one folder is selected.
556    event.command.setHidden(!CommandUtil.getOnlyOneSelectedDirectory(
557        fileManager.getSelection()));
558  }
559};
560
561CommandHandler.COMMANDS_['cut'] = CommandUtil.defaultCommand;
562CommandHandler.COMMANDS_['copy'] = CommandUtil.defaultCommand;
563
564/**
565 * Initiates file renaming.
566 * @type {Command}
567 */
568CommandHandler.COMMANDS_['rename'] = {
569  execute: function(event, fileManager) {
570    fileManager.initiateRename();
571  },
572  canExecute: function(event, fileManager) {
573    var selection = fileManager.getSelection();
574    event.canExecute = !fileManager.isRenamingInProgress() &&
575                       !fileManager.isOnReadonlyDirectory() &&
576                       selection &&
577                       selection.totalCount == 1;
578  }
579};
580
581/**
582 * Opens drive help.
583 * @type {Command}
584 */
585CommandHandler.COMMANDS_['volume-help'] = {
586  execute: function(event, fileManager) {
587    if (fileManager.isOnDrive())
588      util.visitURL(str('GOOGLE_DRIVE_HELP_URL'));
589    else
590      util.visitURL(str('FILES_APP_HELP_URL'));
591  },
592  canExecute: function(event, fileManager) {
593    // Hides the help menu in modal dialog mode. It does not make much sense
594    // because after all, users cannot view the help without closing, and
595    // besides that the help page is about Files.app as an app, not about the
596    // dialog mode itself. It can also lead to hard-to-fix bug crbug.com/339089.
597    var hideHelp = DialogType.isModal(fileManager.dialogType);
598    event.canExecute = !hideHelp;
599    event.command.setHidden(hideHelp);
600    fileManager.document_.getElementById('help-separator').hidden = hideHelp;
601  }
602};
603
604/**
605 * Opens drive buy-more-space url.
606 * @type {Command}
607 */
608CommandHandler.COMMANDS_['drive-buy-more-space'] = {
609  execute: function(event, fileManager) {
610    util.visitURL(str('GOOGLE_DRIVE_BUY_STORAGE_URL'));
611  },
612  canExecute: CommandUtil.canExecuteVisibleOnDriveInNormalAppModeOnly
613};
614
615/**
616 * Opens drive.google.com.
617 * @type {Command}
618 */
619CommandHandler.COMMANDS_['drive-go-to-drive'] = {
620  execute: function(event, fileManager) {
621    util.visitURL(str('GOOGLE_DRIVE_ROOT_URL'));
622  },
623  canExecute: CommandUtil.canExecuteVisibleOnDriveInNormalAppModeOnly
624};
625
626/**
627 * Displays open with dialog for current selection.
628 * @type {Command}
629 */
630CommandHandler.COMMANDS_['open-with'] = {
631  execute: function(event, fileManager) {
632    var tasks = fileManager.getSelection().tasks;
633    if (tasks) {
634      tasks.showTaskPicker(fileManager.defaultTaskPicker,
635          str('OPEN_WITH_BUTTON_LABEL'),
636          '',
637          function(task) {
638            tasks.execute(task.taskId);
639          });
640    }
641  },
642  canExecute: function(event, fileManager) {
643    var tasks = fileManager.getSelection().tasks;
644    event.canExecute = tasks && tasks.size() > 1;
645  }
646};
647
648/**
649 * Focuses search input box.
650 * @type {Command}
651 */
652CommandHandler.COMMANDS_['search'] = {
653  execute: function(event, fileManager) {
654    var element = fileManager.document.querySelector('#search-box input');
655    element.focus();
656    element.select();
657  },
658  canExecute: function(event, fileManager) {
659    event.canExecute = !fileManager.isRenamingInProgress();
660  }
661};
662
663/**
664 * Activates the n-th volume.
665 * @type {Command}
666 */
667CommandHandler.COMMANDS_['volume-switch-1'] =
668    CommandUtil.createVolumeSwitchCommand(1);
669CommandHandler.COMMANDS_['volume-switch-2'] =
670    CommandUtil.createVolumeSwitchCommand(2);
671CommandHandler.COMMANDS_['volume-switch-3'] =
672    CommandUtil.createVolumeSwitchCommand(3);
673CommandHandler.COMMANDS_['volume-switch-4'] =
674    CommandUtil.createVolumeSwitchCommand(4);
675CommandHandler.COMMANDS_['volume-switch-5'] =
676    CommandUtil.createVolumeSwitchCommand(5);
677CommandHandler.COMMANDS_['volume-switch-6'] =
678    CommandUtil.createVolumeSwitchCommand(6);
679CommandHandler.COMMANDS_['volume-switch-7'] =
680    CommandUtil.createVolumeSwitchCommand(7);
681CommandHandler.COMMANDS_['volume-switch-8'] =
682    CommandUtil.createVolumeSwitchCommand(8);
683CommandHandler.COMMANDS_['volume-switch-9'] =
684    CommandUtil.createVolumeSwitchCommand(9);
685
686/**
687 * Flips 'available offline' flag on the file.
688 * @type {Command}
689 */
690CommandHandler.COMMANDS_['toggle-pinned'] = {
691  execute: function(event, fileManager) {
692    var pin = !event.command.checked;
693    event.command.checked = pin;
694    var entries = CommandUtil.getPinTargetEntries();
695    var currentEntry;
696    var error = false;
697    var steps = {
698      // Pick an entry and pin it.
699      start: function() {
700        // Check if all the entries are pinned or not.
701        if (entries.length == 0)
702          return;
703        currentEntry = entries.shift();
704        chrome.fileManagerPrivate.pinDriveFile(
705            currentEntry.toURL(),
706            pin,
707            steps.entryPinned);
708      },
709
710      // Check the result of pinning
711      entryPinned: function() {
712        // Convert to boolean.
713        error = !!chrome.runtime.lastError;
714        if (error && pin) {
715          fileManager.metadataCache_.getOne(
716              currentEntry, 'filesystem', steps.showError);
717        }
718        fileManager.metadataCache_.clear(currentEntry, 'external');
719        fileManager.metadataCache_.getOne(
720            currentEntry, 'external', steps.updateUI.bind(this));
721      },
722
723      // Update the user interface according to the cache state.
724      updateUI: function(drive /* not used */) {
725        fileManager.updateMetadataInUI_('external', [currentEntry]);
726        if (!error)
727          steps.start();
728      },
729
730      // Show the error
731      showError: function(filesystem) {
732        fileManager.alert.showHtml(str('DRIVE_OUT_OF_SPACE_HEADER'),
733                                   strf('DRIVE_OUT_OF_SPACE_MESSAGE',
734                                        unescape(currentEntry.name),
735                                        util.bytesToString(filesystem.size)));
736      }
737    };
738    steps.start();
739  },
740
741  canExecute: function(event, fileManager) {
742    var entries = CommandUtil.getPinTargetEntries();
743    var checked = true;
744    for (var i = 0; i < entries.length; i++) {
745      checked = checked && entries[i].pinned;
746    }
747    if (entries.length > 0) {
748      event.canExecute = true;
749      event.command.setHidden(false);
750      event.command.checked = checked;
751    } else {
752      event.canExecute = false;
753      event.command.setHidden(true);
754    }
755  }
756};
757
758/**
759 * Creates zip file for current selection.
760 * @type {Command}
761 */
762CommandHandler.COMMANDS_['zip-selection'] = {
763  execute: function(event, fileManager) {
764    var dirEntry = fileManager.getCurrentDirectoryEntry();
765    var selectionEntries = fileManager.getSelection().entries;
766    fileManager.fileOperationManager_.zipSelection(dirEntry, selectionEntries);
767  },
768  canExecute: function(event, fileManager) {
769    var dirEntry = fileManager.getCurrentDirectoryEntry();
770    var selection = fileManager.getSelection();
771    event.canExecute =
772        dirEntry &&
773        !fileManager.isOnReadonlyDirectory() &&
774        !fileManager.isOnDrive() &&
775        selection && selection.totalCount > 0;
776  }
777};
778
779/**
780 * Shows the share dialog for the current selection (single only).
781 * @type {Command}
782 */
783CommandHandler.COMMANDS_['share'] = {
784  execute: function(event, fileManager) {
785    fileManager.shareSelection();
786  },
787  canExecute: function(event, fileManager) {
788    var selection = fileManager.getSelection();
789    var isDriveOffline =
790        fileManager.volumeManager.getDriveConnectionState().type ===
791            VolumeManagerCommon.DriveConnectionType.OFFLINE;
792    event.canExecute = fileManager.isOnDrive() &&
793        !isDriveOffline &&
794        selection && selection.totalCount == 1;
795    event.command.setHidden(!fileManager.isOnDrive());
796  }
797};
798
799/**
800 * Creates a shortcut of the selected folder (single only).
801 * @type {Command}
802 */
803CommandHandler.COMMANDS_['create-folder-shortcut'] = {
804  /**
805   * @param {Event} event Command event.
806   * @param {FileManager} fileManager The file manager instance.
807   */
808  execute: function(event, fileManager) {
809    var entry = CommandUtil.getCommandEntry(event.target);
810    if (!entry) {
811      console.warn('create-folder-shortcut command executed on an element ' +
812                   'which does not have corresponding entry.');
813      return;
814    }
815    fileManager.createFolderShortcut(entry);
816  },
817
818  /**
819   * @param {Event} event Command event.
820   * @param {FileManager} fileManager The file manager instance.
821   */
822  canExecute: function(event, fileManager) {
823    var entry = CommandUtil.getCommandEntry(event.target);
824    var folderShortcutExists = entry &&
825                               fileManager.folderShortcutExists(entry);
826
827    var onlyOneFolderSelected = true;
828    // Only on list, user can select multiple files. The command is enabled only
829    // when a single file is selected.
830    if (event.target instanceof cr.ui.List) {
831      var items = event.target.selectedItems;
832      onlyOneFolderSelected = (items.length == 1 && items[0].isDirectory);
833    }
834
835    var location = entry && fileManager.volumeManager.getLocationInfo(entry);
836    var eligible = location && location.isEligibleForFolderShortcut;
837    event.canExecute =
838        eligible && onlyOneFolderSelected && !folderShortcutExists;
839    event.command.setHidden(!eligible || !onlyOneFolderSelected);
840  }
841};
842
843/**
844 * Removes the folder shortcut.
845 * @type {Command}
846 */
847CommandHandler.COMMANDS_['remove-folder-shortcut'] = {
848  /**
849   * @param {Event} event Command event.
850   * @param {FileManager} fileManager The file manager instance.
851   */
852  execute: function(event, fileManager) {
853    var entry = CommandUtil.getCommandEntry(event.target);
854    if (!entry) {
855      console.warn('remove-folder-shortcut command executed on an element ' +
856                   'which does not have corresponding entry.');
857      return;
858    }
859    fileManager.removeFolderShortcut(entry);
860  },
861
862  /**
863   * @param {Event} event Command event.
864   * @param {FileManager} fileManager The file manager instance.
865   */
866  canExecute: function(event, fileManager) {
867    var entry = CommandUtil.getCommandEntry(event.target);
868    var location = entry && fileManager.volumeManager.getLocationInfo(entry);
869
870    var eligible = location && location.isEligibleForFolderShortcut;
871    var isShortcut = entry && fileManager.folderShortcutExists(entry);
872    event.canExecute = isShortcut && eligible;
873    event.command.setHidden(!event.canExecute);
874  }
875};
876
877/**
878 * Zoom in to the Files.app.
879 * @type {Command}
880 */
881CommandHandler.COMMANDS_['zoom-in'] = {
882  execute: function(event, fileManager) {
883    chrome.fileManagerPrivate.zoom('in');
884  },
885  canExecute: CommandUtil.canExecuteAlways
886};
887
888/**
889 * Zoom out from the Files.app.
890 * @type {Command}
891 */
892CommandHandler.COMMANDS_['zoom-out'] = {
893  execute: function(event, fileManager) {
894    chrome.fileManagerPrivate.zoom('out');
895  },
896  canExecute: CommandUtil.canExecuteAlways
897};
898
899/**
900 * Reset the zoom factor.
901 * @type {Command}
902 */
903CommandHandler.COMMANDS_['zoom-reset'] = {
904  execute: function(event, fileManager) {
905    chrome.fileManagerPrivate.zoom('reset');
906  },
907  canExecute: CommandUtil.canExecuteAlways
908};
909
910/**
911 * Open inspector for foreground page.
912 * @type {Command}
913 */
914CommandHandler.COMMANDS_['inspect-normal'] = {
915  execute: function(event, fileManager) {
916    chrome.fileManagerPrivate.openInspector('normal');
917  },
918  canExecute: CommandUtil.canExecuteAlways
919};
920
921/**
922 * Open inspector for foreground page and bring focus to the console.
923 * @type {Command}
924 */
925CommandHandler.COMMANDS_['inspect-console'] = {
926  execute: function(event, fileManager) {
927    chrome.fileManagerPrivate.openInspector('console');
928  },
929  canExecute: CommandUtil.canExecuteAlways
930};
931
932/**
933 * Open inspector for foreground page in inspect element mode.
934 * @type {Command}
935 */
936CommandHandler.COMMANDS_['inspect-element'] = {
937  execute: function(event, fileManager) {
938    chrome.fileManagerPrivate.openInspector('element');
939  },
940  canExecute: CommandUtil.canExecuteAlways
941};
942
943/**
944 * Open inspector for background page.
945 * @type {Command}
946 */
947CommandHandler.COMMANDS_['inspect-background'] = {
948  execute: function(event, fileManager) {
949    chrome.fileManagerPrivate.openInspector('background');
950  },
951  canExecute: CommandUtil.canExecuteAlways
952};
953