Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(76)

Side by Side Diff: chrome/browser/resources/file_manager/foreground/js/file_tasks.js

Issue 247123002: Move Files.app files to ui/file_manager (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix the test failure on non-chromeos Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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 * This object encapsulates everything related to tasks execution.
9 *
10 * TODO(hirono): Pass each component instead of the entire FileManager.
11 * @param {FileManager} fileManager FileManager instance.
12 * @param {Object=} opt_params File manager load parameters.
13 * @constructor
14 */
15 function FileTasks(fileManager, opt_params) {
16 this.fileManager_ = fileManager;
17 this.params_ = opt_params;
18 this.tasks_ = null;
19 this.defaultTask_ = null;
20 this.entries_ = null;
21
22 /**
23 * List of invocations to be called once tasks are available.
24 *
25 * @private
26 * @type {Array.<Object>}
27 */
28 this.pendingInvocations_ = [];
29 }
30
31 /**
32 * Location of the Chrome Web Store.
33 *
34 * @const
35 * @type {string}
36 */
37 FileTasks.CHROME_WEB_STORE_URL = 'https://chrome.google.com/webstore';
38
39 /**
40 * Base URL of apps list in the Chrome Web Store. This constant is used in
41 * FileTasks.createWebStoreLink().
42 *
43 * @const
44 * @type {string}
45 */
46 FileTasks.WEB_STORE_HANDLER_BASE_URL =
47 'https://chrome.google.com/webstore/category/collection/file_handlers';
48
49
50 /**
51 * The app ID of the video player app.
52 * @const
53 * @type {string}
54 */
55 FileTasks.VIDEO_PLAYER_ID = 'jcgeabjmjgoblfofpppfkcoakmfobdko';
56
57 /**
58 * Returns URL of the Chrome Web Store which show apps supporting the given
59 * file-extension and mime-type.
60 *
61 * @param {string} extension Extension of the file (with the first dot).
62 * @param {string} mimeType Mime type of the file.
63 * @return {string} URL
64 */
65 FileTasks.createWebStoreLink = function(extension, mimeType) {
66 if (!extension)
67 return FileTasks.CHROME_WEB_STORE_URL;
68
69 if (extension[0] === '.')
70 extension = extension.substr(1);
71 else
72 console.warn('Please pass an extension with a dot to createWebStoreLink.');
73
74 var url = FileTasks.WEB_STORE_HANDLER_BASE_URL;
75 url += '?_fe=' + extension.toLowerCase().replace(/[^\w]/g, '');
76
77 // If a mime is given, add it into the URL.
78 if (mimeType)
79 url += '&_fmt=' + mimeType.replace(/[^-\w\/]/g, '');
80 return url;
81 };
82
83 /**
84 * Complete the initialization.
85 *
86 * @param {Array.<Entry>} entries List of file entries.
87 * @param {Array.<string>=} opt_mimeTypes List of MIME types for each
88 * of the files.
89 */
90 FileTasks.prototype.init = function(entries, opt_mimeTypes) {
91 this.entries_ = entries;
92 this.mimeTypes_ = opt_mimeTypes || [];
93
94 // TODO(mtomasz): Move conversion from entry to url to custom bindings.
95 var urls = util.entriesToURLs(entries);
96 if (urls.length > 0) {
97 chrome.fileBrowserPrivate.getFileTasks(urls, this.mimeTypes_,
98 this.onTasks_.bind(this));
99 }
100 };
101
102 /**
103 * Returns amount of tasks.
104 *
105 * @return {number} amount of tasks.
106 */
107 FileTasks.prototype.size = function() {
108 return (this.tasks_ && this.tasks_.length) || 0;
109 };
110
111 /**
112 * Callback when tasks found.
113 *
114 * @param {Array.<Object>} tasks The tasks.
115 * @private
116 */
117 FileTasks.prototype.onTasks_ = function(tasks) {
118 this.processTasks_(tasks);
119 for (var index = 0; index < this.pendingInvocations_.length; index++) {
120 var name = this.pendingInvocations_[index][0];
121 var args = this.pendingInvocations_[index][1];
122 this[name].apply(this, args);
123 }
124 this.pendingInvocations_ = [];
125 };
126
127 /**
128 * The list of known extensions to record UMA.
129 * Note: Because the data is recorded by the index, so new item shouldn't be
130 * inserted.
131 *
132 * @const
133 * @type {Array.<string>}
134 * @private
135 */
136 FileTasks.UMA_INDEX_KNOWN_EXTENSIONS_ = Object.freeze([
137 'other', '.3ga', '.3gp', '.aac', '.alac', '.asf', '.avi', '.bmp', '.csv',
138 '.doc', '.docx', '.flac', '.gif', '.jpeg', '.jpg', '.log', '.m3u', '.m3u8',
139 '.m4a', '.m4v', '.mid', '.mkv', '.mov', '.mp3', '.mp4', '.mpg', '.odf',
140 '.odp', '.ods', '.odt', '.oga', '.ogg', '.ogv', '.pdf', '.png', '.ppt',
141 '.pptx', '.ra', '.ram', '.rar', '.rm', '.rtf', '.wav', '.webm', '.webp',
142 '.wma', '.wmv', '.xls', '.xlsx',
143 ]);
144
145 /**
146 * The list of executable file extensions.
147 *
148 * @const
149 * @type {Array.<string>}
150 */
151 FileTasks.EXECUTABLE_EXTENSIONS = Object.freeze([
152 '.exe', '.lnk', '.deb', '.dmg', '.jar', '.msi',
153 ]);
154
155 /**
156 * The list of extensions to skip the suggest app dialog.
157 * @const
158 * @type {Array.<string>}
159 * @private
160 */
161 FileTasks.EXTENSIONS_TO_SKIP_SUGGEST_APPS_ = Object.freeze([
162 '.crdownload', '.dsc', '.inf', '.crx',
163 ]);
164
165 /**
166 * Records trial of opening file grouped by extensions.
167 *
168 * @param {Array.<Entry>} entries The entries to be opened.
169 * @private
170 */
171 FileTasks.recordViewingFileTypeUMA_ = function(entries) {
172 for (var i = 0; i < entries.length; i++) {
173 var entry = entries[i];
174 var extension = FileType.getExtension(entry).toLowerCase();
175 if (FileTasks.UMA_INDEX_KNOWN_EXTENSIONS_.indexOf(extension) < 0) {
176 extension = 'other';
177 }
178 metrics.recordEnum(
179 'ViewingFileType', extension, FileTasks.UMA_INDEX_KNOWN_EXTENSIONS_);
180 }
181 };
182
183 /**
184 * Returns true if the taskId is for an internal task.
185 *
186 * @param {string} taskId Task identifier.
187 * @return {boolean} True if the task ID is for an internal task.
188 * @private
189 */
190 FileTasks.isInternalTask_ = function(taskId) {
191 var taskParts = taskId.split('|');
192 var appId = taskParts[0];
193 var taskType = taskParts[1];
194 var actionId = taskParts[2];
195 // The action IDs here should match ones used in executeInternalTask_().
196 return (appId === chrome.runtime.id &&
197 taskType === 'file' &&
198 (actionId === 'play' ||
199 actionId === 'mount-archive' ||
200 actionId === 'gallery' ||
201 actionId === 'gallery-video'));
202 };
203
204 /**
205 * Processes internal tasks.
206 *
207 * @param {Array.<Object>} tasks The tasks.
208 * @private
209 */
210 FileTasks.prototype.processTasks_ = function(tasks) {
211 this.tasks_ = [];
212 var id = chrome.runtime.id;
213 var isOnDrive = false;
214 var fm = this.fileManager_;
215 for (var index = 0; index < this.entries_.length; ++index) {
216 var locationInfo = fm.volumeManager.getLocationInfo(this.entries_[index]);
217 if (locationInfo && locationInfo.isDriveBased) {
218 isOnDrive = true;
219 break;
220 }
221 }
222
223 for (var i = 0; i < tasks.length; i++) {
224 var task = tasks[i];
225 var taskParts = task.taskId.split('|');
226
227 // Skip internal Files.app's handlers.
228 if (taskParts[0] === id && (taskParts[2] === 'auto-open' ||
229 taskParts[2] === 'select' || taskParts[2] === 'open')) {
230 continue;
231 }
232
233 // Tweak images, titles of internal tasks.
234 if (taskParts[0] === id && taskParts[1] === 'file') {
235 if (taskParts[2] === 'play') {
236 // TODO(serya): This hack needed until task.iconUrl is working
237 // (see GetFileTasksFileBrowserFunction::RunImpl).
238 task.iconType = 'audio';
239 task.title = loadTimeData.getString('ACTION_LISTEN');
240 } else if (taskParts[2] === 'mount-archive') {
241 task.iconType = 'archive';
242 task.title = loadTimeData.getString('MOUNT_ARCHIVE');
243 } else if (taskParts[2] === 'gallery' ||
244 taskParts[2] === 'gallery-video') {
245 task.iconType = 'image';
246 task.title = loadTimeData.getString('ACTION_OPEN');
247 } else if (taskParts[2] === 'open-hosted-generic') {
248 if (this.entries_.length > 1)
249 task.iconType = 'generic';
250 else // Use specific icon.
251 task.iconType = FileType.getIcon(this.entries_[0]);
252 task.title = loadTimeData.getString('ACTION_OPEN');
253 } else if (taskParts[2] === 'open-hosted-gdoc') {
254 task.iconType = 'gdoc';
255 task.title = loadTimeData.getString('ACTION_OPEN_GDOC');
256 } else if (taskParts[2] === 'open-hosted-gsheet') {
257 task.iconType = 'gsheet';
258 task.title = loadTimeData.getString('ACTION_OPEN_GSHEET');
259 } else if (taskParts[2] === 'open-hosted-gslides') {
260 task.iconType = 'gslides';
261 task.title = loadTimeData.getString('ACTION_OPEN_GSLIDES');
262 } else if (taskParts[2] === 'view-swf') {
263 // Do not render this task if disabled.
264 if (!loadTimeData.getBoolean('SWF_VIEW_ENABLED'))
265 continue;
266 task.iconType = 'generic';
267 task.title = loadTimeData.getString('ACTION_VIEW');
268 } else if (taskParts[2] === 'view-pdf') {
269 // Do not render this task if disabled.
270 if (!loadTimeData.getBoolean('PDF_VIEW_ENABLED'))
271 continue;
272 task.iconType = 'pdf';
273 task.title = loadTimeData.getString('ACTION_VIEW');
274 } else if (taskParts[2] === 'view-in-browser') {
275 task.iconType = 'generic';
276 task.title = loadTimeData.getString('ACTION_VIEW');
277 }
278 }
279
280 if (!task.iconType && taskParts[1] === 'web-intent') {
281 task.iconType = 'generic';
282 }
283
284 this.tasks_.push(task);
285 if (this.defaultTask_ === null && task.isDefault) {
286 this.defaultTask_ = task;
287 }
288 }
289 if (!this.defaultTask_ && this.tasks_.length > 0) {
290 // If we haven't picked a default task yet, then just pick the first one.
291 // This is not the preferred way we want to pick this, but better this than
292 // no default at all if the C++ code didn't set one.
293 this.defaultTask_ = this.tasks_[0];
294 }
295 };
296
297 /**
298 * Executes default task.
299 *
300 * @param {function(boolean, Array.<string>)=} opt_callback Called when the
301 * default task is executed, or the error is occurred.
302 * @private
303 */
304 FileTasks.prototype.executeDefault_ = function(opt_callback) {
305 FileTasks.recordViewingFileTypeUMA_(this.entries_);
306 this.executeDefaultInternal_(this.entries_, opt_callback);
307 };
308
309 /**
310 * Executes default task.
311 *
312 * @param {Array.<Entry>} entries Entries to execute.
313 * @param {function(boolean, Array.<Entry>)=} opt_callback Called when the
314 * default task is executed, or the error is occurred.
315 * @private
316 */
317 FileTasks.prototype.executeDefaultInternal_ = function(entries, opt_callback) {
318 var callback = opt_callback || function(arg1, arg2) {};
319
320 if (this.defaultTask_ !== null) {
321 this.executeInternal_(this.defaultTask_.taskId, entries);
322 callback(true, entries);
323 return;
324 }
325
326 // We don't have tasks, so try to show a file in a browser tab.
327 // We only do that for single selection to avoid confusion.
328 if (entries.length !== 1 || !entries[0])
329 return;
330
331 var filename = entries[0].name;
332 var extension = PathUtil.splitExtension(filename)[1];
333 var mimeType = this.mimeTypes_[0];
334
335 var showAlert = function() {
336 var textMessageId;
337 var titleMessageId;
338 switch (extension) {
339 case '.exe':
340 textMessageId = 'NO_ACTION_FOR_EXECUTABLE';
341 break;
342 case '.crx':
343 textMessageId = 'NO_ACTION_FOR_CRX';
344 titleMessageId = 'NO_ACTION_FOR_CRX_TITLE';
345 break;
346 default:
347 textMessageId = 'NO_ACTION_FOR_FILE';
348 }
349
350 var webStoreUrl = FileTasks.createWebStoreLink(extension, mimeType);
351 var text = strf(textMessageId, webStoreUrl, str('NO_ACTION_FOR_FILE_URL'));
352 var title = titleMessageId ? str(titleMessageId) : filename;
353 this.fileManager_.alert.showHtml(title, text, function() {});
354 callback(false, urls);
355 }.bind(this);
356
357 var onViewFilesFailure = function() {
358 var fm = this.fileManager_;
359 if (!fm.isOnDrive() ||
360 !entries[0] ||
361 FileTasks.EXTENSIONS_TO_SKIP_SUGGEST_APPS_.indexOf(extension) !== -1) {
362 showAlert();
363 return;
364 }
365
366 fm.openSuggestAppsDialog(
367 entries[0],
368 function() {
369 var newTasks = new FileTasks(fm);
370 newTasks.init(entries, this.mimeTypes_);
371 newTasks.executeDefault();
372 callback(true, entries);
373 }.bind(this),
374 // Cancelled callback.
375 function() {
376 callback(false, entries);
377 },
378 showAlert);
379 }.bind(this);
380
381 var onViewFiles = function(result) {
382 switch (result) {
383 case 'opened':
384 callback(success, entries);
385 break;
386 case 'message_sent':
387 util.isTeleported(window).then(function(teleported) {
388 if (teleported) {
389 util.showOpenInOtherDesktopAlert(
390 this.fileManager_.ui.alertDialog, entries);
391 }
392 }.bind(this));
393 callback(success, entries);
394 break;
395 case 'empty':
396 callback(success, entries);
397 break;
398 case 'failed':
399 onViewFilesFailure();
400 break;
401 }
402 }.bind(this);
403
404 this.checkAvailability_(function() {
405 // TODO(mtomasz): Pass entries instead.
406 var urls = util.entriesToURLs(entries);
407 var taskId = chrome.runtime.id + '|file|view-in-browser';
408 chrome.fileBrowserPrivate.executeTask(taskId, urls, onViewFiles);
409 }.bind(this));
410 };
411
412 /**
413 * Executes a single task.
414 *
415 * @param {string} taskId Task identifier.
416 * @param {Array.<Entry>=} opt_entries Entries to xecute on instead of
417 * this.entries_|.
418 * @private
419 */
420 FileTasks.prototype.execute_ = function(taskId, opt_entries) {
421 var entries = opt_entries || this.entries_;
422 FileTasks.recordViewingFileTypeUMA_(entries);
423 this.executeInternal_(taskId, entries);
424 };
425
426 /**
427 * The core implementation to execute a single task.
428 *
429 * @param {string} taskId Task identifier.
430 * @param {Array.<Entry>} entries Entries to execute.
431 * @private
432 */
433 FileTasks.prototype.executeInternal_ = function(taskId, entries) {
434 this.checkAvailability_(function() {
435 if (FileTasks.isInternalTask_(taskId)) {
436 var taskParts = taskId.split('|');
437 this.executeInternalTask_(taskParts[2], entries);
438 } else {
439 // TODO(mtomasz): Pass entries instead.
440 var urls = util.entriesToURLs(entries);
441 chrome.fileBrowserPrivate.executeTask(taskId, urls, function(result) {
442 if (result !== 'message_sent')
443 return;
444 util.isTeleported(window).then(function(teleported) {
445 if (teleported) {
446 util.showOpenInOtherDesktopAlert(
447 this.fileManager_.ui.alertDialog, entries);
448 }
449 }.bind(this));
450 }.bind(this));
451 }
452 }.bind(this));
453 };
454
455 /**
456 * Checks whether the remote files are available right now.
457 *
458 * @param {function} callback The callback.
459 * @private
460 */
461 FileTasks.prototype.checkAvailability_ = function(callback) {
462 var areAll = function(props, name) {
463 var isOne = function(e) {
464 // If got no properties, we safely assume that item is unavailable.
465 return e && e[name];
466 };
467 return props.filter(isOne).length === props.length;
468 };
469
470 var fm = this.fileManager_;
471 var entries = this.entries_;
472
473 var isDriveOffline = fm.volumeManager.getDriveConnectionState().type ===
474 util.DriveConnectionType.OFFLINE;
475
476 if (fm.isOnDrive() && isDriveOffline) {
477 fm.metadataCache_.get(entries, 'drive', function(props) {
478 if (areAll(props, 'availableOffline')) {
479 callback();
480 return;
481 }
482
483 fm.alert.showHtml(
484 loadTimeData.getString('OFFLINE_HEADER'),
485 props[0].hosted ?
486 loadTimeData.getStringF(
487 entries.length === 1 ?
488 'HOSTED_OFFLINE_MESSAGE' :
489 'HOSTED_OFFLINE_MESSAGE_PLURAL') :
490 loadTimeData.getStringF(
491 entries.length === 1 ?
492 'OFFLINE_MESSAGE' :
493 'OFFLINE_MESSAGE_PLURAL',
494 loadTimeData.getString('OFFLINE_COLUMN_LABEL')));
495 });
496 return;
497 }
498
499 var isOnMetered = fm.volumeManager.getDriveConnectionState().type ===
500 util.DriveConnectionType.METERED;
501
502 if (fm.isOnDrive() && isOnMetered) {
503 fm.metadataCache_.get(entries, 'drive', function(driveProps) {
504 if (areAll(driveProps, 'availableWhenMetered')) {
505 callback();
506 return;
507 }
508
509 fm.metadataCache_.get(entries, 'filesystem', function(fileProps) {
510 var sizeToDownload = 0;
511 for (var i = 0; i !== entries.length; i++) {
512 if (!driveProps[i].availableWhenMetered)
513 sizeToDownload += fileProps[i].size;
514 }
515 fm.confirm.show(
516 loadTimeData.getStringF(
517 entries.length === 1 ?
518 'CONFIRM_MOBILE_DATA_USE' :
519 'CONFIRM_MOBILE_DATA_USE_PLURAL',
520 util.bytesToString(sizeToDownload)),
521 callback);
522 });
523 });
524 return;
525 }
526
527 callback();
528 };
529
530 /**
531 * Executes an internal task.
532 *
533 * @param {string} id The short task id.
534 * @param {Array.<Entry>} entries The entries to execute on.
535 * @private
536 */
537 FileTasks.prototype.executeInternalTask_ = function(id, entries) {
538 var fm = this.fileManager_;
539
540 if (id === 'play') {
541 var position = 0;
542 if (entries.length === 1) {
543 // If just a single audio file is selected pass along every audio file
544 // in the directory.
545 var selectedEntries = entries[0];
546 entries = fm.getAllEntriesInCurrentDirectory().filter(FileType.isAudio);
547 position = entries.indexOf(selectedEntries);
548 }
549 // TODO(mtomasz): Pass entries instead.
550 var urls = util.entriesToURLs(entries);
551 chrome.fileBrowserPrivate.getProfiles(function(profiles,
552 currentId,
553 displayedId) {
554 fm.backgroundPage.launchAudioPlayer({items: urls, position: position},
555 displayedId);
556 });
557 return;
558 }
559
560 if (id === 'mount-archive') {
561 this.mountArchivesInternal_(entries);
562 return;
563 }
564
565 if (id === 'gallery' || id === 'gallery-video') {
566 this.openGalleryInternal_(entries);
567 return;
568 }
569
570 console.error('Unexpected action ID: ' + id);
571 };
572
573 /**
574 * Mounts archives.
575 *
576 * @param {Array.<Entry>} entries Mount file entries list.
577 */
578 FileTasks.prototype.mountArchives = function(entries) {
579 FileTasks.recordViewingFileTypeUMA_(entries);
580 this.mountArchivesInternal_(entries);
581 };
582
583 /**
584 * The core implementation of mounts archives.
585 *
586 * @param {Array.<Entry>} entries Mount file entries list.
587 * @private
588 */
589 FileTasks.prototype.mountArchivesInternal_ = function(entries) {
590 var fm = this.fileManager_;
591
592 var tracker = fm.directoryModel.createDirectoryChangeTracker();
593 tracker.start();
594
595 // TODO(mtomasz): Pass Entries instead of URLs.
596 var urls = util.entriesToURLs(entries);
597 fm.resolveSelectResults_(urls, function(resolvedURLs) {
598 for (var index = 0; index < resolvedURLs.length; ++index) {
599 // TODO(mtomasz): Pass Entry instead of URL.
600 fm.volumeManager.mountArchive(resolvedURLs[index],
601 function(volumeInfo) {
602 if (tracker.hasChanged) {
603 tracker.stop();
604 return;
605 }
606 volumeInfo.resolveDisplayRoot(function(displayRoot) {
607 if (tracker.hasChanged) {
608 tracker.stop();
609 return;
610 }
611 fm.directoryModel.changeDirectoryEntry(displayRoot);
612 }, function() {
613 console.warn('Failed to resolve the display root after mounting.');
614 tracker.stop();
615 });
616 }, function(url, error) {
617 tracker.stop();
618 var path = util.extractFilePath(url);
619 var namePos = path.lastIndexOf('/');
620 fm.alert.show(strf('ARCHIVE_MOUNT_FAILED',
621 path.substr(namePos + 1), error));
622 }.bind(null, resolvedURLs[index]));
623 }
624 });
625 };
626
627 /**
628 * Open the Gallery.
629 *
630 * @param {Array.<Entry>} entries List of selected entries.
631 */
632 FileTasks.prototype.openGallery = function(entries) {
633 FileTasks.recordViewingFileTypeUMA_(entries);
634 this.openGalleryInternal_(entries);
635 };
636
637 /**
638 * The core implementation to open the Gallery.
639 *
640 * @param {Array.<Entry>} entries List of selected entries.
641 * @private
642 */
643 FileTasks.prototype.openGalleryInternal_ = function(entries) {
644 var fm = this.fileManager_;
645
646 var allEntries =
647 fm.getAllEntriesInCurrentDirectory().filter(FileType.isImageOrVideo);
648
649 var galleryFrame = fm.document_.createElement('iframe');
650 galleryFrame.className = 'overlay-pane';
651 galleryFrame.scrolling = 'no';
652 galleryFrame.setAttribute('webkitallowfullscreen', true);
653
654 if (this.params_ && this.params_.gallery) {
655 // Remove the Gallery state from the location, we do not need it any more.
656 // TODO(mtomasz): Consider keeping the selection path.
657 util.updateAppState(
658 null, /* keep current directory */
659 '', /* remove current selection */
660 '' /* remove search. */);
661 }
662
663 var savedAppState = JSON.parse(JSON.stringify(window.appState));
664 var savedTitle = document.title;
665
666 // Push a temporary state which will be replaced every time the selection
667 // changes in the Gallery and popped when the Gallery is closed.
668 util.updateAppState();
669
670 var onBack = function(selectedEntries) {
671 fm.directoryModel.selectEntries(selectedEntries);
672 fm.closeFilePopup(); // Will call Gallery.unload.
673 window.appState = savedAppState;
674 util.saveAppState();
675 document.title = savedTitle;
676 };
677
678 var onAppRegionChanged = function(visible) {
679 fm.onFilePopupAppRegionChanged(visible);
680 };
681
682 galleryFrame.onload = function() {
683 galleryFrame.contentWindow.ImageUtil.metrics = metrics;
684
685 // TODO(haruki): isOnReadonlyDirectory() only checks the permission for the
686 // root. We should check more granular permission to know whether the file
687 // is writable or not.
688 var readonly = fm.isOnReadonlyDirectory();
689 var currentDir = fm.getCurrentDirectoryEntry();
690 var downloadsVolume =
691 fm.volumeManager.getCurrentProfileVolumeInfo(RootType.DOWNLOADS);
692 var downloadsDir = downloadsVolume && downloadsVolume.fileSystem.root;
693
694 // TODO(mtomasz): Pass Entry instead of localized name. Conversion to a
695 // display string should be done in gallery.js.
696 var readonlyDirName = null;
697 if (readonly && currentDir)
698 readonlyDirName = util.getEntryLabel(fm.volumeManager, currentDir);
699
700 var context = {
701 // We show the root label in readonly warning (e.g. archive name).
702 readonlyDirName: readonlyDirName,
703 curDirEntry: currentDir,
704 saveDirEntry: readonly ? downloadsDir : null,
705 searchResults: fm.directoryModel.isSearching(),
706 metadataCache: fm.metadataCache_,
707 pageState: this.params_,
708 appWindow: chrome.app.window.current(),
709 onBack: onBack,
710 onClose: fm.onClose.bind(fm),
711 onMaximize: fm.onMaximize.bind(fm),
712 onMinimize: fm.onMinimize.bind(fm),
713 onAppRegionChanged: onAppRegionChanged,
714 loadTimeData: fm.backgroundPage.background.stringData
715 };
716 galleryFrame.contentWindow.Gallery.open(
717 context, fm.volumeManager, allEntries, entries);
718 }.bind(this);
719
720 galleryFrame.src = 'gallery.html';
721 fm.openFilePopup(galleryFrame, fm.updateTitle_.bind(fm));
722 };
723
724 /**
725 * Displays the list of tasks in a task picker combobutton.
726 *
727 * @param {cr.ui.ComboButton} combobutton The task picker element.
728 * @private
729 */
730 FileTasks.prototype.display_ = function(combobutton) {
731 if (this.tasks_.length === 0) {
732 combobutton.hidden = true;
733 return;
734 }
735
736 combobutton.clear();
737 combobutton.hidden = false;
738 combobutton.defaultItem = this.createCombobuttonItem_(this.defaultTask_);
739
740 var items = this.createItems_();
741
742 if (items.length > 1) {
743 var defaultIdx = 0;
744
745 for (var j = 0; j < items.length; j++) {
746 combobutton.addDropDownItem(items[j]);
747 if (items[j].task.taskId === this.defaultTask_.taskId)
748 defaultIdx = j;
749 }
750
751 combobutton.addSeparator();
752 var changeDefaultMenuItem = combobutton.addDropDownItem({
753 label: loadTimeData.getString('CHANGE_DEFAULT_MENU_ITEM')
754 });
755 changeDefaultMenuItem.classList.add('change-default');
756 }
757 };
758
759 /**
760 * Creates sorted array of available task descriptions such as title and icon.
761 *
762 * @return {Array} created array can be used to feed combobox, menus and so on.
763 * @private
764 */
765 FileTasks.prototype.createItems_ = function() {
766 var items = [];
767 var title = this.defaultTask_.title + ' ' +
768 loadTimeData.getString('DEFAULT_ACTION_LABEL');
769 items.push(this.createCombobuttonItem_(this.defaultTask_, title, true));
770
771 for (var index = 0; index < this.tasks_.length; index++) {
772 var task = this.tasks_[index];
773 if (task !== this.defaultTask_)
774 items.push(this.createCombobuttonItem_(task));
775 }
776
777 items.sort(function(a, b) {
778 return a.label.localeCompare(b.label);
779 });
780
781 return items;
782 };
783
784 /**
785 * Updates context menu with default item.
786 * @private
787 */
788
789 FileTasks.prototype.updateMenuItem_ = function() {
790 this.fileManager_.updateContextMenuActionItems(this.defaultTask_,
791 this.tasks_.length > 1);
792 };
793
794 /**
795 * Creates combobutton item based on task.
796 *
797 * @param {Object} task Task to convert.
798 * @param {string=} opt_title Title.
799 * @param {boolean=} opt_bold Make a menu item bold.
800 * @return {Object} Item appendable to combobutton drop-down list.
801 * @private
802 */
803 FileTasks.prototype.createCombobuttonItem_ = function(task, opt_title,
804 opt_bold) {
805 return {
806 label: opt_title || task.title,
807 iconUrl: task.iconUrl,
808 iconType: task.iconType,
809 task: task,
810 bold: opt_bold || false
811 };
812 };
813
814 /**
815 * Shows modal action picker dialog with currently available list of tasks.
816 *
817 * @param {DefaultActionDialog} actionDialog Action dialog to show and update.
818 * @param {string} title Title to use.
819 * @param {string} message Message to use.
820 * @param {function(Object)} onSuccess Callback to pass selected task.
821 */
822 FileTasks.prototype.showTaskPicker = function(actionDialog, title, message,
823 onSuccess) {
824 var items = this.createItems_();
825
826 var defaultIdx = 0;
827 for (var j = 0; j < items.length; j++) {
828 if (items[j].task.taskId === this.defaultTask_.taskId)
829 defaultIdx = j;
830 }
831
832 actionDialog.show(
833 title,
834 message,
835 items, defaultIdx,
836 function(item) {
837 onSuccess(item.task);
838 });
839 };
840
841 /**
842 * Decorates a FileTasks method, so it will be actually executed after the tasks
843 * are available.
844 * This decorator expects an implementation called |method + '_'|.
845 *
846 * @param {string} method The method name.
847 */
848 FileTasks.decorate = function(method) {
849 var privateMethod = method + '_';
850 FileTasks.prototype[method] = function() {
851 if (this.tasks_) {
852 this[privateMethod].apply(this, arguments);
853 } else {
854 this.pendingInvocations_.push([privateMethod, arguments]);
855 }
856 return this;
857 };
858 };
859
860 FileTasks.decorate('display');
861 FileTasks.decorate('updateMenuItem');
862 FileTasks.decorate('execute');
863 FileTasks.decorate('executeDefault');
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698