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

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

Issue 10399047: [Photo Editor] Save edited images immediately. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 7 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
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 * Base class that Ribbon uses to display photos. 6 * Base class that Ribbon uses to display photos.
7 */ 7 */
8 8
9 function RibbonClient() {} 9 function RibbonClient() {}
10 10
11 RibbonClient.prototype.prefetchImage = function(id, content, metadata) {}; 11 RibbonClient.prototype.prefetchImage = function(id, url) {};
12 12
13 RibbonClient.prototype.openImage = function(id, content, metadata, direction) { 13 RibbonClient.prototype.openImage = function(id, url, metadata, direction) {
14 }; 14 };
15 15
16 RibbonClient.prototype.closeImage = function(item) {}; 16 RibbonClient.prototype.closeImage = function(item) {};
17 17
18 /** 18 /**
19 * Image gallery for viewing and editing image files. 19 * Image gallery for viewing and editing image files.
20 * 20 *
21 * @param {HTMLDivElement} container 21 * @param {HTMLDivElement} container
22 * @param {Object} context Object containing the following: 22 * @param {Object} context Object containing the following:
23 * {function(string)} onNameChange Called every time a selected 23 * {function(string)} onNameChange Called every time a selected
(...skipping 13 matching lines...) Expand all
37 var strf = context.displayStringFunction; 37 var strf = context.displayStringFunction;
38 this.displayStringFunction_ = function(id, formatArgs) { 38 this.displayStringFunction_ = function(id, formatArgs) {
39 var args = Array.prototype.slice.call(arguments); 39 var args = Array.prototype.slice.call(arguments);
40 args[0] = 'GALLERY_' + id.toUpperCase(); 40 args[0] = 'GALLERY_' + id.toUpperCase();
41 return strf.apply(null, args); 41 return strf.apply(null, args);
42 }; 42 };
43 43
44 this.onFadeTimeoutBound_ = this.onFadeTimeout_.bind(this); 44 this.onFadeTimeoutBound_ = this.onFadeTimeout_.bind(this);
45 this.fadeTimeoutId_ = null; 45 this.fadeTimeoutId_ = null;
46 this.mouseOverTool_ = false; 46 this.mouseOverTool_ = false;
47 this.imageChanges_ = 0;
48 47
49 this.initDom_(); 48 this.initDom_();
50 } 49 }
51 50
52 Gallery.prototype = { __proto__: RibbonClient.prototype }; 51 Gallery.prototype = { __proto__: RibbonClient.prototype };
53 52
54 Gallery.open = function(context, items, selectedItem) { 53 Gallery.open = function(context, items, selectedItem) {
55 var container = document.querySelector('.gallery'); 54 var container = document.querySelector('.gallery');
56 ImageUtil.removeChildren(container); 55 ImageUtil.removeChildren(container);
57 var gallery = new Gallery(container, context); 56 var gallery = new Gallery(container, context);
(...skipping 247 matching lines...) Expand 10 before | Expand all | Expand 10 after
305 // Append a file name after an anchor so that the Gallery 304 // Append a file name after an anchor so that the Gallery
306 // code can find the file extension at the end of the url. 305 // code can find the file extension at the end of the url.
307 // A File instance contains the real file name in the 'name' property. 306 // A File instance contains the real file name in the 'name' property.
308 // For a Blob instance we make up a fake file name out of the mime type 307 // For a Blob instance we make up a fake file name out of the mime type
309 // (image/jpeg -> image.jpg). 308 // (image/jpeg -> image.jpg).
310 return window.webkitURL.createObjectURL(blob) + 309 return window.webkitURL.createObjectURL(blob) +
311 '#' + (blob.name || (blob.type.replace('/', '.'))); 310 '#' + (blob.name || (blob.type.replace('/', '.')));
312 }; 311 };
313 312
314 Gallery.prototype.onBeforeUnload_ = function(event) { 313 Gallery.prototype.onBeforeUnload_ = function(event) {
315 if (this.imageChanges_ > 0) { 314 if (this.editor_.isBusy())
316 setTimeout(this.saveChanges_.bind(this), 1000);
317 return this.displayStringFunction_('unsaved_changes'); 315 return this.displayStringFunction_('unsaved_changes');
318 }
319 return null; 316 return null;
320 }; 317 };
321 318
322 Gallery.prototype.load = function(items, selectedItem) { 319 Gallery.prototype.load = function(items, selectedItem) {
323 var urls = []; 320 var urls = [];
324 var selectedIndex = -1; 321 var selectedIndex = -1;
325 322
326 // Convert canvas and blob items to blob urls. 323 // Convert canvas and blob items to blob urls.
327 for (var i = 0; i != items.length; i++) { 324 for (var i = 0; i != items.length; i++) {
328 var item = items[i]; 325 var item = items[i];
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
380 this.toolbar_, tasks, 377 this.toolbar_, tasks,
381 this.onShare_.bind(this), this.onActionExecute_.bind(this), 378 this.onShare_.bind(this), this.onActionExecute_.bind(this),
382 this.displayStringFunction_); 379 this.displayStringFunction_);
383 } else { 380 } else {
384 this.shareMode_ = null; 381 this.shareMode_ = null;
385 } 382 }
386 }.bind(this)); 383 }.bind(this));
387 }; 384 };
388 385
389 Gallery.prototype.onImageContentChanged_ = function() { 386 Gallery.prototype.onImageContentChanged_ = function() {
390 this.imageChanges_++; 387 var revision = this.imageView_.getContentRevision();
391 if (this.imageChanges_ == 0) return; 388 if (revision == 0) return; // Just loaded.
392 389
393 if (this.imageChanges_ == 1) { 390 if (revision == 1) {
394 // First edit. 391 // First edit.
395 this.filenameSpacer_.setAttribute('saved', 'saved'); 392 this.filenameSpacer_.setAttribute('saved', 'saved');
396 this.filenameSpacer_.setAttribute('overwrite', 'overwrite'); 393 this.filenameSpacer_.setAttribute('overwrite', 'overwrite');
397 394
398 var key = 'gallery-overwrite-original'; 395 var key = 'gallery-overwrite-original';
399 var overwrite = key in localStorage ? (localStorage[key] == "true") : true; 396 var overwrite = key in localStorage ? (localStorage[key] == "true") : true;
400 this.overwriteOriginal_.checked = overwrite; 397 this.overwriteOriginal_.checked = overwrite;
401 this.applyOverwrite_(overwrite); 398 this.applyOverwrite_(overwrite);
402 399
403 key = 'gallery-overwrite-bubble'; 400 key = 'gallery-overwrite-bubble';
404 var times = key in localStorage ? parseInt(localStorage[key], 10) : 0; 401 var times = key in localStorage ? parseInt(localStorage[key], 10) : 0;
405 if (times < Gallery.OVERWRITE_BUBBLE_MAX_TIMES) { 402 if (times < Gallery.OVERWRITE_BUBBLE_MAX_TIMES) {
406 this.bubble_.removeAttribute('hidden'); 403 this.bubble_.removeAttribute('hidden');
407 localStorage[key] = times + 1; 404 localStorage[key] = times + 1;
408 } 405 }
409 406
410 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('Edit')); 407 ImageUtil.metrics.recordUserAction(ImageUtil.getMetricName('Edit'));
411 } 408 }
409 };
412 410
411 Gallery.prototype.flashSavedLabel_ = function() {
413 var label = this.savedLabel_; 412 var label = this.savedLabel_;
414 setTimeout(function(){ label.setAttribute('highlighted', 'true'); }, 0); 413 setTimeout(function(){ label.setAttribute('highlighted', 'true'); }, 0);
415 setTimeout(function(){ label.removeAttribute('highlighted'); }, 300); 414 setTimeout(function(){ label.removeAttribute('highlighted'); }, 300);
416 }; 415 };
417 416
418 Gallery.prototype.applyOverwrite_ = function(overwrite) { 417 Gallery.prototype.applyOverwrite_ = function(overwrite) {
419 if (overwrite) { 418 if (overwrite) {
420 this.ribbon_.getSelectedItem().setOriginalName(this.context_.saveDirEntry, 419 this.ribbon_.getSelectedItem().setOriginalName(this.context_.saveDirEntry,
421 this.updateFilename_.bind(this)); 420 this.updateFilename_.bind(this));
422 } else { 421 } else {
423 this.ribbon_.getSelectedItem().setCopyName(this.context_.saveDirEntry, 422 this.ribbon_.getSelectedItem().setCopyName(this.context_.saveDirEntry,
424 this.updateFilename_.bind(this)); 423 this.selectedImageMetadata_,
424 this.updateFilename_.bind(this));
425 } 425 }
426 }; 426 };
427 427
428 Gallery.prototype.onOverwriteOriginalClick_ = function(event) { 428 Gallery.prototype.onOverwriteOriginalClick_ = function(event) {
429 var overwrite = event.target.checked; 429 var overwrite = event.target.checked;
430 localStorage['gallery-overwrite-original'] = overwrite; 430 localStorage['gallery-overwrite-original'] = overwrite;
431 this.applyOverwrite_(overwrite); 431 this.applyOverwrite_(overwrite);
432 }; 432 };
433 433
434 Gallery.prototype.onCloseBubble_ = function(event) { 434 Gallery.prototype.onCloseBubble_ = function(event) {
435 this.bubble_.setAttribute('hidden', 'hidden'); 435 this.bubble_.setAttribute('hidden', 'hidden');
436 localStorage['gallery-overwrite-bubble'] = Gallery.OVERWRITE_BUBBLE_MAX_TIMES; 436 localStorage['gallery-overwrite-bubble'] = Gallery.OVERWRITE_BUBBLE_MAX_TIMES;
437 }; 437 };
438 438
439 Gallery.prototype.saveItem_ = function(item, callback, canvas, modified) { 439 Gallery.prototype.saveCurrentImage_ = function(callback) {
440 if (modified) { 440 var item = this.ribbon_.getSelectedItem();
441 item.save(this.context_.saveDirEntry, this.context_.metadataProvider, 441 var canvas = this.imageView_.getCanvas();
442 canvas, callback);
443 } else {
444 if (callback) callback();
445 }
446 };
447 442
448 Gallery.prototype.saveChanges_ = function(opt_callback) { 443 var metadataEncoder = ImageEncoder.encodeMetadata(
449 this.imageChanges_ = 0; 444 this.selectedImageMetadata_, canvas, 1);
dgozman 2012/05/16 16:05:40 Comment what 1 means.
Vladislav Kaznacheev 2012/05/17 09:04:40 Done.
450 this.bubble_.setAttribute('hidden', 'hidden'); 445
451 if (this.isShowingVideo_()) { 446 this.selectedImageMetadata_ = metadataEncoder.getMetadata();
452 // This call ensures that editor leaves the mode and closes respective UI 447 item.setThumbnail(this.selectedImageMetadata_);
453 // elements. Currently, the only mode for videos is sharing. 448
454 this.editor_.leaveModeGently(); 449 item.saveToFile(
455 if (opt_callback) opt_callback(); 450 this.context_.saveDirEntry,
456 return; 451 canvas,
457 } 452 metadataEncoder,
458 this.editor_.requestImage( 453 function(success) {
459 this.saveItem_.bind(this, this.ribbon_.getSelectedItem(), opt_callback)); 454 // TODO(kaznacheev): Implement write error handling.
455 // Until then pretend that the save succeeded.
456 this.flashSavedLabel_();
457 this.context_.metadataProvider.reset(item.getUrl());
458 callback();
459 }.bind(this));
460 }; 460 };
461 461
462 Gallery.prototype.onActionExecute_ = function(action) { 462 Gallery.prototype.onActionExecute_ = function(action) {
463 var url = this.ribbon_.getSelectedItem().getUrl(); 463 // |executeWhenReady| closes the sharing menu.
464 // saveChanges_ makes the editor leave the mode and close the sharing menu. 464 this.editor_.executeWhenReady(function() {
465 this.saveChanges_(action.execute.bind(action, [url])); 465 action.execute([this.ribbon_.getSelectedItem().getUrl()]);
466 }.bind(this));
466 }; 467 };
467 468
468 Gallery.prototype.updateFilename_ = function(opt_url) { 469 Gallery.prototype.updateFilename_ = function(opt_url) {
469 var fullName; 470 var fullName;
470 471
471 var item = this.ribbon_.getSelectedItem(); 472 var item = this.ribbon_.getSelectedItem();
472 if (item) { 473 if (item) {
473 fullName = item.getNameAfterSaving(); 474 fullName = item.getNameAfterSaving();
474 } else if (opt_url) { 475 } else if (opt_url) {
475 fullName = ImageUtil.getFullNameFromUrl(opt_url); 476 fullName = ImageUtil.getFullNameFromUrl(opt_url);
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
588 } 589 }
589 this.context_.onClose(); 590 this.context_.onClose();
590 }.bind(this)); 591 }.bind(this));
591 }; 592 };
592 593
593 /** 594 /**
594 * Handle user's 'Close' action (Escape or a click on the X icon). 595 * Handle user's 'Close' action (Escape or a click on the X icon).
595 */ 596 */
596 Gallery.prototype.onClose_ = function() { 597 Gallery.prototype.onClose_ = function() {
597 // TODO: handle write errors gracefully (suggest retry or saving elsewhere). 598 // TODO: handle write errors gracefully (suggest retry or saving elsewhere).
598 this.saveChanges_(this.close_.bind(this)); 599 this.editor_.executeWhenReady(this.close_.bind(this));
599 }; 600 };
600 601
601 Gallery.prototype.prefetchImage = function(id, content, metadata) { 602 Gallery.prototype.prefetchImage = function(id, url) {
602 this.editor_.prefetchImage(id, content, metadata); 603 this.editor_.prefetchImage(id, url);
603 }; 604 };
604 605
605 Gallery.prototype.openImage = function(id, content, metadata, slide, callback) { 606 Gallery.prototype.openImage = function(id, url, metadata, slide, callback) {
606 // The first change is load, we should not count it.
607 this.imageChanges_ = -1;
608 this.filenameSpacer_.removeAttribute('overwrite'); 607 this.filenameSpacer_.removeAttribute('overwrite');
609 this.filenameSpacer_.removeAttribute('saved'); 608 this.filenameSpacer_.removeAttribute('saved');
610 609
611 var item = this.ribbon_.getSelectedItem(); 610 this.selectedImageMetadata_ = metadata;
612 this.updateFilename_(content); 611 this.updateFilename_(url);
613 612
614 this.showSpinner_(true); 613 this.showSpinner_(true);
615 614
616 var self = this; 615 var self = this;
617 function loadDone(loadType) { 616 function loadDone(loadType) {
618 var video = self.isShowingVideo_(); 617 var video = self.isShowingVideo_();
619 ImageUtil.setAttribute(self.container_, 'video', video); 618 ImageUtil.setAttribute(self.container_, 'video', video);
620 619
621 self.showSpinner_(false); 620 self.showSpinner_(false);
622 if (loadType == ImageView.LOAD_TYPE_ERROR) { 621 if (loadType == ImageView.LOAD_TYPE_ERROR) {
(...skipping 12 matching lines...) Expand all
635 634
636 function toMillions(number) { return Math.round(number / (1000 * 1000)) } 635 function toMillions(number) { return Math.round(number / (1000 * 1000)) }
637 636
638 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MB'), 637 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MB'),
639 toMillions(metadata.fileSize)); 638 toMillions(metadata.fileSize));
640 639
641 var canvas = self.imageView_.getCanvas(); 640 var canvas = self.imageView_.getCanvas();
642 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MPix'), 641 ImageUtil.metrics.recordSmallCount(ImageUtil.getMetricName('Size.MPix'),
643 toMillions(canvas.width * canvas.height)); 642 toMillions(canvas.width * canvas.height));
644 643
645 var url = item ? item.getUrl() : content;
646 var extIndex = url.lastIndexOf('.'); 644 var extIndex = url.lastIndexOf('.');
647 var ext = extIndex < 0 ? '' : url.substr(extIndex + 1).toLowerCase(); 645 var ext = extIndex < 0 ? '' : url.substr(extIndex + 1).toLowerCase();
648 if (ext == 'jpeg') ext = 'jpg'; 646 if (ext == 'jpeg') ext = 'jpg';
649 ImageUtil.metrics.recordEnum( 647 ImageUtil.metrics.recordEnum(
650 ImageUtil.getMetricName('FileType'), ext, ImageUtil.FILE_TYPES); 648 ImageUtil.getMetricName('FileType'), ext, ImageUtil.FILE_TYPES);
651 } 649 }
652 650
653 callback(loadType); 651 callback(loadType);
654 } 652 }
655 653
656 this.editor_.openSession(id, content, metadata, slide, loadDone); 654 this.editor_.openSession(
655 id, url, metadata, slide, this.saveCurrentImage_.bind(this), loadDone);
657 }; 656 };
658 657
659 Gallery.prototype.closeImage = function(item) { 658 Gallery.prototype.closeImage = function(callback) {
660 this.showSpinner_(false); 659 this.showSpinner_(false);
661 this.showErrorBanner_(false); 660 this.showErrorBanner_(false);
662 this.editor_.getPrompt().hide(); 661 this.editor_.getPrompt().hide();
663 if (this.isShowingVideo_()) { 662 if (this.isShowingVideo_()) {
664 this.mediaControls_.pause(); 663 this.mediaControls_.pause();
665 this.mediaControls_.detachMedia(); 664 this.mediaControls_.detachMedia();
666 } 665 }
667 this.editor_.closeSession(this.saveItem_.bind(this, item, null)); 666 this.editor_.closeSession(callback);
668 }; 667 };
669 668
670 Gallery.prototype.showSpinner_ = function(on) { 669 Gallery.prototype.showSpinner_ = function(on) {
671 if (this.spinnerTimer_) { 670 if (this.spinnerTimer_) {
672 clearTimeout(this.spinnerTimer_); 671 clearTimeout(this.spinnerTimer_);
673 this.spinnerTimer_ = null; 672 this.spinnerTimer_ = null;
674 } 673 }
675 674
676 if (on) { 675 if (on) {
677 this.spinnerTimer_ = setTimeout(function() { 676 this.spinnerTimer_ = setTimeout(function() {
678 this.spinnerTimer_ = null; 677 this.spinnerTimer_ = null;
679 ImageUtil.setAttribute(this.container_, 'spinner', true); 678 ImageUtil.setAttribute(this.container_, 'spinner', true);
680 }.bind(this), 1000); 679 }.bind(this), 1000);
681 } else { 680 } else {
682 ImageUtil.setAttribute(this.container_, 'spinner', false); 681 ImageUtil.setAttribute(this.container_, 'spinner', false);
683 } 682 }
684 } 683 };
685 684
686 Gallery.prototype.showErrorBanner_ = function(message) { 685 Gallery.prototype.showErrorBanner_ = function(message) {
687 if (message) { 686 if (message) {
688 this.errorBanner_.textContent = this.displayStringFunction_(message); 687 this.errorBanner_.textContent = this.displayStringFunction_(message);
689 } 688 }
690 ImageUtil.setAttribute(this.container_, 'error', !!message); 689 ImageUtil.setAttribute(this.container_, 'error', !!message);
691 }; 690 };
692 691
693 Gallery.prototype.isShowingVideo_ = function() { 692 Gallery.prototype.isShowingVideo_ = function() {
694 return !!this.imageView_.getVideo(); 693 return !!this.imageView_.getVideo();
(...skipping 30 matching lines...) Expand all
725 724
726 // isEditing_ has just been flipped to a new value. 725 // isEditing_ has just been flipped to a new value.
727 if (this.isEditing_()) { 726 if (this.isEditing_()) {
728 if (this.context_.readonlyDirName) { 727 if (this.context_.readonlyDirName) {
729 this.editor_.getPrompt().showAt( 728 this.editor_.getPrompt().showAt(
730 'top', 'readonly_warning', 0, this.context_.readonlyDirName); 729 'top', 'readonly_warning', 0, this.context_.readonlyDirName);
731 } 730 }
732 this.cancelFading_(); 731 this.cancelFading_();
733 } else { 732 } else {
734 this.editor_.getPrompt().hide(); 733 this.editor_.getPrompt().hide();
735 if (!this.isShowingVideo_()) {
736 var item = this.ribbon_.getSelectedItem();
737 this.editor_.requestImage(item.updateThumbnail.bind(item));
738 }
739 this.filenameSpacer_.removeAttribute('saved'); 734 this.filenameSpacer_.removeAttribute('saved');
740 this.filenameSpacer_.removeAttribute('overwrite'); 735 this.filenameSpacer_.removeAttribute('overwrite');
741 this.saveChanges_();
742 this.initiateFading_(); 736 this.initiateFading_();
743 } 737 }
744 738
745 ImageUtil.setAttribute(this.editButton_, 'pressed', this.isEditing_()); 739 ImageUtil.setAttribute(this.editButton_, 'pressed', this.isEditing_());
746 }; 740 };
747 741
748 Gallery.prototype.isSharing_ = function(event) { 742 Gallery.prototype.isSharing_ = function(event) {
749 return this.shareMode_ && this.shareMode_ == this.editor_.getMode(); 743 return this.shareMode_ && this.shareMode_ == this.editor_.getMode();
750 }; 744 };
751 745
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after
900 this.lastVisibleIndex_ = -1; // Zero thumbnails 894 this.lastVisibleIndex_ = -1; // Zero thumbnails
901 this.sequenceDirection_ = 0; 895 this.sequenceDirection_ = 0;
902 this.sequenceLength_ = 0; 896 this.sequenceLength_ = 0;
903 }; 897 };
904 898
905 Ribbon.prototype.add = function(url) { 899 Ribbon.prototype.add = function(url) {
906 var index = this.items_.length; 900 var index = this.items_.length;
907 var selectClosure = this.select.bind(this, index, 0, null); 901 var selectClosure = this.select.bind(this, index, 0, null);
908 var item = new Ribbon.Item(index, url, this.document_, selectClosure); 902 var item = new Ribbon.Item(index, url, this.document_, selectClosure);
909 this.items_.push(item); 903 this.items_.push(item);
910 this.metadataProvider_.fetch(url, item.setMetadata.bind(item));
911 }; 904 };
912 905
913 Ribbon.prototype.load = function(urls, selectedIndex) { 906 Ribbon.prototype.load = function(urls, selectedIndex) {
914 this.clear(); 907 this.clear();
915 for (var index = 0; index < urls.length; ++index) { 908 for (var index = 0; index < urls.length; ++index) {
916 this.add(urls[index]); 909 this.add(urls[index]);
917 } 910 }
918 this.selectedIndex_ = selectedIndex; 911 this.selectedIndex_ = selectedIndex;
919 912
920 // We do not want to call this.select because the selected image is already 913 // We do not want to call this.select because the selected image is already
921 // displayed. Instead we just update the UI. 914 // displayed. Instead we just update the UI.
922 this.getSelectedItem().select(true); 915 this.getSelectedItem().select(true);
923 this.redraw(); 916 this.redraw();
924 917
925 // Let the thumbnails load before prefetching the next image. 918 // Let the thumbnails load before prefetching the next image.
926 setTimeout(this.requestPrefetch.bind(this, 1), 1000); 919 setTimeout(this.requestPrefetch.bind(this, 1), 1000);
927 920
928 // Make the arrows visible if there are more than 1 image. 921 // Make the arrows visible if there are more than 1 image.
929 ImageUtil.setAttribute(this.arrowLeft_, 'active', this.items_.length > 1); 922 ImageUtil.setAttribute(this.arrowLeft_, 'active', this.items_.length > 1);
930 ImageUtil.setAttribute(this.arrowRight_, 'active', this.items_.length > 1); 923 ImageUtil.setAttribute(this.arrowRight_, 'active', this.items_.length > 1);
931 }; 924 };
932 925
933 Ribbon.prototype.select = function(index, opt_forceStep, opt_callback) { 926 Ribbon.prototype.select = function(index, opt_forceStep, opt_callback) {
934 if (index == this.selectedIndex_) 927 if (index == this.selectedIndex_)
935 return; // Do not reselect. 928 return; // Do not reselect.
936 929
937 var oldSelectedItem = this.getSelectedItem(); 930 this.client_.closeImage(
938 oldSelectedItem.select(false); 931 this.doSelect_.bind(this, index, opt_forceStep, opt_callback));
939 this.client_.closeImage(oldSelectedItem); 932 };
933
934 Ribbon.prototype.doSelect_ = function(index, opt_forceStep, opt_callback) {
935 if (index == this.selectedIndex_)
936 return; // Do not reselect
937
938 var selectedItem = this.getSelectedItem();
939 selectedItem.select(false);
940 940
941 var step = opt_forceStep || (index - this.selectedIndex_); 941 var step = opt_forceStep || (index - this.selectedIndex_);
942 942
943 if (Math.abs(step) != 1) { 943 if (Math.abs(step) != 1) {
944 // Long leap, the sequence is broken, we have no good prefetch candidate. 944 // Long leap, the sequence is broken, we have no good prefetch candidate.
945 this.sequenceDirection_ = 0; 945 this.sequenceDirection_ = 0;
946 this.sequenceLength_ = 0; 946 this.sequenceLength_ = 0;
947 } else if (this.sequenceDirection_ == step) { 947 } else if (this.sequenceDirection_ == step) {
948 // Keeping going in sequence. 948 // Keeping going in sequence.
949 this.sequenceLength_++; 949 this.sequenceLength_++;
950 } else { 950 } else {
951 // Reversed the direction. Reset the counter. 951 // Reversed the direction. Reset the counter.
952 this.sequenceDirection_ = step; 952 this.sequenceDirection_ = step;
953 this.sequenceLength_ = 1; 953 this.sequenceLength_ = 1;
954 } 954 }
955 955
956 if (this.sequenceLength_ <= 1) { 956 if (this.sequenceLength_ <= 1) {
957 // We have just broke the sequence. Touch the current image so that it stays 957 // We have just broke the sequence. Touch the current image so that it stays
958 // in the cache longer. 958 // in the cache longer.
959 this.client_.prefetchImage(oldSelectedItem.getIndex(), 959 this.client_.prefetchImage(selectedItem.getIndex(), selectedItem.getUrl());
960 oldSelectedItem.getContent(), oldSelectedItem.getMetadata());
961 } 960 }
962 961
963 this.selectedIndex_ = index; 962 this.selectedIndex_ = index;
964 963
965 var selectedItem = this.getSelectedItem(); 964 selectedItem = this.getSelectedItem();
966 selectedItem.select(true); 965 selectedItem.select(true);
967 this.redraw(); 966 this.redraw();
968 967
969 function shouldPrefetch(loadType, step, sequenceLength) { 968 function shouldPrefetch(loadType, step, sequenceLength) {
970 // Never prefetch when selecting out of sequence. 969 // Never prefetch when selecting out of sequence.
971 if (Math.abs(step) != 1) 970 if (Math.abs(step) != 1)
972 return false; 971 return false;
973 972
974 // Never prefetch after a video load (decoding the next image can freeze 973 // Never prefetch after a video load (decoding the next image can freeze
975 // the UI for a second or two). 974 // the UI for a second or two).
976 if (loadType == ImageView.LOAD_TYPE_VIDEO_FILE) 975 if (loadType == ImageView.LOAD_TYPE_VIDEO_FILE)
977 return false; 976 return false;
978 977
979 // Always prefetch if the previous load was from cache. 978 // Always prefetch if the previous load was from cache.
980 if (loadType == ImageView.LOAD_TYPE_CACHED_FULL) 979 if (loadType == ImageView.LOAD_TYPE_CACHED_FULL)
981 return true; 980 return true;
982 981
983 // Prefetch if we have been going in the same direction for long enough. 982 // Prefetch if we have been going in the same direction for long enough.
984 return sequenceLength >= 3; 983 return sequenceLength >= 3;
985 } 984 }
986 985
987 var self = this; 986 var self = this;
988 selectedItem.fetchMetadata(this.metadataProvider_, function(metadata){ 987 this.metadataProvider_.fetch(selectedItem.getUrl(), function(metadata){
989 if (!selectedItem.isSelected()) return; 988 if (!selectedItem.isSelected()) return;
990 self.client_.openImage( 989 self.client_.openImage(
991 selectedItem.getIndex(), selectedItem.getContent(), metadata, step, 990 selectedItem.getIndex(), selectedItem.getUrl(), metadata, step,
992 function(loadType) { 991 function(loadType) {
993 if (!selectedItem.isSelected()) return; 992 if (!selectedItem.isSelected()) return;
994 if (shouldPrefetch(loadType, step, self.sequenceLength_)) { 993 if (shouldPrefetch(loadType, step, self.sequenceLength_)) {
995 self.requestPrefetch(step); 994 self.requestPrefetch(step);
996 } 995 }
997 if (opt_callback) opt_callback(); 996 if (opt_callback) opt_callback();
998 }); 997 });
999 }); 998 });
1000 }; 999 };
1001 1000
1002 Ribbon.prototype.requestPrefetch = function(direction) { 1001 Ribbon.prototype.requestPrefetch = function(direction) {
1003 if (this.items_.length < 2) return; 1002 if (this.items_.length < 2) return;
1004 1003
1005 var index = this.getNextSelectedIndex_(direction); 1004 var index = this.getNextSelectedIndex_(direction);
1005 var nextItemUrl = this.items_[index].getUrl();
1006 1006
1007 var selectedItem = this.getSelectedItem(); 1007 var selectedItem = this.getSelectedItem();
1008 var self = this; 1008 this.metadataProvider_.fetch(nextItemUrl, function(metadata) {
1009 var item = this.items_[index];
1010 item.fetchMetadata(this.metadataProvider_, function(metadata) {
1011 if (!selectedItem.isSelected()) return; 1009 if (!selectedItem.isSelected()) return;
1012 self.client_.prefetchImage(index, item.getContent(), metadata); 1010 this.client_.prefetchImage(index, nextItemUrl, metadata);
1013 }); 1011 }.bind(this));
1014 }; 1012 };
1015 1013
1016 Ribbon.ITEMS_COUNT = 5; 1014 Ribbon.ITEMS_COUNT = 5;
1017 1015
1018 Ribbon.prototype.redraw = function() { 1016 Ribbon.prototype.redraw = function() {
1019 // Never show a single thumbnail. 1017 // Never show a single thumbnail.
1020 if (this.items_.length == 1) 1018 if (this.items_.length == 1)
1021 return; 1019 return;
1022 1020
1021 var initThumbnail = function(index) {
1022 var item = this.items_[index];
1023 if (!item.hasThumbnail())
1024 this.metadataProvider_.fetch(item.getUrl(), item.setThumbnail.bind(item));
1025 }.bind(this);
1026
1023 // TODO(dgozman): use margin instead of 2 here. 1027 // TODO(dgozman): use margin instead of 2 here.
1024 var itemWidth = this.bar_.clientHeight - 2; 1028 var itemWidth = this.bar_.clientHeight - 2;
1025 var fullItems = Ribbon.ITEMS_COUNT; 1029 var fullItems = Ribbon.ITEMS_COUNT;
1026 fullItems = Math.min(fullItems, this.items_.length); 1030 fullItems = Math.min(fullItems, this.items_.length);
1027 var right = Math.floor((fullItems - 1) / 2); 1031 var right = Math.floor((fullItems - 1) / 2);
1028 1032
1029 var fullWidth = fullItems * itemWidth; 1033 var fullWidth = fullItems * itemWidth;
1030 this.bar_.style.width = fullWidth + 'px'; 1034 this.bar_.style.width = fullWidth + 'px';
1031 1035
1032 var lastIndex = this.selectedIndex_ + right; 1036 var lastIndex = this.selectedIndex_ + right;
(...skipping 11 matching lines...) Expand all
1044 this.lastVisibleIndex_ = lastIndex; 1048 this.lastVisibleIndex_ = lastIndex;
1045 } 1049 }
1046 1050
1047 this.bar_.textContent = ''; 1051 this.bar_.textContent = '';
1048 var startIndex = Math.min(firstIndex, this.firstVisibleIndex_); 1052 var startIndex = Math.min(firstIndex, this.firstVisibleIndex_);
1049 var toRemove = []; 1053 var toRemove = [];
1050 // All the items except the first one treated equally. 1054 // All the items except the first one treated equally.
1051 for (var index = startIndex + 1; 1055 for (var index = startIndex + 1;
1052 index <= Math.max(lastIndex, this.lastVisibleIndex_); 1056 index <= Math.max(lastIndex, this.lastVisibleIndex_);
1053 ++index) { 1057 ++index) {
1058 initThumbnail(index);
1054 var box = this.items_[index].getBox(); 1059 var box = this.items_[index].getBox();
1055 box.style.marginLeft = '0'; 1060 box.style.marginLeft = '0';
1056 this.bar_.appendChild(box); 1061 this.bar_.appendChild(box);
1057 if (index < firstIndex || index > lastIndex) { 1062 if (index < firstIndex || index > lastIndex) {
1058 toRemove.push(index); 1063 toRemove.push(index);
1059 } 1064 }
1060 } 1065 }
1061 1066
1062 var margin = itemWidth * Math.abs(firstIndex - this.firstVisibleIndex_); 1067 var margin = itemWidth * Math.abs(firstIndex - this.firstVisibleIndex_);
1068 initThumbnail(startIndex);
1063 var startBox = this.items_[startIndex].getBox(); 1069 var startBox = this.items_[startIndex].getBox();
1064 if (startIndex == firstIndex) { 1070 if (startIndex == firstIndex) {
1065 // Sliding to the right. 1071 // Sliding to the right.
1066 startBox.style.marginLeft = -margin + 'px'; 1072 startBox.style.marginLeft = -margin + 'px';
1067 if (this.bar_.firstChild) 1073 if (this.bar_.firstChild)
1068 this.bar_.insertBefore(startBox, this.bar_.firstChild); 1074 this.bar_.insertBefore(startBox, this.bar_.firstChild);
1069 else 1075 else
1070 this.bar_.appendChild(startBox); 1076 this.bar_.appendChild(startBox);
1071 setTimeout(function() { 1077 setTimeout(function() {
1072 startBox.style.marginLeft = '0'; 1078 startBox.style.marginLeft = '0';
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after
1186 }; 1192 };
1187 1193
1188 Ribbon.Item.prototype.isSelected = function() { 1194 Ribbon.Item.prototype.isSelected = function() {
1189 return this.box_.hasAttribute('selected'); 1195 return this.box_.hasAttribute('selected');
1190 }; 1196 };
1191 1197
1192 Ribbon.Item.prototype.select = function(on) { 1198 Ribbon.Item.prototype.select = function(on) {
1193 ImageUtil.setAttribute(this.box_, 'selected', on); 1199 ImageUtil.setAttribute(this.box_, 'selected', on);
1194 }; 1200 };
1195 1201
1196 Ribbon.Item.prototype.updateThumbnail = function(canvas) { 1202 Ribbon.Item.prototype.saveToFile = function(
1197 if (this.canvas_) 1203 dirEntry, canvas, metadataEncoder, opt_callback) {
1198 return; // The image is being saved, the thumbnail is already up-to-date.
1199
1200 var metadataEncoder =
1201 ImageEncoder.encodeMetadata(this.getMetadata(), canvas, 1);
1202 this.setMetadata(metadataEncoder.getMetadata());
1203 };
1204
1205 Ribbon.Item.prototype.save = function(
1206 dirEntry, metadataProvider, canvas, opt_callback) {
1207 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('SaveTime')); 1204 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('SaveTime'));
1208 1205
1209 var metadataEncoder =
1210 ImageEncoder.encodeMetadata(this.getMetadata(), canvas, 1);
1211
1212 this.overrideContent(canvas, metadataEncoder.getMetadata());
1213
1214 var self = this; 1206 var self = this;
1215 1207
1216 if (!dirEntry) { // Happens only in gallery_demo.js
dgozman 2012/05/16 16:05:40 I've just found the gallery_demo.js. We should rem
Vladislav Kaznacheev 2012/05/17 09:04:40 Done. Also removed the code needed to support it.
1217 self.onSaveSuccess(
1218 Gallery.blobToURL_(ImageEncoder.getBlob(canvas, metadataEncoder)));
1219 if (opt_callback) opt_callback();
1220 return;
1221 }
1222
1223 var name = this.getNameAfterSaving(); 1208 var name = this.getNameAfterSaving();
1224 this.original_ = false; 1209 this.original_ = false;
1225 this.nameForSaving_ = null; 1210 this.nameForSaving_ = null;
1226 1211
1227 function onSuccess(url) { 1212 function onSuccess(url) {
1228 console.log('Saved from gallery', name); 1213 console.log('Saved from gallery', name);
1229 // Force the metadata provider to reread the metadata from the file. 1214 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 1, 2);
1230 metadataProvider.reset(url); 1215 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('SaveTime'));
1231 self.onSaveSuccess(url); 1216 self.setUrl(url);
1232 if (opt_callback) opt_callback(); 1217 if (opt_callback) opt_callback(true);
1233 } 1218 }
1234 1219
1235 function onError(error) { 1220 function onError(error) {
1236 console.log('Error saving from gallery', name, error); 1221 console.log('Error saving from gallery', name, error);
1237 self.onSaveError(error); 1222 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 0, 2);
1238 if (opt_callback) opt_callback(); 1223 if (opt_callback) opt_callback(false);
1239 } 1224 }
1240 1225
1241 function doSave(newFile, fileEntry) { 1226 function doSave(newFile, fileEntry) {
1242 fileEntry.createWriter(function(fileWriter) { 1227 fileEntry.createWriter(function(fileWriter) {
1243 function writeContent() { 1228 function writeContent() {
1244 fileWriter.onwriteend = onSuccess.bind(null, fileEntry.toURL()); 1229 fileWriter.onwriteend = onSuccess.bind(null, fileEntry.toURL());
1245 fileWriter.write(ImageEncoder.getBlob(canvas, metadataEncoder)); 1230 fileWriter.write(ImageEncoder.getBlob(canvas, metadataEncoder));
1246 } 1231 }
1247 fileWriter.onerror = onError; 1232 fileWriter.onerror = onError;
1248 if (newFile) { 1233 if (newFile) {
(...skipping 17 matching lines...) Expand all
1266 1251
1267 // TODO: Localize? 1252 // TODO: Localize?
1268 Ribbon.Item.COPY_SIGNATURE = 'Edited'; 1253 Ribbon.Item.COPY_SIGNATURE = 'Edited';
1269 1254
1270 Ribbon.Item.REGEXP_COPY_N = 1255 Ribbon.Item.REGEXP_COPY_N =
1271 new RegExp('^' + Ribbon.Item.COPY_SIGNATURE + ' \\((\\d+)\\)( - .+)$'); 1256 new RegExp('^' + Ribbon.Item.COPY_SIGNATURE + ' \\((\\d+)\\)( - .+)$');
1272 1257
1273 Ribbon.Item.REGEXP_COPY_0 = 1258 Ribbon.Item.REGEXP_COPY_0 =
1274 new RegExp('^' + Ribbon.Item.COPY_SIGNATURE + '( - .+)$'); 1259 new RegExp('^' + Ribbon.Item.COPY_SIGNATURE + '( - .+)$');
1275 1260
1276 Ribbon.Item.prototype.createCopyName_ = function(dirEntry, callback) { 1261 Ribbon.Item.prototype.createCopyName_ = function(dirEntry, metadata, callback) {
1277 var name = ImageUtil.getFullNameFromUrl(this.url_); 1262 var name = ImageUtil.getFullNameFromUrl(this.url_);
1278 1263
1279 // If the item represents a file created during the current Gallery session 1264 // If the item represents a file created during the current Gallery session
1280 // we reuse it for subsequent saves instead of creating multiple copies. 1265 // we reuse it for subsequent saves instead of creating multiple copies.
1281 if (!this.original_) 1266 if (!this.original_)
1282 return name; 1267 return name;
1283 1268
1284 var ext = ''; 1269 var ext = '';
1285 var index = name.lastIndexOf('.'); 1270 var index = name.lastIndexOf('.');
1286 if (index != -1) { 1271 if (index != -1) {
1287 ext = name.substr(index); 1272 ext = name.substr(index);
1288 name = name.substr(0, index); 1273 name = name.substr(0, index);
1289 } 1274 }
1290 1275
1291 var mimeType = this.metadata_.mimeType.toLowerCase(); 1276 var mimeType = metadata.mimeType.toLowerCase();
1292 if (mimeType != 'image/jpeg') { 1277 if (mimeType != 'image/jpeg') {
1293 // Chrome can natively encode only two formats: JPEG and PNG. 1278 // Chrome can natively encode only two formats: JPEG and PNG.
1294 // All non-JPEG images are saved in PNG, hence forcing the file extension. 1279 // All non-JPEG images are saved in PNG, hence forcing the file extension.
1295 ext = '.png'; 1280 ext = '.png';
1296 } 1281 }
1297 1282
1298 function tryNext(tries) { 1283 function tryNext(tries) {
1299 // All the names are used. Let's overwrite the last one. 1284 // All the names are used. Let's overwrite the last one.
1300 if (tries == 0) { 1285 if (tries == 0) {
1301 setTimeout(callback, 0, name + ext); 1286 setTimeout(callback, 0, name + ext);
(...skipping 14 matching lines...) Expand all
1316 } 1301 }
1317 1302
1318 dirEntry.getFile(name + ext, {create: false, exclusive: false}, 1303 dirEntry.getFile(name + ext, {create: false, exclusive: false},
1319 tryNext.bind(null, tries - 1), 1304 tryNext.bind(null, tries - 1),
1320 callback.bind(null, name + ext)); 1305 callback.bind(null, name + ext));
1321 } 1306 }
1322 1307
1323 tryNext(10); 1308 tryNext(10);
1324 }; 1309 };
1325 1310
1326 Ribbon.Item.prototype.setCopyName = function(dirEntry, opt_callback) { 1311 Ribbon.Item.prototype.setCopyName = function(dirEntry, metadata, opt_callback) {
1327 this.createCopyName_(dirEntry, function(name) { 1312 this.createCopyName_(dirEntry, metadata, function(name) {
1328 this.nameForSaving_ = name; 1313 this.nameForSaving_ = name;
1329 if (opt_callback) opt_callback(); 1314 if (opt_callback) opt_callback();
1330 }.bind(this)); 1315 }.bind(this));
1331 }; 1316 };
1332 1317
1333 Ribbon.Item.prototype.setOriginalName = function(dirEntry, opt_callback) { 1318 Ribbon.Item.prototype.setOriginalName = function(dirEntry, opt_callback) {
1334 this.nameForSaving_ = null; 1319 this.nameForSaving_ = null;
1335 if (opt_callback) opt_callback(); 1320 if (opt_callback) opt_callback();
1336 }; 1321 };
1337 1322
1338 Ribbon.Item.prototype.setNameForSaving = function(newName) { 1323 Ribbon.Item.prototype.setNameForSaving = function(newName) {
1339 this.nameForSaving_ = newName; 1324 this.nameForSaving_ = newName;
1340 }; 1325 };
1341 1326
1342 // The url and metadata stored in the item are not valid while the modified 1327 Ribbon.Item.prototype.hasThumbnail = function() {
1343 // image is being saved. Use the results of the latest edit instead. 1328 return this.img_.hasAttribute('src');
1344
1345 Ribbon.Item.prototype.overrideContent = function(canvas, metadata) {
1346 this.canvas_ = canvas;
1347 this.backupMetadata_ = this.metadata_;
1348 this.setMetadata(metadata);
1349 }; 1329 };
1350 1330
1351 Ribbon.Item.prototype.getContent = function () { 1331 Ribbon.Item.prototype.setThumbnail = function(metadata) {
1352 return this.canvas_ || this.url_;
1353 };
1354
1355 Ribbon.Item.prototype.getMetadata = function () {
1356 return this.metadata_;
1357 };
1358
1359 Ribbon.Item.prototype.fetchMetadata = function (metadataProvider, callback) {
1360 if (this.metadata_) {
1361 callback(this.metadata_); // Every millisecond counts, call directly
1362 } else {
1363 metadataProvider.fetch(this.getUrl(), callback);
1364 }
1365 };
1366
1367 Ribbon.Item.prototype.onSaveSuccess = function(url) {
1368 this.url_ = url;
1369 delete this.backupMetadata_;
1370 delete this.canvas_;
1371 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 1, 2);
1372 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('SaveTime'));
1373 };
1374
1375 Ribbon.Item.prototype.onSaveError = function(error) {
1376 // TODO(kaznacheev): notify the user that the file write failed and
1377 // suggest ways to rescue the modified image (retry/save elsewhere).
1378 // For now - just drop the modified content and revert the thumbnail.
1379 this.setMetadata(this.backupMetadata_);
1380 delete this.backupMetadata_;
1381 delete this.canvas_;
1382 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 0, 2);
1383 };
1384
1385 Ribbon.Item.prototype.setMetadata = function(metadata) {
1386 this.metadata_ = metadata;
1387
1388 var url; 1332 var url;
1389 var transform; 1333 var transform;
1390 1334
1391 var mediaType = FileType.getMediaType(this.url_); 1335 var mediaType = FileType.getMediaType(this.url_);
1392 1336
1393 if (metadata.thumbnailURL) { 1337 if (metadata.thumbnailURL) {
1394 url = metadata.thumbnailURL; 1338 url = metadata.thumbnailURL;
1395 transform = metadata.thumbnailTransform; 1339 transform = metadata.thumbnailTransform;
1396 } else if (mediaType == 'image' && 1340 } else if (mediaType == 'image' &&
1397 FileType.canUseImageUrlForPreview(metadata)) { 1341 FileType.canUseImageUrlForPreview(metadata)) {
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
1489 ShareMode.prototype.setUp = function() { 1433 ShareMode.prototype.setUp = function() {
1490 ImageEditor.Mode.prototype.setUp.apply(this, arguments); 1434 ImageEditor.Mode.prototype.setUp.apply(this, arguments);
1491 ImageUtil.setAttribute(this.menu_, 'hidden', false); 1435 ImageUtil.setAttribute(this.menu_, 'hidden', false);
1492 ImageUtil.setAttribute(this.button_, 'pressed', false); 1436 ImageUtil.setAttribute(this.button_, 'pressed', false);
1493 }; 1437 };
1494 1438
1495 ShareMode.prototype.cleanUpUI = function() { 1439 ShareMode.prototype.cleanUpUI = function() {
1496 ImageEditor.Mode.prototype.cleanUpUI.apply(this, arguments); 1440 ImageEditor.Mode.prototype.cleanUpUI.apply(this, arguments);
1497 ImageUtil.setAttribute(this.menu_, 'hidden', true); 1441 ImageUtil.setAttribute(this.menu_, 'hidden', true);
1498 }; 1442 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698