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

Side by Side Diff: chrome/browser/resources/gallery/js/gallery.js

Issue 246543002: Add script files of the separated Gallery.app. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Apply recent changes for the Gallery to the separated one. 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) 2014 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 * Called from the main frame when unloading.
9 * @param {boolean=} opt_exiting True if the app is exiting.
10 */
11 function unload(opt_exiting) { Gallery.instance.onUnload(opt_exiting); }
12
13 /**
14 * Overrided metadata worker's path.
15 * @type {string}
16 * @const
17 */
18 ContentProvider.WORKER_SCRIPT = '/js/metadata_worker.js';
19
20 /**
21 * Gallery for viewing and editing image files.
22 *
23 * @param {VolumeManager} volumeManager The VolumeManager instance of the
yoshiki 2014/04/23 07:08:39 non-nullable
hirono 2014/04/23 08:13:15 Done.
24 * system.
yoshiki 2014/04/23 07:08:39 4 space indent.
hirono 2014/04/23 08:13:15 Done.
25 * @class
26 * @constructor
27 */
28 function Gallery(volumeManager) {
29 this.context_ = {
30 appWindow: chrome.app.window.current(),
yoshiki 2014/04/23 07:08:39 4 space indent.
hirono 2014/04/23 08:13:15 2 space looks OK according to the style guide. htt
31 onBack: function() {},
32 onClose: function() {},
33 onMaximize: function() {},
34 onMinimize: function() {},
35 onAppRegionChanged: function() {},
36 metadataCache: MetadataCache.createFull(volumeManager),
37 shareActions: [],
38 readonlyDirName: '',
39 saveDirEntry: null,
40 displayStringFunction: function() { return ''; },
41 loadTimeData: {}
42 };
43 this.container_ = document.querySelector('.gallery');
44 this.document_ = document;
45 this.metadataCache_ = this.context_.metadataCache;
46 this.volumeManager_ = volumeManager;
47 this.selectedEntry_ = null;
48 this.metadataCacheObserverId_ = null;
49 this.onExternallyUnmountedBound_ = this.onExternallyUnmounted_.bind(this);
50
51 this.dataModel_ = new cr.ui.ArrayDataModel([]);
52 this.selectionModel_ = new cr.ui.ListSelectionModel();
53
54 this.initDom_();
55 this.initListeners_();
56 }
57
58 /**
59 * Gallery extends cr.EventTarget.
60 */
61 Gallery.prototype.__proto__ = cr.EventTarget.prototype;
62
63 /**
64 * Creates and initializes a Gallery object based on a context.
65 *
66 * @param {Object} context Gallery context.
yoshiki 2014/04/23 07:08:39 non-nullable
hirono 2014/04/23 08:13:15 Removed the method itself.
67 * @param {VolumeManagerWrapper} volumeManager VolumeManager of the system.
68 * @param {Array.<Entry>} entries Array of entries.
69 * @param {Array.<Entry>} selectedEntries Array of selected entries.
70 */
71 Gallery.open = function(context, volumeManager, entries, selectedEntries) {
72 Gallery.instance = new Gallery(context, volumeManager);
73 Gallery.instance.load(entries, selectedEntries);
74 };
75
76 /**
77 * Tools fade-out timeout im milliseconds.
78 * @const
79 * @type {number}
80 */
81 Gallery.FADE_TIMEOUT = 3000;
82
83 /**
84 * First time tools fade-out timeout im milliseconds.
85 * @const
86 * @type {number}
87 */
88 Gallery.FIRST_FADE_TIMEOUT = 1000;
89
90 /**
91 * Time until mosaic is initialized in the background. Used to make gallery
92 * in the slide mode load faster. In miiliseconds.
93 * @const
94 * @type {number}
95 */
96 Gallery.MOSAIC_BACKGROUND_INIT_DELAY = 1000;
97
98 /**
99 * Types of metadata Gallery uses (to query the metadata cache).
100 * @const
101 * @type {string}
102 */
103 Gallery.METADATA_TYPE = 'thumbnail|filesystem|media|streaming|drive';
104
105 /**
106 * Initializes listeners.
107 * @private
108 */
109 Gallery.prototype.initListeners_ = function() {
110 this.keyDownBound_ = this.onKeyDown_.bind(this);
111 this.document_.body.addEventListener('keydown', this.keyDownBound_);
112
113 this.inactivityWatcher_ = new MouseInactivityWatcher(
114 this.container_, Gallery.FADE_TIMEOUT, this.hasActiveTool.bind(this));
115
116 // Search results may contain files from different subdirectories so
117 // the observer is not going to work.
118 if (!this.context_.searchResults && this.context_.curDirEntry) {
119 this.metadataCacheObserverId_ = this.metadataCache_.addObserver(
120 this.context_.curDirEntry,
121 MetadataCache.CHILDREN,
122 'thumbnail',
123 this.updateThumbnails_.bind(this));
124 }
125 this.volumeManager_.addEventListener(
126 'externally-unmounted', this.onExternallyUnmountedBound_);
127 };
128
129 /**
130 * Closes gallery when a volume containing the selected item is unmounted.
131 * @param {Event} event The unmount event.
yoshiki 2014/04/23 07:08:39 non-nullable
hirono 2014/04/23 08:13:15 Done.
132 * @private
133 */
134 Gallery.prototype.onExternallyUnmounted_ = function(event) {
135 if (!this.selectedEntry_)
136 return;
137
138 if (this.volumeManager_.getVolumeInfo(this.selectedEntry_) ===
139 event.volumeInfo) {
140 this.onBack_();
141 }
142 };
143
144 /**
145 * Unloads the Gallery.
146 * @param {boolean} exiting True if the app is exiting.
147 */
148 Gallery.prototype.onUnload = function(exiting) {
149 if (this.metadataCacheObserverId_ !== null)
150 this.metadataCache_.removeObserver(this.metadataCacheObserverId_);
151 this.volumeManager_.removeEventListener(
152 'externally-unmounted', this.onExternallyUnmountedBound_);
153 this.slideMode_.onUnload(exiting);
154 };
155
156 /**
157 * Initializes DOM UI
158 * @private
159 */
160 Gallery.prototype.initDom_ = function() {
161 // Initialize the dialog label.
162 cr.ui.dialogs.BaseDialog.OK_LABEL = str('GALLERY_OK_LABEL');
163 cr.ui.dialogs.BaseDialog.CANCEL_LABEL = str('GALLERY_CANCEL_LABEL');
164
165 var content = util.createChild(this.container_, 'content');
166 content.addEventListener('click', this.onContentClick_.bind(this));
167
168 this.header_ = util.createChild(this.container_, 'header tool dimmable');
169 this.toolbar_ = util.createChild(this.container_, 'toolbar tool dimmable');
170
171 var backButton = util.createChild(this.container_,
172 'back-button tool dimmable');
173 util.createChild(backButton);
174 backButton.addEventListener('click', this.onBack_.bind(this));
175
176 var preventDefault = function(event) { event.preventDefault(); };
177
178 var minimizeButton = util.createChild(this.header_,
179 'minimize-button tool dimmable',
180 'button');
181 minimizeButton.tabIndex = -1;
182 minimizeButton.addEventListener('click', this.onMinimize_.bind(this));
183 minimizeButton.addEventListener('mousedown', preventDefault);
184
185 var maximizeButton = util.createChild(this.header_,
186 'maximize-button tool dimmable',
187 'button');
188 maximizeButton.tabIndex = -1;
189 maximizeButton.addEventListener('click', this.onMaximize_.bind(this));
190 maximizeButton.addEventListener('mousedown', preventDefault);
191
192 var closeButton = util.createChild(this.header_,
193 'close-button tool dimmable',
194 'button');
195 closeButton.tabIndex = -1;
196 closeButton.addEventListener('click', this.onClose_.bind(this));
197 closeButton.addEventListener('mousedown', preventDefault);
198
199 this.filenameSpacer_ = util.createChild(this.toolbar_, 'filename-spacer');
200 this.filenameEdit_ = util.createChild(this.filenameSpacer_,
201 'namebox', 'input');
202
203 this.filenameEdit_.setAttribute('type', 'text');
204 this.filenameEdit_.addEventListener('blur',
205 this.onFilenameEditBlur_.bind(this));
206
207 this.filenameEdit_.addEventListener('focus',
208 this.onFilenameFocus_.bind(this));
209
210 this.filenameEdit_.addEventListener('keydown',
211 this.onFilenameEditKeydown_.bind(this));
212
213 util.createChild(this.toolbar_, 'button-spacer');
214
215 this.prompt_ = new ImageEditor.Prompt(this.container_, str);
216
217 this.modeButton_ = util.createChild(this.toolbar_, 'button mode', 'button');
218 this.modeButton_.addEventListener('click',
219 this.toggleMode_.bind(this, null));
220
221 this.mosaicMode_ = new MosaicMode(content,
222 this.dataModel_,
223 this.selectionModel_,
224 this.metadataCache_,
225 this.volumeManager_,
226 this.toggleMode_.bind(this, null));
227
228 this.slideMode_ = new SlideMode(this.container_,
229 content,
230 this.toolbar_,
231 this.prompt_,
232 this.dataModel_,
233 this.selectionModel_,
234 this.context_,
235 this.toggleMode_.bind(this),
236 str);
237
238 this.slideMode_.addEventListener('image-displayed', function() {
239 cr.dispatchSimpleEvent(this, 'image-displayed');
240 }.bind(this));
241 this.slideMode_.addEventListener('image-saved', function() {
242 cr.dispatchSimpleEvent(this, 'image-saved');
243 }.bind(this));
244
245 var deleteButton = this.createToolbarButton_('delete', 'GALLERY_DELETE');
246 deleteButton.addEventListener('click', this.delete_.bind(this));
247
248 this.shareButton_ = this.createToolbarButton_('share', 'GALLERY_SHARE');
249 this.shareButton_.setAttribute('disabled', '');
250 this.shareButton_.addEventListener('click', this.toggleShare_.bind(this));
251
252 this.shareMenu_ = util.createChild(this.container_, 'share-menu');
253 this.shareMenu_.hidden = true;
254 util.createChild(this.shareMenu_, 'bubble-point');
255
256 this.dataModel_.addEventListener('splice', this.onSplice_.bind(this));
257 this.dataModel_.addEventListener('content', this.onContentChange_.bind(this));
258
259 this.selectionModel_.addEventListener('change', this.onSelection_.bind(this));
260 this.slideMode_.addEventListener('useraction', this.onUserAction_.bind(this));
261 };
262
263 /**
264 * Creates toolbar button.
265 *
266 * @param {string} className Class to add.
267 * @param {string} title Button title.
268 * @return {HTMLElement} Newly created button.
yoshiki 2014/04/23 07:08:39 non-nullable
hirono 2014/04/23 08:13:15 Done.
269 * @private
270 */
271 Gallery.prototype.createToolbarButton_ = function(className, title) {
272 var button = util.createChild(this.toolbar_, className, 'button');
273 button.title = str(title);
274 return button;
275 };
276
277 /**
278 * Loads the content.
279 *
280 * @param {Array.<Entry>} entries Array of entries.
yoshiki 2014/04/23 07:08:39 non-nullable
hirono 2014/04/23 08:13:15 Done.
281 * @param {Array.<Entry>} selectedEntries Array of selected entries.
282 */
283 Gallery.prototype.load = function(entries, selectedEntries) {
284 var items = [];
285 for (var index = 0; index < entries.length; ++index) {
286 items.push(new Gallery.Item(entries[index]));
287 }
288 this.dataModel_.push.apply(this.dataModel_, items);
289
290 this.selectionModel_.adjustLength(this.dataModel_.length);
291
292 // Comparing Entries by reference is not safe. Therefore we have to use URLs.
293 var entryIndexesByURLs = {};
294 for (var index = 0; index < entries.length; index++) {
295 entryIndexesByURLs[entries[index].toURL()] = index;
296 }
297
298 for (var i = 0; i !== selectedEntries.length; i++) {
299 var selectedIndex = entryIndexesByURLs[selectedEntries[i].toURL()];
300 if (selectedIndex !== undefined)
301 this.selectionModel_.setIndexSelected(selectedIndex, true);
302 else
303 console.error('Cannot select ' + selectedEntries[i]);
304 }
305
306 if (this.selectionModel_.selectedIndexes.length === 0)
307 this.onSelection_();
308
309 var mosaic = this.mosaicMode_ && this.mosaicMode_.getMosaic();
310
311 // Mosaic view should show up if most of the selected files are images.
312 var imagesCount = 0;
313 for (var i = 0; i !== selectedEntries.length; i++) {
314 if (FileType.getMediaType(selectedEntries[i]) === 'image')
315 imagesCount++;
316 }
317 var mostlyImages = imagesCount > (selectedEntries.length / 2.0);
318
319 var forcedMosaic = (this.context_.pageState &&
320 this.context_.pageState.gallery === 'mosaic');
yoshiki 2014/04/23 07:08:39 4 space
hirono 2014/04/23 08:13:15 Done.
321
322 var showMosaic = (mostlyImages && selectedEntries.length > 1) || forcedMosaic;
323 if (mosaic && showMosaic) {
324 this.setCurrentMode_(this.mosaicMode_);
325 mosaic.init();
326 mosaic.show();
327 this.inactivityWatcher_.check(); // Show the toolbar.
328 cr.dispatchSimpleEvent(this, 'loaded');
329 } else {
330 this.setCurrentMode_(this.slideMode_);
331 var maybeLoadMosaic = function() {
332 if (mosaic)
333 mosaic.init();
334 cr.dispatchSimpleEvent(this, 'loaded');
335 }.bind(this);
336 /* TODO: consider nice blow-up animation for the first image */
337 this.slideMode_.enter(null, function() {
338 // Flash the toolbar briefly to show it is there.
339 this.inactivityWatcher_.kick(Gallery.FIRST_FADE_TIMEOUT);
340 }.bind(this),
341 maybeLoadMosaic);
342 }
343 };
344
345 /**
346 * Closes the Gallery and go to Files.app.
347 * @private
348 */
349 Gallery.prototype.back_ = function() {
350 if (util.isFullScreen(this.context_.appWindow)) {
351 util.toggleFullScreen(this.context_.appWindow,
352 false); // Leave the full screen mode.
353 }
354 this.context_.onBack(this.getSelectedEntries());
355 };
356
357 /**
358 * Handles user's 'Back' action (Escape or a click on the X icon).
359 * @private
360 */
361 Gallery.prototype.onBack_ = function() {
362 this.executeWhenReady(this.back_.bind(this));
363 };
364
365 /**
366 * Handles user's 'Close' action.
367 * @private
368 */
369 Gallery.prototype.onClose_ = function() {
370 this.executeWhenReady(this.context_.onClose);
371 };
372
373 /**
374 * Handles user's 'Maximize' action (Escape or a click on the X icon).
375 * @private
376 */
377 Gallery.prototype.onMaximize_ = function() {
378 this.executeWhenReady(this.context_.onMaximize);
379 };
380
381 /**
382 * Handles user's 'Maximize' action (Escape or a click on the X icon).
383 * @private
384 */
385 Gallery.prototype.onMinimize_ = function() {
386 this.executeWhenReady(this.context_.onMinimize);
387 };
388
389 /**
390 * Executes a function when the editor is done with the modifications.
391 * @param {function} callback Function to execute.
392 */
393 Gallery.prototype.executeWhenReady = function(callback) {
394 this.currentMode_.executeWhenReady(callback);
395 };
396
397 /**
398 * @return {Object} File browser private API.
399 */
400 Gallery.getFileBrowserPrivate = function() {
401 return chrome.fileBrowserPrivate || window.top.chrome.fileBrowserPrivate;
402 };
403
404 /**
405 * @return {boolean} True if some tool is currently active.
406 */
407 Gallery.prototype.hasActiveTool = function() {
408 return this.currentMode_.hasActiveTool() ||
409 this.isSharing_() || this.isRenaming_();
410 };
411
412 /**
413 * External user action event handler.
414 * @private
415 */
416 Gallery.prototype.onUserAction_ = function() {
417 this.closeShareMenu_();
418 // Show the toolbar and hide it after the default timeout.
419 this.inactivityWatcher_.kick();
420 };
421
422 /**
423 * Sets the current mode, update the UI.
424 * @param {Object} mode Current mode.
425 * @private
426 */
427 Gallery.prototype.setCurrentMode_ = function(mode) {
428 if (mode !== this.slideMode_ && mode !== this.mosaicMode_)
429 console.error('Invalid Gallery mode');
430
431 this.currentMode_ = mode;
432 this.container_.setAttribute('mode', this.currentMode_.getName());
433 this.updateSelectionAndState_();
434 this.updateButtons_();
435 };
436
437 /**
438 * Mode toggle event handler.
439 * @param {function=} opt_callback Callback.
440 * @param {Event=} opt_event Event that caused this call.
441 * @private
442 */
443 Gallery.prototype.toggleMode_ = function(opt_callback, opt_event) {
444 if (!this.modeButton_)
445 return;
446
447 if (this.changingMode_) // Do not re-enter while changing the mode.
448 return;
449
450 if (opt_event)
451 this.onUserAction_();
452
453 this.changingMode_ = true;
454
455 var onModeChanged = function() {
456 this.changingMode_ = false;
457 if (opt_callback) opt_callback();
458 }.bind(this);
459
460 var tileIndex = Math.max(0, this.selectionModel_.selectedIndex);
461
462 var mosaic = this.mosaicMode_.getMosaic();
463 var tileRect = mosaic.getTileRect(tileIndex);
464
465 if (this.currentMode_ === this.slideMode_) {
466 this.setCurrentMode_(this.mosaicMode_);
467 mosaic.transform(
468 tileRect, this.slideMode_.getSelectedImageRect(), true /* instant */);
469 this.slideMode_.leave(tileRect,
yoshiki 2014/04/23 07:08:39 new line before the first argument.
hirono 2014/04/23 08:13:15 Done.
470 function() {
471 // Animate back to normal position.
472 mosaic.transform();
473 mosaic.show();
474 onModeChanged();
475 }.bind(this));
476 } else {
477 this.setCurrentMode_(this.slideMode_);
478 this.slideMode_.enter(tileRect,
yoshiki 2014/04/23 07:08:39 new line before the first argument.
hirono 2014/04/23 08:13:15 Done.
479 function() {
480 // Animate to zoomed position.
481 mosaic.transform(tileRect, this.slideMode_.getSelectedImageRect());
482 mosaic.hide();
483 }.bind(this),
484 onModeChanged);
485 }
486 };
487
488 /**
489 * Deletes the selected items.
490 * @private
491 */
492 Gallery.prototype.delete_ = function() {
493 this.onUserAction_();
494
495 // Clone the sorted selected indexes array.
496 var indexesToRemove = this.selectionModel_.selectedIndexes.slice();
497 if (!indexesToRemove.length)
498 return;
499
500 /* TODO(dgozman): Implement Undo delete, Remove the confirmation dialog. */
501
502 var itemsToRemove = this.getSelectedItems();
503 var plural = itemsToRemove.length > 1;
504 var param = plural ? itemsToRemove.length : itemsToRemove[0].getFileName();
505
506 function deleteNext() {
507 if (!itemsToRemove.length)
508 return; // All deleted.
509
510 // TODO(hirono): Use fileOperationManager.
511 var entry = itemsToRemove.pop().getEntry();
512 entry.remove(deleteNext, function() {
513 util.flog('Error deleting: ' + entry.name, deleteNext);
514 });
515 }
516
517 // Prevent the Gallery from handling Esc and Enter.
518 this.document_.body.removeEventListener('keydown', this.keyDownBound_);
519 var restoreListener = function() {
520 this.document_.body.addEventListener('keydown', this.keyDownBound_);
521 }.bind(this);
522
523
524 var confirm = new cr.ui.dialogs.ConfirmDialog(this.container_);
525 confirm.show(strf(plural ?
526 'GALLERY_CONFIRM_DELETE_SOME' : 'GALLERY_CONFIRM_DELETE_ONE', param),
527 function() {
528 restoreListener();
529 this.selectionModel_.unselectAll();
530 this.selectionModel_.leadIndex = -1;
531 // Remove items from the data model, starting from the highest index.
532 while (indexesToRemove.length)
533 this.dataModel_.splice(indexesToRemove.pop(), 1);
534 // Delete actual files.
535 deleteNext();
536 }.bind(this),
537 function() {
538 // Restore the listener after a timeout so that ESC is processed.
539 setTimeout(restoreListener, 0);
540 });
541 };
542
543 /**
544 * @return {Array.<Gallery.Item>} Current selection.
545 */
546 Gallery.prototype.getSelectedItems = function() {
547 return this.selectionModel_.selectedIndexes.map(
548 this.dataModel_.item.bind(this.dataModel_));
549 };
550
551 /**
552 * @return {Array.<Entry>} Array of currently selected entries.
553 */
554 Gallery.prototype.getSelectedEntries = function() {
555 return this.selectionModel_.selectedIndexes.map(function(index) {
556 return this.dataModel_.item(index).getEntry();
557 }.bind(this));
558 };
559
560 /**
561 * @return {Gallery.Item} Current single selection.
562 */
563 Gallery.prototype.getSingleSelectedItem = function() {
564 var items = this.getSelectedItems();
565 if (items.length > 1)
566 throw new Error('Unexpected multiple selection');
yoshiki 2014/04/23 07:08:39 I think passing an exception is not good idea. Cou
hirono 2014/04/23 08:13:15 Done.
567 return items[0];
568 };
569
570 /**
571 * Selection change event handler.
572 * @private
573 */
574 Gallery.prototype.onSelection_ = function() {
575 this.updateSelectionAndState_();
576 this.updateShareMenu_();
577 };
578
579 /**
580 * Data model splice event handler.
581 * @private
582 */
583 Gallery.prototype.onSplice_ = function() {
584 this.selectionModel_.adjustLength(this.dataModel_.length);
585 };
586
587 /**
588 * Content change event handler.
589 * @param {Event} event Event.
590 * @private
591 */
592 Gallery.prototype.onContentChange_ = function(event) {
593 var index = this.dataModel_.indexOf(event.item);
594 if (index !== this.selectionModel_.selectedIndex)
595 console.error('Content changed for unselected item');
596 this.updateSelectionAndState_();
597 };
598
599 /**
600 * Keydown handler.
601 *
602 * @param {Event} event Event.
603 * @private
604 */
605 Gallery.prototype.onKeyDown_ = function(event) {
606 var wasSharing = this.isSharing_();
607 this.closeShareMenu_();
608
609 if (this.currentMode_.onKeyDown(event))
610 return;
611
612 switch (util.getKeyModifiers(event) + event.keyIdentifier) {
613 case 'U+0008': // Backspace.
614 // The default handler would call history.back and close the Gallery.
615 event.preventDefault();
616 break;
617
618 case 'U+001B': // Escape
619 // Swallow Esc if it closed the Share menu, otherwise close the Gallery.
620 if (!wasSharing)
621 this.onBack_();
622 break;
623
624 case 'U+004D': // 'm' switches between Slide and Mosaic mode.
625 this.toggleMode_(null, event);
626 break;
627
628 case 'U+0056': // 'v'
629 this.slideMode_.startSlideshow(SlideMode.SLIDESHOW_INTERVAL_FIRST, event);
630 break;
631
632 case 'U+007F': // Delete
633 case 'Shift-U+0033': // Shift+'3' (Delete key might be missing).
634 this.delete_();
635 break;
636 }
637 };
638
639 // Name box and rename support.
640
641 /**
642 * Updates the UI related to the selected item and the persistent state.
643 *
644 * @private
645 */
646 Gallery.prototype.updateSelectionAndState_ = function() {
647 var numSelectedItems = this.selectionModel_.selectedIndexes.length;
648 var displayName = '';
649 var selectedEntryURL = null;
650
651 // If it's selecting something, update the variable values.
652 if (numSelectedItems) {
653 var selectedItem =
654 this.dataModel_.item(this.selectionModel_.selectedIndex);
655 this.selectedEntry_ = selectedItem.getEntry();
656 selectedEntryURL = this.selectedEntry_.toURL();
657
658 if (numSelectedItems === 1) {
659 window.top.document.title = this.selectedEntry_.name;
660 displayName = ImageUtil.getDisplayNameFromName(this.selectedEntry_.name);
661 } else if (this.context_.curDirEntry) {
662 // If the Gallery was opened on search results the search query will not
663 // be recorded in the app state and the relaunch will just open the
664 // gallery in the curDirEntry directory.
665 window.top.document.title = this.context_.curDirEntry.name;
666 displayName = strf('GALLERY_ITEMS_SELECTED', numSelectedItems);
667 }
668 }
669
670 window.top.util.updateAppState(
671 null, // Keep the current directory.
672 selectedEntryURL, // Update the selection.
673 {gallery: (this.currentMode_ === this.mosaicMode_ ? 'mosaic' : 'slide')});
674
675 // We can't rename files in readonly directory.
676 // We can only rename a single file.
677 this.filenameEdit_.disabled = numSelectedItems !== 1 ||
678 this.context_.readonlyDirName;
679 this.filenameEdit_.value = displayName;
680 };
681
682 /**
683 * Click event handler on filename edit box
684 * @private
685 */
686 Gallery.prototype.onFilenameFocus_ = function() {
687 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', true);
688 this.filenameEdit_.originalValue = this.filenameEdit_.value;
689 setTimeout(this.filenameEdit_.select.bind(this.filenameEdit_), 0);
690 this.onUserAction_();
691 };
692
693 /**
694 * Blur event handler on filename edit box.
695 *
696 * @param {Event} event Blur event.
697 * @return {boolean} if default action should be prevented.
698 * @private
699 */
700 Gallery.prototype.onFilenameEditBlur_ = function(event) {
701 if (this.filenameEdit_.value && this.filenameEdit_.value[0] === '.') {
702 this.prompt_.show('GALLERY_FILE_HIDDEN_NAME', 5000);
703 this.filenameEdit_.focus();
704 event.stopPropagation();
705 event.preventDefault();
706 return false;
707 }
708
709 var item = this.getSingleSelectedItem();
710 var oldEntry = item.getEntry();
711
712 var onFileExists = function() {
713 this.prompt_.show('GALLERY_FILE_EXISTS', 3000);
714 this.filenameEdit_.value = name;
715 this.filenameEdit_.focus();
716 }.bind(this);
717
718 var onSuccess = function() {
719 var event = new Event('content');
720 event.item = item;
721 event.oldEntry = oldEntry;
722 event.metadata = null; // Metadata unchanged.
723 this.dataModel_.dispatchEvent(event);
724 }.bind(this);
725
726 if (this.filenameEdit_.value) {
727 this.getSingleSelectedItem().rename(
728 this.filenameEdit_.value, onSuccess, onFileExists);
729 }
730
731 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', false);
732 this.onUserAction_();
733 };
734
735 /**
736 * Keydown event handler on filename edit box
737 * @private
738 */
739 Gallery.prototype.onFilenameEditKeydown_ = function() {
740 switch (event.keyCode) {
741 case 27: // Escape
742 this.filenameEdit_.value = this.filenameEdit_.originalValue;
743 this.filenameEdit_.blur();
744 break;
745
746 case 13: // Enter
747 this.filenameEdit_.blur();
748 break;
749 }
750 event.stopPropagation();
751 };
752
753 /**
754 * @return {boolean} True if file renaming is currently in progress.
755 * @private
756 */
757 Gallery.prototype.isRenaming_ = function() {
758 return this.filenameSpacer_.hasAttribute('renaming');
759 };
760
761 /**
762 * Content area click handler.
763 * @private
764 */
765 Gallery.prototype.onContentClick_ = function() {
766 this.closeShareMenu_();
767 this.filenameEdit_.blur();
768 };
769
770 // Share button support.
771
772 /**
773 * @return {boolean} True if the Share menu is active.
774 * @private
775 */
776 Gallery.prototype.isSharing_ = function() {
777 return !this.shareMenu_.hidden;
778 };
779
780 /**
781 * Close Share menu if it is open.
782 * @private
783 */
784 Gallery.prototype.closeShareMenu_ = function() {
785 if (this.isSharing_())
786 this.toggleShare_();
787 };
788
789 /**
790 * Share button handler.
791 * @private
792 */
793 Gallery.prototype.toggleShare_ = function() {
794 if (!this.shareButton_.hasAttribute('disabled'))
795 this.shareMenu_.hidden = !this.shareMenu_.hidden;
796 this.inactivityWatcher_.check();
797 };
798
799 /**
800 * Updates available actions list based on the currently selected urls.
801 * @private.
802 */
803 Gallery.prototype.updateShareMenu_ = function() {
804 var entries = this.getSelectedEntries();
805
806 function isShareAction(task) {
807 var taskParts = task.taskId.split('|');
808 return taskParts[0] !== chrome.runtime.id;
809 }
810
811 var api = Gallery.getFileBrowserPrivate();
812 var mimeTypes = []; // TODO(kaznacheev) Collect mime types properly.
813
814 var createShareMenu = function(tasks) {
815 var wasHidden = this.shareMenu_.hidden;
816 this.shareMenu_.hidden = true;
817 var items = this.shareMenu_.querySelectorAll('.item');
818 for (var i = 0; i !== items.length; i++) {
819 items[i].parentNode.removeChild(items[i]);
820 }
821
822 for (var t = 0; t !== tasks.length; t++) {
823 var task = tasks[t];
824 if (!isShareAction(task)) continue;
825
826 var item = util.createChild(this.shareMenu_, 'item');
827 item.textContent = task.title;
828 item.style.backgroundImage = 'url(' + task.iconUrl + ')';
829 item.addEventListener('click', function(taskId) {
830 this.toggleShare_(); // Hide the menu.
831 // TODO(hirono): Use entries instead of URLs.
832 this.executeWhenReady(
833 api.executeTask.bind(
834 api,
835 taskId,
836 util.entriesToURLs(entries),
837 function(result) {
838 var alertDialog =
839 new cr.ui.dialogs.AlertDialog(this.container_);
840 util.isTeleported(window).then(function(teleported) {
841 if (teleported)
842 util.showOpenInOtherDesktopAlert(alertDialog, entries);
843 }.bind(this));
844 }.bind(this)));
845 }.bind(this, task.taskId));
846 }
847
848 var empty = this.shareMenu_.querySelector('.item') === null;
849 ImageUtil.setAttribute(this.shareButton_, 'disabled', empty);
850 this.shareMenu_.hidden = wasHidden || empty;
851 }.bind(this);
852
853 // Create or update the share menu with a list of sharing tasks and show
854 // or hide the share button.
855 // TODO(mtomasz): Pass Entries directly, instead of URLs.
856 if (!entries.length)
857 createShareMenu([]); // Empty list of tasks, since there is no selection.
858 else
859 api.getFileTasks(util.entriesToURLs(entries), mimeTypes, createShareMenu);
860 };
861
862 /**
863 * Updates thumbnails.
864 * @private
865 */
866 Gallery.prototype.updateThumbnails_ = function() {
867 if (this.currentMode_ === this.slideMode_)
868 this.slideMode_.updateThumbnails();
869
870 if (this.mosaicMode_) {
871 var mosaic = this.mosaicMode_.getMosaic();
872 if (mosaic.isInitialized())
873 mosaic.reload();
874 }
875 };
876
877 /**
878 * Updates buttons.
879 * @private
880 */
881 Gallery.prototype.updateButtons_ = function() {
882 if (this.modeButton_) {
883 var oppositeMode =
884 this.currentMode_ === this.slideMode_ ? this.mosaicMode_ :
885 this.slideMode_;
886 this.modeButton_.title = str(oppositeMode.getTitle());
887 }
888 };
889
890 window.addEventListener('load', function() {
891 var entries = window.launchData.items.map(
892 function(item) { return item.entry; });
893 trackPromise(window.backgroundComponent.then(
yoshiki 2014/04/23 07:08:39 I think, it's better to handle the reject here exp
hirono 2014/04/23 08:13:15 Done.
894 function(inBackgroundComponent) {
895 window.loadTimeData.data = inBackgroundComponent.stringData;
896 var gallery = new Gallery(inBackgroundComponent.volumeManager);
897 gallery.load(entries, entries);
898 }));
899 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698