1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 * PreviewPanel UI class.
9 * @param {HTMLElement} element DOM Element of preview panel.
10 * @param {PreviewPanel.VisibilityType} visibilityType Initial value of the
12 * @param {string} currentPath Initial value of the current path.
13 * @param {MetadataCache} metadataCache Metadata cache.
15 * @extends {cr.EventTarget}
17 var PreviewPanel = function(element,
22 * The cached height of preview panel.
29 * Visibility type of the preview panel.
30 * @type {PreviewPanel.VisiblityType}
33 this.visibilityType_ = visibilityType;
36 * Current path to be displayed.
40 this.currentPath_ = currentPath || '/';
43 * Dom element of the preview panel.
47 this.element_ = element;
50 * @type {BreadcrumbsController}
52 this.breadcrumbs = new BreadcrumbsController(
53 element.querySelector('#search-breadcrumbs'), metadataCache);
56 * @type {PreviewPanel.Thumbnails}
58 this.thumbnails = new PreviewPanel.Thumbnails(
59 element.querySelector('.preview-thumbnails'), metadataCache);
65 this.summaryElement_ = element.querySelector('.preview-summary');
68 * @type {PreviewPanel.CalculatingSizeLabel}
71 this.calculatingSizeLabel_ = new PreviewPanel.CalculatingSizeLabel(
72 this.summaryElement_.querySelector('.calculating-size'));
78 this.previewText_ = element.querySelector('.preview-text');
81 * FileSelection to be displayed.
82 * @type {FileSelection}
85 this.selection_ = {entries: [], computeBytes: function() {}};
88 * Sequence value that is incremented by every selection update and is used to
89 * check if the callback is up to date or not.
95 cr.EventTarget.call(this);
99 * Name of PreviewPanels's event.
103 PreviewPanel.Event = Object.freeze({
104 // Event to be triggered at the end of visibility change.
105 VISIBILITY_CHANGE: 'visibilityChange'
109 * Visibility type of the preview panel.
111 PreviewPanel.VisibilityType = Object.freeze({
112 // Preview panel always shows.
113 ALWAYS_VISIBLE: 'alwaysVisible',
114 // Preview panel shows when the selection property are set.
116 // Preview panel does not show.
117 ALWAYS_HIDDEN: 'alwaysHidden'
123 PreviewPanel.Visibility_ = Object.freeze({
129 PreviewPanel.prototype = {
130 __proto__: cr.EventTarget.prototype,
133 * Setter for the current path.
134 * @param {string} path New path.
136 set currentPath(path) {
137 this.currentPath_ = path;
138 this.updateVisibility_();
139 this.updatePreviewArea_();
143 * Setter for the visibility type.
144 * @param {PreviewPanel.VisibilityType} visibilityType New value of visibility
147 set visibilityType(visibilityType) {
148 this.visibilityType_ = visibilityType;
149 this.updateVisibility_();
153 return this.element_.getAttribute('visibility') ==
154 PreviewPanel.Visibility_.VISIBLE;
158 * Obtains the height of preview panel.
159 * @return {number} Height of preview panel.
162 this.height_ = this.height_ || this.element_.clientHeight;
168 * Initializes the element.
170 PreviewPanel.prototype.initialize = function() {
171 this.element_.addEventListener('webkitTransitionEnd',
172 this.onTransitionEnd_.bind(this));
173 this.updatePreviewArea_();
174 this.updateVisibility_();
178 * Apply the selection and update the view of the preview panel.
179 * @param {FileSelection} selection Selection to be applied.
181 PreviewPanel.prototype.setSelection = function(selection) {
183 this.selection_ = selection;
184 this.updateVisibility_();
185 this.updatePreviewArea_();
189 * Update the visibility of the preview panel.
192 PreviewPanel.prototype.updateVisibility_ = function() {
193 // Get the new visibility value.
194 var visibility = this.element_.getAttribute('visibility');
195 var newVisible = null;
196 switch (this.visibilityType_) {
197 case PreviewPanel.VisibilityType.ALWAYS_VISIBLE:
200 case PreviewPanel.VisibilityType.AUTO:
201 newVisible = this.selection_.entries.length != 0 ||
202 !PathUtil.isRootPath(this.currentPath_);
204 case PreviewPanel.VisibilityType.ALWAYS_HIDDEN:
208 console.error('Invalid visibilityType.');
212 // If the visibility has been already the new value, just return.
213 if ((visibility == PreviewPanel.Visibility_.VISIBLE && newVisible) ||
214 (visibility == PreviewPanel.Visibility_.HIDDEN && !newVisible))
217 // Set the new visibility value.
219 this.element_.setAttribute('visibility', PreviewPanel.Visibility_.VISIBLE);
220 cr.dispatchSimpleEvent(this, PreviewPanel.Event.VISIBILITY_CHANGE);
222 this.element_.setAttribute('visibility', PreviewPanel.Visibility_.HIDING);
227 * Update the text in the preview panel.
229 * @param {boolean} breadCrumbsVisible Whether the bread crumbs is visible or
233 PreviewPanel.prototype.updatePreviewArea_ = function(breadCrumbsVisible) {
234 var selection = this.selection_;
236 // Check if the breadcrumb list should show instead on the preview text.
238 if (this.selection_.totalCount == 1)
239 path = this.selection_.entries[0].fullPath;
240 else if (this.selection_.totalCount == 0)
241 path = this.currentPath_;
244 this.breadcrumbs.show(PathUtil.getRootPath(path), path);
245 this.calculatingSizeLabel_.hidden = true;
246 this.previewText_.textContent = '';
249 this.breadcrumbs.hide();
251 // Obtains the preview text.
253 if (selection.directoryCount == 0)
254 text = strf('MANY_FILES_SELECTED', selection.fileCount);
255 else if (selection.fileCount == 0)
256 text = strf('MANY_DIRECTORIES_SELECTED', selection.directoryCount);
258 text = strf('MANY_ENTRIES_SELECTED', selection.totalCount);
260 // Obtains the size of files.
261 this.calculatingSizeLabel_.hidden = selection.bytesKnown;
262 if (selection.bytesKnown && selection.showBytes)
263 text += ', ' + util.bytesToString(selection.bytes);
265 // Set the preview text to the element.
266 this.previewText_.textContent = text;
268 // Request the byte calculation if needed.
269 if (!selection.bytesKnown) {
270 this.selection_.computeBytes(function(sequence) {
271 // Selection has been already updated.
272 if (this.sequence_ != sequence)
274 this.updatePreviewArea_();
275 }.bind(this, this.sequence_));
280 * Event handler to be called at the end of hiding transition.
281 * @param {Event} event The webkitTransitionEnd event.
284 PreviewPanel.prototype.onTransitionEnd_ = function(event) {
285 if (event.target != this.element_ || event.propertyName != 'opacity')
287 var visibility = this.element_.getAttribute('visibility');
288 if (visibility != PreviewPanel.Visibility_.HIDING)
290 this.element_.setAttribute('visibility', PreviewPanel.Visibility_.HIDDEN);
291 cr.dispatchSimpleEvent(this, PreviewPanel.Event.VISIBILITY_CHANGE);
295 * Animating label that is shown during the bytes of selection entries is being
298 * This label shows dots and varying the number of dots every
299 * CalculatingSizeLabel.PERIOD milliseconds.
300 * @param {HTMLElement} element DOM element of the label.
303 PreviewPanel.CalculatingSizeLabel = function(element) {
304 this.element_ = element;
306 this.intervalID_ = null;
311 * Time period in milliseconds.
314 PreviewPanel.CalculatingSizeLabel.PERIOD = 500;
316 PreviewPanel.CalculatingSizeLabel.prototype = {
318 * Set visibility of the label.
319 * When it is displayed, the text is animated.
320 * @param {boolean} hidden Whether to hide the label or not.
323 this.element_.hidden = hidden;
325 if (this.intervalID_ != null)
329 setInterval(this.onStep_.bind(this),
330 PreviewPanel.CalculatingSizeLabel.PERIOD);
333 if (this.intervalID_ == null)
335 clearInterval(this.intervalID_);
336 this.intervalID_ = null;
342 * Increments the counter and updates the number of dots.
345 PreviewPanel.CalculatingSizeLabel.prototype.onStep_ = function() {
346 var text = str('CALCULATING_SIZE');
347 for (var i = 0; i < ~~(this.count_ / 2) % 4; i++) {
350 this.element_.textContent = text;
355 * Thumbnails on the preview panel.
357 * @param {HTMLElement} element DOM Element of thumbnail container.
358 * @param {MetadataCache} metadataCache MetadataCache.
361 PreviewPanel.Thumbnails = function(element, metadataCache) {
362 this.element_ = element;
363 this.metadataCache_ = metadataCache;
369 * Maximum number of thumbnails.
372 PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT = 4;
375 * Edge length of the thumbnail square.
378 PreviewPanel.Thumbnails.THUMBNAIL_SIZE = 35;
381 * Longer edge length of zoomed thumbnail rectangle.
384 PreviewPanel.Thumbnails.ZOOMED_THUMBNAIL_SIZE = 200;
386 PreviewPanel.Thumbnails.prototype = {
388 * Sets entries to be displayed in the view.
389 * @param {Array.<Entry>} value Entries.
391 set selection(value) {
393 this.loadThumbnails_(value);
398 * Loads thumbnail images.
399 * @param {FileSelection} selection Selection containing entries that are
403 PreviewPanel.Thumbnails.prototype.loadThumbnails_ = function(selection) {
404 var entries = selection.entries;
405 this.element_.classList.remove('has-zoom');
406 this.element_.innerText = '';
407 var clickHandler = selection.tasks &&
408 selection.tasks.executeDefault.bind(selection.tasks);
409 var length = Math.min(entries.length,
410 PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT);
411 for (var i = 0; i < length; i++) {
413 var box = this.element_.ownerDocument.createElement('div');
414 box.style.zIndex = PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT + 1 - i;
418 FileGrid.decorateThumbnailBox(box,
421 ThumbnailLoader.FillMode.FILL,
422 FileGrid.ThumbnailQuality.LOW,
423 i == 0 && length == 1 &&
424 this.setZoomedImage_.bind(this));
427 // Register the click handler.
429 box.addEventListener('click', clickHandler);
432 this.element_.appendChild(box);
437 * Create the zoomed version of image and set it to the DOM element to show the
440 * @param {Image} image Image to be source of the zoomed image.
441 * @param {transform} transform Transformation to be applied to the image.
444 PreviewPanel.Thumbnails.prototype.setZoomedImage_ =
445 function(image, transform) {
448 var width = image.width || 0;
449 var height = image.height || 0;
452 (width < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2 &&
453 height < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2))
456 var scale = Math.min(1,
457 PreviewPanel.Thumbnails.ZOOMED_THUMBNAIL_SIZE /
458 Math.max(width, height));
459 var imageWidth = ~~(width * scale);
460 var imageHeight = ~~(height * scale);
461 var zoomedImage = this.element_.ownerDocument.createElement('img');
464 // Scaling large images kills animation. Downscale it in advance.
465 // Canvas scales images with liner interpolation. Make a larger
466 // image (but small enough to not kill animation) and let IMAGE
467 // scale it smoothly.
468 var INTERMEDIATE_SCALE = 3;
469 var canvas = this.element_.ownerDocument.createElement('canvas');
470 canvas.width = imageWidth * INTERMEDIATE_SCALE;
471 canvas.height = imageHeight * INTERMEDIATE_SCALE;
472 var ctx = canvas.getContext('2d');
473 ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
474 // Using bigger than default compression reduces image size by
475 // several times. Quality degradation compensated by greater resolution.
476 zoomedImage.src = canvas.toDataURL('image/jpeg', 0.6);
478 zoomedImage.src = image.src;
481 var boxWidth = Math.max(PreviewPanel.Thumbnails.THUMBNAIL_SIZE, imageWidth);
482 var boxHeight = Math.max(PreviewPanel.Thumbnails.THUMBNAIL_SIZE, imageHeight);
483 if (transform && transform.rotate90 % 2 == 1) {
485 boxWidth = boxHeight;
489 util.applyTransform(zoomedImage, transform);
491 var zoomedBox = this.element_.ownerDocument.createElement('div');
492 zoomedBox.className = 'popup';
493 zoomedBox.style.width = boxWidth + 'px';
494 zoomedBox.style.height = boxHeight + 'px';
495 zoomedBox.appendChild(zoomedImage);
497 this.element_.appendChild(zoomedBox);
498 this.element_.classList.add('has-zoom');