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.
6 * PreviewPanel UI class.
7 * @param {Element} element DOM Element of preview panel.
8 * @param {PreviewPanel.VisibilityType} visibilityType Initial value of the
10 * @param {MetadataCache} metadataCache Metadata cache.
11 * @param {VolumeManagerWrapper} volumeManager Volume manager.
13 * @extends {cr.EventTarget}
15 var PreviewPanel = function(element,
20 * The cached height of preview panel.
27 * Visibility type of the preview panel.
28 * @type {PreviewPanel.VisibilityType}
31 this.visibilityType_ = visibilityType;
34 * Current entry to be displayed.
38 this.currentEntry_ = null;
41 * Dom element of the preview panel.
45 this.element_ = element;
48 * @type {PreviewPanel.Thumbnails}
50 this.thumbnails = new PreviewPanel.Thumbnails(
51 element.querySelector('.preview-thumbnails'),
59 this.summaryElement_ = element.querySelector('.preview-summary');
62 * @type {PreviewPanel.CalculatingSizeLabel}
65 this.calculatingSizeLabel_ = new PreviewPanel.CalculatingSizeLabel(
66 this.summaryElement_.querySelector('.calculating-size'));
72 this.previewText_ = element.querySelector('.preview-text');
75 * FileSelection to be displayed.
76 * @type {FileSelection}
79 this.selection_ = /** @type {FileSelection} */
80 ({entries: [], computeBytes: function() {}});
83 * Sequence value that is incremented by every selection update and is used to
84 * check if the callback is up to date or not.
91 * @type {VolumeManagerWrapper}
94 this.volumeManager_ = volumeManager;
96 cr.EventTarget.call(this);
100 * Name of PreviewPanels's event.
104 PreviewPanel.Event = {
105 // Event to be triggered at the end of visibility change.
106 VISIBILITY_CHANGE: 'visibilityChange'
108 Object.freeze(PreviewPanel.Event);
111 * Visibility type of the preview panel.
115 PreviewPanel.VisibilityType = {
116 // Preview panel always shows.
117 ALWAYS_VISIBLE: 'alwaysVisible',
118 // Preview panel shows when the selection property are set.
120 // Preview panel does not show.
121 ALWAYS_HIDDEN: 'alwaysHidden'
123 Object.freeze(PreviewPanel.VisibilityType);
130 PreviewPanel.Visibility_ = {
135 Object.freeze(PreviewPanel.Visibility_);
137 PreviewPanel.prototype = {
138 __proto__: cr.EventTarget.prototype,
141 * Setter for the current entry.
142 * @param {Entry} entry New entry.
144 set currentEntry(entry) {
145 if (util.isSameEntry(this.currentEntry_, entry))
147 this.currentEntry_ = entry;
148 this.updateVisibility_();
149 this.updatePreviewArea_();
153 * Setter for the visibility type.
154 * @param {PreviewPanel.VisibilityType} visibilityType New value of visibility
157 set visibilityType(visibilityType) {
158 this.visibilityType_ = visibilityType;
159 this.updateVisibility_();
160 // Also update the preview area contents, because the update is suppressed
161 // while the visibility is hiding or hidden.
162 this.updatePreviewArea_();
166 return this.element_.getAttribute('visibility') ==
167 PreviewPanel.Visibility_.VISIBLE;
171 * Obtains the height of preview panel.
172 * @return {number} Height of preview panel.
175 this.height_ = this.height_ || this.element_.clientHeight;
181 * Initializes the element.
183 PreviewPanel.prototype.initialize = function() {
184 this.element_.addEventListener('webkitTransitionEnd',
185 this.onTransitionEnd_.bind(this));
186 this.updateVisibility_();
187 // Also update the preview area contents, because the update is suppressed
188 // while the visibility is hiding or hidden.
189 this.updatePreviewArea_();
193 * Apply the selection and update the view of the preview panel.
194 * @param {FileSelection} selection Selection to be applied.
196 PreviewPanel.prototype.setSelection = function(selection) {
198 this.selection_ = selection;
199 this.updateVisibility_();
200 this.updatePreviewArea_();
204 * Update the visibility of the preview panel.
207 PreviewPanel.prototype.updateVisibility_ = function() {
208 // Get the new visibility value.
209 var visibility = this.element_.getAttribute('visibility');
210 var newVisible = null;
211 switch (this.visibilityType_) {
212 case PreviewPanel.VisibilityType.ALWAYS_VISIBLE:
215 case PreviewPanel.VisibilityType.AUTO:
216 newVisible = this.selection_.entries.length !== 0;
218 case PreviewPanel.VisibilityType.ALWAYS_HIDDEN:
222 console.error('Invalid visibilityType.');
226 // If the visibility has been already the new value, just return.
227 if ((visibility == PreviewPanel.Visibility_.VISIBLE && newVisible) ||
228 (visibility == PreviewPanel.Visibility_.HIDDEN && !newVisible))
231 // Set the new visibility value.
233 this.element_.setAttribute('visibility', PreviewPanel.Visibility_.VISIBLE);
234 cr.dispatchSimpleEvent(this, PreviewPanel.Event.VISIBILITY_CHANGE);
236 this.element_.setAttribute('visibility', PreviewPanel.Visibility_.HIDING);
241 * Update the text in the preview panel.
244 PreviewPanel.prototype.updatePreviewArea_ = function() {
245 // If the preview panel is hiding, does not update the current view.
248 var selection = this.selection_;
250 // If no item is selected, no information is displayed on the footer.
251 if (selection.totalCount === 0) {
252 this.thumbnails.hidden = true;
253 this.calculatingSizeLabel_.hidden = true;
254 this.previewText_.textContent = '';
258 // If one item is selected, show thumbnail and entry name of the item.
259 if (selection.totalCount === 1) {
260 this.thumbnails.hidden = false;
261 this.thumbnails.selection = selection;
262 this.calculatingSizeLabel_.hidden = true;
263 this.previewText_.textContent = util.getEntryLabel(
264 this.volumeManager_, selection.entries[0]);
268 // Update thumbnails.
269 this.thumbnails.hidden = false;
270 this.thumbnails.selection = selection;
272 // Obtains the preview text.
274 if (selection.directoryCount == 0)
275 text = strf('MANY_FILES_SELECTED', selection.fileCount);
276 else if (selection.fileCount == 0)
277 text = strf('MANY_DIRECTORIES_SELECTED', selection.directoryCount);
279 text = strf('MANY_ENTRIES_SELECTED', selection.totalCount);
281 // Obtains the size of files.
282 this.calculatingSizeLabel_.hidden = selection.bytesKnown;
283 if (selection.bytesKnown && selection.showBytes)
284 text += ', ' + util.bytesToString(selection.bytes);
286 // Set the preview text to the element.
287 this.previewText_.textContent = text;
289 // Request the byte calculation if needed.
290 if (!selection.bytesKnown) {
291 this.selection_.computeBytes(function(sequence) {
292 // Selection has been already updated.
293 if (this.sequence_ != sequence)
295 this.updatePreviewArea_();
296 }.bind(this, this.sequence_));
301 * Event handler to be called at the end of hiding transition.
302 * @param {Event} event The webkitTransitionEnd event.
305 PreviewPanel.prototype.onTransitionEnd_ = function(event) {
306 if (event.target != this.element_ || event.propertyName != 'opacity')
308 var visibility = this.element_.getAttribute('visibility');
309 if (visibility != PreviewPanel.Visibility_.HIDING)
311 this.element_.setAttribute('visibility', PreviewPanel.Visibility_.HIDDEN);
312 cr.dispatchSimpleEvent(this, PreviewPanel.Event.VISIBILITY_CHANGE);
316 * Animating label that is shown during the bytes of selection entries is being
319 * This label shows dots and varying the number of dots every
320 * CalculatingSizeLabel.PERIOD milliseconds.
321 * @param {Element} element DOM element of the label.
324 PreviewPanel.CalculatingSizeLabel = function(element) {
325 this.element_ = element;
327 this.intervalID_ = 0;
332 * Time period in milliseconds.
335 PreviewPanel.CalculatingSizeLabel.PERIOD = 500;
337 PreviewPanel.CalculatingSizeLabel.prototype = {
339 * Set visibility of the label.
340 * When it is displayed, the text is animated.
341 * @param {boolean} hidden Whether to hide the label or not.
344 this.element_.hidden = hidden;
346 if (this.intervalID_ !== 0)
350 setInterval(this.onStep_.bind(this),
351 PreviewPanel.CalculatingSizeLabel.PERIOD);
354 if (this.intervalID_ === 0)
356 clearInterval(this.intervalID_);
357 this.intervalID_ = 0;
363 * Increments the counter and updates the number of dots.
366 PreviewPanel.CalculatingSizeLabel.prototype.onStep_ = function() {
367 var text = str('CALCULATING_SIZE');
368 for (var i = 0; i < ~~(this.count_ / 2) % 4; i++) {
371 this.element_.textContent = text;
376 * Thumbnails on the preview panel.
378 * @param {Element} element DOM Element of thumbnail container.
379 * @param {MetadataCache} metadataCache MetadataCache.
380 * @param {VolumeManagerWrapper} volumeManager Volume manager instance.
383 PreviewPanel.Thumbnails = function(element, metadataCache, volumeManager) {
384 this.element_ = element;
385 this.metadataCache_ = metadataCache;
386 this.volumeManager_ = volumeManager;
392 * Maximum number of thumbnails.
395 PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT = 4;
398 * Edge length of the thumbnail square.
401 PreviewPanel.Thumbnails.THUMBNAIL_SIZE = 35;
404 * Longer edge length of zoomed thumbnail rectangle.
407 PreviewPanel.Thumbnails.ZOOMED_THUMBNAIL_SIZE = 200;
409 PreviewPanel.Thumbnails.prototype = {
411 * Sets entries to be displayed in the view.
412 * @param {FileSelection} value Entries.
414 set selection(value) {
416 this.loadThumbnails_(value);
420 * Set visibility of the thumbnails.
421 * @param {boolean} value Whether to hide the thumbnails or not.
424 this.element_.hidden = value;
429 * Loads thumbnail images.
430 * @param {FileSelection} selection Selection containing entries that are
434 PreviewPanel.Thumbnails.prototype.loadThumbnails_ = function(selection) {
435 var entries = selection.entries;
436 this.element_.classList.remove('has-zoom');
437 this.element_.innerText = '';
438 var clickHandler = selection.tasks &&
439 selection.tasks.executeDefault.bind(selection.tasks);
440 var length = Math.min(entries.length,
441 PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT);
442 for (var i = 0; i < length; i++) {
444 var box = this.element_.ownerDocument.createElement('div');
445 box.style.zIndex = PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT + 1 - i;
449 FileGrid.decorateThumbnailBox(
454 ThumbnailLoader.FillMode.FILL,
455 FileGrid.ThumbnailQuality.LOW,
456 i == 0 && length == 1 ? this.setZoomedImage_.bind(this) : undefined);
459 // Register the click handler.
461 box.addEventListener('click', clickHandler);
464 this.element_.appendChild(box);
469 * Create the zoomed version of image and set it to the DOM element to show the
472 * @param {HTMLImageElement} image Image to be source of the zoomed image.
473 * @param {Object=} opt_transform Transformation to be applied to the image.
476 PreviewPanel.Thumbnails.prototype.setZoomedImage_ = function(image,
480 var width = image.width || 0;
481 var height = image.height || 0;
484 (width < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2 &&
485 height < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2))
488 var scale = Math.min(1,
489 PreviewPanel.Thumbnails.ZOOMED_THUMBNAIL_SIZE /
490 Math.max(width, height));
491 var imageWidth = ~~(width * scale);
492 var imageHeight = ~~(height * scale);
493 var zoomedImage = this.element_.ownerDocument.createElement('img');
496 // Scaling large images kills animation. Downscale it in advance.
497 // Canvas scales images with liner interpolation. Make a larger
498 // image (but small enough to not kill animation) and let IMAGE
499 // scale it smoothly.
500 var INTERMEDIATE_SCALE = 3;
501 var canvas = this.element_.ownerDocument.createElement('canvas');
502 canvas.width = imageWidth * INTERMEDIATE_SCALE;
503 canvas.height = imageHeight * INTERMEDIATE_SCALE;
504 var ctx = canvas.getContext('2d');
505 ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
506 // Using bigger than default compression reduces image size by
507 // several times. Quality degradation compensated by greater resolution.
508 zoomedImage.src = canvas.toDataURL('image/jpeg', 0.6);
510 zoomedImage.src = image.src;
513 var boxWidth = Math.max(PreviewPanel.Thumbnails.THUMBNAIL_SIZE, imageWidth);
514 var boxHeight = Math.max(PreviewPanel.Thumbnails.THUMBNAIL_SIZE, imageHeight);
515 if (opt_transform && opt_transform.rotate90 % 2 == 1) {
517 boxWidth = boxHeight;
522 util.applyTransform(zoomedImage, opt_transform);
524 var zoomedBox = this.element_.ownerDocument.createElement('div');
525 zoomedBox.className = 'popup';
526 zoomedBox.style.width = boxWidth + 'px';
527 zoomedBox.style.height = boxHeight + 'px';
528 zoomedBox.appendChild(zoomedImage);
530 this.element_.appendChild(zoomedBox);
531 this.element_.classList.add('has-zoom');