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