OLD | NEW |
| (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 * @param {FileManager} fileManager FileManager instance. | |
11 * @param {Object=} opt_params File manager load parameters. | |
12 * @constructor | |
13 */ | |
14 function FileTasks(fileManager, opt_params) { | |
15 this.fileManager_ = fileManager; | |
16 this.params_ = opt_params; | |
17 this.tasks_ = null; | |
18 this.defaultTask_ = null; | |
19 | |
20 /** | |
21 * List of invocations to be called once tasks are available. | |
22 * | |
23 * @private | |
24 * @type {Array.<Object>} | |
25 */ | |
26 this.pendingInvocations_ = []; | |
27 } | |
28 | |
29 /** | |
30 * Location of the FAQ about the file actions. | |
31 * | |
32 * @const | |
33 * @type {string} | |
34 */ | |
35 FileTasks.NO_ACTION_FOR_FILE_URL = 'http://support.google.com/chromeos/bin/' + | |
36 'answer.py?answer=1700055&topic=29026&ctx=topic'; | |
37 | |
38 /** | |
39 * Location of the Chrome Web Store. | |
40 * | |
41 * @const | |
42 * @type {string} | |
43 */ | |
44 FileTasks.CHROME_WEB_STORE_URL = 'https://chrome.google.com/webstore'; | |
45 | |
46 /** | |
47 * Base URL of apps list in the Chrome Web Store. This constant is used in | |
48 * FileTasks.createWebStoreLink(). | |
49 * | |
50 * @const | |
51 * @type {string} | |
52 */ | |
53 FileTasks.WEB_STORE_HANDLER_BASE_URL = | |
54 'https://chrome.google.com/webstore/category/collection/file_handlers'; | |
55 | |
56 /** | |
57 * Returns URL of the Chrome Web Store which show apps supporting the given | |
58 * file-extension and mime-type. | |
59 * | |
60 * @param {string} extension Extension of the file. | |
61 * @param {string} mimeType Mime type of the file. | |
62 * @return {string} URL | |
63 */ | |
64 FileTasks.createWebStoreLink = function(extension, mimeType) { | |
65 if (!extension) | |
66 return FileTasks.CHROME_WEB_STORE_URL; | |
67 | |
68 var url = FileTasks.WEB_STORE_HANDLER_BASE_URL; | |
69 url += '?_fe=' + extension.toLowerCase().replace(/[^\w]/g, ''); | |
70 | |
71 // If a mime is given, add it into the URL. | |
72 if (mimeType) | |
73 url += '&_fmt=' + mimeType.replace(/[^-\w\/]/g, ''); | |
74 return url; | |
75 }; | |
76 | |
77 /** | |
78 * Complete the initialization. | |
79 * | |
80 * @param {Array.<string>} urls List of file urls. | |
81 * @param {Array.<string>=} opt_mimeTypes List of MIME types for each | |
82 * of the files. | |
83 */ | |
84 FileTasks.prototype.init = function(urls, opt_mimeTypes) { | |
85 this.urls_ = urls; | |
86 if (urls.length > 0) | |
87 chrome.fileBrowserPrivate.getFileTasks(urls, opt_mimeTypes || [], | |
88 this.onTasks_.bind(this)); | |
89 }; | |
90 | |
91 /** | |
92 * Returns amount of tasks. | |
93 * | |
94 * @return {number} amount of tasks. | |
95 */ | |
96 FileTasks.prototype.size = function() { | |
97 return (this.tasks_ && this.tasks_.length) || 0; | |
98 }; | |
99 | |
100 /** | |
101 * Callback when tasks found. | |
102 * | |
103 * @param {Array.<Object>} tasks The tasks. | |
104 * @private | |
105 */ | |
106 FileTasks.prototype.onTasks_ = function(tasks) { | |
107 this.processTasks_(tasks); | |
108 for (var index = 0; index < this.pendingInvocations_.length; index++) { | |
109 var name = this.pendingInvocations_[index][0]; | |
110 var args = this.pendingInvocations_[index][1]; | |
111 this[name].apply(this, args); | |
112 } | |
113 this.pendingInvocations_ = []; | |
114 }; | |
115 | |
116 /** | |
117 * The list of known extensions to record UMA. | |
118 * Note: Because the data is recorded by the index, so new item shouldn't be | |
119 * inserted. | |
120 * | |
121 * @const | |
122 * @type {Array.<string>} | |
123 * @private | |
124 */ | |
125 FileTasks.knownExtensions_ = [ | |
126 'other', '.3ga', '.3gp', '.aac', '.alac', '.asf', '.avi', '.bmp', '.csv', | |
127 '.doc', '.docx', '.flac', '.gif', '.jpeg', '.jpg', '.log', '.m3u', '.m3u8', | |
128 '.m4a', '.m4v', '.mid', '.mkv', '.mov', '.mp3', '.mp4', '.mpg', '.odf', | |
129 '.odp', '.ods', '.odt', '.oga', '.ogg', '.ogv', '.pdf', '.png', '.ppt', | |
130 '.pptx', '.ra', '.ram', '.rar', '.rm', '.rtf', '.wav', '.webm', '.webp', | |
131 '.wma', '.wmv', '.xls', '.xlsx', | |
132 ]; | |
133 | |
134 /** | |
135 * Records trial of opening file grouped by extensions. | |
136 * | |
137 * @param {Array.<string>} urls The path to be opened. | |
138 * @private | |
139 */ | |
140 FileTasks.recordViewingFileTypeUMA_ = function(urls) { | |
141 for (var i = 0; i < urls.length; i++) { | |
142 var url = urls[i]; | |
143 var extension = FileType.getExtension(url).toLowerCase(); | |
144 if (FileTasks.knownExtensions_.indexOf(extension) < 0) { | |
145 extension = 'other'; | |
146 } | |
147 metrics.recordEnum( | |
148 'ViewingFileType', extension, FileTasks.knownExtensions_); | |
149 } | |
150 }; | |
151 | |
152 /** | |
153 * Returns true if the taskId is for an internal task. | |
154 * | |
155 * @param {string} taskId Task identifier. | |
156 * @return {boolean} True if the task ID is for an internal task. | |
157 * @private | |
158 */ | |
159 FileTasks.isInternalTask_ = function(taskId) { | |
160 var taskParts = taskId.split('|'); | |
161 var appId = taskParts[0]; | |
162 var taskType = taskParts[1]; | |
163 var actionId = taskParts[2]; | |
164 // The action IDs here should match ones used in executeInternalTask_(). | |
165 return (appId == chrome.runtime.id && | |
166 taskType == 'file' && | |
167 (actionId == 'play' || | |
168 actionId == 'watch' || | |
169 actionId == 'mount-archive' || | |
170 actionId == 'format-device' || | |
171 actionId == 'gallery')); | |
172 }; | |
173 | |
174 /** | |
175 * Processes internal tasks. | |
176 * | |
177 * @param {Array.<Object>} tasks The tasks. | |
178 * @private | |
179 */ | |
180 FileTasks.prototype.processTasks_ = function(tasks) { | |
181 this.tasks_ = []; | |
182 var id = chrome.runtime.id; | |
183 var isOnDrive = false; | |
184 for (var index = 0; index < this.urls_.length; ++index) { | |
185 if (FileType.isOnDrive(this.urls_[index])) { | |
186 isOnDrive = true; | |
187 break; | |
188 } | |
189 } | |
190 | |
191 for (var i = 0; i < tasks.length; i++) { | |
192 var task = tasks[i]; | |
193 var taskParts = task.taskId.split('|'); | |
194 | |
195 // Skip internal Files.app's handlers. | |
196 if (taskParts[0] == id && (taskParts[2] == 'auto-open' || | |
197 taskParts[2] == 'select' || taskParts[2] == 'open')) { | |
198 continue; | |
199 } | |
200 | |
201 // Tweak images, titles of internal tasks. | |
202 if (taskParts[0] == id && taskParts[1] == 'file') { | |
203 if (taskParts[2] == 'play') { | |
204 // TODO(serya): This hack needed until task.iconUrl is working | |
205 // (see GetFileTasksFileBrowserFunction::RunImpl). | |
206 task.iconType = 'audio'; | |
207 task.title = loadTimeData.getString('ACTION_LISTEN'); | |
208 } else if (taskParts[2] == 'mount-archive') { | |
209 task.iconType = 'archive'; | |
210 task.title = loadTimeData.getString('MOUNT_ARCHIVE'); | |
211 } else if (taskParts[2] == 'gallery') { | |
212 task.iconType = 'image'; | |
213 task.title = loadTimeData.getString('ACTION_OPEN'); | |
214 } else if (taskParts[2] == 'watch') { | |
215 task.iconType = 'video'; | |
216 task.title = loadTimeData.getString('ACTION_WATCH'); | |
217 } else if (taskParts[2] == 'open-hosted-generic') { | |
218 if (this.urls_.length > 1) | |
219 task.iconType = 'generic'; | |
220 else // Use specific icon. | |
221 task.iconType = FileType.getIcon(this.urls_[0]); | |
222 task.title = loadTimeData.getString('ACTION_OPEN'); | |
223 } else if (taskParts[2] == 'open-hosted-gdoc') { | |
224 task.iconType = 'gdoc'; | |
225 task.title = loadTimeData.getString('ACTION_OPEN_GDOC'); | |
226 } else if (taskParts[2] == 'open-hosted-gsheet') { | |
227 task.iconType = 'gsheet'; | |
228 task.title = loadTimeData.getString('ACTION_OPEN_GSHEET'); | |
229 } else if (taskParts[2] == 'open-hosted-gslides') { | |
230 task.iconType = 'gslides'; | |
231 task.title = loadTimeData.getString('ACTION_OPEN_GSLIDES'); | |
232 } else if (taskParts[2] == 'view-swf') { | |
233 // Do not render this task if disabled. | |
234 if (!loadTimeData.getBoolean('SWF_VIEW_ENABLED')) | |
235 continue; | |
236 task.iconType = 'generic'; | |
237 task.title = loadTimeData.getString('ACTION_VIEW'); | |
238 } else if (taskParts[2] == 'view-pdf') { | |
239 // Do not render this task if disabled. | |
240 if (!loadTimeData.getBoolean('PDF_VIEW_ENABLED')) | |
241 continue; | |
242 task.iconType = 'pdf'; | |
243 task.title = loadTimeData.getString('ACTION_VIEW'); | |
244 } else if (taskParts[2] == 'view-in-browser') { | |
245 task.iconType = 'generic'; | |
246 task.title = loadTimeData.getString('ACTION_VIEW'); | |
247 } | |
248 } | |
249 | |
250 if (!task.iconType && taskParts[1] == 'web-intent') { | |
251 task.iconType = 'generic'; | |
252 } | |
253 | |
254 this.tasks_.push(task); | |
255 if (this.defaultTask_ == null && task.isDefault) { | |
256 this.defaultTask_ = task; | |
257 } | |
258 } | |
259 if (!this.defaultTask_ && this.tasks_.length > 0) { | |
260 // If we haven't picked a default task yet, then just pick the first one. | |
261 // This is not the preferred way we want to pick this, but better this than | |
262 // no default at all if the C++ code didn't set one. | |
263 this.defaultTask_ = this.tasks_[0]; | |
264 } | |
265 }; | |
266 | |
267 /** | |
268 * Executes default task. | |
269 * | |
270 * @param {function(boolean, Array.<string>)=} opt_callback Called when the | |
271 * default task is executed, or the error is occurred. | |
272 * @private | |
273 */ | |
274 FileTasks.prototype.executeDefault_ = function(opt_callback) { | |
275 var urls = this.urls_; | |
276 FileTasks.recordViewingFileTypeUMA_(urls); | |
277 this.executeDefaultInternal_(urls, opt_callback); | |
278 }; | |
279 | |
280 /** | |
281 * Executes default task. | |
282 * | |
283 * @param {Array.<string>} urls Urls to execute. | |
284 * @param {function(boolean, Array.<string>)=} opt_callback Called when the | |
285 * default task is executed, or the error is occurred. | |
286 * @private | |
287 */ | |
288 FileTasks.prototype.executeDefaultInternal_ = function(urls, opt_callback) { | |
289 var callback = opt_callback || function(arg1, arg2) {}; | |
290 | |
291 if (this.defaultTask_ != null) { | |
292 this.executeInternal_(this.defaultTask_.taskId, urls); | |
293 callback(true, urls); | |
294 return; | |
295 } | |
296 | |
297 // We don't have tasks, so try to show a file in a browser tab. | |
298 // We only do that for single selection to avoid confusion. | |
299 if (urls.length != 1) | |
300 return; | |
301 | |
302 var onViewFiles = function(success) { | |
303 callback(success, urls); | |
304 }.bind(this); | |
305 | |
306 this.checkAvailability_(function() { | |
307 util.viewFilesInBrowser(urls, onViewFiles); | |
308 }.bind(this)); | |
309 }; | |
310 | |
311 /** | |
312 * Executes a single task. | |
313 * | |
314 * @param {string} taskId Task identifier. | |
315 * @param {Array.<string>=} opt_urls Urls to execute on instead of |this.urls_|. | |
316 * @private | |
317 */ | |
318 FileTasks.prototype.execute_ = function(taskId, opt_urls) { | |
319 var urls = opt_urls || this.urls_; | |
320 FileTasks.recordViewingFileTypeUMA_(urls); | |
321 this.executeInternal_(taskId, urls); | |
322 }; | |
323 | |
324 /** | |
325 * The core implementation to execute a single task. | |
326 * | |
327 * @param {string} taskId Task identifier. | |
328 * @param {Array.<string>} urls Urls to execute. | |
329 * @private | |
330 */ | |
331 FileTasks.prototype.executeInternal_ = function(taskId, urls) { | |
332 this.checkAvailability_(function() { | |
333 if (FileTasks.isInternalTask_(taskId)) { | |
334 var taskParts = taskId.split('|'); | |
335 this.executeInternalTask_(taskParts[2], urls); | |
336 } else { | |
337 chrome.fileBrowserPrivate.executeTask(taskId, urls); | |
338 } | |
339 }.bind(this)); | |
340 }; | |
341 | |
342 /** | |
343 * Checks whether the remote files are available right now. | |
344 * | |
345 * @param {function} callback The callback. | |
346 * @private | |
347 */ | |
348 FileTasks.prototype.checkAvailability_ = function(callback) { | |
349 var areAll = function(props, name) { | |
350 var isOne = function(e) { | |
351 // If got no properties, we safely assume that item is unavailable. | |
352 return e && e[name]; | |
353 }; | |
354 return props.filter(isOne).length == props.length; | |
355 }; | |
356 | |
357 var fm = this.fileManager_; | |
358 var urls = this.urls_; | |
359 | |
360 if (fm.isOnDrive() && fm.isDriveOffline()) { | |
361 fm.metadataCache_.get(urls, 'drive', function(props) { | |
362 if (areAll(props, 'availableOffline')) { | |
363 callback(); | |
364 return; | |
365 } | |
366 | |
367 fm.alert.showHtml( | |
368 loadTimeData.getString('OFFLINE_HEADER'), | |
369 props[0].hosted ? | |
370 loadTimeData.getStringF( | |
371 urls.length == 1 ? | |
372 'HOSTED_OFFLINE_MESSAGE' : | |
373 'HOSTED_OFFLINE_MESSAGE_PLURAL') : | |
374 loadTimeData.getStringF( | |
375 urls.length == 1 ? | |
376 'OFFLINE_MESSAGE' : | |
377 'OFFLINE_MESSAGE_PLURAL', | |
378 loadTimeData.getString('OFFLINE_COLUMN_LABEL'))); | |
379 }); | |
380 return; | |
381 } | |
382 | |
383 if (fm.isOnDrive() && fm.isDriveOnMeteredConnection()) { | |
384 fm.metadataCache_.get(urls, 'drive', function(driveProps) { | |
385 if (areAll(driveProps, 'availableWhenMetered')) { | |
386 callback(); | |
387 return; | |
388 } | |
389 | |
390 fm.metadataCache_.get(urls, 'filesystem', function(fileProps) { | |
391 var sizeToDownload = 0; | |
392 for (var i = 0; i != urls.length; i++) { | |
393 if (!driveProps[i].availableWhenMetered) | |
394 sizeToDownload += fileProps[i].size; | |
395 } | |
396 fm.confirm.show( | |
397 loadTimeData.getStringF( | |
398 urls.length == 1 ? | |
399 'CONFIRM_MOBILE_DATA_USE' : | |
400 'CONFIRM_MOBILE_DATA_USE_PLURAL', | |
401 util.bytesToString(sizeToDownload)), | |
402 callback); | |
403 }); | |
404 }); | |
405 return; | |
406 } | |
407 | |
408 callback(); | |
409 }; | |
410 | |
411 /** | |
412 * Executes an internal task. | |
413 * | |
414 * @param {string} id The short task id. | |
415 * @param {Array.<string>} urls The urls to execute on. | |
416 * @private | |
417 */ | |
418 FileTasks.prototype.executeInternalTask_ = function(id, urls) { | |
419 var fm = this.fileManager_; | |
420 | |
421 if (id == 'play') { | |
422 var position = 0; | |
423 if (urls.length == 1) { | |
424 // If just a single audio file is selected pass along every audio file | |
425 // in the directory. | |
426 var selectedUrl = urls[0]; | |
427 urls = fm.getAllUrlsInCurrentDirectory().filter(FileType.isAudio); | |
428 position = urls.indexOf(selectedUrl); | |
429 } | |
430 fm.backgroundPage.launchAudioPlayer({ items: urls, position: position }); | |
431 return; | |
432 } | |
433 | |
434 if (id == 'watch') { | |
435 console.assert(urls.length == 1, 'Cannot open multiple videos'); | |
436 fm.backgroundPage.launchVideoPlayer(urls[0]); | |
437 return; | |
438 } | |
439 | |
440 if (id == 'mount-archive') { | |
441 this.mountArchivesInternal_(urls); | |
442 return; | |
443 } | |
444 | |
445 if (id == 'format-device') { | |
446 fm.confirm.show(loadTimeData.getString('FORMATTING_WARNING'), function() { | |
447 chrome.fileBrowserPrivate.formatDevice(urls[0]); | |
448 }); | |
449 return; | |
450 } | |
451 | |
452 if (id == 'gallery') { | |
453 this.openGalleryInternal_(urls); | |
454 return; | |
455 } | |
456 | |
457 console.error('Unexpected action ID: ' + id); | |
458 }; | |
459 | |
460 /** | |
461 * Mounts archives. | |
462 * | |
463 * @param {Array.<string>} urls Mount file urls list. | |
464 */ | |
465 FileTasks.prototype.mountArchives = function(urls) { | |
466 FileTasks.recordViewingFileTypeUMA_(urls); | |
467 this.mountArchivesInternal_(urls); | |
468 }; | |
469 | |
470 /** | |
471 * The core implementation of mounts archives. | |
472 * | |
473 * @param {Array.<string>} urls Mount file urls list. | |
474 * @private | |
475 */ | |
476 FileTasks.prototype.mountArchivesInternal_ = function(urls) { | |
477 var fm = this.fileManager_; | |
478 | |
479 var tracker = fm.directoryModel_.createDirectoryChangeTracker(); | |
480 tracker.start(); | |
481 | |
482 fm.resolveSelectResults_(urls, function(urls) { | |
483 for (var index = 0; index < urls.length; ++index) { | |
484 fm.volumeManager_.mountArchive(urls[index], function(mountPath) { | |
485 tracker.stop(); | |
486 if (!tracker.hasChanged) | |
487 fm.directoryModel_.changeDirectory(mountPath); | |
488 }, function(url, error) { | |
489 var path = util.extractFilePath(url); | |
490 tracker.stop(); | |
491 var namePos = path.lastIndexOf('/'); | |
492 fm.alert.show(strf('ARCHIVE_MOUNT_FAILED', | |
493 path.substr(namePos + 1), error)); | |
494 }.bind(null, urls[index])); | |
495 } | |
496 }); | |
497 }; | |
498 | |
499 /** | |
500 * Open the Gallery. | |
501 * | |
502 * @param {Array.<string>} urls List of selected urls. | |
503 */ | |
504 FileTasks.prototype.openGallery = function(urls) { | |
505 FileTasks.recordViewingFileTypeUMA_(urls); | |
506 this.openGalleryInternal_(urls); | |
507 }; | |
508 | |
509 /** | |
510 * The core implementation to open the Gallery. | |
511 * | |
512 * @param {Array.<string>} urls List of selected urls. | |
513 * @private | |
514 */ | |
515 FileTasks.prototype.openGalleryInternal_ = function(urls) { | |
516 var fm = this.fileManager_; | |
517 | |
518 var allUrls = | |
519 fm.getAllUrlsInCurrentDirectory().filter(FileType.isImageOrVideo); | |
520 | |
521 var galleryFrame = fm.document_.createElement('iframe'); | |
522 galleryFrame.className = 'overlay-pane'; | |
523 galleryFrame.scrolling = 'no'; | |
524 galleryFrame.setAttribute('webkitallowfullscreen', true); | |
525 | |
526 if (this.params_ && this.params_.gallery) { | |
527 // Remove the Gallery state from the location, we do not need it any more. | |
528 util.updateAppState(null /* keep path */, '' /* remove search. */); | |
529 } | |
530 | |
531 var savedAppState = window.appState; | |
532 var savedTitle = document.title; | |
533 | |
534 // Push a temporary state which will be replaced every time the selection | |
535 // changes in the Gallery and popped when the Gallery is closed. | |
536 util.updateAppState(); | |
537 | |
538 var onBack = function(selectedUrls) { | |
539 fm.directoryModel_.selectUrls(selectedUrls); | |
540 fm.closeFilePopup_(); // Will call Gallery.unload. | |
541 window.appState = savedAppState; | |
542 util.saveAppState(); | |
543 document.title = savedTitle; | |
544 }; | |
545 | |
546 var onClose = function() { | |
547 fm.onClose(); | |
548 }; | |
549 | |
550 var onMaximize = function() { | |
551 fm.onMaximize(); | |
552 }; | |
553 | |
554 galleryFrame.onload = function() { | |
555 galleryFrame.contentWindow.ImageUtil.metrics = metrics; | |
556 | |
557 // TODO(haruki): isOnReadonlyDirectory() only checks the permission for the | |
558 // root. We should check more granular permission to know whether the file | |
559 // is writable or not. | |
560 var readonly = fm.isOnReadonlyDirectory(); | |
561 var currentDir = fm.getCurrentDirectoryEntry(); | |
562 var downloadsVolume = | |
563 fm.volumeManager_.getVolumeInfo(RootDirectory.DOWNLOADS); | |
564 var downloadsDir = downloadsVolume && downloadsVolume.root; | |
565 var readonlyDirName = null; | |
566 if (readonly && currentDir) { | |
567 var rootPath = PathUtil.getRootPath(currentDir.fullPath); | |
568 readonlyDirName = fm.isOnDrive() ? | |
569 PathUtil.getRootLabel(rootPath) : | |
570 PathUtil.basename(rootPath); | |
571 } | |
572 | |
573 var context = { | |
574 // We show the root label in readonly warning (e.g. archive name). | |
575 readonlyDirName: readonlyDirName, | |
576 curDirEntry: currentDir, | |
577 saveDirEntry: readonly ? downloadsDir : null, | |
578 searchResults: fm.directoryModel_.isSearching(), | |
579 metadataCache: fm.metadataCache_, | |
580 pageState: this.params_, | |
581 appWindow: chrome.app.window.current(), | |
582 onBack: onBack, | |
583 onClose: onClose, | |
584 onMaximize: onMaximize, | |
585 displayStringFunction: strf | |
586 }; | |
587 galleryFrame.contentWindow.Gallery.open( | |
588 context, fm.volumeManager_, allUrls, urls); | |
589 }.bind(this); | |
590 | |
591 galleryFrame.src = 'gallery.html'; | |
592 fm.openFilePopup_(galleryFrame, fm.updateTitle_.bind(fm)); | |
593 }; | |
594 | |
595 /** | |
596 * Displays the list of tasks in a task picker combobutton. | |
597 * | |
598 * @param {cr.ui.ComboButton} combobutton The task picker element. | |
599 * @private | |
600 */ | |
601 FileTasks.prototype.display_ = function(combobutton) { | |
602 if (this.tasks_.length == 0) { | |
603 combobutton.hidden = true; | |
604 return; | |
605 } | |
606 | |
607 combobutton.clear(); | |
608 combobutton.hidden = false; | |
609 combobutton.defaultItem = this.createCombobuttonItem_(this.defaultTask_); | |
610 | |
611 var items = this.createItems_(); | |
612 | |
613 if (items.length > 1) { | |
614 var defaultIdx = 0; | |
615 | |
616 for (var j = 0; j < items.length; j++) { | |
617 combobutton.addDropDownItem(items[j]); | |
618 if (items[j].task.taskId == this.defaultTask_.taskId) | |
619 defaultIdx = j; | |
620 } | |
621 | |
622 combobutton.addSeparator(); | |
623 var changeDefaultMenuItem = combobutton.addDropDownItem({ | |
624 label: loadTimeData.getString('CHANGE_DEFAULT_MENU_ITEM') | |
625 }); | |
626 changeDefaultMenuItem.classList.add('change-default'); | |
627 } | |
628 }; | |
629 | |
630 /** | |
631 * Creates sorted array of available task descriptions such as title and icon. | |
632 * | |
633 * @return {Array} created array can be used to feed combobox, menus and so on. | |
634 * @private | |
635 */ | |
636 FileTasks.prototype.createItems_ = function() { | |
637 var items = []; | |
638 var title = this.defaultTask_.title + ' ' + | |
639 loadTimeData.getString('DEFAULT_ACTION_LABEL'); | |
640 items.push(this.createCombobuttonItem_(this.defaultTask_, title, true)); | |
641 | |
642 for (var index = 0; index < this.tasks_.length; index++) { | |
643 var task = this.tasks_[index]; | |
644 if (task != this.defaultTask_) | |
645 items.push(this.createCombobuttonItem_(task)); | |
646 } | |
647 | |
648 items.sort(function(a, b) { | |
649 return a.label.localeCompare(b.label); | |
650 }); | |
651 | |
652 return items; | |
653 }; | |
654 | |
655 /** | |
656 * Updates context menu with default item. | |
657 * @private | |
658 */ | |
659 | |
660 FileTasks.prototype.updateMenuItem_ = function() { | |
661 this.fileManager_.updateContextMenuActionItems(this.defaultTask_, | |
662 this.tasks_.length > 1); | |
663 }; | |
664 | |
665 /** | |
666 * Creates combobutton item based on task. | |
667 * | |
668 * @param {Object} task Task to convert. | |
669 * @param {string=} opt_title Title. | |
670 * @param {boolean=} opt_bold Make a menu item bold. | |
671 * @return {Object} Item appendable to combobutton drop-down list. | |
672 * @private | |
673 */ | |
674 FileTasks.prototype.createCombobuttonItem_ = function(task, opt_title, | |
675 opt_bold) { | |
676 return { | |
677 label: opt_title || task.title, | |
678 iconUrl: task.iconUrl, | |
679 iconType: task.iconType, | |
680 task: task, | |
681 bold: opt_bold || false | |
682 }; | |
683 }; | |
684 | |
685 | |
686 /** | |
687 * Decorates a FileTasks method, so it will be actually executed after the tasks | |
688 * are available. | |
689 * This decorator expects an implementation called |method + '_'|. | |
690 * | |
691 * @param {string} method The method name. | |
692 */ | |
693 FileTasks.decorate = function(method) { | |
694 var privateMethod = method + '_'; | |
695 FileTasks.prototype[method] = function() { | |
696 if (this.tasks_) { | |
697 this[privateMethod].apply(this, arguments); | |
698 } else { | |
699 this.pendingInvocations_.push([privateMethod, arguments]); | |
700 } | |
701 return this; | |
702 }; | |
703 }; | |
704 | |
705 /** | |
706 * Shows modal action picker dialog with currently available list of tasks. | |
707 * | |
708 * @param {DefaultActionDialog} actionDialog Action dialog to show and update. | |
709 * @param {string} title Title to use. | |
710 * @param {string} message Message to use. | |
711 * @param {function(Object)} onSuccess Callback to pass selected task. | |
712 */ | |
713 FileTasks.prototype.showTaskPicker = function(actionDialog, title, message, | |
714 onSuccess) { | |
715 var items = this.createItems_(); | |
716 | |
717 var defaultIdx = 0; | |
718 for (var j = 0; j < items.length; j++) { | |
719 if (items[j].task.taskId == this.defaultTask_.taskId) | |
720 defaultIdx = j; | |
721 } | |
722 | |
723 actionDialog.show( | |
724 title, | |
725 message, | |
726 items, defaultIdx, | |
727 function(item) { | |
728 onSuccess(item.task); | |
729 }); | |
730 }; | |
731 | |
732 FileTasks.decorate('display'); | |
733 FileTasks.decorate('updateMenuItem'); | |
734 FileTasks.decorate('execute'); | |
735 FileTasks.decorate('executeDefault'); | |
OLD | NEW |