summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormtomasz@chromium.org <mtomasz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-28 11:34:43 +0000
committermtomasz@chromium.org <mtomasz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-28 11:34:43 +0000
commit6fc069f8379d954063b314cba416916923d0d93c (patch)
tree16dd72284d89d9df965ef6f5a01d9deb3c4f276b
parent53ab5a4d4e5e1307bd0b290e2fdcadaf3e065a59 (diff)
downloadchromium_src-6fc069f8379d954063b314cba416916923d0d93c.zip
chromium_src-6fc069f8379d954063b314cba416916923d0d93c.tar.gz
chromium_src-6fc069f8379d954063b314cba416916923d0d93c.tar.bz2
Enabled Mosaic view on each volume.
Since the Image loader extension has been introduced, there is no image size bottle-neck anymore. Therefore, we can generate thumbnails for full-resolution images without affecting the ui. However, generating thumbnails from a full-resolution image takes time, therefore the mosaic mode's been modified, so it is possible to use the mosaic view while loading pictures. There is no need to wait until all the images are loaded. TEST=Check mosaic view on usb drive in downloads and on google drive. BUG=176216 Review URL: https://chromiumcodereview.appspot.com/12316118 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@185227 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/resources/file_manager/css/gallery.css17
-rw-r--r--chrome/browser/resources/file_manager/css/photo_import.css2
-rw-r--r--chrome/browser/resources/file_manager/js/file_tasks.js1
-rw-r--r--chrome/browser/resources/file_manager/js/media/media_util.js49
-rw-r--r--chrome/browser/resources/file_manager/js/photo/gallery.js31
-rw-r--r--chrome/browser/resources/file_manager/js/photo/mosaic_mode.js190
6 files changed, 215 insertions, 75 deletions
diff --git a/chrome/browser/resources/file_manager/css/gallery.css b/chrome/browser/resources/file_manager/css/gallery.css
index bea91d1..8d820b2 100644
--- a/chrome/browser/resources/file_manager/css/gallery.css
+++ b/chrome/browser/resources/file_manager/css/gallery.css
@@ -1228,9 +1228,20 @@ body {
top: 0;
}
-.mosaic-tile img {
- -webkit-user-drag: none;
- position: absolute;
+.mosaic-tile .img-wrapper[generic-thumbnail],
+.mosaic-tile .img-wrapper:not([generic-thumbnail]) canvas {
+ -webkit-animation: fadeIn ease-in 1;
+ -webkit-animation-duration: 500ms;
+ -webkit-animation-fill-mode: forwards;
+}
+
+@-webkit-keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
}
/* In order to do mode animated transitions smoothly we keep both mosaic and
diff --git a/chrome/browser/resources/file_manager/css/photo_import.css b/chrome/browser/resources/file_manager/css/photo_import.css
index 41a1254..91d4cc0 100644
--- a/chrome/browser/resources/file_manager/css/photo_import.css
+++ b/chrome/browser/resources/file_manager/css/photo_import.css
@@ -162,7 +162,7 @@ button.import {
opacity: 0;
}
to {
- opacity :1;
+ opacity: 1;
}
}
diff --git a/chrome/browser/resources/file_manager/js/file_tasks.js b/chrome/browser/resources/file_manager/js/file_tasks.js
index 5d404f7..e65d65a 100644
--- a/chrome/browser/resources/file_manager/js/file_tasks.js
+++ b/chrome/browser/resources/file_manager/js/file_tasks.js
@@ -458,7 +458,6 @@ FileTasks.prototype.openGallery = function(urls) {
metadataCache: fm.metadataCache_,
pageState: this.params_,
onClose: onClose,
- allowMosaic: fm.isOnDrive(),
onThumbnailError: function(imageURL) {
fm.metadataCache_.refreshFileMetadata(imageURL);
},
diff --git a/chrome/browser/resources/file_manager/js/media/media_util.js b/chrome/browser/resources/file_manager/js/media/media_util.js
index 07d7166..d73f529 100644
--- a/chrome/browser/resources/file_manager/js/media/media_util.js
+++ b/chrome/browser/resources/file_manager/js/media/media_util.js
@@ -12,9 +12,14 @@
* default: IMAGE.
* @param {Object=} opt_metadata Metadata object.
* @param {string=} opt_mediaType Media type.
+ * @param {ThumbnailLoader.UseEmbedded=} opt_useEmbedded If to use embedded
+ * jpeg thumbnail if available. Default: USE_EMBEDDED.
* @constructor
*/
-function ThumbnailLoader(url, opt_loaderType, opt_metadata, opt_mediaType) {
+function ThumbnailLoader(
+ url, opt_loaderType, opt_metadata, opt_mediaType, opt_useEmbedded) {
+ opt_useEmbedded = opt_useEmbedded || ThumbnailLoader.UseEmbedded.USE_EMBEDDED;
+
this.mediaType_ = opt_mediaType || FileType.getMediaType(url);
this.loaderType_ = opt_loaderType || ThumbnailLoader.LoaderType.IMAGE;
this.metadata_ = opt_metadata;
@@ -36,7 +41,8 @@ function ThumbnailLoader(url, opt_loaderType, opt_metadata, opt_mediaType) {
}
}
- if (opt_metadata.thumbnail && opt_metadata.thumbnail.url) {
+ if (opt_metadata.thumbnail && opt_metadata.thumbnail.url &&
+ opt_useEmbedded == ThumbnailLoader.UseEmbedded.USE_EMBEDDED) {
this.thumbnailUrl_ = opt_metadata.thumbnail.url;
this.transform_ = opt_metadata.thumbnail.transform;
} else if (FileType.isImage(url)) {
@@ -59,7 +65,7 @@ ThumbnailLoader.AUTO_FILL_THRESHOLD = 0.3;
/**
* Type of displaying a thumbnail within a box.
- * @enum
+ * @enum {number}
*/
ThumbnailLoader.FillMode = {
FILL: 0, // Fill whole box. Image may be cropped.
@@ -69,7 +75,7 @@ ThumbnailLoader.FillMode = {
/**
* Optimization mode for downloading thumbnails.
- * @enum
+ * @enum {number}
*/
ThumbnailLoader.OptimizationMode = {
NEVER_DISCARD: 0, // Never discards downloading. No optimization.
@@ -78,7 +84,7 @@ ThumbnailLoader.OptimizationMode = {
/**
* Type of element to store the image.
- * @enum
+ * @enum {number}
*/
ThumbnailLoader.LoaderType = {
IMAGE: 0,
@@ -86,6 +92,16 @@ ThumbnailLoader.LoaderType = {
};
/**
+ * Whether to use the embedded thumbnail, or not. The embedded thumbnail may
+ * be small.
+ * @enum {number}
+ */
+ThumbnailLoader.UseEmbedded = {
+ USE_EMBEDDED: 0,
+ NO_EMBEDDED: 1
+};
+
+/**
* Maximum thumbnail's width when generating from the full resolution image.
* @const
* @type {number}
@@ -123,6 +139,7 @@ ThumbnailLoader.prototype.load = function(box, fillMode, opt_optimizationMode,
return;
}
+ this.cancel();
this.canvasUpToDate_ = false;
this.image_ = new Image();
this.image_.onload = function() {
@@ -155,7 +172,7 @@ ThumbnailLoader.prototype.load = function(box, fillMode, opt_optimizationMode,
this.metadata_.filesystem &&
this.metadata_.filesystem.modificationTime &&
this.metadata_.filesystem.modificationTime.getTime();
- var taskId = util.loadImage(
+ this.taskId_ = util.loadImage(
this.image_,
this.thumbnailUrl_,
{ maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH,
@@ -172,11 +189,22 @@ ThumbnailLoader.prototype.load = function(box, fillMode, opt_optimizationMode,
return true;
});
- if (!taskId)
+ if (!this.taskId_)
this.image_.classList.add('cached');
};
/**
+ * Cancels loading the current image.
+ */
+ThumbnailLoader.prototype.cancel = function() {
+ if (this.taskId_) {
+ this.image_.onload = function() {};
+ this.image_.onerror = function() {};
+ util.cancelLoadImage(this.taskId_);
+ }
+};
+
+/**
* @return {boolean} True if a valid image is loaded.
*/
ThumbnailLoader.prototype.hasValidImage = function() {
@@ -217,6 +245,7 @@ ThumbnailLoader.prototype.loadDetachedImage = function(callback) {
return;
}
+ this.cancel();
this.canvasUpToDate_ = false;
this.image_ = new Image();
this.image_.onload = callback.bind(null, true);
@@ -227,7 +256,7 @@ ThumbnailLoader.prototype.loadDetachedImage = function(callback) {
this.metadata_.filesystem &&
this.metadata_.filesystem.modificationTime &&
this.metadata_.filesystem.modificationTime.getTime();
- var taskId = util.loadImage(
+ this.taskId_ = util.loadImage(
this.image_,
this.thumbnailUrl_,
{ maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH,
@@ -235,7 +264,7 @@ ThumbnailLoader.prototype.loadDetachedImage = function(callback) {
cache: true,
timestamp: modificationTime });
- if (!taskId)
+ if (!this.taskId_)
this.image_.classList.add('cached');
};
@@ -274,7 +303,7 @@ ThumbnailLoader.prototype.attachImage = function(container, fillMode) {
util.applyTransform(container, this.transform_);
ThumbnailLoader.centerImage_(
container, attachableMedia, fillMode, this.isRotated_());
- if (this.image_.parentNode != container) {
+ if (attachableMedia.parentNode != container) {
container.textContent = '';
container.appendChild(attachableMedia);
}
diff --git a/chrome/browser/resources/file_manager/js/photo/gallery.js b/chrome/browser/resources/file_manager/js/photo/gallery.js
index a27e5d4..a330743 100644
--- a/chrome/browser/resources/file_manager/js/photo/gallery.js
+++ b/chrome/browser/resources/file_manager/js/photo/gallery.js
@@ -137,7 +137,6 @@ Gallery.openStandalone = function(path, pageState, opt_callback) {
metadataCache: MetadataCache.createFull(),
pageState: pageState,
onClose: onClose,
- allowMosaic: true, /* For debugging purposes */
displayStringFunction: strf
};
Gallery.open(context, urls, selectedUrls);
@@ -148,18 +147,30 @@ Gallery.openStandalone = function(path, pageState, opt_callback) {
/**
* Tools fade-out timeout im milliseconds.
+ * @const
* @type {number}
*/
Gallery.FADE_TIMEOUT = 3000;
/**
* First time tools fade-out timeout im milliseconds.
+ * @const
* @type {number}
*/
Gallery.FIRST_FADE_TIMEOUT = 1000;
/**
+ * Time until mosaic is initialized in the background. Used to make gallery
+ * in the slide mode load faster. In miiliseconds.
+ * @const
+ * @type {number}
+ */
+Gallery.MOSAIC_BACKGROUND_INIT_DELAY = 1000;
+
+/**
* Types of metadata Gallery uses (to query the metadata cache).
+ * @const
+ * @type {string}
*/
Gallery.METADATA_TYPE = 'thumbnail|filesystem|media|streaming';
@@ -167,7 +178,6 @@ Gallery.METADATA_TYPE = 'thumbnail|filesystem|media|streaming';
* Initialize listeners.
* @private
*/
-
Gallery.prototype.initListeners_ = function() {
if (!util.TEST_HARNESS)
this.document_.oncontextmenu = function(e) { e.preventDefault(); };
@@ -259,15 +269,13 @@ Gallery.prototype.initDom_ = function() {
var onThumbnailError = this.context_.onThumbnailError || function() {};
- if (this.context_.allowMosaic) {
- this.modeButton_ = util.createChild(this.toolbar_, 'button mode', 'button');
- this.modeButton_.addEventListener('click',
- this.toggleMode_.bind(this, null));
+ this.modeButton_ = util.createChild(this.toolbar_, 'button mode', 'button');
+ this.modeButton_.addEventListener('click',
+ this.toggleMode_.bind(this, null));
- this.mosaicMode_ = new MosaicMode(content,
- this.dataModel_, this.selectionModel_, this.metadataCache_,
- this.toggleMode_.bind(this, null), onThumbnailError);
- }
+ this.mosaicMode_ = new MosaicMode(content,
+ this.dataModel_, this.selectionModel_, this.metadataCache_,
+ this.toggleMode_.bind(this, null), onThumbnailError);
this.slideMode_ = new SlideMode(this.container_, content,
this.toolbar_, this.prompt_,
@@ -363,7 +371,8 @@ Gallery.prototype.load = function(urls, selectedUrls) {
} else {
this.setCurrentMode_(this.slideMode_);
var maybeLoadMosaic = function() {
- if (mosaic) mosaic.init();
+ if (mosaic)
+ mosaic.init();
cr.dispatchSimpleEvent(this, 'loaded');
}.bind(this);
/* TODO: consider nice blow-up animation for the first image */
diff --git a/chrome/browser/resources/file_manager/js/photo/mosaic_mode.js b/chrome/browser/resources/file_manager/js/photo/mosaic_mode.js
index 16036d0..e1dd380 100644
--- a/chrome/browser/resources/file_manager/js/photo/mosaic_mode.js
+++ b/chrome/browser/resources/file_manager/js/photo/mosaic_mode.js
@@ -85,12 +85,16 @@ Mosaic.prototype.__proto__ = HTMLDivElement.prototype;
/**
* Default layout delay in ms.
+ * @const
+ * @type {number}
*/
Mosaic.LAYOUT_DELAY = 200;
/**
* Smooth scroll animation duration when scrolling using keyboard or
* clicking on a partly visible tile. In ms.
+ * @const
+ * @type {number}
*/
Mosaic.ANIMATED_SCROLL_DURATION = 500;
@@ -137,7 +141,7 @@ Mosaic.prototype.init = function() {
this.tiles_[index].select(true);
}.bind(this));
- this.loadTiles_(this.tiles_);
+ this.initTiles_(this.tiles_);
// The listeners might be called while some tiles are still loading.
this.initListeners_();
@@ -166,6 +170,7 @@ Mosaic.prototype.initListeners_ = function() {
this.addEventListener('mousemove', mouseEventBound);
this.addEventListener('mousedown', mouseEventBound);
this.addEventListener('mouseup', mouseEventBound);
+ this.addEventListener('scroll', this.onScroll_.bind(this));
this.selectionModel_.addEventListener('change', this.onSelection_.bind(this));
this.selectionModel_.addEventListener('leadIndexChange',
@@ -261,13 +266,13 @@ Mosaic.prototype.scrollIntoView = function(index) {
};
/**
- * Load multiple tiles.
+ * Initializes multiple tiles.
*
* @param {Array.<Mosaic.Tile>} tiles Array of tiles.
- * @param {function=} opt_callback Completion callback.
+ * @param {function()=} opt_callback Completion callback.
* @private
*/
-Mosaic.prototype.loadTiles_ = function(tiles, opt_callback) {
+Mosaic.prototype.initTiles_ = function(tiles, opt_callback) {
// We do not want to use tile indices in asynchronous operations because they
// do not survive data model splices. Copy tile references instead.
tiles = tiles.slice();
@@ -283,7 +288,7 @@ Mosaic.prototype.loadTiles_ = function(tiles, opt_callback) {
var chunkSize = Math.min(tiles.length, MAX_CHUNK_SIZE);
var loaded = 0;
for (var i = 0; i != chunkSize; i++) {
- this.loadTile_(tiles.shift(), function() {
+ this.initTile_(tiles.shift(), function() {
if (++loaded == chunkSize) {
this.layout();
loadChunk();
@@ -296,22 +301,19 @@ Mosaic.prototype.loadTiles_ = function(tiles, opt_callback) {
};
/**
- * Load a single tile.
+ * Initializes a single tile.
*
* @param {Mosaic.Tile} tile Tile.
- * @param {function} callback Completion callback.
+ * @param {function()} callback Completion callback.
* @private
*/
-Mosaic.prototype.loadTile_ = function(tile, callback) {
+Mosaic.prototype.initTile_ = function(tile, callback) {
var url = tile.getItem().getUrl();
- var onImageLoaded = function(success) {
- if (!success && this.onThumbnailError_) {
- this.onThumbnailError_(url);
- }
- callback();
- }.bind(this);
+ var onImageMeasured = callback;
this.metadataCache_.get(url, Gallery.METADATA_TYPE,
- function(metadata) { tile.load(metadata, onImageLoaded) });
+ function(metadata) {
+ tile.init(metadata, onImageMeasured);
+ });
};
/**
@@ -320,7 +322,7 @@ Mosaic.prototype.loadTile_ = function(tile, callback) {
Mosaic.prototype.reload = function() {
this.layoutModel_.reset_();
this.tiles_.forEach(function(t) { t.markUnloaded() });
- this.loadTiles_(this.tiles_);
+ this.initTiles_(this.tiles_);
};
/**
@@ -339,10 +341,11 @@ Mosaic.prototype.layout = function() {
if (index == this.tiles_.length)
break; // All tiles done.
var tile = this.tiles_[index];
- if (!tile.isLoaded())
+ if (!tile.isInitialized())
break; // Next layout will try to restart from here.
this.layoutModel_.add(tile, index + 1 == this.tiles_.length);
}
+ this.loadVisibleTiles_();
};
/**
@@ -397,6 +400,14 @@ Mosaic.prototype.onMouseEvent_ = function(event) {
};
/**
+ * Scroll handler.
+ * @private
+ */
+Mosaic.prototype.onScroll_ = function() {
+ this.loadVisibleTiles_();
+};
+
+/**
* Selection change handler.
*
* @param {Event} event Event.
@@ -448,7 +459,7 @@ Mosaic.prototype.onSplice_ = function(event) {
newTiles.push(new Mosaic.Tile(this, this.dataModel_.item(index + t)));
this.tiles_.splice.apply(this.tiles_, [index, 0].concat(newTiles));
- this.loadTiles_(newTiles);
+ this.initTiles_(newTiles);
}
if (this.tiles_.length != this.dataModel_.length)
@@ -473,8 +484,11 @@ Mosaic.prototype.onContentChange_ = function(event) {
console.error('Content changed for unselected item');
this.layoutModel_.invalidateFromTile_(index);
- this.tiles_[index].load(
- event.metadata, this.scheduleLayout.bind(this, Mosaic.LAYOUT_DELAY));
+ this.tiles_[index].init(event.metadata, function() {
+ this.tiles_[index].load(
+ this.scheduleLayout.bind(this, Mosaic.LAYOUT_DELAY),
+ this.onThumbnailError_);
+ }.bind(this));
};
/**
@@ -528,6 +542,29 @@ Mosaic.prototype.hide = function() {
};
/**
+ * Loads visible tiles. Ignores consecutive calls. Does not reload already
+ * loaded images.
+ * @private
+ */
+Mosaic.prototype.loadVisibleTiles_ = function() {
+ if (this.loadVisibleTilesTimer_) {
+ clearTimeout(this.loadVisibleTilesTimer_);
+ this.loadVisibleTilesTimer_ = null;
+ }
+ this.loadVisibleTilesTimer_ = setTimeout(function() {
+ var viewportRect = new Rect(0, 0, this.clientWidth, this.clientHeight);
+ for (var index = 0; index < this.tiles_.length; index++) {
+ var tile = this.tiles_[index];
+ var imageRect = tile.getImageRect();
+ if (!tile.isLoading() && !tile.isLoaded() && imageRect &&
+ imageRect.intersects(viewportRect)) {
+ tile.load(function() {}, this.onThumbnailError_);
+ }
+ }
+ }.bind(this), 100);
+};
+
+/**
* Apply or reset the zoom transform.
*
* @param {Rect} tileRect Tile rectangle. Reset the transform if null.
@@ -1588,54 +1625,107 @@ Mosaic.Tile.prototype.getMaxContentHeight = function() {
Mosaic.Tile.prototype.getAspectRatio = function() { return this.aspectRatio_ };
/**
+ * @return {boolean} True if the tile is initialized.
+ */
+Mosaic.Tile.prototype.isInitialized = function() {
+ return !!this.maxContentHeight_;
+};
+
+/**
* @return {boolean} True if the tile is loaded.
*/
-Mosaic.Tile.prototype.isLoaded = function() { return !!this.maxContentHeight_ };
+Mosaic.Tile.prototype.isLoaded = function() {
+ return this.imageLoaded_;
+};
+
+/**
+ * @return {boolean} True if the tile is being loaded.
+ */
+Mosaic.Tile.prototype.isLoading = function() {
+ return this.imageLoading_;
+};
/**
* Mark the tile as not loaded to prevent it from participating in the layout.
*/
Mosaic.Tile.prototype.markUnloaded = function() {
this.maxContentHeight_ = 0;
+ if (this.thumbnailLoader_) {
+ this.thumbnailLoader_.cancel();
+ this.imageLoaded_ = false;
+ this.imageLoading_ = false;
+ }
};
/**
- * Load the thumbnail image into the tile.
+ * Initializes the thumbnail in the tile. Does not load an image, but sets
+ * target dimensions using metadata.
*
* @param {Object} metadata Metadata object.
- * @param {function} callback Completion callback.
+ * @param {function()} onImageMeasured Image measured callback.
*/
-Mosaic.Tile.prototype.load = function(metadata, callback) {
+Mosaic.Tile.prototype.init = function(metadata, onImageMeasured) {
this.markUnloaded();
this.left_ = null; // Mark as not laid out.
- this.thumbnailLoader_ = new ThumbnailLoader(this.getItem().getUrl(),
- ThumbnailLoader.LoaderType.CANVAS,
- metadata);
-
- this.thumbnailLoader_.loadDetachedImage(function(success) {
- if (this.thumbnailLoader_.hasValidImage()) {
- var width = this.thumbnailLoader_.getWidth();
- var height = this.thumbnailLoader_.getHeight();
- if (width > height) {
- if (width > Mosaic.Tile.MAX_CONTENT_SIZE) {
- height = Math.round(height * Mosaic.Tile.MAX_CONTENT_SIZE / width);
- width = Mosaic.Tile.MAX_CONTENT_SIZE;
- }
- } else {
- if (height > Mosaic.Tile.MAX_CONTENT_SIZE) {
- width = Math.round(width * Mosaic.Tile.MAX_CONTENT_SIZE / height);
- height = Mosaic.Tile.MAX_CONTENT_SIZE;
- }
+ this.thumbnailLoader_ = new ThumbnailLoader(
+ this.getItem().getUrl(),
+ ThumbnailLoader.LoaderType.CANVAS,
+ metadata,
+ undefined, // Media type.
+ ThumbnailLoader.UseEmbedded.NO_EMBEDDED);
+
+ var setDimensions = function(width, height) {
+ if (width > height) {
+ if (width > Mosaic.Tile.MAX_CONTENT_SIZE) {
+ height = Math.round(height * Mosaic.Tile.MAX_CONTENT_SIZE / width);
+ width = Mosaic.Tile.MAX_CONTENT_SIZE;
}
- this.maxContentHeight_ = Math.max(Mosaic.Tile.MIN_CONTENT_SIZE, height);
- this.aspectRatio_ = width / height;
} else {
- this.maxContentHeight_ = Mosaic.Tile.GENERIC_ICON_SIZE;
- this.aspectRatio_ = 1;
+ if (height > Mosaic.Tile.MAX_CONTENT_SIZE) {
+ width = Math.round(width * Mosaic.Tile.MAX_CONTENT_SIZE / height);
+ height = Mosaic.Tile.MAX_CONTENT_SIZE;
+ }
}
+ this.maxContentHeight_ = Math.max(Mosaic.Tile.MIN_CONTENT_SIZE, height);
+ this.aspectRatio_ = width / height;
+ onImageMeasured();
+ }.bind(this);
- callback(success);
+ // Dimensions are always acquired from the metadata. If it is not available,
+ // then the image will not be displayed.
+ if (metadata.media && metadata.media.width) {
+ setDimensions(metadata.media.width, metadata.media.height);
+ } else {
+ // No dimensions in metadata, then display the generic icon instead.
+ // TODO(mtomasz): Display a gneric icon instead of a black rectangle.
+ setDimensions(Mosaic.Tile.GENERIC_ICON_SIZE,
+ Mosaic.Tile.GENERIC_ICON_SIZE);
+ }
+};
+
+/**
+ * Loads an image into the tile.
+ *
+ * @param {function(boolean)} onImageLoaded Callback when image is loaded.
+ * The argument is true for success, false for failure.
+ * @param {function()=} opt_onThumbnailError Callback for image loading error.
+ */
+Mosaic.Tile.prototype.load = function(onImageLoaded, opt_onThumbnailError) {
+ this.imageLoaded_ = false;
+ this.imageLoading_ = true;
+ this.thumbnailLoader_.loadDetachedImage(function(success) {
+ if (!success) {
+ if (opt_onThumbnailError)
+ opt_onThumbnailError();
+ }
+ if (this.wrapper_) {
+ this.thumbnailLoader_.attachImage(this.wrapper_,
+ ThumbnailLoader.FillMode.FILL);
+ }
+ onImageLoaded(success);
+ this.imageLoaded_ = true;
+ this.imageLoading_ = false;
}.bind(this));
};
@@ -1678,8 +1768,10 @@ Mosaic.Tile.prototype.layout = function(left, top, width, height) {
if (this.hasAttribute('selected'))
this.scrollIntoView(false);
- this.thumbnailLoader_.attachImage(this.wrapper_,
- ThumbnailLoader.FillMode.FILL);
+ if (this.imageLoaded_) {
+ this.thumbnailLoader_.attachImage(this.wrapper_,
+ ThumbnailLoader.FillMode.FILL);
+ }
};
/**