OLD | NEW |
---|---|
(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 }; | |
OLD | NEW |