Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(524)

Side by Side Diff: chrome/browser/resources/file_manager/js/photo/tile_view.js

Issue 39123003: [Files.app] Split the JavaScript files into subdirectories: common, background, and foreground (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed test failure. Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 * Tile view displays images/videos tiles.
9 *
10 * @param {HTMLDocument} document Document.
11 * @param {function(TailBox, callback)} prepareBox This function should provide
12 * the passed box with width and height properties of the image.
13 * @param {function(TailBox, callback)} loadBox This function should display
14 * the image in the box respecting clientWidth and clientHeight.
15 * @constructor
16 */
17 function TileView(document, prepareBox, loadBox) {
18 var self = document.createElement('div');
19 TileView.decorate(self, prepareBox, loadBox);
20 return self;
21 }
22
23 TileView.prototype = { __proto__: HTMLDivElement.prototype };
24
25 /**
26 * The number of boxes updated at once after loading.
27 */
28 TileView.LOAD_CHUNK = 10;
29
30 /**
31 * The margin between the boxes (in pixels).
32 */
33 TileView.MARGIN = 10;
34
35 /**
36 * The delay between loading of two consecutive images.
37 */
38 TileView.LOAD_DELAY = 100;
39
40 /**
41 * @param {HTMLDivElement} self Element to decorate.
42 * @param {function(TailBox, callback)} prepareBox See constructor.
43 * @param {function(TailBox, callback)} loadBox See constructor.
44 */
45 TileView.decorate = function(self, prepareBox, loadBox) {
46 self.__proto__ = TileView.prototype;
47 self.classList.add('tile-view');
48 self.prepareBox_ = prepareBox;
49 self.loadBox_ = loadBox;
50 };
51
52 /**
53 * Load and display media entries.
54 * @param {Array.<FileEntry>} entries Entries list.
55 */
56 TileView.prototype.load = function(entries) {
57 this.boxes_ = [];
58
59 /**
60 * The number of boxes for which the image size is already known.
61 */
62 this.preparedCount_ = 0;
63
64 /**
65 * The number of boxes already displaying the image.
66 */
67 this.loadedCount_ = 0;
68
69 for (var index = 0; index < entries.length; index++) {
70 var box = new TileBox(this, entries[index]);
71 box.index = index;
72 this.boxes_.push(box);
73 this.prepareBox_(box, this.onBoxPrepared_.bind(this, box));
74 }
75
76 this.redraw();
77 };
78
79 /**
80 * Redraws everything.
81 */
82 TileView.prototype.redraw = function() {
83 // TODO(dgozman): if we decide to support resize or virtual scrolling,
84 // we should save the chosen position for ready boxes, so they will not
85 // move around.
86
87 this.cellSize_ = Math.floor((this.clientHeight - 3 * TileView.MARGIN) / 2);
88 this.textContent = '';
89 for (var index = 0; index < this.boxes_.length; index++) {
90 this.appendChild(this.boxes_[index]);
91 }
92 this.repositionBoxes_(0);
93 };
94
95 /**
96 * This function sets positions for boxes.
97 *
98 * To do this we keep a 2x4 array of cells marked busy or empty.
99 * When trying to put the next box, we choose a pattern (horizontal, vertical,
100 * square, etc.) which fits into the empty cells and place an image there.
101 * The preferred pattern has the same orientation as image itself. Images
102 * with unknown size are always shown in 1x1 cell.
103 *
104 * @param {number} from First index.
105 * @private
106 */
107 TileView.prototype.repositionBoxes_ = function(from) {
108
109 var cellSize = this.cellSize_;
110 var margin = TileView.MARGIN;
111 var baseColAndEmpty = this.getBaseColAndEmpty_();
112
113 // |empty| is a 2x4 array of busy/empty cells.
114 var empty = baseColAndEmpty.empty;
115
116 // |baseCol| is the (tileview-wide) number of first column in |empty| array.
117 var baseCol = baseColAndEmpty.baseCol;
118
119 for (var index = from; index < this.boxes_.length; index++) {
120 while (!empty[0][0] && !empty[1][0]) {
121 // Skip the full columns at the start.
122 empty[0].shift();
123 empty[0].push(true);
124 empty[1].shift();
125 empty[1].push(true);
126 baseCol++;
127 }
128 // Here we always have an empty cell in the first column fo |empty| array,
129 // and |baseCol| is the column number of it.
130
131 var box = this.boxes_[index];
132 var imageWidth = box.width || 0;
133 var imageHeight = box.height || 0;
134
135 // Possible positions of the box:
136 // p - the probability of this pattern to be used;
137 // w - the width of resulting image (in columns);
138 // h - the height of resulting image (in rows);
139 // allowed - whether this pattern is allowed for this particular box.
140 var patterns = [
141 {p: 0.2, w: 2, h: 2, allowed: index < this.preparedCount_},
142 {p: 0.6, w: 1, h: 2, allowed: imageHeight > imageWidth &&
143 index < this.preparedCount_},
144 {p: 0.3, w: 2, h: 1, allowed: imageHeight < imageWidth &&
145 index < this.preparedCount_},
146 {p: 1.0, w: 1, h: 1, allowed: true} // Every image can be shown as 1x1.
147 ];
148
149 // The origin point is top-left empty cell, which must be in the
150 // first column.
151 var col = 0;
152 var row = empty[0][0] ? 0 : 1;
153
154 for (var pIndex = 0; pIndex < patterns.length; pIndex++) {
155 var pattern = patterns[pIndex];
156 if (Math.random() > pattern.p || !pattern.allowed) continue;
157 if (!this.canUsePattern_(empty, row, col, pattern)) continue;
158
159 // Found a pattern to use.
160 box.rect.row = row;
161 box.rect.col = col + baseCol;
162 box.rect.width = pattern.w;
163 box.rect.height = pattern.h;
164
165 // Now mark the cells as busy and stop.
166 this.usePattern_(empty, row, col, pattern);
167 break;
168 }
169
170 box.setPositionFromRect(margin, cellSize);
171 }
172 };
173
174 /**
175 * @param {number} from Starting index.
176 * @return {Object} An object containing the array of cells marked empty/busy
177 * and a base (left one) column number.
178 * @private
179 */
180 TileView.prototype.getBaseColAndEmpty_ = function(from) {
181 // 2x4 array indicating whether the place is empty or not.
182 var empty = [[true, true, true, true], [true, true, true, true]];
183 var baseCol = 0;
184
185 if (from > 0) {
186 baseCol = this.boxes_[from - 1].rect.col;
187 if (from > 1) {
188 baseCol = Math.min(baseCol, this.boxes_[from - 2].rect.col);
189 }
190
191 for (var b = from - 2; b < from; b++) {
192 if (b < 0) continue;
193 var rect = this.boxes_[b].rect;
194 for (var i = 0; i < rect.height; i++) {
195 for (var j = 0; j < rect.width; j++) {
196 empty[i + rect.row][j + rect.col - baseCol] = false;
197 }
198 }
199 }
200 }
201
202 return {empty: empty, baseCol: baseCol};
203 };
204
205 /**
206 * @param {Array} empty The empty/busy cells array.
207 * @param {number} row The origin row.
208 * @param {number} col The origin column.
209 * @param {Object} pattern The pattern (see |repositionBoxes_|).
210 * @return {boolean} Whether the pattern may be used at this origin.
211 * @private
212 */
213 TileView.prototype.canUsePattern_ = function(empty, row, col, pattern) {
214 if (row + pattern.h > 2 || col + pattern.w > 4)
215 return false;
216
217 var can = true;
218 for (var r = 0; r < pattern.h; r++) {
219 for (var c = 0; c < pattern.w; c++) {
220 can = can && empty[row + r][col + c];
221 }
222 }
223 return can;
224 };
225
226 /**
227 * Marks pattern's cells as busy.
228 * @param {Array} empty The empty/busy cells array.
229 * @param {number} row The origin row.
230 * @param {number} col The origin column.
231 * @param {Object} pattern The pattern (see |repositionBoxes_|).
232 * @private
233 */
234 TileView.prototype.usePattern_ = function(empty, row, col, pattern) {
235 for (var r = 0; r < pattern.h; r++) {
236 for (var c = 0; c < pattern.w; c++) {
237 empty[row + r][col + c] = false;
238 }
239 }
240 };
241
242 /**
243 * Called when box is ready.
244 * @param {TileBox} box The box.
245 * @private
246 */
247 TileView.prototype.onBoxPrepared_ = function(box) {
248 box.ready = true;
249 var to = this.preparedCount_;
250 while (to < this.boxes_.length && this.boxes_[to].ready) {
251 to++;
252 }
253
254 if (to >= Math.min(this.preparedCount_ + TileView.LOAD_CHUNK,
255 this.boxes_.length)) {
256 var last = this.preparedCount_;
257 this.preparedCount_ = to;
258 this.repositionBoxes_(last);
259
260 if (this.loadedCount_ == last) {
261 // All previously prepared boxes have been loaded - start the next one.
262 var nextBox = this.boxes_[this.loadedCount_];
263 setTimeout(this.loadBox_, TileView.LOAD_DELAY,
264 nextBox, this.onBoxLoaded.bind(this, nextBox));
265 }
266 }
267 };
268
269 /**
270 * Called when box is loaded.
271 * @param {TileBox} box The box.
272 */
273 TileView.prototype.onBoxLoaded = function(box) {
274 if (this.loadedCount_ != box.index)
275 console.error('inconsistent loadedCount');
276 this.loadedCount_ = box.index + 1;
277
278 var nextIndex = box.index + 1;
279 if (nextIndex < this.preparedCount_) {
280 var nextBox = this.boxes_[nextIndex];
281 setTimeout(this.loadBox_, TileView.LOAD_DELAY,
282 nextBox, this.onBoxLoaded.bind(this, nextBox));
283 }
284 };
285
286
287
288 /**
289 * Container for functions to work with local TileView.
290 */
291 TileView.local = {};
292
293 /**
294 * Decorates a TileView to show local files.
295 * @param {HTMLDivElement} view The view.
296 * @param {MetadataCache} metadataCache Metadata cache.
297 */
298 TileView.local.decorate = function(view, metadataCache) {
299 TileView.decorate(view, TileView.local.prepareBox, TileView.local.loadBox);
300 view.metadataCache = metadataCache;
301 };
302
303 /**
304 * Prepares image for local tile view box.
305 * @param {TileBox} box The box.
306 * @param {function} callback The callback.
307 */
308 TileView.local.prepareBox = function(box, callback) {
309 box.view_.metadataCache.get(box.entry, 'media', function(media) {
310 if (!media) {
311 box.width = 0;
312 box.height = 0;
313 box.imageTransform = null;
314 } else {
315 if (media.imageTransform && media.imageTransform.rotate90 % 2 == 1) {
316 box.width = media.height;
317 box.height = media.width;
318 } else {
319 box.width = media.width;
320 box.height = media.height;
321 }
322 box.imageTransform = media.imageTransform;
323 }
324
325 callback();
326 });
327 };
328
329 /**
330 * Loads the image for local tile view box.
331 * @param {TileBox} box The box.
332 * @param {function} callback The callback.
333 */
334 TileView.local.loadBox = function(box, callback) {
335 var onLoaded = function(fullCanvas) {
336 try {
337 var canvas = box.ownerDocument.createElement('canvas');
338 canvas.width = box.clientWidth;
339 canvas.height = box.clientHeight;
340 var context = canvas.getContext('2d');
341 context.drawImage(fullCanvas, 0, 0, canvas.width, canvas.height);
342 box.appendChild(canvas);
343 } catch (e) {
344 // TODO(dgozman): classify possible exceptions here and reraise others.
345 }
346 callback();
347 };
348
349 var transformFetcher = function(url, onFetched) {
350 onFetched(box.imageTransform);
351 };
352
353 var imageLoader = new ImageUtil.ImageLoader(box.ownerDocument);
354 imageLoader.load(box.entry.toURL(), transformFetcher, onLoaded);
355 };
356
357
358
359 /**
360 * Container for functions to work with drive TileView.
361 */
362 TileView.drive = {};
363
364 /**
365 * Decorates a TileView to show drive files.
366 * @param {HTMLDivElement} view The view.
367 * @param {MetadataCache} metadataCache Metadata cache.
368 */
369 TileView.drive.decorate = function(view, metadataCache) {
370 TileView.decorate(view, TileView.drive.prepareBox, TileView.drive.loadBox);
371 view.metadataCache = metadataCache;
372 };
373
374 /**
375 * Prepares image for drive tile view box.
376 * @param {TileBox} box The box.
377 * @param {function} callback The callback.
378 */
379 TileView.drive.prepareBox = function(box, callback) {
380 box.view_.metadataCache.get(box.entry, 'thumbnail', function(thumbnail) {
381 if (!thumbnail) {
382 box.width = 0;
383 box.height = 0;
384 callback();
385 return;
386 }
387
388 // TODO(dgozman): remove this hack if we ask for larger thumbnails in
389 // drive code.
390 var thumbnailUrl = thumbnail.url.replace(/240$/, '512');
391
392 box.image = new Image();
393 box.image.onload = function(e) {
394 box.width = box.image.width;
395 box.height = box.image.height;
396 callback();
397 };
398 box.image.onerror = function() {
399 box.image = null;
400 callback();
401 };
402 box.image.src = thumbnailUrl;
403 });
404 };
405
406 /**
407 * Loads the image for drive tile view box.
408 * @param {TileBox} box The box.
409 * @param {function} callback The callback.
410 */
411 TileView.drive.loadBox = function(box, callback) {
412 box.appendChild(box.image);
413 callback();
414 };
415
416
417
418
419 /**
420 * Tile box is a part of tile view.
421 * @param {TailView} view The parent view.
422 * @param {Entry} entry Image file entry.
423 * @constructor
424 */
425 function TileBox(view, entry) {
426 var self = view.ownerDocument.createElement('div');
427 TileBox.decorate(self, view, entry);
428 return self;
429 }
430
431 TileBox.prototype = { __proto__: HTMLDivElement.prototype };
432
433 /**
434 * @param {HTMLDivElement} self Element to decorate.
435 * @param {TailView} view The parent view.
436 * @param {Entry} entry Image file entry.
437 */
438 TileBox.decorate = function(self, view, entry) {
439 self.__proto__ = TileBox.prototype;
440 self.classList.add('tile-box');
441
442 self.view_ = view;
443 self.entry = entry;
444
445 self.ready = false;
446 self.rect = {row: 0, col: 0, width: 0, height: 0};
447
448 self.index = null;
449 self.height = null;
450 self.width = null;
451 };
452
453 /**
454 * Sets box position according to the |rect| property and given sizes.
455 * @param {number} margin Margin between cells.
456 * @param {number} cellSize The size of one cell.
457 * @constructor
458 */
459 TileBox.setPositionFromRect = function(margin, cellSize) {
460 this.style.top = margin + (cellSize + margin) * this.rect.row + 'px';
461 this.style.left = margin + (cellSize + margin) * this.rect.col + 'px';
462 this.style.height = (cellSize + margin) * this.rect.height - margin + 'px';
463 this.style.width = (cellSize + margin) * this.rect.width - margin + 'px';
464 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698