| 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 |