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

Side by Side Diff: chrome/browser/resources/file_manager/js/action_choice.js

Issue 39123003: [Files.app] Split the JavaScript files into subdirectories: common, background, and foreground (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed test failure. Created 7 years, 1 month 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) 2013 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 document.addEventListener('DOMContentLoaded', function() {
8 ActionChoice.load();
9 });
10
11 /**
12 * The main ActionChoice object.
13 *
14 * @param {HTMLElement} dom Container.
15 * @param {Object} params Parameters.
16 * @constructor
17 */
18 function ActionChoice(dom, params) {
19 this.dom_ = dom;
20 this.params_ = params;
21 this.document_ = this.dom_.ownerDocument;
22 this.metadataCache_ = this.params_.metadataCache;
23 this.volumeManager_ = new VolumeManagerWrapper(
24 VolumeManagerWrapper.DriveEnabledStatus.DRIVE_ENABLED);
25 this.volumeManager_.addEventListener('externally-unmounted',
26 this.onDeviceUnmounted_.bind(this));
27 this.initDom_();
28
29 // Load defined actions and remembered choice, then initialize volumes.
30 this.actions_ = [];
31 this.actionsById_ = {};
32 this.rememberedChoice_ = null;
33
34 ActionChoiceUtil.getDefinedActions(loadTimeData, function(actions) {
35 for (var i = 0; i < actions.length; i++) {
36 this.registerAction_(actions[i]);
37 }
38
39 this.viewFilesAction_ = this.actionsById_['view-files'];
40 this.importPhotosToDriveAction_ =
41 this.actionsById_['import-photos-to-drive'];
42 this.watchSingleVideoAction_ =
43 this.actionsById_['watch-single-video'];
44
45 // Special case: if Google+ Photos is installed, then do not show Drive.
46 for (var i = 0; i < actions.length; i++) {
47 if (actions[i].extensionId == ActionChoice.GPLUS_PHOTOS_EXTENSION_ID) {
48 this.importPhotosToDriveAction_.hidden = true;
49 break;
50 }
51 }
52
53 if (this.params_.advancedMode) {
54 // In the advanced mode, skip auto-choice.
55 this.initializeVolumes_();
56 } else {
57 // Get the remembered action before initializing volumes.
58 ActionChoiceUtil.getRememberedActionId(function(actionId) {
59 this.rememberedChoice_ = actionId;
60 this.initializeVolumes_();
61 }.bind(this));
62 }
63 this.renderList_();
64 }.bind(this));
65
66 // Try to render, what is already available.
67 this.renderList_();
68 }
69
70 ActionChoice.prototype = { __proto__: cr.EventTarget.prototype };
71
72 /**
73 * The number of previews shown.
74 * @type {number}
75 * @const
76 */
77 ActionChoice.PREVIEW_COUNT = 3;
78
79 /**
80 * Extension id of Google+ Photos app.
81 * @type {string}
82 * @const
83 */
84 ActionChoice.GPLUS_PHOTOS_EXTENSION_ID = 'efjnaogkjbogokcnohkmnjdojkikgobo';
85
86 /**
87 * Loads app in the document body.
88 * @param {Object=} opt_params Parameters.
89 */
90 ActionChoice.load = function(opt_params) {
91 ImageUtil.metrics = metrics;
92
93 var hash = location.hash ? decodeURIComponent(location.hash.substr(1)) : '';
94 var query =
95 location.search ? decodeURIComponent(location.search.substr(1)) : '';
96 var params = opt_params || {};
97 if (!params.source) params.source = hash;
98 if (!params.advancedMode) params.advancedMode = (query == 'advanced-mode');
99 if (!params.metadataCache) params.metadataCache = MetadataCache.createFull();
100
101 chrome.fileBrowserPrivate.getStrings(function(strings) {
102 loadTimeData.data = strings;
103 i18nTemplate.process(document, loadTimeData);
104 var dom = document.querySelector('.action-choice');
105 ActionChoice.instance = new ActionChoice(dom, params);
106 });
107 };
108
109 /**
110 * Registers an action.
111 * @param {Object} action Action item.
112 * @private
113 */
114 ActionChoice.prototype.registerAction_ = function(action) {
115 this.actions_.push(action);
116 this.actionsById_[action.id] = action;
117 };
118
119 /**
120 * Initializes the source and Drive. If the remembered choice is available,
121 * then performs the action.
122 * @private
123 */
124 ActionChoice.prototype.initializeVolumes_ = function() {
125 var checkDriveFinished = false;
126 var loadSourceFinished = false;
127
128 var maybeRunRememberedAction = function() {
129 if (!checkDriveFinished || !loadSourceFinished)
130 return;
131
132 // Run the remembered action if it is available.
133 if (this.rememberedChoice_) {
134 var action = this.actionsById_[this.rememberedChoice_];
135 if (action && !action.disabled)
136 this.runAction_(action);
137 }
138 }.bind(this);
139
140 var onCheckDriveFinished = function() {
141 checkDriveFinished = true;
142 maybeRunRememberedAction();
143 };
144
145 var onLoadSourceFinished = function() {
146 loadSourceFinished = true;
147 maybeRunRememberedAction();
148 };
149
150 this.checkDrive_(onCheckDriveFinished);
151 this.loadSource_(this.params_.source, onLoadSourceFinished);
152 };
153
154 /**
155 * One-time initialization of dom elements.
156 * @private
157 */
158 ActionChoice.prototype.initDom_ = function() {
159 this.list_ = new cr.ui.List();
160 this.list_.id = 'actions-list';
161 this.document_.querySelector('.choices').appendChild(this.list_);
162
163 var self = this; // .bind(this) doesn't work on constructors.
164 this.list_.itemConstructor = function(item) {
165 return self.renderItem(item);
166 };
167
168 this.list_.selectionModel = new cr.ui.ListSingleSelectionModel();
169 this.list_.dataModel = new cr.ui.ArrayDataModel([]);
170 this.list_.autoExpands = true;
171
172 var acceptActionBound = function() {
173 this.acceptAction_();
174 }.bind(this);
175 this.list_.activateItemAtIndex = acceptActionBound;
176 this.list_.addEventListener('click', acceptActionBound);
177
178 this.previews_ = this.document_.querySelector('.previews');
179 this.counter_ = this.document_.querySelector('.counter');
180 this.document_.addEventListener('keydown', this.onKeyDown_.bind(this));
181
182 metrics.startInterval('PhotoImport.Load');
183 this.dom_.setAttribute('loading', '');
184 };
185
186 /**
187 * Renders the list.
188 * @private
189 */
190 ActionChoice.prototype.renderList_ = function() {
191 var currentItem = this.list_.dataModel.item(
192 this.list_.selectionModel.selectedIndex);
193
194 this.list_.startBatchUpdates();
195 this.list_.dataModel.splice(0, this.list_.dataModel.length);
196
197 for (var i = 0; i < this.actions_.length; i++) {
198 if (!this.actions_[i].hidden)
199 this.list_.dataModel.push(this.actions_[i]);
200 }
201
202 for (var i = 0; i < this.list_.dataModel.length; i++) {
203 if (this.list_.dataModel.item(i) == currentItem) {
204 this.list_.selectionModel.selectedIndex = i;
205 break;
206 }
207 }
208
209 this.list_.endBatchUpdates();
210 };
211
212 /**
213 * Renders an item in the list.
214 * @param {Object} item Item to render.
215 * @return {Element} DOM element with representing the item.
216 */
217 ActionChoice.prototype.renderItem = function(item) {
218 var result = this.document_.createElement('li');
219
220 var div = this.document_.createElement('div');
221 if (item.disabled && item.disabledTitle)
222 div.textContent = item.disabledTitle;
223 else
224 div.textContent = item.title;
225
226 if (item.class)
227 div.classList.add(item.class);
228 if (item.icon100 && item.icon200)
229 div.style.backgroundImage = '-webkit-image-set(' +
230 'url(' + item.icon100 + ') 1x,' +
231 'url(' + item.icon200 + ') 2x)';
232 if (item.disabled)
233 div.classList.add('disabled');
234
235 cr.defineProperty(result, 'lead', cr.PropertyKind.BOOL_ATTR);
236 cr.defineProperty(result, 'selected', cr.PropertyKind.BOOL_ATTR);
237 result.appendChild(div);
238
239 return result;
240 };
241
242 /**
243 * Checks whether Drive is reachable.
244 *
245 * @param {function()} callback Completion callback.
246 * @private
247 */
248 ActionChoice.prototype.checkDrive_ = function(callback) {
249 this.volumeManager_.ensureInitialized(function() {
250 this.importPhotosToDriveAction_.disabled =
251 !this.volumeManager_.getVolumeInfo(RootDirectory.DRIVE);
252 this.renderList_();
253 callback();
254 }.bind(this));
255 };
256
257 /**
258 * Load the source contents.
259 *
260 * @param {string} source Path to source.
261 * @param {function()} callback Completion callback.
262 * @private
263 */
264 ActionChoice.prototype.loadSource_ = function(source, callback) {
265 var onTraversed = function(results) {
266 metrics.recordInterval('PhotoImport.Scan');
267 var videos = results.filter(FileType.isVideo);
268 if (videos.length == 1) {
269 this.singleVideo_ = videos[0];
270 this.watchSingleVideoAction_.title = loadTimeData.getStringF(
271 'ACTION_CHOICE_WATCH_SINGLE_VIDEO', videos[0].name);
272 this.watchSingleVideoAction_.hidden = false;
273 this.watchSingleVideoAction_.disabled = false;
274 this.renderList_();
275 }
276
277 var mediaFiles = results.filter(FileType.isImageOrVideo);
278 if (mediaFiles.length == 0) {
279 // If we have no media files, the only choice is view files. So, don't
280 // confuse user with a single choice, and just open file manager.
281 this.viewFiles_();
282 this.recordAction_('view-files-auto');
283 this.close_();
284 }
285
286 if (mediaFiles.length < ActionChoice.PREVIEW_COUNT) {
287 this.counter_.textContent = loadTimeData.getStringF(
288 'ACTION_CHOICE_COUNTER_NO_MEDIA', results.length);
289 } else {
290 this.counter_.textContent = loadTimeData.getStringF(
291 'ACTION_CHOICE_COUNTER', mediaFiles.length);
292 }
293 var previews = mediaFiles.length ? mediaFiles : results;
294 var previewsCount = Math.min(ActionChoice.PREVIEW_COUNT, previews.length);
295 this.renderPreview_(previews, previewsCount);
296 callback();
297 }.bind(this);
298
299 var onEntry = function(entry) {
300 this.sourceEntry_ = entry;
301 this.document_.querySelector('title').textContent = entry.name;
302
303 var volumeInfo = this.volumeManager_.getVolumeInfo(entry.fullPath);
304 var deviceType = volumeInfo && volumeInfo.deviceType;
305 if (deviceType != 'sd') deviceType = 'usb';
306 this.dom_.querySelector('.device-type').setAttribute('device-type',
307 deviceType);
308 this.dom_.querySelector('.loading-text').textContent =
309 loadTimeData.getString('ACTION_CHOICE_LOADING_' +
310 deviceType.toUpperCase());
311
312 var entryList = [];
313 util.traverseTree(
314 entry,
315 function(traversedEntry) {
316 if (!FileType.isVisible(traversedEntry))
317 return false;
318 entryList.push(traversedEntry);
319 return true;
320 },
321 function() {
322 onTraversed(entryList);
323 },
324 function(error) {
325 console.error(
326 'Failed to traverse [' + entry.fullPath + ']: ' + error.code);
327 });
328 }.bind(this);
329
330 this.sourceEntry_ = null;
331 metrics.startInterval('PhotoImport.Scan');
332 this.volumeManager_.ensureInitialized(function() {
333 this.volumeManager_.resolvePath(
334 source, onEntry,
335 function(error) {
336 this.recordAction_('error');
337 this.close_();
338 }.bind(this));
339 }.bind(this));
340 };
341
342 /**
343 * Renders a preview for a media entry.
344 * @param {Array.<FileEntry>} entries The entries.
345 * @param {number} count Remaining count.
346 * @private
347 */
348 ActionChoice.prototype.renderPreview_ = function(entries, count) {
349 var entry = entries.shift();
350 var box = this.document_.createElement('div');
351 box.className = 'img-container';
352
353 var done = function() {
354 this.dom_.removeAttribute('loading');
355 metrics.recordInterval('PhotoImport.Load');
356 }.bind(this);
357
358 var onSuccess = function() {
359 this.previews_.appendChild(box);
360 if (--count == 0) {
361 done();
362 } else {
363 this.renderPreview_(entries, count);
364 }
365 }.bind(this);
366
367 var onError = function() {
368 if (entries.length == 0) {
369 // Append one image with generic thumbnail.
370 this.previews_.appendChild(box);
371 done();
372 } else {
373 this.renderPreview_(entries, count);
374 }
375 }.bind(this);
376
377 this.metadataCache_.get(entry, 'thumbnail|filesystem',
378 function(metadata) {
379 new ThumbnailLoader(entry.toURL(),
380 ThumbnailLoader.LoaderType.IMAGE,
381 metadata).load(
382 box,
383 ThumbnailLoader.FillMode.FILL,
384 ThumbnailLoader.OptimizationMode.NEVER_DISCARD,
385 onSuccess,
386 onError,
387 onError);
388 });
389 };
390
391 /**
392 * Closes the window.
393 * @private
394 */
395 ActionChoice.prototype.close_ = function() {
396 window.close();
397 };
398
399 /**
400 * Keydown event handler.
401 * @param {Event} e The event.
402 * @private
403 */
404 ActionChoice.prototype.onKeyDown_ = function(e) {
405 switch (util.getKeyModifiers(e) + e.keyCode) {
406 case '13':
407 this.acceptAction_();
408 break;
409 case '27':
410 this.recordAction_('close');
411 this.close_();
412 break;
413 }
414 };
415
416 /**
417 * Runs an action.
418 * @param {Object} action Action item to perform.
419 * @private
420 */
421 ActionChoice.prototype.runAction_ = function(action) {
422 // TODO(mtomasz): Remove these predefined actions in Apps v2.
423 if (action == this.importPhotosToDriveAction_) {
424 var url = chrome.runtime.getURL('photo_import.html') +
425 '#' + this.sourceEntry_.fullPath;
426 var width = 728;
427 var height = 656;
428 var top = Math.round((window.screen.availHeight - height) / 2);
429 var left = Math.round((window.screen.availWidth - width) / 2);
430 chrome.app.window.create(url,
431 {height: height, width: width, left: left, top: top});
432 this.recordAction_('import-photos-to-drive');
433 this.close_();
434 return;
435 }
436
437 if (action == this.watchSingleVideoAction_) {
438 util.viewFilesInBrowser([this.singleVideo_.toURL()],
439 function(success) {});
440 this.recordAction_('watch-single-video');
441 this.close_();
442 return;
443 }
444
445 if (action == this.viewFilesAction_) {
446 this.viewFiles_();
447 this.recordAction_('view-files');
448 this.close_();
449 return;
450 }
451
452 if (!action.extensionId) {
453 console.error('Unknown predefined action.');
454 return;
455 }
456
457 // Run the media galleries handler.
458 chrome.mediaGalleriesPrivate.launchHandler(action.extensionId,
459 action.actionId,
460 this.params_.source);
461 this.close_();
462 };
463
464 /**
465 * Handles accepting an action. Checks if the action is available, remembers
466 * and runs it.
467 * @private
468 */
469 ActionChoice.prototype.acceptAction_ = function() {
470 var action =
471 this.list_.dataModel.item(this.list_.selectionModel.selectedIndex);
472 if (!action || action.hidden || action.disabled)
473 return;
474
475 this.runAction_(action);
476 ActionChoiceUtil.setRememberedActionId(action.id);
477 };
478
479 /**
480 * Called when some device is unmounted.
481 * @param {Event} event Event object.
482 * @private
483 */
484 ActionChoice.prototype.onDeviceUnmounted_ = function(event) {
485 if (this.sourceEntry_ && event.mountPath == this.sourceEntry_.fullPath)
486 window.close();
487 };
488
489 /**
490 * Perform the 'view files' action.
491 * @private
492 */
493 ActionChoice.prototype.viewFiles_ = function() {
494 var path = this.sourceEntry_.fullPath;
495 chrome.runtime.getBackgroundPage(function(bg) {
496 bg.launchFileManager({defaultPath: path});
497 });
498 };
499
500 /**
501 * Records an action chosen.
502 * @param {string} action Action name.
503 * @private
504 */
505 ActionChoice.prototype.recordAction_ = function(action) {
506 metrics.recordEnum('PhotoImport.Action', action,
507 ['import-photos-to-drive',
508 'view-files',
509 'view-files-auto',
510 'watch-single-video',
511 'error',
512 'close']);
513 };
514
515 /**
516 * Called when the page is unloaded.
517 */
518 ActionChoice.prototype.onUnload = function() {
519 this.volumeManager_.dispose();
520 };
521
522 function unload() {
523 if (ActionChoice.instance)
524 ActionChoice.instance.onUnload();
525 }
OLDNEW
« no previous file with comments | « chrome/browser/resources/file_manager/gallery.html ('k') | chrome/browser/resources/file_manager/js/action_choice_scripts.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698