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

Side by Side Diff: chrome/browser/resources/file_manager/js/photo/photo_import.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) 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 document.addEventListener('DOMContentLoaded', function() {
8 PhotoImport.load();
9 });
10
11 /**
12 * The main Photo App object.
13 * @param {HTMLElement} dom Container.
14 * @param {VolumeManagerWrapper} volumeManager The initialized
15 * VolumeManagerWrapper instance.
16 * @param {Object} params Parameters.
17 * @constructor
18 */
19 function PhotoImport(dom, volumeManager, params) {
20 this.dom_ = dom;
21 this.document_ = this.dom_.ownerDocument;
22 this.metadataCache_ = params.metadataCache;
23 this.volumeManager_ = volumeManager;
24 this.fileOperationManager_ = FileOperationManagerWrapper.getInstance();
25 this.mediaFilesList_ = null;
26 this.destination_ = null;
27 this.myPhotosDirectory_ = null;
28 this.parentWindowId_ = params.parentWindowId;
29
30 this.initDom_();
31 this.initMyPhotos_();
32 this.loadSource_(params.source);
33 }
34
35 PhotoImport.prototype = { __proto__: cr.EventTarget.prototype };
36
37 /**
38 * Single item width.
39 * Keep in sync with .grid-item rule in photo_import.css.
40 */
41 PhotoImport.ITEM_WIDTH = 164 + 8;
42
43 /**
44 * Number of tries in creating a destination directory.
45 */
46 PhotoImport.CREATE_DESTINATION_TRIES = 100;
47
48 /**
49 * Loads app in the document body.
50 * @param {Object=} opt_params Parameters.
51 */
52 PhotoImport.load = function(opt_params) {
53 ImageUtil.metrics = metrics;
54
55 var hash = location.hash ? location.hash.substr(1) : '';
56 var query = location.search ? location.search.substr(1) : '';
57 var params = opt_params || {};
58 if (!params.source) params.source = hash;
59 if (!params.parentWindowId && query) params.parentWindowId = query;
60 if (!params.metadataCache) params.metadataCache = MetadataCache.createFull();
61
62 var api = chrome.fileBrowserPrivate || window.top.chrome.fileBrowserPrivate;
63 api.getStrings(function(strings) {
64 loadTimeData.data = strings;
65 var dom = document.querySelector('.photo-import');
66
67 var volumeManager = new VolumeManagerWrapper(
68 VolumeManagerWrapper.DriveEnabledStatus.DRIVE_ENABLED);
69 volumeManager.ensureInitialized(function() {
70 new PhotoImport(dom, volumeManager, params);
71 });
72 });
73 };
74
75 /**
76 * One-time initialization of dom elements.
77 * @private
78 */
79 PhotoImport.prototype.initDom_ = function() {
80 this.dom_.setAttribute('loading', '');
81 this.dom_.ownerDocument.defaultView.addEventListener(
82 'resize', this.onResize_.bind(this));
83
84 this.spinner_ = this.dom_.querySelector('.spinner');
85
86 this.document_.querySelector('title').textContent =
87 loadTimeData.getString('PHOTO_IMPORT_TITLE');
88 this.dom_.querySelector('.caption').textContent =
89 loadTimeData.getString('PHOTO_IMPORT_CAPTION');
90 this.selectAllNone_ = this.dom_.querySelector('.select');
91 this.selectAllNone_.addEventListener('click',
92 this.onSelectAllNone_.bind(this));
93
94 this.dom_.querySelector('label[for=delete-after-checkbox]').textContent =
95 loadTimeData.getString('PHOTO_IMPORT_DELETE_AFTER');
96 this.selectedCount_ = this.dom_.querySelector('.selected-count');
97
98 this.importButton_ = this.dom_.querySelector('button.import');
99 this.importButton_.textContent =
100 loadTimeData.getString('PHOTO_IMPORT_IMPORT_BUTTON');
101 this.importButton_.addEventListener('click', this.onImportClick_.bind(this));
102
103 this.cancelButton_ = this.dom_.querySelector('button.cancel');
104 this.cancelButton_.textContent = str('CANCEL_LABEL');
105 this.cancelButton_.addEventListener('click', this.onCancelClick_.bind(this));
106
107 this.grid_ = this.dom_.querySelector('grid');
108 cr.ui.Grid.decorate(this.grid_);
109 this.grid_.itemConstructor = GridItem.bind(null, this);
110 this.fileList_ = new cr.ui.ArrayDataModel([]);
111 this.grid_.selectionModel = new cr.ui.ListSelectionModel();
112 this.grid_.dataModel = this.fileList_;
113 this.grid_.selectionModel.addEventListener('change',
114 this.onSelectionChanged_.bind(this));
115 this.onSelectionChanged_();
116
117 this.importingDialog_ = new ImportingDialog(
118 this.dom_, this.fileOperationManager_,
119 this.metadataCache_, this.parentWindowId_);
120
121 var dialogs = cr.ui.dialogs;
122 dialogs.BaseDialog.OK_LABEL = str('OK_LABEL');
123 dialogs.BaseDialog.CANCEL_LABEL = str('CANCEL_LABEL');
124 this.alert_ = new dialogs.AlertDialog(this.dom_);
125 };
126
127 /**
128 * One-time initialization of the My Photos directory.
129 * @private
130 */
131 PhotoImport.prototype.initMyPhotos_ = function() {
132 var driveVolume = this.volumeManager_.getVolumeInfo(RootDirectory.DRIVE);
133 if (!driveVolume || driveVolume.error || !driveVolume.root) {
134 this.onError_(loadTimeData.getString('PHOTO_IMPORT_DRIVE_ERROR'));
135 return;
136 }
137
138 util.getOrCreateDirectory(
139 driveVolume.root,
140 loadTimeData.getString('PHOTO_IMPORT_MY_PHOTOS_DIRECTORY_NAME'),
141 function(entry) {
142 // This may enable the import button, so check that.
143 this.myPhotosDirectory_ = entry;
144 this.onSelectionChanged_();
145 },
146 function(error) {
147 this.onError_(loadTimeData.getString('PHOTO_IMPORT_DRIVE_ERROR'));
148 }.bind(this));
149 };
150
151 /**
152 * Creates the destination directory.
153 * @param {function} onSuccess Callback on success.
154 * @private
155 */
156 PhotoImport.prototype.createDestination_ = function(onSuccess) {
157 var onError = this.onError_.bind(
158 this, loadTimeData.getString('PHOTO_IMPORT_DESTINATION_ERROR'));
159
160 var dateFormatter = Intl.DateTimeFormat(
161 [] /* default locale */,
162 {year: 'numeric', month: 'short', day: 'numeric'});
163
164 var baseName = PathUtil.join(
165 RootDirectory.DRIVE,
166 loadTimeData.getString('PHOTO_IMPORT_MY_PHOTOS_DIRECTORY_NAME'),
167 dateFormatter.format(new Date()));
168
169 var driveVolume = this.volumeManager_.getVolumeInfo(RootDirectory.DRIVE);
170 if (!driveVolume || driveVolume.error || !driveVolume.root) {
171 onError();
172 return;
173 }
174
175 var tryNext = function(number) {
176 if (number > PhotoImport.CREATE_DESTINATION_TRIES) {
177 console.error('Too many directories with the same base name exist.');
178 onError();
179 return;
180 }
181
182 var directoryName = baseName;
183 if (number > 1)
184 directoryName += ' (' + (tryNumber) + ')';
185 driveVolume.root.getDirectory(
186 directoryName,
187 {create: true, exclusive: true},
188 function(entry) {
189 this.destination_ = entry;
190 onSuccess();
191 }.bind(this),
192 function(error) {
193 if (error.code === FileError.PATH_EXISTS_ERR) {
194 // If there already exists an entry, retry with incrementing the
195 // number.
196 tryNext(number + 1);
197 return;
198 }
199 onError();
200 }.bind(this));
201 }.bind(this);
202
203 tryNext(1);
204 };
205
206
207 /**
208 * Load the source contents.
209 * @param {string} source Path to source.
210 * @private
211 */
212 PhotoImport.prototype.loadSource_ = function(source) {
213 var onError = this.onError_.bind(
214 this, loadTimeData.getString('PHOTO_IMPORT_SOURCE_ERROR'));
215
216 var result = [];
217 this.volumeManager_.resolvePath(
218 source,
219 function(sourceEntry) {
220 util.traverseTree(
221 entry,
222 function(entry) {
223 if (!FileType.isVisible(entry))
224 return false;
225 if (FileType.isImageOrVideo(entry))
226 result.push(entry);
227 return true;
228 },
229 function() {
230 this.dom_.removeAttribute('loading');
231 this.mediaFilesList_ = result;
232 this.fillGrid_();
233 }.bind(this),
234 onError);
235 }.bind(this),
236 onError);
237 };
238
239 /**
240 * Renders files into grid.
241 * @private
242 */
243 PhotoImport.prototype.fillGrid_ = function() {
244 if (!this.mediaFilesList_) return;
245 this.fileList_.splice(0, this.fileList_.length);
246 this.fileList_.push.apply(this.fileList_, this.mediaFilesList_);
247 };
248
249 /**
250 * Creates groups for files based on modification date.
251 * @param {Array.<Entry>} files File list.
252 * @param {Object} filesystem Filesystem metadata.
253 * @return {Array.<Object>} List of grouped items.
254 * @private
255 */
256 PhotoImport.prototype.createGroups_ = function(files, filesystem) {
257 var dateFormatter = Intl.DateTimeFormat(
258 [] /* default locale */,
259 {year: 'numeric', month: 'short', day: 'numeric'});
260
261 var columns = this.grid_.columns;
262
263 var unknownGroup = {
264 type: 'group',
265 date: 0,
266 title: loadTimeData.getString('PHOTO_IMPORT_UNKNOWN_DATE'),
267 items: []
268 };
269
270 var groupsMap = {};
271
272 for (var index = 0; index < files.length; index++) {
273 var props = filesystem[index];
274 var item = { type: 'entry', entry: files[index] };
275
276 if (!props || !props.modificationTime) {
277 item.group = unknownGroup;
278 unknownGroup.items.push(item);
279 continue;
280 }
281
282 var date = new Date(props.modificationTime);
283 date.setHours(0);
284 date.setMinutes(0);
285 date.setSeconds(0);
286 date.setMilliseconds(0);
287
288 var time = date.getTime();
289 if (!(time in groupsMap)) {
290 groupsMap[time] = {
291 type: 'group',
292 date: date,
293 title: dateFormatter.format(date),
294 items: []
295 };
296 }
297
298 var group = groupsMap[time];
299 group.items.push(item);
300 item.group = group;
301 }
302
303 var groups = [];
304 for (var time in groupsMap) {
305 if (groupsMap.hasOwnProperty(time)) {
306 groups.push(groupsMap[time]);
307 }
308 }
309 if (unknownGroup.items.length > 0)
310 groups.push(unknownGroup);
311
312 groups.sort(function(a, b) {
313 return b.date.getTime() - a.date.getTime();
314 });
315
316 var list = [];
317 for (var index = 0; index < groups.length; index++) {
318 var group = groups[index];
319
320 list.push(group);
321 for (var t = 1; t < columns; t++) {
322 list.push({ type: 'empty' });
323 }
324
325 for (var j = 0; j < group.items.length; j++) {
326 list.push(group.items[j]);
327 }
328
329 var count = group.items.length;
330 while (count % columns != 0) {
331 list.push({ type: 'empty' });
332 count++;
333 }
334 }
335
336 return list;
337 };
338
339 /**
340 * Decorates grid item.
341 * @param {HTMLLIElement} li The list item.
342 * @param {FileEntry} entry The file entry.
343 * @private
344 */
345 PhotoImport.prototype.decorateGridItem_ = function(li, entry) {
346 li.className = 'grid-item';
347 li.entry = entry;
348
349 var frame = this.document_.createElement('div');
350 frame.className = 'grid-frame';
351 li.appendChild(frame);
352
353 var box = this.document_.createElement('div');
354 box.className = 'img-container';
355 this.metadataCache_.get(entry, 'thumbnail|filesystem',
356 function(metadata) {
357 new ThumbnailLoader(entry.toURL(),
358 ThumbnailLoader.LoaderType.IMAGE,
359 metadata).
360 load(box, ThumbnailLoader.FillMode.FIT,
361 ThumbnailLoader.OptimizationMode.DISCARD_DETACHED);
362 });
363 frame.appendChild(box);
364
365 var check = this.document_.createElement('div');
366 check.className = 'check';
367 li.appendChild(check);
368 };
369
370 /**
371 * Handles the 'pick all/none' action.
372 * @private
373 */
374 PhotoImport.prototype.onSelectAllNone_ = function() {
375 var sm = this.grid_.selectionModel;
376 if (sm.selectedIndexes.length == this.fileList_.length) {
377 sm.unselectAll();
378 } else {
379 sm.selectAll();
380 }
381 };
382
383 /**
384 * Show error message.
385 * @param {string} message Error message.
386 * @private
387 */
388 PhotoImport.prototype.onError_ = function(message) {
389 this.importingDialog_.hide(function() {
390 this.alert_.show(message,
391 function() {
392 window.close();
393 });
394 }.bind(this));
395 };
396
397 /**
398 * Resize event handler.
399 * @private
400 */
401 PhotoImport.prototype.onResize_ = function() {
402 var g = this.grid_;
403 g.startBatchUpdates();
404 setTimeout(function() {
405 g.columns = 0;
406 g.redraw();
407 g.endBatchUpdates();
408 }, 0);
409 };
410
411 /**
412 * @return {Array.<Object>} The list of selected entries.
413 * @private
414 */
415 PhotoImport.prototype.getSelectedItems_ = function() {
416 var indexes = this.grid_.selectionModel.selectedIndexes;
417 var list = [];
418 for (var i = 0; i < indexes.length; i++) {
419 list.push(this.fileList_.item(indexes[i]));
420 }
421 return list;
422 };
423
424 /**
425 * Event handler for picked items change.
426 * @private
427 */
428 PhotoImport.prototype.onSelectionChanged_ = function() {
429 var count = this.grid_.selectionModel.selectedIndexes.length;
430 this.selectedCount_.textContent = count == 0 ? '' :
431 count == 1 ? loadTimeData.getString('PHOTO_IMPORT_ONE_SELECTED') :
432 loadTimeData.getStringF('PHOTO_IMPORT_MANY_SELECTED', count);
433 this.importButton_.disabled = count == 0 || this.myPhotosDirectory_ == null;
434 this.selectAllNone_.textContent = loadTimeData.getString(
435 count == this.fileList_.length && count > 0 ?
436 'PHOTO_IMPORT_SELECT_NONE' : 'PHOTO_IMPORT_SELECT_ALL');
437 };
438
439 /**
440 * Event handler for import button click.
441 * @param {Event} event The event.
442 * @private
443 */
444 PhotoImport.prototype.onImportClick_ = function(event) {
445 var entries = this.getSelectedItems_();
446 var move = this.dom_.querySelector('#delete-after-checkbox').checked;
447 this.importingDialog_.show(entries, move);
448
449 this.createDestination_(function() {
450 var percentage = Math.round(entries.length / this.fileList_.length * 100);
451 metrics.recordMediumCount('PhotoImport.ImportCount', entries.length);
452 metrics.recordSmallCount('PhotoImport.ImportPercentage', percentage);
453
454 this.importingDialog_.start(this.destination_);
455 }.bind(this));
456 };
457
458 /**
459 * Click event handler for the cancel button.
460 * @param {Event} event The event.
461 * @private
462 */
463 PhotoImport.prototype.onCancelClick_ = function(event) {
464 window.close();
465 };
466
467 /**
468 * Item in the grid.
469 * @param {PhotoImport} app Application instance.
470 * @param {Entry} entry File entry.
471 * @constructor
472 */
473 function GridItem(app, entry) {
474 var li = app.document_.createElement('li');
475 li.__proto__ = GridItem.prototype;
476 app.decorateGridItem_(li, entry);
477 return li;
478 }
479
480 GridItem.prototype = {
481 __proto__: cr.ui.ListItem.prototype,
482 get label() {},
483 set label(value) {}
484 };
485
486 /**
487 * Creates a selection controller that is to be used with grid.
488 * @param {cr.ui.ListSelectionModel} selectionModel The selection model to
489 * interact with.
490 * @param {cr.ui.Grid} grid The grid to interact with.
491 * @constructor
492 * @extends {!cr.ui.ListSelectionController}
493 */
494 function GridSelectionController(selectionModel, grid) {
495 this.selectionModel_ = selectionModel;
496 this.grid_ = grid;
497 }
498
499 /**
500 * Extends cr.ui.ListSelectionController.
501 */
502 GridSelectionController.prototype.__proto__ =
503 cr.ui.ListSelectionController.prototype;
504
505 /** @override */
506 GridSelectionController.prototype.getIndexBelow = function(index) {
507 if (index == this.getLastIndex()) {
508 return -1;
509 }
510
511 var dm = this.grid_.dataModel;
512 var columns = this.grid_.columns;
513 var min = (Math.floor(index / columns) + 1) * columns;
514
515 for (var row = 1; true; row++) {
516 var end = index + columns * row;
517 var start = Math.max(min, index + columns * (row - 1));
518 if (start > dm.length) break;
519
520 for (var i = end; i > start; i--) {
521 if (i < dm.length && dm.item(i).type == 'entry')
522 return i;
523 }
524 }
525
526 return this.getLastIndex();
527 };
528
529 /** @override */
530 GridSelectionController.prototype.getIndexAbove = function(index) {
531 if (index == this.getFirstIndex()) {
532 return -1;
533 }
534
535 var dm = this.grid_.dataModel;
536 index -= this.grid_.columns;
537 while (index >= 0 && dm.item(index).type != 'entry') {
538 index--;
539 }
540
541 return index < 0 ? this.getFirstIndex() : index;
542 };
543
544 /** @override */
545 GridSelectionController.prototype.getIndexBefore = function(index) {
546 var dm = this.grid_.dataModel;
547 index--;
548 while (index >= 0 && dm.item(index).type != 'entry') {
549 index--;
550 }
551 return index;
552 };
553
554 /** @override */
555 GridSelectionController.prototype.getIndexAfter = function(index) {
556 var dm = this.grid_.dataModel;
557 index++;
558 while (index < dm.length && dm.item(index).type != 'entry') {
559 index++;
560 }
561 return index == dm.length ? -1 : index;
562 };
563
564 /** @override */
565 GridSelectionController.prototype.getFirstIndex = function() {
566 var dm = this.grid_.dataModel;
567 for (var index = 0; index < dm.length; index++) {
568 if (dm.item(index).type == 'entry')
569 return index;
570 }
571 return -1;
572 };
573
574 /** @override */
575 GridSelectionController.prototype.getLastIndex = function() {
576 var dm = this.grid_.dataModel;
577 for (var index = dm.length - 1; index >= 0; index--) {
578 if (dm.item(index).type == 'entry')
579 return index;
580 }
581 return -1;
582 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698