cros: Remove default pinned apps trial.
[chromium-blink-merge.git] / chrome / browser / resources / file_manager / foreground / js / ui / preview_panel.js
bloba8d0db482df799cf1d6ceaf1f81e71bdc64d7676
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.
5 'use strict';
7 /**
8  * PreviewPanel UI class.
9  * @param {HTMLElement} element DOM Element of preview panel.
10  * @param {PreviewPanel.VisibilityType} visibilityType Initial value of the
11  *     visibility type.
12  * @param {MetadataCache} metadataCache Metadata cache.
13  * @param {VolumeManagerWrapper} volumeManager Volume manager.
14  * @constructor
15  * @extends {cr.EventTarget}
16  */
17 var PreviewPanel = function(element,
18                             visibilityType,
19                             metadataCache,
20                             volumeManager) {
21   /**
22    * The cached height of preview panel.
23    * @type {number}
24    * @private
25    */
26   this.height_ = 0;
28   /**
29    * Visibility type of the preview panel.
30    * @type {PreviewPanel.VisiblityType}
31    * @private
32    */
33   this.visibilityType_ = visibilityType;
35   /**
36    * Current entry to be displayed.
37    * @type {Entry}
38    * @private
39    */
40   this.currentEntry_ = null;
42   /**
43    * Dom element of the preview panel.
44    * @type {HTMLElement}
45    * @private
46    */
47   this.element_ = element;
49   /**
50    * @type {BreadcrumbsController}
51    */
52   this.breadcrumbs = new BreadcrumbsController(
53       element.querySelector('#search-breadcrumbs'),
54       metadataCache,
55       volumeManager);
57   /**
58    * @type {PreviewPanel.Thumbnails}
59    */
60   this.thumbnails = new PreviewPanel.Thumbnails(
61       element.querySelector('.preview-thumbnails'), metadataCache);
63   /**
64    * @type {HTMLElement}
65    * @private
66    */
67   this.summaryElement_ = element.querySelector('.preview-summary');
69   /**
70    * @type {PreviewPanel.CalculatingSizeLabel}
71    * @private
72    */
73   this.calculatingSizeLabel_ = new PreviewPanel.CalculatingSizeLabel(
74       this.summaryElement_.querySelector('.calculating-size'));
76   /**
77    * @type {HTMLElement}
78    * @private
79    */
80   this.previewText_ = element.querySelector('.preview-text');
82   /**
83    * FileSelection to be displayed.
84    * @type {FileSelection}
85    * @private
86    */
87   this.selection_ = {entries: [], computeBytes: function() {}};
89   /**
90    * Sequence value that is incremented by every selection update and is used to
91    * check if the callback is up to date or not.
92    * @type {number}
93    * @private
94    */
95   this.sequence_ = 0;
97   /**
98    * @type {VolumeManager}
99    * @private
100    */
101   this.volumeManager_ = volumeManager;
103   cr.EventTarget.call(this);
107  * Name of PreviewPanels's event.
108  * @enum {string}
109  * @const
110  */
111 PreviewPanel.Event = Object.freeze({
112   // Event to be triggered at the end of visibility change.
113   VISIBILITY_CHANGE: 'visibilityChange'
117  * Visibility type of the preview panel.
118  */
119 PreviewPanel.VisibilityType = Object.freeze({
120   // Preview panel always shows.
121   ALWAYS_VISIBLE: 'alwaysVisible',
122   // Preview panel shows when the selection property are set.
123   AUTO: 'auto',
124   // Preview panel does not show.
125   ALWAYS_HIDDEN: 'alwaysHidden'
129  * @private
130  */
131 PreviewPanel.Visibility_ = Object.freeze({
132   VISIBLE: 'visible',
133   HIDING: 'hiding',
134   HIDDEN: 'hidden'
137 PreviewPanel.prototype = {
138   __proto__: cr.EventTarget.prototype,
140   /**
141    * Setter for the current entry.
142    * @param {Entry} entry New entry.
143    */
144   set currentEntry(entry) {
145     if (util.isSameEntry(this.currentEntry_, entry))
146       return;
147     this.currentEntry_ = entry;
148     this.updateVisibility_();
149     this.updatePreviewArea_();
150   },
152   /**
153    * Setter for the visibility type.
154    * @param {PreviewPanel.VisibilityType} visibilityType New value of visibility
155    *     type.
156    */
157   set visibilityType(visibilityType) {
158     this.visibilityType_ = visibilityType;
159     this.updateVisibility_();
160   },
162   get visible() {
163     return this.element_.getAttribute('visibility') ==
164         PreviewPanel.Visibility_.VISIBLE;
165   },
167   /**
168    * Obtains the height of preview panel.
169    * @return {number} Height of preview panel.
170    */
171   get height() {
172     this.height_ = this.height_ || this.element_.clientHeight;
173     return this.height_;
174   }
178  * Initializes the element.
179  */
180 PreviewPanel.prototype.initialize = function() {
181   this.element_.addEventListener('webkitTransitionEnd',
182                                  this.onTransitionEnd_.bind(this));
183   this.updatePreviewArea_();
184   this.updateVisibility_();
188  * Apply the selection and update the view of the preview panel.
189  * @param {FileSelection} selection Selection to be applied.
190  */
191 PreviewPanel.prototype.setSelection = function(selection) {
192   this.sequence_++;
193   this.selection_ = selection;
194   this.updateVisibility_();
195   // If the previw panel is hiding, does not update the current view.
196   if (this.visible)
197     this.updatePreviewArea_();
201  * Update the visibility of the preview panel.
202  * @private
203  */
204 PreviewPanel.prototype.updateVisibility_ = function() {
205   // Get the new visibility value.
206   var visibility = this.element_.getAttribute('visibility');
207   var newVisible = null;
208   switch (this.visibilityType_) {
209     case PreviewPanel.VisibilityType.ALWAYS_VISIBLE:
210       newVisible = true;
211       break;
212     case PreviewPanel.VisibilityType.AUTO:
213       newVisible =
214           this.selection_.entries.length !== 0 ||
215           (this.currentEntry_ &&
216            !this.volumeManager_.getLocationInfo(
217                this.currentEntry_).isRootEntry);
218       break;
219     case PreviewPanel.VisibilityType.ALWAYS_HIDDEN:
220       newVisible = false;
221       break;
222     default:
223       console.error('Invalid visibilityType.');
224       return;
225   }
227   // If the visibility has been already the new value, just return.
228   if ((visibility == PreviewPanel.Visibility_.VISIBLE && newVisible) ||
229       (visibility == PreviewPanel.Visibility_.HIDDEN && !newVisible))
230     return;
232   // Set the new visibility value.
233   if (newVisible) {
234     this.element_.setAttribute('visibility', PreviewPanel.Visibility_.VISIBLE);
235     cr.dispatchSimpleEvent(this, PreviewPanel.Event.VISIBILITY_CHANGE);
236   } else {
237     this.element_.setAttribute('visibility', PreviewPanel.Visibility_.HIDING);
238   }
242  * Update the text in the preview panel.
244  * @param {boolean} breadCrumbsVisible Whether the bread crumbs is visible or
245  *     not.
246  * @private
247  */
248 PreviewPanel.prototype.updatePreviewArea_ = function(breadCrumbsVisible) {
249   var selection = this.selection_;
251   // Update thumbnails.
252   this.thumbnails.selection = selection.totalCount !== 0 ?
253       selection : {entries: [this.currentEntry_]};
255   // Check if the breadcrumb list should show instead on the preview text.
256   var entry;
257   if (this.selection_.totalCount == 1)
258     entry = this.selection_.entries[0];
259   else if (this.selection_.totalCount == 0)
260     entry = this.currentEntry_;
262   if (entry) {
263     this.breadcrumbs.show(entry);
264     this.calculatingSizeLabel_.hidden = true;
265     this.previewText_.textContent = '';
266     return;
267   }
268   this.breadcrumbs.hide();
270   // Obtains the preview text.
271   var text;
272   if (selection.directoryCount == 0)
273     text = strf('MANY_FILES_SELECTED', selection.fileCount);
274   else if (selection.fileCount == 0)
275     text = strf('MANY_DIRECTORIES_SELECTED', selection.directoryCount);
276   else
277     text = strf('MANY_ENTRIES_SELECTED', selection.totalCount);
279   // Obtains the size of files.
280   this.calculatingSizeLabel_.hidden = selection.bytesKnown;
281   if (selection.bytesKnown && selection.showBytes)
282     text += ', ' + util.bytesToString(selection.bytes);
284   // Set the preview text to the element.
285   this.previewText_.textContent = text;
287   // Request the byte calculation if needed.
288   if (!selection.bytesKnown) {
289     this.selection_.computeBytes(function(sequence) {
290       // Selection has been already updated.
291       if (this.sequence_ != sequence)
292         return;
293       this.updatePreviewArea_();
294     }.bind(this, this.sequence_));
295   }
299  * Event handler to be called at the end of hiding transition.
300  * @param {Event} event The webkitTransitionEnd event.
301  * @private
302  */
303 PreviewPanel.prototype.onTransitionEnd_ = function(event) {
304   if (event.target != this.element_ || event.propertyName != 'opacity')
305     return;
306   var visibility = this.element_.getAttribute('visibility');
307   if (visibility != PreviewPanel.Visibility_.HIDING)
308     return;
309   this.element_.setAttribute('visibility', PreviewPanel.Visibility_.HIDDEN);
310   cr.dispatchSimpleEvent(this, PreviewPanel.Event.VISIBILITY_CHANGE);
314  * Animating label that is shown during the bytes of selection entries is being
315  * calculated.
317  * This label shows dots and varying the number of dots every
318  * CalculatingSizeLabel.PERIOD milliseconds.
319  * @param {HTMLElement} element DOM element of the label.
320  * @constructor
321  */
322 PreviewPanel.CalculatingSizeLabel = function(element) {
323   this.element_ = element;
324   this.count_ = 0;
325   this.intervalID_ = null;
326   Object.seal(this);
330  * Time period in milliseconds.
331  * @const {number}
332  */
333 PreviewPanel.CalculatingSizeLabel.PERIOD = 500;
335 PreviewPanel.CalculatingSizeLabel.prototype = {
336   /**
337    * Set visibility of the label.
338    * When it is displayed, the text is animated.
339    * @param {boolean} hidden Whether to hide the label or not.
340    */
341   set hidden(hidden) {
342     this.element_.hidden = hidden;
343     if (!hidden) {
344       if (this.intervalID_ != null)
345         return;
346       this.count_ = 2;
347       this.intervalID_ =
348           setInterval(this.onStep_.bind(this),
349                       PreviewPanel.CalculatingSizeLabel.PERIOD);
350       this.onStep_();
351     } else {
352       if (this.intervalID_ == null)
353         return;
354       clearInterval(this.intervalID_);
355       this.intervalID_ = null;
356     }
357   }
361  * Increments the counter and updates the number of dots.
362  * @private
363  */
364 PreviewPanel.CalculatingSizeLabel.prototype.onStep_ = function() {
365   var text = str('CALCULATING_SIZE');
366   for (var i = 0; i < ~~(this.count_ / 2) % 4; i++) {
367     text += '.';
368   }
369   this.element_.textContent = text;
370   this.count_++;
374  * Thumbnails on the preview panel.
376  * @param {HTMLElement} element DOM Element of thumbnail container.
377  * @param {MetadataCache} metadataCache MetadataCache.
378  * @constructor
379  */
380 PreviewPanel.Thumbnails = function(element, metadataCache) {
381   this.element_ = element;
382   this.metadataCache_ = metadataCache;
383   this.sequence_ = 0;
384   Object.seal(this);
388  * Maximum number of thumbnails.
389  * @const {number}
390  */
391 PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT = 4;
394  * Edge length of the thumbnail square.
395  * @const {number}
396  */
397 PreviewPanel.Thumbnails.THUMBNAIL_SIZE = 35;
400  * Longer edge length of zoomed thumbnail rectangle.
401  * @const {number}
402  */
403 PreviewPanel.Thumbnails.ZOOMED_THUMBNAIL_SIZE = 200;
405 PreviewPanel.Thumbnails.prototype = {
406   /**
407    * Sets entries to be displayed in the view.
408    * @param {Array.<Entry>} value Entries.
409    */
410   set selection(value) {
411     this.sequence_++;
412     this.loadThumbnails_(value);
413   }
417  * Loads thumbnail images.
418  * @param {FileSelection} selection Selection containing entries that are
419  *     sources of images.
420  * @private
421  */
422 PreviewPanel.Thumbnails.prototype.loadThumbnails_ = function(selection) {
423   var entries = selection.entries;
424   this.element_.classList.remove('has-zoom');
425   this.element_.innerText = '';
426   var clickHandler = selection.tasks &&
427       selection.tasks.executeDefault.bind(selection.tasks);
428   var length = Math.min(entries.length,
429                         PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT);
430   for (var i = 0; i < length; i++) {
431     // Create a box.
432     var box = this.element_.ownerDocument.createElement('div');
433     box.style.zIndex = PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT + 1 - i;
435     // Load the image.
436     if (entries[i]) {
437       FileGrid.decorateThumbnailBox(box,
438                                     entries[i],
439                                     this.metadataCache_,
440                                     ThumbnailLoader.FillMode.FILL,
441                                     FileGrid.ThumbnailQuality.LOW,
442                                     i == 0 && length == 1 &&
443                                         this.setZoomedImage_.bind(this));
444     }
446     // Register the click handler.
447     if (clickHandler)
448       box.addEventListener('click', clickHandler);
450     // Append
451     this.element_.appendChild(box);
452   }
456  * Create the zoomed version of image and set it to the DOM element to show the
457  * zoomed image.
459  * @param {Image} image Image to be source of the zoomed image.
460  * @param {transform} transform Transformation to be applied to the image.
461  * @private
462  */
463 PreviewPanel.Thumbnails.prototype.setZoomedImage_ = function(image, transform) {
464   if (!image)
465     return;
466   var width = image.width || 0;
467   var height = image.height || 0;
468   if (width == 0 ||
469       height == 0 ||
470       (width < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2 &&
471        height < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2))
472     return;
474   var scale = Math.min(1,
475                        PreviewPanel.Thumbnails.ZOOMED_THUMBNAIL_SIZE /
476                            Math.max(width, height));
477   var imageWidth = ~~(width * scale);
478   var imageHeight = ~~(height * scale);
479   var zoomedImage = this.element_.ownerDocument.createElement('img');
481   if (scale < 0.3) {
482     // Scaling large images kills animation. Downscale it in advance.
483     // Canvas scales images with liner interpolation. Make a larger
484     // image (but small enough to not kill animation) and let IMAGE
485     // scale it smoothly.
486     var INTERMEDIATE_SCALE = 3;
487     var canvas = this.element_.ownerDocument.createElement('canvas');
488     canvas.width = imageWidth * INTERMEDIATE_SCALE;
489     canvas.height = imageHeight * INTERMEDIATE_SCALE;
490     var ctx = canvas.getContext('2d');
491     ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
492     // Using bigger than default compression reduces image size by
493     // several times. Quality degradation compensated by greater resolution.
494     zoomedImage.src = canvas.toDataURL('image/jpeg', 0.6);
495   } else {
496     zoomedImage.src = image.src;
497   }
499   var boxWidth = Math.max(PreviewPanel.Thumbnails.THUMBNAIL_SIZE, imageWidth);
500   var boxHeight = Math.max(PreviewPanel.Thumbnails.THUMBNAIL_SIZE, imageHeight);
501   if (transform && transform.rotate90 % 2 == 1) {
502     var t = boxWidth;
503     boxWidth = boxHeight;
504     boxHeight = t;
505   }
507   util.applyTransform(zoomedImage, transform);
509   var zoomedBox = this.element_.ownerDocument.createElement('div');
510   zoomedBox.className = 'popup';
511   zoomedBox.style.width = boxWidth + 'px';
512   zoomedBox.style.height = boxHeight + 'px';
513   zoomedBox.appendChild(zoomedImage);
515   this.element_.appendChild(zoomedBox);
516   this.element_.classList.add('has-zoom');
517   return;