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

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

Issue 10834354: Refactor the Photo Editor to enable new feature work (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Addressed comments Created 8 years, 4 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) 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 document.addEventListener('DOMContentLoaded', function() {
6 if (document.location.hash) // File path passed after the #.
7 Gallery.openStandalone(decodeURI(document.location.hash.substr(1)));
8 });
9
10 /**
11 * Gallery for viewing and editing image files.
12 *
13 * @param {Object} context Object containing the following:
14 * {function(string)} onNameChange Called every time a selected
15 * item name changes (on rename and on selection change).
16 * {function} onClose
17 * {MetadataCache} metadataCache
18 * {Array.<Object>} shareActions
19 * {string} readonlyDirName Directory name for readonly warning or null.
20 * {DirEntry} saveDirEntry Directory to save to.
21 * {function(string)} displayStringFunction
22 * @class
23 * @constructor
24 */
25 function Gallery(context) {
26 this.container_ = document.querySelector('.gallery');
27 this.document_ = document;
28 this.context_ = context;
29 this.metadataCache_ = context.metadataCache;
30
31 var strf = context.displayStringFunction;
32 this.displayStringFunction_ = function(id, formatArgs) {
33 var args = Array.prototype.slice.call(arguments);
34 args[0] = 'GALLERY_' + id.toUpperCase();
35 return strf.apply(null, args);
36 };
37
38 this.initListeners_();
39 this.initDom_();
40 }
41
42 /**
43 * Create and initialize a Gallery object based on a context.
44 *
45 * @param {Object} context Gallery context.
46 * @param {Array.<string>} urls Array of image urls.
47 * @param {string} selectedUrl Selected url.
48 */
49 Gallery.open = function(context, urls, selectedUrl) {
50 Gallery.instance = new Gallery(context);
51 Gallery.instance.load(urls, selectedUrl);
52 };
53
54 /**
55 * Create a Gallery object in a tab.
56 * @param {string} path File system path to a selected file.
57 */
58 Gallery.openStandalone = function(path) {
59 ImageUtil.metrics = metrics;
60
61 var currentDir;
62 var urls = [];
63 var selectedUrl;
64
65 Gallery.getFileBrowserPrivate().requestLocalFileSystem(function(filesystem) {
66 // If the path points to the directory scan it.
67 filesystem.root.getDirectory(path, {create: false}, scanDirectory,
68 function() {
69 // Try to scan the parent directory.
70 var pathParts = path.split('/');
71 pathParts.pop();
72 var parentPath = pathParts.join('/');
73 filesystem.root.getDirectory(parentPath, {create: false},
74 scanDirectory, open /* no data, just display an error */);
75 });
76 });
77
78 function scanDirectory(dirEntry) {
79 currentDir = dirEntry;
80 util.forEachDirEntry(currentDir, function(entry) {
81 if (entry == null) {
82 open();
83 } else if (FileType.isImageOrVideo(entry)) {
dgozman 2012/08/17 09:55:00 Did you change file_type.js?
Vladislav Kaznacheev 2012/08/17 10:30:49 Actually it already works. I updated JSDoc to make
84 var url = entry.toURL();
85 urls.push(url);
86 if (entry.fullPath == path)
87 selectedUrl = url;
88 }
89 });
90 }
91
92 function onNameChange(name) {
93 window.top.document.title = name;
94
95 var newPath = currentDir.fullPath + '/' + name;
96 var location = document.location.origin + document.location.pathname +
97 '#' + encodeURI(newPath);
98 history.replaceState(undefined, newPath, location);
99 }
100
101 function onClose() {
102 document.location = 'main.html?' +
103 JSON.stringify({defaultPath: document.location.hash.substr(1)});
104 }
105
106 function open() {
107 Gallery.getFileBrowserPrivate().getStrings(function(strings) {
108 loadTimeData.data = strings;
109 var context = {
110 readonlyDirName: null,
111 saveDirEntry: currentDir,
112 metadataCache: MetadataCache.createFull(),
113 onNameChange: onNameChange,
114 onClose: onClose,
115 displayStringFunction: strf
116 };
117 Gallery.open(context, urls, selectedUrl || urls[0]);
118 });
119 }
120 };
121
122 /**
123 * Tools fade-out timeout im milliseconds.
124 * @type {Number}
125 */
126 Gallery.FADE_TIMEOUT = 3000;
127
128 /**
129 * First time tools fade-out timeout im milliseconds.
130 * @type {Number}
131 */
132 Gallery.FIRST_FADE_TIMEOUT = 1000;
133
134 /**
135 * Types of metadata Gallery uses (to query the metadata cache).
136 */
137 Gallery.METADATA_TYPE = 'thumbnail|filesystem|media|streaming';
138
139 /**
140 * Initialize listeners.
141 * @private
142 */
143
144 Gallery.prototype.initListeners_ = function() {
145 this.document_.oncontextmenu = function(e) { e.preventDefault(); };
146
147 this.document_.body.addEventListener('keydown', this.onKeyDown_.bind(this));
148
149 this.inactivityWatcher_ = new MouseInactivityWatcher(
150 this.container_, Gallery.FADE_TIMEOUT, this.hasActiveTool.bind(this));
151
152 // Show tools when the user touches the screen.
153 this.document_.body.addEventListener('touchstart',
154 this.inactivityWatcher_.startActivity.bind(this.inactivityWatcher_));
155 var initiateFading =
156 this.inactivityWatcher_.stopActivity.bind(this.inactivityWatcher_,
157 Gallery.FADE_TIMEOUT);
158 this.document_.body.addEventListener('touchend', initiateFading);
159 this.document_.body.addEventListener('touchcancel', initiateFading);
160 };
161
162 /**
163 * Initializes DOM UI
164 * @private
165 */
166 Gallery.prototype.initDom_ = function() {
167 var content = util.createChild(this.container_, 'content');
168 content.addEventListener('click', this.onContentClick_.bind(this));
169
170 var closeButton = util.createChild(this.container_, 'close tool dimmable');
171 util.createChild(closeButton);
172 closeButton.addEventListener('click', this.onClose_.bind(this));
173
174 this.toolbar_ = util.createChild(this.container_, 'toolbar tool dimmable');
175
176 this.filenameSpacer_ = util.createChild(this.toolbar_, 'filename-spacer');
177
178 var nameBox = util.createChild(this.filenameSpacer_, 'namebox');
179
180 this.filenameText_ = util.createChild(nameBox);
181 this.filenameText_.addEventListener('click',
182 this.onFilenameClick_.bind(this));
183
184 this.filenameEdit_ = this.document_.createElement('input');
185 this.filenameEdit_.setAttribute('type', 'text');
186 this.filenameEdit_.addEventListener('blur',
187 this.onFilenameEditBlur_.bind(this));
188 this.filenameEdit_.addEventListener('keydown',
189 this.onFilenameEditKeydown_.bind(this));
190 nameBox.appendChild(this.filenameEdit_);
191
192 util.createChild(this.toolbar_, 'button-spacer');
193
194 this.prompt_ = new ImageEditor.Prompt(
195 this.container_, this.displayStringFunction_);
196
197 this.slideMode_ = new SlideMode(this.container_, this.toolbar_, this.prompt_,
198 this.context_, this.displayStringFunction_);
199 this.slideMode_.addEventListener('edit', this.onEdit_.bind(this));
200 this.slideMode_.addEventListener('selection', this.onSelection_.bind(this));
201
202 this.shareMode_ = new ShareMode(
203 this.slideMode_.editor_, this.container_, this.toolbar_,
204 this.onShare_.bind(this), this.executeWhenReady.bind(this),
205 this.displayStringFunction_);
206
207 Gallery.getFileBrowserPrivate().isFullscreen(function(fullscreen) {
208 this.originalFullscreen_ = fullscreen;
209 }.bind(this));
210 };
211
212 /**
213 * Load the content.
214 *
215 * @param {Array.<string>} urls Array of urls.
216 * @param {string} selectedUrl Selected url.
217 */
218 Gallery.prototype.load = function(urls, selectedUrl) {
219 this.items_ = [];
220 for (var index = 0; index < urls.length; ++index) {
221 this.items_.push(new Gallery.Item(urls[index]));
222 }
223
224 var selectedIndex = urls.indexOf(selectedUrl);
225 this.slideMode_.load(this.items_, selectedIndex, function() {
226 // Flash the toolbar briefly to show it is there.
227 this.inactivityWatcher_.startActivity();
228 this.inactivityWatcher_.stopActivity(Gallery.FIRST_FADE_TIMEOUT);
229 }.bind(this));
230 };
231
232 /**
233 * Close the Gallery.
234 * @private
235 */
236 Gallery.prototype.close_ = function() {
237 Gallery.getFileBrowserPrivate().isFullscreen(function(fullscreen) {
238 if (this.originalFullscreen_ != fullscreen) {
239 Gallery.toggleFullscreen();
240 }
241 this.context_.onClose();
242 }.bind(this));
243 };
244
245 /**
246 * Handle user's 'Close' action (Escape or a click on the X icon).
247 * @private
248 */
249 Gallery.prototype.onClose_ = function() {
250 this.executeWhenReady(this.close_.bind(this));
251 };
252
253 /**
254 * Execute a function when the editor is done with the modifications.
255 * @param {function} callback Function to execute.
256 */
257 Gallery.prototype.executeWhenReady = function(callback) {
258 //TODO(kaznacheev): Execute directly when in grid mode.
259 this.slideMode_.editor_.executeWhenReady(callback);
260 };
261
262 /**
263 * @return {Object} File browser private API.
264 */
265 Gallery.getFileBrowserPrivate = function() {
266 return chrome.fileBrowserPrivate || window.top.chrome.fileBrowserPrivate;
267 };
268
269 /**
270 * Switches gallery to fullscreen mode and back.
271 */
272 Gallery.toggleFullscreen = function() {
273 Gallery.getFileBrowserPrivate().toggleFullscreen();
274 };
275
276 /**
277 * @return {boolean} True if some tool is currently active.
278 */
279 Gallery.prototype.hasActiveTool = function() {
280 return this.slideMode_.isEditing() || this.isSharing_() || this.isRenaming_();
281 };
282
283 /**
284 * Check if the tools are active and notify the inactivity watcher.
285 * @private
286 */
287 Gallery.prototype.checkActivity_ = function() {
288 if (this.hasActiveTool())
289 this.inactivityWatcher_.startActivity();
290 else
291 this.inactivityWatcher_.stopActivity();
292 };
293
294 /**
295 * Edit toggle event handler.
296 * @private
297 */
298 Gallery.prototype.onEdit_ = function() {
299 // The user has just clicked on the Edit button. Dismiss the Share menu.
300 if (this.isSharing_())
301 this.onShare_();
302 this.checkActivity_();
303 };
304
305 /**
306 * @return {Array.<Gallery.Item>} Current selection.
307 */
308 Gallery.prototype.getSelectedItems = function() {
309 // TODO(kaznacheev) support multiple selection grid/mosaic mode.
310 return [this.slideMode_.getSelectedItem()];
311 };
312
313 /**
314 * @return {Gallery.Item} Current single selection.
315 */
316 Gallery.prototype.getSingleSelectedItem = function() {
317 var items = this.getSelectedItems();
318 if (items.length > 1)
319 throw new Error('Unexpected multiple selection');
320 return items[0];
321 };
322
323 /**
324 * Selection change event handler.
325 * @private
326 */
327 Gallery.prototype.onSelection_ = function() {
328 this.updateFilename_();
329 this.shareMode_.updateMenu(
330 this.getSelectedItems().map(function(item) { return item.getUrl() }));
331 };
332
333 /**
334 * Keydown handler.
335 * @param {Event} event Event.
336 * @private
337 */
338 Gallery.prototype.onKeyDown_ = function(event) {
339 if (this.slideMode_.onKeyDown(event))
340 return;
341
342 switch (util.getKeyModifiers(event) + event.keyIdentifier) {
343 case 'U+0008': // Backspace.
344 // The default handler would call history.back and close the Gallery.
345 event.preventDefault();
346 break;
347
348 case 'U+001B': // Escape
349 if (this.isSharing_())
350 this.onShare_();
351 else
352 this.onClose_();
353 break;
354 }
355 };
356
357 // Name box and rename support.
358
359 /**
360 * Update the displayed current item file name.
361 *
362 * @private
363 */
364 Gallery.prototype.updateFilename_ = function() {
365 var fullName = this.getSingleSelectedItem().getFileName();
366
367 this.context_.onNameChange(fullName);
368
369 var displayName = ImageUtil.getFileNameFromFullName(fullName);
370 this.filenameEdit_.value = displayName;
371 this.filenameText_.textContent = displayName;
372 };
373
374 /**
375 * Click event handler on filename edit box
376 * @private
377 */
378 Gallery.prototype.onFilenameClick_ = function() {
379 // We can't rename files in readonly directory.
380 if (this.context_.readonlyDirName)
381 return;
382
383 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', true);
384 setTimeout(this.filenameEdit_.select.bind(this.filenameEdit_), 0);
385 this.inactivityWatcher_.startActivity();
386 };
387
388 /**
389 * Blur event handler on filename edit box
390 * @private
391 */
392 Gallery.prototype.onFilenameEditBlur_ = function() {
393 if (this.filenameEdit_.value && this.filenameEdit_.value[0] == '.') {
394 this.prompt_.show('file_hidden_name', 5000);
395 this.filenameEdit_.focus();
396 return;
397 }
398
399 var item = this.getSingleSelectedItem();
400 var oldUrl = item.getUrl();
401
402 var onFileExists = function() {
403 this.prompt_.show('file_exists', 3000);
404 this.filenameEdit_.value = name;
405 this.onFilenameClick_();
406 }.bind(this);
407
408 var onSuccess = function() {
409 this.slideMode_.updateSelectedUrl_(oldUrl, item.getUrl());
410 }.bind(this);
411
412 if (this.filenameEdit_.value) {
413 this.getSingleSelectedItem().rename(this.context_.saveDirEntry,
414 this.filenameEdit_.value, onSuccess, onFileExists);
415 }
416
417 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', false);
418 this.checkActivity_();
419 };
420
421 /**
422 * Keydown event handler on filename edit box
423 * @private
424 */
425 Gallery.prototype.onFilenameEditKeydown_ = function() {
426 switch (event.keyCode) {
427 case 27: // Escape
428 this.updateFilename_();
429 this.filenameEdit_.blur();
430 break;
431
432 case 13: // Enter
433 this.filenameEdit_.blur();
434 break;
435 }
436 event.stopPropagation();
437 };
438
439 /**
440 * @return {Boolean} True if file renaming is currently in progress
441 * @private
442 */
443 Gallery.prototype.isRenaming_ = function() {
444 return this.filenameSpacer_.hasAttribute('renaming');
445 };
446
447 /**
448 * Content area click handler.
449 * @private
450 */
451 Gallery.prototype.onContentClick_ = function() {
452 this.filenameEdit_.blur();
453 };
454
455 // Share button support.
456
457 /**
458 * @return {boolean} True if the Share mode is active.
459 * @private
460 */
461 Gallery.prototype.isSharing_ = function() {
462 return this.shareMode_.isActive();
463 };
464
465 /**
466 * Share button handler.
467 * @param {Event} event Event.
468 * @private
469 */
470 Gallery.prototype.onShare_ = function(event) {
471 this.shareMode_.toggle(event);
472 this.checkActivity_();
473 };
474
475 /**
476 *
477 * @param {ImageEditor} editor Editor.
478 * @param {Element} container Container element.
479 * @param {Element} toolbar Toolbar element.
480 * @param {function} onClick Click handler.
481 * @param {function(function())} actionCallback Function to execute the action.
482 * @param {function(string):string} displayStringFunction String formatting
483 * function.
484 * @constructor
485 */
486 function ShareMode(editor, container, toolbar,
487 onClick, actionCallback, displayStringFunction) {
488 ImageEditor.Mode.call(this, 'share');
489
490 this.message_ = null;
491
492 var button = util.createChild(toolbar, 'button share');
493 button.textContent = displayStringFunction('share');
494 button.addEventListener('click', onClick);
495 this.bind(editor, button);
496
497 this.actionCallback_ = actionCallback;
498
499 this.menu_ = util.createChild(container, 'share-menu');
500 this.menu_.hidden = true;
501
502 util.createChild(this.menu_, 'bubble-point');
503 }
504
505 ShareMode.prototype = { __proto__: ImageEditor.Mode.prototype };
506
507 /**
508 * Shows share mode UI.
509 */
510 ShareMode.prototype.setUp = function() {
511 ImageEditor.Mode.prototype.setUp.apply(this, arguments);
512 this.menu_.hidden = false;
513 ImageUtil.setAttribute(this.button_, 'pressed', false);
514 };
515
516 /**
517 * Hides share mode UI.
518 */
519 ShareMode.prototype.cleanUpUI = function() {
520 ImageEditor.Mode.prototype.cleanUpUI.apply(this, arguments);
521 this.menu_.hidden = true;
522 };
523
524 /**
525 * @return {boolean} True if the menu is currently open.
526 */
527 ShareMode.prototype.isActive = function() {
528 return !this.menu_.hidden;
529 };
530
531 /**
532 * Show/hide the menu.
533 * @param {Event} event Event.
534 */
535 ShareMode.prototype.toggle = function(event) {
536 this.editor_.enterMode(this, event);
537 };
538
539 /**
540 * Update available actions list based on the currently selected urls.
541 *
542 * @param {Array.<string>} urls Array of urls.
543 */
544 ShareMode.prototype.updateMenu = function(urls) {
545 var internalId = util.getExtensionId();
546 function isShareAction(task) {
547 var task_parts = task.taskId.split('|');
548 return task_parts[0] != internalId;
549 }
550
551 var items = this.menu_.querySelectorAll('.item');
552 for (var i = 0; i != items.length; i++) {
553 items[i].parentNode.removeChild(items[i]);
554 }
555
556 var api = Gallery.getFileBrowserPrivate();
557 api.getFileTasks(urls, function(tasks) {
558 for (var i = 0; i != tasks.length; i++) {
559 var task = tasks[i];
560 if (!isShareAction(task)) continue;
561
562 var item = document.createElement('div');
563 item.className = 'item';
564 this.menu_.appendChild(item);
565
566 item.textContent = task.title;
567 item.style.backgroundImage = 'url(' + task.iconUrl + ')';
568 item.addEventListener('click', this.actionCallback_.bind(null,
569 api.executeTask.bind(api, task.taskId, urls)));
570 }
571
572 if (this.menu_.firstChild)
573 this.button_.removeAttribute('disabled');
574 else
575 this.button_.setAttribute('disabled', 'true');
576 }.bind(this));
577 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698