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

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: Rebase, .grd fix 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('/');
dgozman 2012/08/17 07:11:03 Is there a method in PathUtil?
Vladislav Kaznacheev 2012/08/17 09:20:40 No. On 2012/08/17 07:11:03, dgozman wrote:
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.name)) {
dgozman 2012/08/17 07:11:03 You can pass entry instead of name here. This is
Vladislav Kaznacheev 2012/08/17 09:20:40 Done.
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
dgozman 2012/08/17 07:11:03 strf is not defined.
Vladislav Kaznacheev 2012/08/17 09:20:40 It is now. It recently moved from file_manager.js
dgozman 2012/08/17 09:55:00 Well, do we need displayStringFunction anymore? Ju
Vladislav Kaznacheev 2012/08/17 10:30:49 We can avoid passing it to Gallery, but the Galler
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 = createChild(this.container_, 'content');
168 content.addEventListener('click', this.onContentClick_.bind(this));
169
170 var closeButton = createChild(this.container_, 'close tool dimmable');
171 createChild(closeButton);
172 closeButton.addEventListener('click', this.onClose_.bind(this));
173
174 this.toolbar_ = createChild(this.container_, 'toolbar tool dimmable');
175
176 this.filenameSpacer_ = createChild(this.toolbar_, 'filename-spacer');
177
178 var nameBox = createChild(this.filenameSpacer_, 'namebox');
179
180 this.filenameText_ = 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 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 // TODO: handle write errors gracefully (suggest retry or saving elsewhere).
dgozman 2012/08/17 07:11:03 I think, this TODO should go to the save code now.
Vladislav Kaznacheev 2012/08/17 09:20:40 We already have it there. Removing here. On 2012/0
251 this.executeWhenReady(this.close_.bind(this));
252 };
253
254 /**
255 * Execute a function when the editor is done with the modifications.
256 * @param {function} callback Function to execute.
257 */
258 Gallery.prototype.executeWhenReady = function(callback) {
259 //TODO(kaznacheev): Execute directly when in grid mode.
260 this.slideMode_.editor_.executeWhenReady(callback);
261 };
262
263 /**
264 * @return {Object} File browser private API.
265 */
266 Gallery.getFileBrowserPrivate = function() {
267 return chrome.fileBrowserPrivate || window.top.chrome.fileBrowserPrivate;
268 };
269
270 /**
271 * Switches gallery to fullscreen mode and back.
272 */
273 Gallery.toggleFullscreen = function() {
274 Gallery.getFileBrowserPrivate().toggleFullscreen();
275 };
276
277 /**
278 * @return {boolean} True if some tool is currently active.
279 */
280 Gallery.prototype.hasActiveTool = function() {
281 return this.slideMode_.isEditing() || this.isSharing_() || this.isRenaming_();
282 };
283
284 /**
285 * Check if the tools are active and notify the inactivity watcher.
286 * @private
287 */
288 Gallery.prototype.checkActivity_ = function() {
289 if (this.hasActiveTool())
290 this.inactivityWatcher_.startActivity();
291 else
292 this.inactivityWatcher_.stopActivity();
293 };
294
295 /**
296 * Edit toggle event handler.
297 * @private
298 */
299 Gallery.prototype.onEdit_ = function() {
300 // The user has just clicked on the Edit button. Dismiss the Share menu.
301 if (this.isSharing_())
302 this.onShare_();
303 this.checkActivity_();
304 };
305
306 /**
307 * @return {Array.<Gallery.Item>} Current selection.
308 */
309 Gallery.prototype.getSelectedItems = function() {
310 // TODO(kaznacheev) support multiple selection grid/mosaic mode.
311 return [this.slideMode_.getSelectedItem()];
312 };
313
314 /**
315 * @return {Gallery.Item} Current single selection.
316 */
317 Gallery.prototype.getSingleSelectedItem = function() {
318 var items = this.getSelectedItems();
319 if (items.length > 1)
320 throw new Error('Unexpected multiple selection');
321 return items[0];
322 };
323
324 /**
325 * Selection change event handler.
326 * @private
327 */
328 Gallery.prototype.onSelection_ = function() {
329 this.updateFilename_();
330 this.shareMode_.updateMenu(
331 this.getSelectedItems().map(function(item) { return item.getUrl() }));
332 };
333
334 /**
335 * Keydown handler.
336 * @param {Event} event Event.
337 * @private
338 */
339 Gallery.prototype.onKeyDown_ = function(event) {
340 if (this.slideMode_.onKeyDown(event))
341 return;
342
343 switch (util.getKeyModifiers(event) + event.keyIdentifier) {
344 case 'U+0008': // Backspace.
345 // The default handler would call history.back and close the Gallery.
346 event.preventDefault();
347 break;
348
349 case 'U+001B': // Escape
350 if (this.isSharing_())
351 this.onShare_();
352 else
353 this.onClose_();
354 break;
355 }
356 };
357
358 // Name box and rename support.
359
360 /**
361 * Update the displayed current item file name.
362 *
363 * @private
364 */
365 Gallery.prototype.updateFilename_ = function() {
366 var fullName = this.getSingleSelectedItem().getFileName();
367
368 this.context_.onNameChange(fullName);
369
370 var displayName = ImageUtil.getFileNameFromFullName(fullName);
371 this.filenameEdit_.value = displayName;
372 this.filenameText_.textContent = displayName;
373 };
374
375 /**
376 * Click event handler on filename edit box
377 * @private
378 */
379 Gallery.prototype.onFilenameClick_ = function() {
380 // We can't rename files in readonly directory.
381 if (this.context_.readonlyDirName)
382 return;
383
384 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', true);
385 setTimeout(this.filenameEdit_.select.bind(this.filenameEdit_), 0);
386 this.inactivityWatcher_.startActivity();
387 };
388
389 /**
390 * Blur event handler on filename edit box
391 * @private
392 */
393 Gallery.prototype.onFilenameEditBlur_ = function() {
394 if (this.filenameEdit_.value && this.filenameEdit_.value[0] == '.') {
395 this.prompt_.show('file_hidden_name', 5000);
396 this.filenameEdit_.focus();
397 return;
398 }
399
400 var item = this.getSingleSelectedItem();
401 var oldUrl = item.getUrl();
402
403 var onFileExists = function() {
404 this.prompt_.show('file_exists', 3000);
405 this.filenameEdit_.value = name;
406 this.onFilenameClick_();
407 }.bind(this);
408
409 var onSuccess = function() {
410 this.slideMode_.updateSelectedUrl_(oldUrl, item.getUrl());
411 }.bind(this);
412
413 if (this.filenameEdit_.value) {
414 this.getSingleSelectedItem().rename(this.context_.saveDirEntry,
415 this.filenameEdit_.value, onSuccess, onFileExists);
416 }
417
418 ImageUtil.setAttribute(this.filenameSpacer_, 'renaming', false);
419 this.checkActivity_();
420 };
421
422 /**
423 * Keydown event handler on filename edit box
424 * @private
425 */
426 Gallery.prototype.onFilenameEditKeydown_ = function() {
427 switch (event.keyCode) {
428 case 27: // Escape
429 this.updateFilename_();
430 this.filenameEdit_.blur();
431 break;
432
433 case 13: // Enter
434 this.filenameEdit_.blur();
435 break;
436 }
437 event.stopPropagation();
438 };
439
440 /**
441 * @return {Boolean} True if file renaming is currently in progress
442 * @private
443 */
444 Gallery.prototype.isRenaming_ = function() {
445 return this.filenameSpacer_.hasAttribute('renaming');
446 };
447
448 /**
449 * Content area click handler.
450 * @private
451 */
452 Gallery.prototype.onContentClick_ = function() {
453 this.filenameEdit_.blur();
454 };
455
456 // Share button support.
457
458 /**
459 * @return {boolean} True if the Share mode is active.
460 * @private
461 */
462 Gallery.prototype.isSharing_ = function() {
463 return this.shareMode_.isActive();
464 };
465
466 /**
467 * Share button handler.
468 * @param {Event} event Event.
469 * @private
470 */
471 Gallery.prototype.onShare_ = function(event) {
472 this.shareMode_.toggle(event);
473 this.checkActivity_();
474 };
475
476 /**
477 *
478 * @param {ImageEditor} editor Editor.
479 * @param {Element} container Container element.
480 * @param {Element} toolbar Toolbar element.
481 * @param {function} onClick Click handler.
482 * @param {function(function())} actionCallback Function to execute the action.
483 * @param {function(string):string} displayStringFunction String formatting
484 * function.
485 * @constructor
486 */
487 function ShareMode(editor, container, toolbar,
488 onClick, actionCallback, displayStringFunction) {
489 ImageEditor.Mode.call(this, 'share');
490
491 this.message_ = null;
492
493 var button = createChild(toolbar, 'button share');
494 button.textContent = displayStringFunction('share');
495 button.addEventListener('click', onClick);
496 this.bind(editor, button);
497
498 this.actionCallback_ = actionCallback;
499
500 this.menu_ = createChild(container, 'share-menu');
501 this.menu_.hidden = true;
502
503 createChild(this.menu_, 'bubble-point');
504 }
505
506 ShareMode.prototype = { __proto__: ImageEditor.Mode.prototype };
507
508 /**
509 * Shows share mode UI.
510 */
511 ShareMode.prototype.setUp = function() {
512 ImageEditor.Mode.prototype.setUp.apply(this, arguments);
513 this.menu_.hidden = false;
514 ImageUtil.setAttribute(this.button_, 'pressed', false);
515 };
516
517 /**
518 * Hides share mode UI.
519 */
520 ShareMode.prototype.cleanUpUI = function() {
521 ImageEditor.Mode.prototype.cleanUpUI.apply(this, arguments);
522 this.menu_.hidden = true;
523 };
524
525 /**
526 * @return {boolean} True if the menu is currently open.
527 */
528 ShareMode.prototype.isActive = function() {
529 return !this.menu_.hidden;
530 };
531
532 /**
533 * Show/hide the menu.
534 * @param {Event} event Event.
535 */
536 ShareMode.prototype.toggle = function(event) {
537 this.editor_.enterMode(this, event);
538 };
539
540 /**
541 * Update available actions list based on the currently selected urls.
542 *
543 * @param {Array.<string>} urls Array of urls.
544 */
545 ShareMode.prototype.updateMenu = function(urls) {
dgozman 2012/08/17 07:11:03 You can use new FileTasks(urls).getExternals(callb
Vladislav Kaznacheev 2012/08/17 09:20:40 My idea is to get rid of FileTasks.getExternals co
546 var internalId = util.getExtensionId();
547 function isShareAction(task) {
548 var task_parts = task.taskId.split('|');
549 return task_parts[0] != internalId;
550 }
551
552 var items = this.menu_.querySelectorAll('.item');
553 for (var i = 0; i != items.length; i++) {
554 items[i].parentNode.removeChild(items[i]);
555 }
556
557 var api = Gallery.getFileBrowserPrivate();
558 api.getFileTasks(urls, function(tasks) {
559 for (var i = 0; i != tasks.length; i++) {
560 var task = tasks[i];
561 if (!isShareAction(task)) continue;
562
563 var item = document.createElement('div');
564 item.className = 'item';
565 this.menu_.appendChild(item);
566
567 item.textContent = task.title;
568 item.style.backgroundImage = 'url(' + task.iconUrl + ')';
569 item.addEventListener('click', this.actionCallback_.bind(null,
570 api.executeTask.bind(api, task.taskId, urls)));
571 }
572
573 if (this.menu_.firstChild)
574 this.button_.removeAttribute('disabled');
575 else
576 this.button_.setAttribute('disabled', 'true');
577 }.bind(this));
578 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698