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

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

Issue 10829421: First cut of the updated Photo Editor UI (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Addressed comments Created 8 years, 3 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
« no previous file with comments | « chrome/browser/resources/file_manager/js/photo/ribbon.js ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * Slide mode displays a single image and has a set of controls to navigate 6 * Slide mode displays a single image and has a set of controls to navigate
7 * between the images and to edit an image. 7 * between the images and to edit an image.
8 * 8 *
9 * @param {Element} container Container element. 9 * TODO(kaznacheev): Introduce a parameter object.
10 *
11 * @param {Element} container Main container element.
12 * @param {Element} content Content container element.
10 * @param {Element} toolbar Toolbar element. 13 * @param {Element} toolbar Toolbar element.
11 * @param {ImageEditor.Prompt} prompt Prompt. 14 * @param {ImageEditor.Prompt} prompt Prompt.
15 * @param {cr.ui.ArrayDataModel} dataModel Data model.
16 * @param {cr.ui.ListSelectionModel} selectionModel Selection model.
12 * @param {Object} context Context. 17 * @param {Object} context Context.
13 * @param {function(string):string} displayStringFunction String formatting 18 * @param {function(string):string} displayStringFunction String formatting
14 * function. 19 * function.
15 * @constructor 20 * @constructor
16 */ 21 */
17 function SlideMode(container, toolbar, prompt, context, displayStringFunction) { 22 function SlideMode(container, content, toolbar, prompt,
23 dataModel, selectionModel,
24 context, displayStringFunction) {
18 this.container_ = container; 25 this.container_ = container;
26 this.document_ = container.ownerDocument;
27 this.content = content;
19 this.toolbar_ = toolbar; 28 this.toolbar_ = toolbar;
20 this.document_ = container.ownerDocument;
21 this.prompt_ = prompt; 29 this.prompt_ = prompt;
30 this.dataModel_ = dataModel;
31 this.selectionModel_ = selectionModel;
22 this.context_ = context; 32 this.context_ = context;
23 this.metadataCache_ = context.metadataCache; 33 this.metadataCache_ = context.metadataCache;
24 this.displayStringFunction_ = displayStringFunction; 34 this.displayStringFunction_ = displayStringFunction;
25 35
36 this.onSelectionBound_ = this.onSelection_.bind(this);
37 this.onSpliceBound_ = this.onSplice_.bind(this);
38
26 this.initListeners_(); 39 this.initListeners_();
27 this.initDom_(); 40 this.initDom_();
28 } 41 }
29 42
30 /** 43 /**
31 * SlideMode extends cr.EventTarget. 44 * SlideMode extends cr.EventTarget.
32 */ 45 */
33 SlideMode.prototype.__proto__ = cr.EventTarget.prototype; 46 SlideMode.prototype.__proto__ = cr.EventTarget.prototype;
34 47
35 /** 48 /**
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
129 142
130 util.createChild(this.arrowBox_, 'arrow-spacer'); 143 util.createChild(this.arrowBox_, 'arrow-spacer');
131 144
132 this.arrowRight_ = 145 this.arrowRight_ =
133 util.createChild(this.arrowBox_, 'arrow right tool dimmable'); 146 util.createChild(this.arrowBox_, 'arrow right tool dimmable');
134 this.arrowRight_.addEventListener('click', 147 this.arrowRight_.addEventListener('click',
135 this.selectNext.bind(this, 1, null)); 148 this.selectNext.bind(this, 1, null));
136 util.createChild(this.arrowRight_); 149 util.createChild(this.arrowRight_);
137 150
138 this.ribbonSpacer_ = util.createChild(this.toolbar_, 'ribbon-spacer'); 151 this.ribbonSpacer_ = util.createChild(this.toolbar_, 'ribbon-spacer');
152 this.ribbon_ = new Ribbon(this.document_,
153 this.metadataCache_, this.dataModel_, this.selectionModel_);
154 this.ribbonSpacer_.appendChild(this.ribbon_);
139 155
140 // Error indicator. 156 // Error indicator.
141 var errorWrapper = util.createChild(this.container_, 'prompt-wrapper'); 157 var errorWrapper = util.createChild(this.container_, 'prompt-wrapper');
142 errorWrapper.setAttribute('pos', 'center'); 158 errorWrapper.setAttribute('pos', 'center');
143 159
144 this.errorBanner_ = util.createChild(errorWrapper, 'error-banner'); 160 this.errorBanner_ = util.createChild(errorWrapper, 'error-banner');
145 161
146 util.createChild(this.container_, 'spinner'); 162 util.createChild(this.container_, 'spinner');
147 163
164 this.slideShowButton_ = util.createChild(this.toolbar_, 'button slideshow');
165 this.slideShowButton_.title = this.displayStringFunction_('slideshow');
166 this.slideShowButton_.addEventListener('click',
167 this.toggleSlideshow_.bind(this, SlideMode.SLIDESHOW_INTERVAL_FIRST));
168
169 // Editor.
148 170
149 this.editButton_ = util.createChild(this.toolbar_, 'button edit'); 171 this.editButton_ = util.createChild(this.toolbar_, 'button edit');
150 this.editButton_.textContent = this.displayStringFunction_('edit'); 172 this.editButton_.title = this.displayStringFunction_('edit');
151 this.editButton_.addEventListener('click', this.onEdit_.bind(this)); 173 this.editButton_.addEventListener('click', this.toggleEditor_.bind(this));
152
153 // Editor toolbar.
154 174
155 this.editBar_ = util.createChild(this.toolbar_, 'edit-bar'); 175 this.editBar_ = util.createChild(this.toolbar_, 'edit-bar');
156 this.editBarMain_ = util.createChild(this.editBar_, 'edit-main'); 176 this.editBarMain_ = util.createChild(this.editBar_, 'edit-main');
157 177
158 this.editBarMode_ = util.createChild(this.container_, 'edit-modal'); 178 this.editBarMode_ = util.createChild(this.container_, 'edit-modal');
159 this.editBarModeWrapper_ = util.createChild( 179 this.editBarModeWrapper_ = util.createChild(
160 this.editBarMode_, 'edit-modal-wrapper'); 180 this.editBarMode_, 'edit-modal-wrapper');
161 this.editBarModeWrapper_.hidden = true; 181 this.editBarModeWrapper_.hidden = true;
162 182
163 // Objects supporting image display and editing. 183 // Objects supporting image display and editing.
164 this.viewport_ = new Viewport(); 184 this.viewport_ = new Viewport();
165 185
166 this.imageView_ = new ImageView( 186 this.imageView_ = new ImageView(
167 this.imageContainer_, 187 this.imageContainer_,
168 this.viewport_, 188 this.viewport_,
169 this.metadataCache_); 189 this.metadataCache_);
170 190
171 this.imageView_.addContentCallback(this.onImageContentChanged_.bind(this));
172
173 this.editor_ = new ImageEditor( 191 this.editor_ = new ImageEditor(
174 this.viewport_, 192 this.viewport_,
175 this.imageView_, 193 this.imageView_,
176 this.prompt_, 194 this.prompt_,
177 { 195 {
178 root: this.container_, 196 root: this.container_,
179 image: this.imageContainer_, 197 image: this.imageContainer_,
180 toolbar: this.editBarMain_, 198 toolbar: this.editBarMain_,
181 mode: this.editBarModeWrapper_ 199 mode: this.editBarModeWrapper_
182 }, 200 },
183 SlideMode.editorModes, 201 SlideMode.editorModes,
184 this.displayStringFunction_); 202 this.displayStringFunction_);
185 203
186 this.editor_.getBuffer().addOverlay( 204 this.editor_.getBuffer().addOverlay(
187 new SwipeOverlay(this.selectNext.bind(this))); 205 new SwipeOverlay(this.selectNext.bind(this)));
188 }; 206 };
189 207
190 /** 208 /**
191 * Load items, display the selected item. 209 * Load items, display the selected item.
192 * 210 *
193 * @param {Array.<Gallery.Item>} items Array of items. 211 * @param {function} opt_callback Callback.
194 * @param {number} selectedIndex Selected index.
195 * @param {function} callback Callback.
196 */ 212 */
197 SlideMode.prototype.load = function(items, selectedIndex, callback) { 213 SlideMode.prototype.enter = function(opt_callback) {
198 var selectedItem = items[selectedIndex]; 214 this.container_.setAttribute('mode', 'slide');
199 if (!selectedItem) { 215
200 this.showErrorBanner_('IMAGE_ERROR'); 216 this.sequenceDirection_ = 0;
201 return; 217 this.sequenceLength_ = 0;
202 }
203 218
204 var loadDone = function() { 219 var loadDone = function() {
205 this.items_ = items; 220 this.active_ = true;
206 this.setSelectedIndex_(selectedIndex); 221
207 this.setupNavigation_(); 222 this.selectionModel_.addEventListener('change', this.onSelectionBound_);
208 setTimeout(this.requestPrefetch.bind(this, 1 /* Next item */), 1000); 223 this.dataModel_.addEventListener('splice', this.onSpliceBound_);
209 callback(); 224
225 this.ribbon_.enable();
226
227 this.prefetchTimer_ = setTimeout(function() {
228 this.prefetchTimer_ = null;
229 this.requestPrefetch(1); // Prefetch the next image.
230 }.bind(this), 1000);
231 if (opt_callback) opt_callback();
210 }.bind(this); 232 }.bind(this);
211 233
212 var selectedUrl = selectedItem.getUrl(); 234 if (this.getItemCount_() == 0) {
213 // Show the selected item ASAP, then complete the initialization 235 this.displayedIndex_ = -1;
214 // (loading the ribbon thumbnails can take some time). 236 //TODO(kaznacheev) Show this message in the grid mode too.
215 this.metadataCache_.get(selectedUrl, Gallery.METADATA_TYPE, 237 this.showErrorBanner_('NO_IMAGES');
216 function(metadata) { 238 loadDone();
217 this.loadItem_(selectedUrl, metadata, 0, loadDone); 239 } else {
218 }.bind(this)); 240 // Ensure valid single selection.
241 // Note that the SlideMode object is not listening to selection change yet.
242 this.select(Math.max(0, this.getSelectedIndex()));
243 cr.dispatchSimpleEvent(this, 'namechange'); // Update name in the UI.
244 this.displayedIndex_ = this.getSelectedIndex();
245
246 var selectedItem = this.getSelectedItem();
247 var selectedUrl = selectedItem.getUrl();
248 // Show the selected item ASAP, then complete the initialization
249 // (loading the ribbon thumbnails can take some time).
250 this.metadataCache_.get(selectedUrl, Gallery.METADATA_TYPE,
251 function(metadata) {
252 this.loadItem_(selectedUrl, metadata, 0, loadDone);
253 }.bind(this));
254
255 }
219 }; 256 };
220 257
221 /** 258 /**
222 * Setup navigation controls. 259 * Leave the mode.
260 * @param {function} opt_callback Callback.
261 */
262 SlideMode.prototype.leave = function(opt_callback) {
263 if (this.prefetchTimer_) {
264 clearTimeout(this.prefetchTimer_);
265 this.prefetchTimer_ = null;
266 }
267
268 var commitDone = function() {
269 this.stopEditing_();
270 this.stopSlideshow_();
271 this.unloadImage_();
272 this.selectionModel_.removeEventListener(
273 'change', this.onSelectionBound_);
274 this.dataModel_.removeEventListener('splice', this.onSpliceBound_);
275 this.ribbon_.disable();
276 this.active_ = false;
277 if (opt_callback) opt_callback();
278 }.bind(this);
279
280 if (this.getItemCount_() == 0) {
281 this.showErrorBanner_(false);
282 commitDone();
283 } else {
284 this.commitItem_(commitDone);
285 }
286 };
287
288 /**
289 * @return {boolean} True if the mode has active tools (that should not fade).
290 */
291 SlideMode.prototype.hasActiveTool = function() {
292 return this.isEditing();
293 };
294
295 /**
296 * @return {number} Item count.
223 * @private 297 * @private
224 */ 298 */
225 SlideMode.prototype.setupNavigation_ = function() { 299 SlideMode.prototype.getItemCount_ = function() {
226 this.sequenceDirection_ = 0; 300 return this.dataModel_.length;
227 this.sequenceLength_ = 0; 301 };
228 302
229 ImageUtil.setAttribute(this.arrowLeft_, 'active', this.items_.length > 1); 303 /**
230 ImageUtil.setAttribute(this.arrowRight_, 'active', this.items_.length > 1); 304 * @param {number} index Index.
305 * @return {Gallery.Item} Item.
306 */
307 SlideMode.prototype.getItem = function(index) {
308 return this.dataModel_.item(index);
309 };
231 310
232 this.ribbon_ = new Ribbon( 311 /**
233 this.document_, this.metadataCache_, this.select.bind(this)); 312 * @return {Gallery.Item} Selected index.
234 this.ribbonSpacer_.appendChild(this.ribbon_); 313 */
235 this.ribbon_.update(this.items_, this.selectedIndex_); 314 SlideMode.prototype.getSelectedIndex = function() {
315 return this.selectionModel_.selectedIndexes.length ?
316 this.selectionModel_.selectedIndexes[0] :
317 -1;
236 }; 318 };
237 319
238 /** 320 /**
239 * @return {Gallery.Item} Selected item 321 * @return {Gallery.Item} Selected item
240 */ 322 */
241 SlideMode.prototype.getSelectedItem = function() { 323 SlideMode.prototype.getSelectedItem = function() {
242 return this.items_ && this.items_[this.selectedIndex_]; 324 return this.getItem(this.getSelectedIndex());
325 };
326
327 /**
328 * Selection change handler.
329 *
330 * Commits the current image and displays the newly selected image.
331 * @private
332 */
333 SlideMode.prototype.onSelection_ = function() {
334 if (this.selectionModel_.selectedIndexes.length == 0)
335 return; // Temporary empty selection.
336
337 if (this.getSelectedIndex() == this.displayedIndex_)
338 return; // Do not reselect.
339
340 this.commitItem_(this.loadSelectedItem_.bind(this));
243 }; 341 };
244 342
245 /** 343 /**
246 * Change the selection. 344 * Change the selection.
247 * 345 *
248 * Commits the current image and changes the selection.
249 *
250 * @param {number} index New selected index. 346 * @param {number} index New selected index.
251 * @param {number} opt_slideDir Slide animation direction (-1|0|1). 347 * @param {number} opt_slideHint Slide animation direction (-1|1).
252 * Derived from the new and the old selected indices if omitted.
253 * @param {function} opt_callback Callback.
254 */ 348 */
255 SlideMode.prototype.select = function(index, opt_slideDir, opt_callback) { 349 SlideMode.prototype.select = function(index, opt_slideHint) {
256 if (!this.items_) 350 this.slideHint_ = opt_slideHint;
257 return; // Not fully initialized, still loading the first image. 351 this.selectionModel_.unselectAll();
258 352 this.selectionModel_.setIndexSelected(index, true);
259 if (index == this.selectedIndex_)
260 return; // Do not reselect.
261
262 this.commitItem_(
263 this.doSelect_.bind(this, index, opt_slideDir, opt_callback));
264 }; 353 };
265 354
266 /** 355 /**
267 * Set the new selected index value. 356 * Load the selected item.
268 * @param {number} index New selected index. 357 *
269 * @private 358 * @private
270 */ 359 */
271 SlideMode.prototype.setSelectedIndex_ = function(index) { 360 SlideMode.prototype.loadSelectedItem_ = function() {
272 this.selectedIndex_ = index; 361 var slideHint = this.slideHint_;
273 cr.dispatchSimpleEvent(this, 'selection'); 362 this.slideHint_ = undefined;
274 363
275 // For once edited image, disallow the 'overwrite' setting change. 364 var index = this.getSelectedIndex();
276 ImageUtil.setAttribute(this.options_, 'saved', 365 if (index == this.displayedIndex_)
277 !this.getSelectedItem().isOriginal()); 366 return; // Do not reselect.
278 };
279 367
280 /** 368 var step = slideHint || (index - this.displayedIndex_);
281 * Perform the actual selection change.
282 *
283 * @param {number} index New selected index.
284 * @param {number} opt_slideDir Slide animation direction (-1|0|1).
285 * Derived from the new and the old selected indices if omitted.
286 * @param {function} opt_callback Callback.
287 * @private
288 */
289 SlideMode.prototype.doSelect_ = function(index, opt_slideDir, opt_callback) {
290 if (index == this.selectedIndex_)
291 return; // Do not reselect
292
293 var step = opt_slideDir != undefined ?
294 opt_slideDir :
295 (index - this.selectedIndex_);
296 369
297 if (Math.abs(step) != 1) { 370 if (Math.abs(step) != 1) {
298 // Long leap, the sequence is broken, we have no good prefetch candidate. 371 // Long leap, the sequence is broken, we have no good prefetch candidate.
299 this.sequenceDirection_ = 0; 372 this.sequenceDirection_ = 0;
300 this.sequenceLength_ = 0; 373 this.sequenceLength_ = 0;
301 } else if (this.sequenceDirection_ == step) { 374 } else if (this.sequenceDirection_ == step) {
302 // Keeping going in sequence. 375 // Keeping going in sequence.
303 this.sequenceLength_++; 376 this.sequenceLength_++;
304 } else { 377 } else {
305 // Reversed the direction. Reset the counter. 378 // Reversed the direction. Reset the counter.
306 this.sequenceDirection_ = step; 379 this.sequenceDirection_ = step;
307 this.sequenceLength_ = 1; 380 this.sequenceLength_ = 1;
308 } 381 }
309 382
310 if (this.sequenceLength_ <= 1) { 383 if (this.sequenceLength_ <= 1) {
311 // We have just broke the sequence. Touch the current image so that it stays 384 // We have just broke the sequence. Touch the current image so that it stays
312 // in the cache longer. 385 // in the cache longer.
313 this.editor_.prefetchImage(this.getSelectedItem().getUrl()); 386 this.imageView_.prefetch(this.imageView_.contentID_);
314 } 387 }
315 388
316 this.setSelectedIndex_(index); 389 this.displayedIndex_ = index;
317
318 if (this.ribbon_)
319 this.ribbon_.update(this.items_, this.selectedIndex_);
320 390
321 function shouldPrefetch(loadType, step, sequenceLength) { 391 function shouldPrefetch(loadType, step, sequenceLength) {
322 // Never prefetch when selecting out of sequence. 392 // Never prefetch when selecting out of sequence.
323 if (Math.abs(step) != 1) 393 if (Math.abs(step) != 1)
324 return false; 394 return false;
325 395
326 // Never prefetch after a video load (decoding the next image can freeze 396 // Never prefetch after a video load (decoding the next image can freeze
327 // the UI for a second or two). 397 // the UI for a second or two).
328 if (loadType == ImageView.LOAD_TYPE_VIDEO_FILE) 398 if (loadType == ImageView.LOAD_TYPE_VIDEO_FILE)
329 return false; 399 return false;
330 400
331 // Always prefetch if the previous load was from cache. 401 // Always prefetch if the previous load was from cache.
332 if (loadType == ImageView.LOAD_TYPE_CACHED_FULL) 402 if (loadType == ImageView.LOAD_TYPE_CACHED_FULL)
333 return true; 403 return true;
334 404
335 // Prefetch if we have been going in the same direction for long enough. 405 // Prefetch if we have been going in the same direction for long enough.
336 return sequenceLength >= 3; 406 return sequenceLength >= 3;
337 } 407 }
338 408
339 var selectedItem = this.getSelectedItem(); 409 var selectedItem = this.getSelectedItem();
340 var onMetadata = function(metadata) { 410 var onMetadata = function(metadata) {
341 if (selectedItem != this.getSelectedItem()) return; 411 if (selectedItem != this.getSelectedItem()) return;
342 this.loadItem_(selectedItem.getUrl(), metadata, step, 412 this.loadItem_(selectedItem.getUrl(), metadata, step,
343 function(loadType) { 413 function(loadType) {
344 if (selectedItem != this.getSelectedItem()) return; 414 if (selectedItem != this.getSelectedItem()) return;
345 if (shouldPrefetch(loadType, step, this.sequenceLength_)) { 415 if (shouldPrefetch(loadType, step, this.sequenceLength_)) {
346 this.requestPrefetch(step); 416 this.requestPrefetch(step);
347 } 417 }
348 if (opt_callback) opt_callback(); 418 if (this.isSlideshowOn_())
419 this.scheduleNextSlide_();
349 }.bind(this)); 420 }.bind(this));
350 }.bind(this); 421 }.bind(this);
351 this.metadataCache_.get( 422 this.metadataCache_.get(
352 selectedItem.getUrl(), Gallery.METADATA_TYPE, onMetadata); 423 selectedItem.getUrl(), Gallery.METADATA_TYPE, onMetadata);
353 }; 424 };
354 425
355 /** 426 /**
427 * Unload the current image.
428 * @private
429 */
430 SlideMode.prototype.unloadImage_ = function() {
431 this.imageView_.unload();
432 this.container_.removeAttribute('video');
433 };
434
435 /**
436 * Data model 'splice' event handler.
437 * @param {Event} event Event.
438 * @private
439 */
440 SlideMode.prototype.onSplice_ = function(event) {
441 ImageUtil.setAttribute(this.arrowLeft_, 'active', this.getItemCount_() > 1);
442 ImageUtil.setAttribute(this.arrowRight_, 'active', this.getItemCount_() > 1);
443
444 if (event.removed.length != 1)
445 return;
446
447 // Delay the selection to let the ribbon splice handler work first.
448 setTimeout(function() {
449 if (event.index < this.dataModel_.length) {
450 // There is the next item, select it.
451 // The next item is now at the same index as the removed one, so we need
452 // to correct displayIndex_ so that loadSelectedItem_ does not think
453 // we are re-selecting the same item (and does right-to-left slide-in
454 // animation).
455 this.displayedIndex_ = event.index - 1;
456 this.select(event.index);
457 } else if (this.dataModel_.length) {
458 // Removed item is the rightmost, but there are more items.
459 this.select(event.index - 1); // Select the new last index.
460 } else {
461 // No items left. Unload the image and show the banner.
462 this.commitItem_(function() {
463 this.unloadImage_();
464 this.showErrorBanner_('NO_IMAGES');
465 }.bind(this));
466 }
467 }.bind(this), 0);
468 };
469
470 /**
356 * @param {number} direction -1 for left, 1 for right. 471 * @param {number} direction -1 for left, 1 for right.
357 * @return {number} Next index in the gived direction, with wrapping. 472 * @return {number} Next index in the gived direction, with wrapping.
358 * @private 473 * @private
359 */ 474 */
360 SlideMode.prototype.getNextSelectedIndex_ = function(direction) { 475 SlideMode.prototype.getNextSelectedIndex_ = function(direction) {
361 var index = this.selectedIndex_ + (direction > 0 ? 1 : -1); 476 var index = this.getSelectedIndex() + (direction > 0 ? 1 : -1);
362 if (index == -1) return this.items_.length - 1; 477 if (index == -1) return this.getItemCount_() - 1;
363 if (index == this.items_.length) return 0; 478 if (index == this.getItemCount_()) return 0;
364 return index; 479 return index;
365 }; 480 };
366 481
367 /** 482 /**
368 * Select the next item. 483 * Select the next item.
369 * @param {number} direction -1 for left, 1 for right. 484 * @param {number} direction -1 for left, 1 for right.
370 * @param {function} opt_callback Callback.
371 */ 485 */
372 SlideMode.prototype.selectNext = function(direction, opt_callback) { 486 SlideMode.prototype.selectNext = function(direction) {
373 this.select(this.getNextSelectedIndex_(direction), direction, opt_callback); 487 this.select(this.getNextSelectedIndex_(direction), direction);
374 }; 488 };
375 489
376 /** 490 /**
377 * Select the first item. 491 * Select the first item.
378 */ 492 */
379 SlideMode.prototype.selectFirst = function() { 493 SlideMode.prototype.selectFirst = function() {
380 this.select(0); 494 this.select(0);
381 }; 495 };
382 496
383 /** 497 /**
384 * Select the last item. 498 * Select the last item.
385 */ 499 */
386 SlideMode.prototype.selectLast = function() { 500 SlideMode.prototype.selectLast = function() {
387 this.select(this.items_.length - 1); 501 this.select(this.getItemCount_() - 1);
388 }; 502 };
389 503
390 // Loading/unloading 504 // Loading/unloading
391 505
392 /** 506 /**
393 * Load and display an item. 507 * Load and display an item.
394 * 508 *
395 * @param {string} url Item url. 509 * @param {string} url Item url.
396 * @param {Object} metadata Item metadata. 510 * @param {Object} metadata Item metadata.
397 * @param {number} slide Slide animation direction (-1|0|1). 511 * @param {number} slide Slide animation direction (-1|0|1).
(...skipping 10 matching lines...) Expand all
408 ImageUtil.setAttribute(this.container_, 'video', video); 522 ImageUtil.setAttribute(this.container_, 'video', video);
409 523
410 this.showSpinner_(false); 524 this.showSpinner_(false);
411 if (loadType == ImageView.LOAD_TYPE_ERROR) { 525 if (loadType == ImageView.LOAD_TYPE_ERROR) {
412 this.showErrorBanner_(video ? 'VIDEO_ERROR' : 'IMAGE_ERROR'); 526 this.showErrorBanner_(video ? 'VIDEO_ERROR' : 'IMAGE_ERROR');
413 } else if (loadType == ImageView.LOAD_TYPE_OFFLINE) { 527 } else if (loadType == ImageView.LOAD_TYPE_OFFLINE) {
414 this.showErrorBanner_(video ? 'VIDEO_OFFLINE' : 'IMAGE_OFFLINE'); 528 this.showErrorBanner_(video ? 'VIDEO_OFFLINE' : 'IMAGE_OFFLINE');
415 } 529 }
416 530
417 if (video) { 531 if (video) {
418 if (this.isEditing()) { 532 // The editor toolbar does not make sense for video, hide it.
419 // The editor toolbar does not make sense for video, hide it. 533 this.stopEditing_();
420 this.onEdit_();
421 }
422 this.mediaControls_.attachMedia(this.imageView_.getVideo()); 534 this.mediaControls_.attachMedia(this.imageView_.getVideo());
423 //TODO(kaznacheev): Add metrics for video playback. 535 //TODO(kaznacheev): Add metrics for video playback.
424 } else { 536 } else {
425 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('View')); 537 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('View'));
426 538
427 function toMillions(number) { return Math.round(number / (1000 * 1000)) } 539 function toMillions(number) { return Math.round(number / (1000 * 1000)) }
428 540
429 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MB'), 541 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MB'),
430 toMillions(metadata.filesystem.size)); 542 toMillions(metadata.filesystem.size));
431 543
432 var canvas = this.imageView_.getCanvas(); 544 var canvas = this.imageView_.getCanvas();
433 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MPix'), 545 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MPix'),
434 toMillions(canvas.width * canvas.height)); 546 toMillions(canvas.width * canvas.height));
435 547
436 var extIndex = url.lastIndexOf('.'); 548 var extIndex = url.lastIndexOf('.');
437 var ext = extIndex < 0 ? '' : url.substr(extIndex + 1).toLowerCase(); 549 var ext = extIndex < 0 ? '' : url.substr(extIndex + 1).toLowerCase();
438 if (ext == 'jpeg') ext = 'jpg'; 550 if (ext == 'jpeg') ext = 'jpg';
439 ImageUtil.metrics.recordEnum( 551 ImageUtil.metrics.recordEnum(
440 ImageUtil.getMetricName('FileType'), ext, ImageUtil.FILE_TYPES); 552 ImageUtil.getMetricName('FileType'), ext, ImageUtil.FILE_TYPES);
441 } 553 }
442 554
555 // For once edited image, disallow the 'overwrite' setting change.
556 ImageUtil.setAttribute(this.options_, 'saved',
557 !this.getSelectedItem().isOriginal());
558
559 var times = SlideMode.OVERWRITE_BUBBLE_KEY in localStorage ?
560 parseInt(localStorage[SlideMode.OVERWRITE_BUBBLE_KEY], 10) : 0;
561 if (times < SlideMode.OVERWRITE_BUBBLE_MAX_TIMES) {
562 this.bubble_.hidden = false;
563 if (this.isEditing()) {
564 localStorage[SlideMode.OVERWRITE_BUBBLE_KEY] = times + 1;
565 }
566 }
567
443 callback(loadType); 568 callback(loadType);
444 }.bind(this); 569 }.bind(this);
445 570
446 this.editor_.openSession( 571 this.editor_.openSession(
447 url, metadata, slide, this.saveCurrentImage_.bind(this), loadDone); 572 url, metadata, slide, this.saveCurrentImage_.bind(this), loadDone);
448 }; 573 };
449 574
450 /** 575 /**
451 * Commit changes to the current item and reset all messages/indicators. 576 * Commit changes to the current item and reset all messages/indicators.
452 * 577 *
(...skipping 10 matching lines...) Expand all
463 } 588 }
464 this.editor_.closeSession(callback); 589 this.editor_.closeSession(callback);
465 }; 590 };
466 591
467 /** 592 /**
468 * Request a prefetch for the next image. 593 * Request a prefetch for the next image.
469 * 594 *
470 * @param {number} direction -1 or 1. 595 * @param {number} direction -1 or 1.
471 */ 596 */
472 SlideMode.prototype.requestPrefetch = function(direction) { 597 SlideMode.prototype.requestPrefetch = function(direction) {
473 if (this.items_.length <= 1) return; 598 if (this.getItemCount_() <= 1) return;
474 599
475 var index = this.getNextSelectedIndex_(direction); 600 var index = this.getNextSelectedIndex_(direction);
476 var nextItemUrl = this.items_[index].getUrl(); 601 var nextItemUrl = this.getItem(index).getUrl();
477 602
478 var selectedItem = this.getSelectedItem(); 603 var selectedItem = this.getSelectedItem();
479 this.metadataCache_.get(nextItemUrl, Gallery.METADATA_TYPE, 604 this.metadataCache_.get(nextItemUrl, Gallery.METADATA_TYPE,
480 function(metadata) { 605 function(metadata) {
481 if (selectedItem != this.getSelectedItem()) return; 606 if (selectedItem != this.getSelectedItem()) return;
482 this.editor_.prefetchImage(nextItemUrl); 607 this.editor_.prefetchImage(nextItemUrl);
483 }.bind(this)); 608 }.bind(this));
484 }; 609 };
485 610
486 // Event handlers. 611 // Event handlers.
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
534 return true; 659 return true;
535 660
536 switch (util.getKeyModifiers(event) + event.keyIdentifier) { 661 switch (util.getKeyModifiers(event) + event.keyIdentifier) {
537 case 'U+0020': // Space toggles the video playback. 662 case 'U+0020': // Space toggles the video playback.
538 if (this.isShowingVideo_()) { 663 if (this.isShowingVideo_()) {
539 this.mediaControls_.togglePlayStateWithFeedback(); 664 this.mediaControls_.togglePlayStateWithFeedback();
540 } 665 }
541 break; 666 break;
542 667
543 case 'U+0045': // 'e' toggles the editor 668 case 'U+0045': // 'e' toggles the editor
544 this.onEdit_(); 669 this.toggleEditor_();
545 break; 670 break;
546 671
547 case 'U+001B': // Escape 672 case 'U+001B': // Escape
548 if (!this.isEditing()) 673 if (!this.isEditing())
549 return false; // Not handled. 674 return false; // Not handled.
550 this.onEdit_(); 675 this.toggleEditor_();
551 break; 676 break;
552 677
553 case 'Ctrl-U+00DD': // Ctrl+] (cryptic on purpose). 678 case 'Ctrl-U+00DD': // Ctrl+]. TODO(kaznacheev): Find a non-cryptic key.
554 this.toggleSlideshow_(); 679 this.toggleSlideshow_(SlideMode.SLIDESHOW_INTERVAL_FIRST);
555 break; 680 break;
556 681
557 case 'Home': 682 case 'Home':
558 this.selectFirst(); 683 this.selectFirst();
559 break; 684 break;
560 case 'End': 685 case 'End':
561 this.selectLast(); 686 this.selectLast();
562 break; 687 break;
563 case 'Left': 688 case 'Left':
564 this.selectNext(-1); 689 this.selectNext(-1);
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
607 this.shouldOverwriteOriginal_(), 732 this.shouldOverwriteOriginal_(),
608 canvas, 733 canvas,
609 metadataEncoder, 734 metadataEncoder,
610 function(success) { 735 function(success) {
611 // TODO(kaznacheev): Implement write error handling. 736 // TODO(kaznacheev): Implement write error handling.
612 // Until then pretend that the save succeeded. 737 // Until then pretend that the save succeeded.
613 this.showSpinner_(false); 738 this.showSpinner_(false);
614 this.flashSavedLabel_(); 739 this.flashSavedLabel_();
615 var newUrl = item.getUrl(); 740 var newUrl = item.getUrl();
616 this.updateSelectedUrl_(oldUrl, newUrl); 741 this.updateSelectedUrl_(oldUrl, newUrl);
617 this.ribbon_.updateThumbnail( 742 this.ribbon_.updateThumbnail(newUrl, this.selectedImageMetadata_);
618 this.selectedIndex_, newUrl, this.selectedImageMetadata_); 743 cr.dispatchSimpleEvent(this, 'content');
744
745 if (this.imageView_.getContentRevision() == 1) { // First edit.
746 // Lock the 'Overwrite original' checkbox for this item.
747 ImageUtil.setAttribute(this.options_, 'saved', true);
748 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('Edit'));
749 }
750
751 if (oldUrl != newUrl) {
752 this.displayedIndex_++;
753 // This splice call will change the selection change event. SlideMode
754 // will ignore it as the selected item is the same.
755 // The ribbon will redraw while being obscured by the Editor toolbar,
756 // so there is no need for nice animation here.
757 this.dataModel_.splice(
758 this.getSelectedIndex(), 0, new Gallery.Item(oldUrl));
759 }
619 callback(); 760 callback();
620 }.bind(this)); 761 }.bind(this));
621 }; 762 };
622 763
623 /** 764 /**
624 * Update caches when the selected item url has changed. 765 * Update caches when the selected item url has changed.
625 * 766 *
626 * @param {string} oldUrl Old url. 767 * @param {string} oldUrl Old url.
627 * @param {string} newUrl New url. 768 * @param {string} newUrl New url.
628 * @private 769 * @private
629 */ 770 */
630 SlideMode.prototype.updateSelectedUrl_ = function(oldUrl, newUrl) { 771 SlideMode.prototype.updateSelectedUrl_ = function(oldUrl, newUrl) {
631 this.metadataCache_.clear(oldUrl, Gallery.METADATA_TYPE); 772 this.metadataCache_.clear(oldUrl, Gallery.METADATA_TYPE);
632 773
633 if (oldUrl == newUrl) 774 if (oldUrl == newUrl)
634 return; 775 return;
635 776
636 this.imageView_.changeUrl(newUrl); 777 this.imageView_.changeUrl(newUrl);
637 this.ribbon_.remapCache(oldUrl, newUrl); 778 this.ribbon_.remapCache(oldUrl, newUrl);
638
639 // Let the gallery know that the selected item url has changed.
640 cr.dispatchSimpleEvent(this, 'selection');
641 }; 779 };
642 780
643 /** 781 /**
644 * Flash 'Saved' label briefly to indicate that the image has been saved. 782 * Flash 'Saved' label briefly to indicate that the image has been saved.
645 * @private 783 * @private
646 */ 784 */
647 SlideMode.prototype.flashSavedLabel_ = function() { 785 SlideMode.prototype.flashSavedLabel_ = function() {
648 var setLabelHighlighted = 786 var setLabelHighlighted =
649 ImageUtil.setAttribute.bind(null, this.savedLabel_, 'highlighted'); 787 ImageUtil.setAttribute.bind(null, this.savedLabel_, 'highlighted');
650 setTimeout(setLabelHighlighted.bind(null, true), 0); 788 setTimeout(setLabelHighlighted.bind(null, true), 0);
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
691 /** 829 /**
692 * Overwrite info bubble close handler. 830 * Overwrite info bubble close handler.
693 * @private 831 * @private
694 */ 832 */
695 SlideMode.prototype.onCloseBubble_ = function() { 833 SlideMode.prototype.onCloseBubble_ = function() {
696 this.bubble_.hidden = true; 834 this.bubble_.hidden = true;
697 localStorage[SlideMode.OVERWRITE_BUBBLE_KEY] = 835 localStorage[SlideMode.OVERWRITE_BUBBLE_KEY] =
698 SlideMode.OVERWRITE_BUBBLE_MAX_TIMES; 836 SlideMode.OVERWRITE_BUBBLE_MAX_TIMES;
699 }; 837 };
700 838
839 // Slideshow
840
701 /** 841 /**
702 * Callback called when the image is edited. 842 * Slideshow interval in ms.
843 */
844 SlideMode.SLIDESHOW_INTERVAL = 5000;
845
846 /**
847 * First slideshow interval in ms. It should be shorter so that the user
848 * is not guessing whether the button worked.
849 */
850 SlideMode.SLIDESHOW_INTERVAL_FIRST = 1000;
851
852 /**
853 * @return {boolean} True if the slideshow is on.
703 * @private 854 * @private
704 */ 855 */
705 SlideMode.prototype.onImageContentChanged_ = function() { 856 SlideMode.prototype.isSlideshowOn_ = function() {
706 var revision = this.imageView_.getContentRevision(); 857 return this.container_.hasAttribute('slideshow');
707 if (revision == 0) { 858 };
708 // Just loaded. 859
709 var times = SlideMode.OVERWRITE_BUBBLE_KEY in localStorage ? 860 /**
710 parseInt(localStorage[SlideMode.OVERWRITE_BUBBLE_KEY], 10) : 0; 861 * Stop the slideshow if it is on.
711 if (times < SlideMode.OVERWRITE_BUBBLE_MAX_TIMES) { 862 * @private
712 this.bubble_.hidden = false; 863 */
713 if (this.isEditing()) { 864 SlideMode.prototype.stopSlideshow_ = function() {
714 localStorage[SlideMode.OVERWRITE_BUBBLE_KEY] = times + 1; 865 if (this.isSlideshowOn_())
715 } 866 this.toggleSlideshow_();
716 } 867 };
868
869 /**
870 * Start/stop the slideshow.
871 *
872 * @param {number} opt_interval First interval in ms.
873 * @param {Event} opt_event Event.
874 * @private
875 */
876
877 SlideMode.prototype.toggleSlideshow_ = function(opt_interval, opt_event) {
878 if (opt_event) // Caused by user action, notify the Gallery.
879 cr.dispatchSimpleEvent(this, 'useraction');
880
881 if (!this.active_) {
882 // Enter the slide mode. Show the first image for the full interval.
883 this.enter(this.toggleSlideshow_.bind(this, SlideMode.SLIDESHOW_INTERVAL));
884 return;
717 } 885 }
718 886
719 if (revision == 1) { 887 this.stopEditing_();
720 // First edit. 888 ImageUtil.setAttribute(this.container_, 'slideshow', !this.isSlideshowOn_());
721 ImageUtil.setAttribute(this.options_, 'saved', true); 889 ImageUtil.setAttribute(
722 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('Edit')); 890 this.slideShowButton_, 'pressed', this.isSlideshowOn_());
891
892 if (this.isSlideshowOn_()) {
893 this.scheduleNextSlide_(opt_interval);
894 } else {
895 if (this.slideShowTimeout_) {
896 clearInterval(this.slideShowTimeout_);
897 this.slideShowTimeout_ = null;
898 }
723 } 899 }
724 }; 900 };
725 901
726 // Misc
727
728 /** 902 /**
729 * Start/stop the slide show. 903 * @param {number} opt_interval Slideshow interval in ms.
730 * @private 904 * @private
731 */ 905 */
732 SlideMode.prototype.toggleSlideshow_ = function() { 906 SlideMode.prototype.scheduleNextSlide_ = function(opt_interval) {
733 if (this.slideShowTimeout_) { 907 if (this.slideShowTimeout_)
734 clearInterval(this.slideShowTimeout_); 908 clearTimeout(this.slideShowTimeout_);
735 this.slideShowTimeout_ = null; 909
736 } else { 910 this.slideShowTimeout_ = setTimeout(function() {
737 var self = this; 911 this.slideShowTimeout_ = null;
738 function nextSlide() { 912 this.selectNext(1);
739 self.selectNext(1, 913 }.bind(this),
740 function() { self.slideShowTimeout_ = setTimeout(nextSlide, 5000) }); 914 opt_interval || SlideMode.SLIDESHOW_INTERVAL);
741 }
742 nextSlide();
743 }
744 }; 915 };
745 916
746 /** 917 /**
747 * @return {boolean} True if the editor is active. 918 * @return {boolean} True if the editor is active.
748 */ 919 */
749 SlideMode.prototype.isEditing = function() { 920 SlideMode.prototype.isEditing = function() {
750 return this.container_.hasAttribute('editing'); 921 return this.container_.hasAttribute('editing');
751 }; 922 };
752 923
753 /** 924 /**
925 * Stop editing.
926 * @private
927 */
928 SlideMode.prototype.stopEditing_ = function() {
929 if (this.isEditing())
930 this.toggleEditor_();
931 };
932
933 /**
754 * Activate/deactivate editor. 934 * Activate/deactivate editor.
935 * @param {Event} opt_event Event.
755 * @private 936 * @private
756 */ 937 */
757 SlideMode.prototype.onEdit_ = function() { 938 SlideMode.prototype.toggleEditor_ = function(opt_event) {
939 if (opt_event) // Caused by user action, notify the Gallery.
940 cr.dispatchSimpleEvent(this, 'useraction');
941
942 if (!this.active_) {
943 this.enter(this.toggleEditor_.bind(this));
944 return;
945 }
946
947 this.stopSlideshow_();
758 if (!this.isEditing() && this.isShowingVideo_()) 948 if (!this.isEditing() && this.isShowingVideo_())
759 return; // No editing for videos. 949 return; // No editing for videos.
760 950
761 ImageUtil.setAttribute(this.container_, 'editing', !this.isEditing()); 951 ImageUtil.setAttribute(this.container_, 'editing', !this.isEditing());
762 952
763 if (this.isEditing()) { // isEditing_ has just been flipped to a new value. 953 if (this.isEditing()) { // isEditing_ has just been flipped to a new value.
764 if (this.context_.readonlyDirName) { 954 if (this.context_.readonlyDirName) {
765 this.editor_.getPrompt().showAt( 955 this.editor_.getPrompt().showAt(
766 'top', 'readonly_warning', 0, this.context_.readonlyDirName); 956 'top', 'readonly_warning', 0, this.context_.readonlyDirName);
767 } 957 }
768 } else { 958 } else {
769 this.editor_.getPrompt().hide(); 959 this.editor_.getPrompt().hide();
770 } 960 }
771 961
772 ImageUtil.setAttribute(this.editButton_, 'pressed', this.isEditing()); 962 ImageUtil.setAttribute(this.editButton_, 'pressed', this.isEditing());
773
774 cr.dispatchSimpleEvent(this, 'edit');
775 }; 963 };
776 964
777 /** 965 /**
778 * Display the error banner. 966 * Display the error banner.
779 * @param {string} message Message. 967 * @param {string} message Message.
780 * @private 968 * @private
781 */ 969 */
782 SlideMode.prototype.showErrorBanner_ = function(message) { 970 SlideMode.prototype.showErrorBanner_ = function(message) {
783 if (message) { 971 if (message) {
784 this.errorBanner_.textContent = this.displayStringFunction_(message); 972 this.errorBanner_.textContent = this.displayStringFunction_(message);
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
862 done = true; 1050 done = true;
863 } 1051 }
864 }.bind(this); 1052 }.bind(this);
865 }; 1053 };
866 1054
867 /** 1055 /**
868 * If the user touched the image and moved the finger more than SWIPE_THRESHOLD 1056 * If the user touched the image and moved the finger more than SWIPE_THRESHOLD
869 * horizontally it's considered as a swipe gesture (change the current image). 1057 * horizontally it's considered as a swipe gesture (change the current image).
870 */ 1058 */
871 SwipeOverlay.SWIPE_THRESHOLD = 100; 1059 SwipeOverlay.SWIPE_THRESHOLD = 100;
OLDNEW
« no previous file with comments | « chrome/browser/resources/file_manager/js/photo/ribbon.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698