Commit b0fddcb8 authored by hirono@chromium.org's avatar hirono@chromium.org

Files.app: Let the thumbnail images in the preview panels managed by PreviewPanel class.

This CL introduce PreviewPanel.Thumbnails class and make it manage the thumbnail
images in the preview panel.  PreviewPanel.Thumbnails receives the selection of
entries, and then it loads the images and show the thumbnail properly.

BUG=241693
TEST=manually
R=yoshiki@chromium.org

Review URL: https://codereview.chromium.org/23676006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@222223 0039d316-1c4b-4281-b951-d872f2087c98
parent ac5a934e
...@@ -1100,24 +1100,42 @@ input.rename { ...@@ -1100,24 +1100,42 @@ input.rename {
padding-left: 25px; padding-left: 25px;
} }
.preview-thumbnails > .thumbnail { .preview-thumbnails > .img-container {
background-color: white; background-color: white;
border: 1px solid #fff; background-size: 35px 35px; /* For file icons. */
border: 2px solid white;
box-shadow: 0 1px 1px rgba(80, 80, 80, 0.5); box-shadow: 0 1px 1px rgba(80, 80, 80, 0.5);
box-sizing: border-box;
cursor: pointer;
height: 35px; height: 35px;
margin: 0 0 0 -25px; /* Overlapped images. */ margin: 0 0 0 -25px; /* Overlapped images. */
overflow: hidden;
position: relative; position: relative;
width: 35px; width: 35px;
} }
.preview-thumbnails > .thumbnail > .img-container { .preview-thumbnails > .popup {
background-size: 35px 35px; -webkit-transform: translate(0, 3px) scale(0.95);
border: 1px solid white; background-color: #f2f2f2;
box-sizing: border-box; border: 2px solid #fff;
height: 35px; bottom: 8px;
overflow: hidden; box-shadow: 0 0 0 1px #F0F0F0,
position: relative; 0 0 0 2px #D0D0D0,
width: 35px; 2px 2px 6px rgba(0, 0, 0, 0.2);
display: -webkit-flex;
left: -8px;
opacity: 0;
pointer-events: none;
position: absolute;
transition: opacity 180ms ease-in 300ms,
-webkit-transform 180ms ease-in 300ms;
z-index: 1000;
}
.preview-thumbnails.has-zoom:hover > .popup {
-webkit-transform: translate(0, 0) scale(1.0);
opacity: 1;
pointer-events: auto;
} }
@-webkit-keyframes fadeIn { @-webkit-keyframes fadeIn {
...@@ -1138,39 +1156,15 @@ input.rename { ...@@ -1138,39 +1156,15 @@ input.rename {
} }
} }
.preview-thumbnails > .popup { .preview-thumbnails img {
-webkit-transform: translate(0, 3px) scale(0.95); -webkit-animation: fadeIn 180ms ease-in-out;
background-color: #f2f2f2;
border: 2px solid #fff;
box-shadow: 0 0 0 1px #F0F0F0,
0 0 0 2px #D0D0D0,
2px 2px 6px rgba(0, 0, 0, 0.2);
margin: 6px 0 0 -40px;
opacity: 0;
pointer-events: none;
position: absolute;
z-index: 1; /* will be overridden with 1000 by script. */
} }
.preview-thumbnails > .popup > img { .preview-thumbnails > .popup > img {
-webkit-flex: 1 1 0;
-webkit-user-drag: none; -webkit-user-drag: none;
} }
.preview-thumbnails > * {
transition: all 180ms ease-in;
transition-delay: 300ms;
}
.preview-thumbnails.has-zoom:hover > .popup {
-webkit-transform: translate(0, 0) scale(1.0);
opacity: 1;
pointer-events: auto;
}
.preview-thumbnails.has-zoom:hover > .thumbnail {
opacity: 0;
}
/* Table splitter element */ /* Table splitter element */
.table-header-splitter { .table-header-splitter {
background-image: -webkit-image-set( background-image: -webkit-image-set(
......
...@@ -856,7 +856,8 @@ var BOTTOM_MARGIN_FOR_PREVIEW_PANEL_PX = 52; ...@@ -856,7 +856,8 @@ var BOTTOM_MARGIN_FOR_PREVIEW_PANEL_PX = 52;
DialogType.isOpenDialog(this.dialogType) ? DialogType.isOpenDialog(this.dialogType) ?
PreviewPanel.VisibilityType.ALWAYS_VISIBLE : PreviewPanel.VisibilityType.ALWAYS_VISIBLE :
PreviewPanel.VisibilityType.AUTO, PreviewPanel.VisibilityType.AUTO,
this.getCurrentDirectory()); this.getCurrentDirectory(),
this.metadataCache_);
this.previewPanel_.addEventListener( this.previewPanel_.addEventListener(
PreviewPanel.Event.VISIBILITY_CHANGE, PreviewPanel.Event.VISIBILITY_CHANGE,
this.onPreviewPanelVisibilityChange_.bind(this)); this.onPreviewPanelVisibilityChange_.bind(this));
......
...@@ -324,16 +324,16 @@ FileSelectionHandler.prototype.updateFileSelectionAsync = function(selection) { ...@@ -324,16 +324,16 @@ FileSelectionHandler.prototype.updateFileSelectionAsync = function(selection) {
// Update preview panels. // Update preview panels.
var wasVisible = this.previewPanel_.visible; var wasVisible = this.previewPanel_.visible;
var thumbnailEntries; var thumbnailSelection;
if (selection.totalCount == 0) { if (selection.totalCount != 0) {
thumbnailEntries = [ thumbnailSelection = selection;
this.fileManager_.getCurrentDirectoryEntry()
];
} else { } else {
thumbnailEntries = selection.entries; thumbnailSelection = {
entries: [this.fileManager_.getCurrentDirectoryEntry()]
};
} }
this.previewPanel_.setSelection(selection); this.previewPanel_.setSelection(selection);
this.showPreviewThumbnails_(thumbnailEntries); this.previewPanel_.thumbnails.selection = thumbnailSelection;
// Update breadcrums. // Update breadcrums.
var updateTarget = null; var updateTarget = null;
...@@ -368,113 +368,6 @@ FileSelectionHandler.prototype.updateFileSelectionAsync = function(selection) { ...@@ -368,113 +368,6 @@ FileSelectionHandler.prototype.updateFileSelectionAsync = function(selection) {
} }
}; };
/**
* Renders preview thumbnails in preview panel.
*
* @param {Array.<FileEntry>} entries The entries of selected object.
* @private
*/
FileSelectionHandler.prototype.showPreviewThumbnails_ = function(entries) {
var selection = this.selection;
var thumbnails = [];
var thumbnailCount = 0;
var thumbnailLoaded = -1;
var forcedShowTimeout = null;
var thumbnailsHaveZoom = false;
var self = this;
var showThumbnails = function() {
// have-zoom class may be updated twice: then timeout exceeds and then
// then all images loaded.
if (self.selection == selection) {
if (thumbnailsHaveZoom) {
self.previewThumbnails_.classList.add('has-zoom');
} else {
self.previewThumbnails_.classList.remove('has-zoom');
}
}
if (forcedShowTimeout === null)
return;
clearTimeout(forcedShowTimeout);
forcedShowTimeout = null;
// FileSelection could change while images are loading.
if (self.selection == selection) {
self.previewThumbnails_.textContent = '';
for (var i = 0; i < thumbnails.length; i++)
self.previewThumbnails_.appendChild(thumbnails[i]);
}
};
var onThumbnailLoaded = function() {
thumbnailLoaded++;
if (thumbnailLoaded == thumbnailCount)
showThumbnails();
};
var thumbnailClickHandler = function() {
if (selection.tasks)
selection.tasks.executeDefault();
};
var doc = this.fileManager_.document_;
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
if (thumbnailCount < FileSelectionHandler.MAX_PREVIEW_THUMBNAIL_COUNT) {
var box = doc.createElement('div');
box.className = 'thumbnail';
if (thumbnailCount == 0) {
var zoomed = doc.createElement('div');
zoomed.hidden = true;
thumbnails.push(zoomed);
var onFirstThumbnailLoaded = function(img, transform) {
if (img && self.decorateThumbnailZoom_(zoomed, img, transform)) {
zoomed.hidden = false;
thumbnailsHaveZoom = true;
}
onThumbnailLoaded();
};
var thumbnail = this.renderThumbnail_(entry, onFirstThumbnailLoaded);
zoomed.addEventListener('click', thumbnailClickHandler);
} else {
var thumbnail = this.renderThumbnail_(entry, onThumbnailLoaded);
}
thumbnailCount++;
box.appendChild(thumbnail);
box.style.zIndex =
FileSelectionHandler.MAX_PREVIEW_THUMBNAIL_COUNT + 1 - i;
box.addEventListener('click', thumbnailClickHandler);
thumbnails.push(box);
}
}
forcedShowTimeout = setTimeout(showThumbnails,
FileManager.THUMBNAIL_SHOW_DELAY);
onThumbnailLoaded();
};
/**
* Renders a thumbnail for the buttom panel.
*
* @param {Entry} entry Entry to render for.
* @param {function} callback Called when image loaded.
* @return {HTMLDivElement} Created element.
* @private
*/
FileSelectionHandler.prototype.renderThumbnail_ = function(entry, callback) {
var thumbnail = this.fileManager_.document_.createElement('div');
FileGrid.decorateThumbnailBox(thumbnail,
entry,
this.fileManager_.metadataCache_,
ThumbnailLoader.FillMode.FILL,
FileGrid.ThumbnailQuality.LOW,
callback);
return thumbnail;
};
/** /**
* Updates the breadcrumbs in the preview panel. * Updates the breadcrumbs in the preview panel.
* *
...@@ -508,76 +401,3 @@ FileSelectionHandler.prototype.updateSearchBreadcrumbs_ = function() { ...@@ -508,76 +401,3 @@ FileSelectionHandler.prototype.updateSearchBreadcrumbs_ = function() {
PathUtil.getRootPath(entry.fullPath), PathUtil.getRootPath(entry.fullPath),
entry.fullPath); entry.fullPath);
}; };
/**
* Creates enlarged image for a bottom pannel thumbnail.
* Image's assumed to be just loaded and not inserted into the DOM.
*
* @param {HTMLElement} largeImageBox DIV element to decorate.
* @param {HTMLElement} img Loaded image.
* @param {Object} transform Image transformation description.
* @return {boolean} True if zoomed image is present.
* @private
*/
FileSelectionHandler.prototype.decorateThumbnailZoom_ = function(
largeImageBox, img, transform) {
var width = img.width;
var height = img.height;
var THUMBNAIL_SIZE = 35;
if (width < THUMBNAIL_SIZE * 2 && height < THUMBNAIL_SIZE * 2)
return false;
var scale = Math.min(1,
FileSelectionHandler.IMAGE_HOVER_PREVIEW_SIZE / Math.max(width, height));
var imageWidth = Math.round(width * scale);
var imageHeight = Math.round(height * scale);
var largeImage = this.fileManager_.document_.createElement('img');
if (scale < 0.3) {
// Scaling large images kills animation. Downscale it in advance.
// Canvas scales images with liner interpolation. Make a larger
// image (but small enough to not kill animation) and let IMG
// scale it smoothly.
var INTERMEDIATE_SCALE = 3;
var canvas = this.fileManager_.document_.createElement('canvas');
canvas.width = imageWidth * INTERMEDIATE_SCALE;
canvas.height = imageHeight * INTERMEDIATE_SCALE;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// Using bigger than default compression reduces image size by
// several times. Quality degradation compensated by greater resolution.
largeImage.src = canvas.toDataURL('image/jpeg', 0.6);
} else {
largeImage.src = img.src;
}
largeImageBox.className = 'popup';
var boxWidth = Math.max(THUMBNAIL_SIZE, imageWidth);
var boxHeight = Math.max(THUMBNAIL_SIZE, imageHeight);
if (transform && transform.rotate90 % 2 == 1) {
var t = boxWidth;
boxWidth = boxHeight;
boxHeight = t;
}
var style = largeImageBox.style;
style.width = boxWidth + 'px';
style.height = boxHeight + 'px';
style.top = (-boxHeight + THUMBNAIL_SIZE) + 'px';
var style = largeImage.style;
style.width = imageWidth + 'px';
style.height = imageHeight + 'px';
style.left = (boxWidth - imageWidth) / 2 + 'px';
style.top = (boxHeight - imageHeight) / 2 + 'px';
style.position = 'relative';
util.applyTransform(largeImage, transform);
largeImageBox.appendChild(largeImage);
largeImageBox.style.zIndex = 1000;
return true;
};
...@@ -10,10 +10,14 @@ ...@@ -10,10 +10,14 @@
* @param {PreviewPanel.VisibilityType} visibilityType Initial value of the * @param {PreviewPanel.VisibilityType} visibilityType Initial value of the
* visibility type. * visibility type.
* @param {string} currentPath Initial value of the current path. * @param {string} currentPath Initial value of the current path.
* @param {MetadataCache} metadataCache Metadata cache.
* @constructor * @constructor
* @extends {cr.EventTarget} * @extends {cr.EventTarget}
*/ */
var PreviewPanel = function(element, visibilityType, currentPath) { var PreviewPanel = function(element,
visibilityType,
currentPath,
metadataCache) {
/** /**
* The cached height of preview panel. * The cached height of preview panel.
* @type {number} * @type {number}
...@@ -43,10 +47,10 @@ var PreviewPanel = function(element, visibilityType, currentPath) { ...@@ -43,10 +47,10 @@ var PreviewPanel = function(element, visibilityType, currentPath) {
this.element_ = element; this.element_ = element;
/** /**
* @type {HTMLElement} * @type {PreviewPanel.Thumbnails}
* @private
*/ */
this.thumbnailElement_ = element.querySelector('.preview-thumbnails'); this.thumbnails = new PreviewPanel.Thumbnails(
element.querySelector('.preview-thumbnails'), metadataCache);
/** /**
* @type {HTMLElement} * @type {HTMLElement}
...@@ -329,3 +333,149 @@ PreviewPanel.CalculatingSizeLabel.prototype.onStep_ = function() { ...@@ -329,3 +333,149 @@ PreviewPanel.CalculatingSizeLabel.prototype.onStep_ = function() {
this.element_.textContent = text; this.element_.textContent = text;
this.count_++; this.count_++;
}; };
/**
* Thumbnails on the preview panel.
*
* @param {HTMLElement} element DOM Element of thumbnail container.
* @param {MetadataCache} metadataCache MetadataCache.
* @constructor
*/
PreviewPanel.Thumbnails = function(element, metadataCache) {
this.element_ = element;
this.metadataCache_ = metadataCache;
this.sequence_ = 0;
Object.seal(this);
};
/**
* Maximium number of thumbnails.
* @const {number}
*/
PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT = 4;
/**
* Edge length of the thumbnail square.
* @const {number}
*/
PreviewPanel.Thumbnails.THUMBNAIL_SIZE = 35;
/**
* Longer edge length of zoomed thumbnail rectangle.
* @const {number}
*/
PreviewPanel.Thumbnails.ZOOMED_THUMBNAIL_SIZE = 200;
PreviewPanel.Thumbnails.prototype = {
/**
* Sets entries to be displayed in the view.
* @param {Array.<Entry>} value Entries.
*/
set selection(value) {
this.sequence_++;
this.loadThumbnails_(value);
}
};
/**
* Loads thumbnail images.
* @param {FileSelection} selection Selection containing entries that are
* sources of images.
* @private
*/
PreviewPanel.Thumbnails.prototype.loadThumbnails_ = function(selection) {
var entries = selection.entries;
this.element_.classList.remove('has-zoom');
this.element_.innerText = '';
var clickHandler = selection.tasks &&
selection.tasks.executeDefault.bind(selection.tasks);
var length = Math.min(entries.length,
PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT);
for (var i = 0; i < length; i++) {
// Create a box.
var box = this.element_.ownerDocument.createElement('div');
box.style.zIndex = PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT + 1 - i;
// Load the image.
FileGrid.decorateThumbnailBox(box,
entries[i],
this.metadataCache_,
ThumbnailLoader.FillMode.FILL,
FileGrid.ThumbnailQuality.LOW,
i == 0 && length == 1 &&
this.setZoomedImage_.bind(this));
// Register the click handler.
if (clickHandler)
box.addEventListener('click', clickHandler);
// Append
this.element_.appendChild(box);
}
};
/**
* Create the zoomed version of image and set it to the DOM element to show the
* zoomed image.
*
* @param {Image} image Image to be source of the zoomed image.
* @param {transform} transform Transoformation to be applied to the image.
* @private
*/
PreviewPanel.Thumbnails.prototype.setZoomedImage_ =
function(image, transform) {
if (!image)
return;
var width = image.width || 0;
var height = image.height || 0;
if (width == 0 ||
height == 0 ||
(width < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2 &&
height < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2))
return;
var scale = Math.min(1,
PreviewPanel.Thumbnails.ZOOMED_THUMBNAIL_SIZE /
Math.max(width, height));
var imageWidth = ~~(width * scale);
var imageHeight = ~~(height * scale);
var zoomedImage = this.element_.ownerDocument.createElement('img');
if (scale < 0.3) {
// Scaling large images kills animation. Downscale it in advance.
// Canvas scales images with liner interpolation. Make a larger
// image (but small enough to not kill animation) and let IMAGE
// scale it smoothly.
var INTERMEDIATE_SCALE = 3;
var canvas = this.element_.ownerDocument.createElement('canvas');
canvas.width = imageWidth * INTERMEDIATE_SCALE;
canvas.height = imageHeight * INTERMEDIATE_SCALE;
var ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
// Using bigger than default compression reduces image size by
// several times. Quality degradation compensated by greater resolution.
zoomedImage.src = canvas.toDataURL('image/jpeg', 0.6);
} else {
zoomedImage.src = image.src;
}
var boxWidth = Math.max(PreviewPanel.Thumbnails.THUMBNAIL_SIZE, imageWidth);
var boxHeight = Math.max(PreviewPanel.Thumbnails.THUMBNAIL_SIZE, imageHeight);
if (transform && transform.rotate90 % 2 == 1) {
var t = boxWidth;
boxWidth = boxHeight;
boxHeight = t;
}
util.applyTransform(zoomedImage, transform);
var zoomedBox = this.element_.ownerDocument.createElement('div');
zoomedBox.className = 'popup';
zoomedBox.style.width = boxWidth + 'px';
zoomedBox.style.height = boxHeight + 'px';
zoomedBox.appendChild(zoomedImage);
this.element_.appendChild(zoomedBox);
this.element_.classList.add('has-zoom');
return;
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment