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 {MetadataCache} metadataCache Metadata cache.
13 * @param {VolumeManagerWrapper} volumeManager Volume manager.
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 entry to be displayed.
40 this.currentEntry_ = null;
43 * Dom element of the preview panel.
47 this.element_ = element;
50 * @type {BreadcrumbsController}
52 this.breadcrumbs = new BreadcrumbsController(
53 element.querySelector('#search-breadcrumbs'),
58 * @type {PreviewPanel.Thumbnails}
60 this.thumbnails = new PreviewPanel.Thumbnails(
61 element.querySelector('.preview-thumbnails'), metadataCache);
67 this.summaryElement_ = element.querySelector('.preview-summary');
70 * @type {PreviewPanel.CalculatingSizeLabel}
73 this.calculatingSizeLabel_ = new PreviewPanel.CalculatingSizeLabel(
74 this.summaryElement_.querySelector('.calculating-size'));
80 this.previewText_ = element.querySelector('.preview-text');
83 * FileSelection to be displayed.
84 * @type {FileSelection}
87 this.selection_ = {entries: [], computeBytes: function() {}};
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.
98 * @type {VolumeManager}
101 this.volumeManager_ = volumeManager;
103 cr.EventTarget.call(this);
107 * Name of PreviewPanels's event.
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.
119 PreviewPanel.VisibilityType = Object.freeze({
120 // Preview panel always shows.
121 ALWAYS_VISIBLE: 'alwaysVisible',
122 // Preview panel shows when the selection property are set.
124 // Preview panel does not show.
125 ALWAYS_HIDDEN: 'alwaysHidden'
131 PreviewPanel.Visibility_ = Object.freeze({
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_();
163 return this.element_.getAttribute('visibility') ==
164 PreviewPanel.Visibility_.VISIBLE;
168 * Obtains the height of preview panel.
169 * @return {number} Height of preview panel.
172 this.height_ = this.height_ || this.element_.clientHeight;
178 * Initializes the element.
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.
191 PreviewPanel.prototype.setSelection = function(selection) {
193 this.selection_ = selection;
194 this.updateVisibility_();
195 // If the previw panel is hiding, does not update the current view.
197 this.updatePreviewArea_();
201 * Update the visibility of the preview panel.
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:
212 case PreviewPanel.VisibilityType.AUTO:
214 this.selection_.entries.length !== 0 ||
215 (this.currentEntry_ &&
216 !this.volumeManager_.getLocationInfo(
217 this.currentEntry_).isRootEntry);
219 case PreviewPanel.VisibilityType.ALWAYS_HIDDEN:
223 console.error('Invalid visibilityType.');
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))
232 // Set the new visibility value.
234 this.element_.setAttribute('visibility', PreviewPanel.Visibility_.VISIBLE);
235 cr.dispatchSimpleEvent(this, PreviewPanel.Event.VISIBILITY_CHANGE);
237 this.element_.setAttribute('visibility', PreviewPanel.Visibility_.HIDING);
242 * Update the text in the preview panel.
244 * @param {boolean} breadCrumbsVisible Whether the bread crumbs is visible or
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.
257 if (this.selection_.totalCount == 1)
258 entry = this.selection_.entries[0];
259 else if (this.selection_.totalCount == 0)
260 entry = this.currentEntry_;
263 this.breadcrumbs.show(entry);
264 this.calculatingSizeLabel_.hidden = true;
265 this.previewText_.textContent = '';
268 this.breadcrumbs.hide();
270 // Obtains the preview 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);
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)
293 this.updatePreviewArea_();
294 }.bind(this, this.sequence_));
299 * Event handler to be called at the end of hiding transition.
300 * @param {Event} event The webkitTransitionEnd event.
303 PreviewPanel.prototype.onTransitionEnd_ = function(event) {
304 if (event.target != this.element_ || event.propertyName != 'opacity')
306 var visibility = this.element_.getAttribute('visibility');
307 if (visibility != PreviewPanel.Visibility_.HIDING)
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
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.
322 PreviewPanel.CalculatingSizeLabel = function(element) {
323 this.element_ = element;
325 this.intervalID_ = null;
330 * Time period in milliseconds.
333 PreviewPanel.CalculatingSizeLabel.PERIOD = 500;
335 PreviewPanel.CalculatingSizeLabel.prototype = {
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.
342 this.element_.hidden = hidden;
344 if (this.intervalID_ != null)
348 setInterval(this.onStep_.bind(this),
349 PreviewPanel.CalculatingSizeLabel.PERIOD);
352 if (this.intervalID_ == null)
354 clearInterval(this.intervalID_);
355 this.intervalID_ = null;
361 * Increments the counter and updates the number of dots.
364 PreviewPanel.CalculatingSizeLabel.prototype.onStep_ = function() {
365 var text = str('CALCULATING_SIZE');
366 for (var i = 0; i < ~~(this.count_ / 2) % 4; i++) {
369 this.element_.textContent = text;
374 * Thumbnails on the preview panel.
376 * @param {HTMLElement} element DOM Element of thumbnail container.
377 * @param {MetadataCache} metadataCache MetadataCache.
380 PreviewPanel.Thumbnails = function(element, metadataCache) {
381 this.element_ = element;
382 this.metadataCache_ = metadataCache;
388 * Maximum number of thumbnails.
391 PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT = 4;
394 * Edge length of the thumbnail square.
397 PreviewPanel.Thumbnails.THUMBNAIL_SIZE = 35;
400 * Longer edge length of zoomed thumbnail rectangle.
403 PreviewPanel.Thumbnails.ZOOMED_THUMBNAIL_SIZE = 200;
405 PreviewPanel.Thumbnails.prototype = {
407 * Sets entries to be displayed in the view.
408 * @param {Array.<Entry>} value Entries.
410 set selection(value) {
412 this.loadThumbnails_(value);
417 * Loads thumbnail images.
418 * @param {FileSelection} selection Selection containing entries that are
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++) {
432 var box = this.element_.ownerDocument.createElement('div');
433 box.style.zIndex = PreviewPanel.Thumbnails.MAX_THUMBNAIL_COUNT + 1 - i;
437 FileGrid.decorateThumbnailBox(box,
440 ThumbnailLoader.FillMode.FILL,
441 FileGrid.ThumbnailQuality.LOW,
442 i == 0 && length == 1 &&
443 this.setZoomedImage_.bind(this));
446 // Register the click handler.
448 box.addEventListener('click', clickHandler);
451 this.element_.appendChild(box);
456 * Create the zoomed version of image and set it to the DOM element to show the
459 * @param {Image} image Image to be source of the zoomed image.
460 * @param {transform} transform Transformation to be applied to the image.
463 PreviewPanel.Thumbnails.prototype.setZoomedImage_ = function(image, transform) {
466 var width = image.width || 0;
467 var height = image.height || 0;
470 (width < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2 &&
471 height < PreviewPanel.Thumbnails.THUMBNAIL_SIZE * 2))
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');
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);
496 zoomedImage.src = image.src;
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) {
503 boxWidth = boxHeight;
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');