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