• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 * FileGrid constructor.
9 *
10 * Represents grid for the Grid View in the File Manager.
11 * @constructor
12 * @extends {cr.ui.Grid}
13 */
14
15function FileGrid() {
16  throw new Error('Use FileGrid.decorate');
17}
18
19/**
20 * Thumbnail quality.
21 * @enum {number}
22 */
23FileGrid.ThumbnailQuality = {
24  LOW: 0,
25  HIGH: 1
26};
27
28/**
29 * Inherits from cr.ui.Grid.
30 */
31FileGrid.prototype.__proto__ = cr.ui.Grid.prototype;
32
33/**
34 * Decorates an HTML element to be a FileGrid.
35 * @param {HTMLElement} self The grid to decorate.
36 * @param {MetadataCache} metadataCache Metadata cache to find entries
37 *                                      metadata.
38 * @param {VolumeManagerWrapper} volumeManager Volume manager instance.
39 */
40FileGrid.decorate = function(self, metadataCache, volumeManager) {
41  cr.ui.Grid.decorate(self);
42  self.__proto__ = FileGrid.prototype;
43  self.metadataCache_ = metadataCache;
44  self.volumeManager_ = volumeManager;
45
46  self.scrollBar_ = new MainPanelScrollBar();
47  self.scrollBar_.initialize(self.parentNode, self);
48  self.setBottomMarginForPanel(0);
49
50  self.itemConstructor = function(entry) {
51    var item = self.ownerDocument.createElement('LI');
52    FileGrid.Item.decorate(item, entry, self);
53    return item;
54  };
55
56  self.relayoutRateLimiter_ =
57      new AsyncUtil.RateLimiter(self.relayoutImmediately_.bind(self));
58};
59
60/**
61 * Updates items to reflect metadata changes.
62 * @param {string} type Type of metadata changed.
63 * @param {Object.<string, Object>} props Map from entry URLs to metadata props.
64 */
65FileGrid.prototype.updateListItemsMetadata = function(type, props) {
66  var boxes = this.querySelectorAll('.img-container');
67  for (var i = 0; i < boxes.length; i++) {
68    var box = boxes[i];
69    var entry = this.dataModel.item(this.getListItemAncestor(box));
70    if (!entry || !(entry.toURL() in props))
71      continue;
72
73    FileGrid.decorateThumbnailBox(box,
74                                  entry,
75                                  this.metadataCache_,
76                                  this.volumeManager_,
77                                  ThumbnailLoader.FillMode.FIT,
78                                  FileGrid.ThumbnailQuality.LOW);
79  }
80};
81
82/**
83 * Redraws the UI. Skips multiple consecutive calls.
84 */
85FileGrid.prototype.relayout = function() {
86  this.relayoutRateLimiter_.run();
87};
88
89/**
90 * Redraws the UI immediately.
91 * @private
92 */
93FileGrid.prototype.relayoutImmediately_ = function() {
94  this.startBatchUpdates();
95  this.columns = 0;
96  this.redraw();
97  this.endBatchUpdates();
98  cr.dispatchSimpleEvent(this, 'relayout');
99};
100
101/**
102 * Decorates thumbnail.
103 * @param {HTMLElement} li List item.
104 * @param {Entry} entry Entry to render a thumbnail for.
105 * @param {MetadataCache} metadataCache To retrieve metadata.
106 * @param {VolumeManagerWrapper} volumeManager Volume manager instance.
107 */
108FileGrid.decorateThumbnail = function(li, entry, metadataCache, volumeManager) {
109  li.className = 'thumbnail-item';
110  if (entry)
111    filelist.decorateListItem(li, entry, metadataCache);
112
113  var frame = li.ownerDocument.createElement('div');
114  frame.className = 'thumbnail-frame';
115  li.appendChild(frame);
116
117  var box = li.ownerDocument.createElement('div');
118  if (entry) {
119    FileGrid.decorateThumbnailBox(box,
120                                  entry,
121                                  metadataCache,
122                                  volumeManager,
123                                  ThumbnailLoader.FillMode.AUTO,
124                                  FileGrid.ThumbnailQuality.LOW);
125  }
126  frame.appendChild(box);
127
128  var bottom = li.ownerDocument.createElement('div');
129  bottom.className = 'thumbnail-bottom';
130  bottom.appendChild(filelist.renderFileNameLabel(li.ownerDocument, entry));
131  frame.appendChild(bottom);
132};
133
134/**
135 * Decorates the box containing a centered thumbnail image.
136 *
137 * @param {HTMLDivElement} box Box to decorate.
138 * @param {Entry} entry Entry which thumbnail is generating for.
139 * @param {MetadataCache} metadataCache To retrieve metadata.
140 * @param {VolumeManagerWrapper} volumeManager Volume manager instance.
141 * @param {ThumbnailLoader.FillMode} fillMode Fill mode.
142 * @param {FileGrid.ThumbnailQuality} quality Thumbnail quality.
143 * @param {function(HTMLElement)=} opt_imageLoadCallback Callback called when
144 *     the image has been loaded before inserting it into the DOM.
145 */
146FileGrid.decorateThumbnailBox = function(
147    box, entry, metadataCache, volumeManager, fillMode, quality,
148    opt_imageLoadCallback) {
149  var locationInfo = volumeManager.getLocationInfo(entry);
150  box.className = 'img-container';
151
152  if (entry.isDirectory) {
153    box.setAttribute('generic-thumbnail', 'folder');
154    if (locationInfo && locationInfo.isDriveBased) {
155      metadataCache.getOne(entry, 'drive', function(metadata) {
156        if (metadata.shared)
157          box.classList.add('shared');
158      });
159    }
160    if (opt_imageLoadCallback)
161      setTimeout(opt_imageLoadCallback, 0, null /* callback parameter */);
162    return;
163  }
164
165  var metadataTypes = 'thumbnail|filesystem';
166
167  if (locationInfo && locationInfo.isDriveBased) {
168    metadataTypes += '|drive';
169  } else {
170    // TODO(dgozman): If we ask for 'media' for a Drive file we fall into an
171    // infinite loop.
172    metadataTypes += '|media';
173  }
174
175  // Drive provides high quality thumbnails via USE_EMBEDDED, however local
176  // images usually provide very tiny thumbnails, therefore USE_EMBEDDE can't
177  // be used to obtain high quality output.
178  var useEmbedded;
179  switch (quality) {
180    case FileGrid.ThumbnailQuality.LOW:
181      useEmbedded = ThumbnailLoader.UseEmbedded.USE_EMBEDDED;
182      break;
183    case FileGrid.ThumbnailQuality.HIGH:
184      // TODO(mtomasz): Use Entry instead of paths.
185      useEmbedded = (locationInfo && locationInfo.isDriveBased) ?
186          ThumbnailLoader.UseEmbedded.USE_EMBEDDED :
187          ThumbnailLoader.UseEmbedded.NO_EMBEDDED;
188      break;
189  }
190
191  metadataCache.getOne(entry, metadataTypes,
192      function(metadata) {
193        new ThumbnailLoader(entry,
194                            ThumbnailLoader.LoaderType.IMAGE,
195                            metadata,
196                            undefined,  // opt_mediaType
197                            useEmbedded).
198            load(box,
199                fillMode,
200                ThumbnailLoader.OptimizationMode.DISCARD_DETACHED,
201                opt_imageLoadCallback);
202      });
203};
204
205/**
206 * Item for the Grid View.
207 * @constructor
208 */
209FileGrid.Item = function() {
210  throw new Error();
211};
212
213/**
214 * Inherits from cr.ui.ListItem.
215 */
216FileGrid.Item.prototype.__proto__ = cr.ui.ListItem.prototype;
217
218Object.defineProperty(FileGrid.Item.prototype, 'label', {
219  /**
220   * @this {FileGrid.Item}
221   * @return {string} Label of the item.
222   */
223  get: function() {
224    return this.querySelector('filename-label').textContent;
225  }
226});
227
228/**
229 * @param {Element} li List item element.
230 * @param {Entry} entry File entry.
231 * @param {FileGrid} grid Owner.
232 */
233FileGrid.Item.decorate = function(li, entry, grid) {
234  li.__proto__ = FileGrid.Item.prototype;
235  // TODO(mtomasz): Pass the metadata cache and the volume manager directly
236  // instead of accessing private members of grid.
237  FileGrid.decorateThumbnail(
238      li, entry, grid.metadataCache_, grid.volumeManager_, true);
239
240  // Override the default role 'listitem' to 'option' to match the parent's
241  // role (listbox).
242  li.setAttribute('role', 'option');
243};
244
245/**
246 * Sets the margin height for the transparent preview panel at the bottom.
247 * @param {number} margin Margin to be set in px.
248 */
249FileGrid.prototype.setBottomMarginForPanel = function(margin) {
250  // +20 bottom margin is needed to match the bottom margin size with the
251  // margin between its items.
252  this.style.paddingBottom = (margin + 20) + 'px';
253  this.scrollBar_.setBottomMarginForPanel(margin);
254};
255
256/**
257 * Obtains if the drag selection should be start or not by referring the mouse
258 * event.
259 * @param {MouseEvent} event Drag start event.
260 * @return {boolean} True if the mouse is hit to the background of the list.
261 */
262FileGrid.prototype.shouldStartDragSelection = function(event) {
263  var pos = DragSelector.getScrolledPosition(this, event);
264  return this.getHitElements(pos.x, pos.y).length === 0;
265};
266
267/**
268 * Obtains the column/row index that the coordinate points.
269 * @param {number} coordinate Vertical/horizontal coordinate value that points
270 *     column/row.
271 * @param {number} step Length from a column/row to the next one.
272 * @param {number} threshold Threshold that determines whether 1 offset is added
273 *     to the return value or not. This is used in order to handle the margin of
274 *     column/row.
275 * @return {number} Index of hit column/row.
276 * @private
277 */
278FileGrid.prototype.getHitIndex_ = function(coordinate, step, threshold) {
279  var index = ~~(coordinate / step);
280  return (coordinate % step >= threshold) ? index + 1 : index;
281};
282
283/**
284 * Obtains the index list of elements that are hit by the point or the
285 * rectangle.
286 *
287 * We should match its argument interface with FileList.getHitElements.
288 *
289 * @param {number} x X coordinate value.
290 * @param {number} y Y coordinate value.
291 * @param {=number} opt_width Width of the coordinate.
292 * @param {=number} opt_height Height of the coordinate.
293 * @return {Array.<number>} Index list of hit elements.
294 */
295FileGrid.prototype.getHitElements = function(x, y, opt_width, opt_height) {
296  var currentSelection = [];
297  var right = x + (opt_width || 0);
298  var bottom = y + (opt_height || 0);
299  var itemMetrics = this.measureItem();
300  var horizontalStartIndex = this.getHitIndex_(
301      x, itemMetrics.width, itemMetrics.width - itemMetrics.marginRight);
302  var horizontalEndIndex = Math.min(this.columns, this.getHitIndex_(
303      right, itemMetrics.width, itemMetrics.marginLeft));
304  var verticalStartIndex = this.getHitIndex_(
305      y, itemMetrics.height, itemMetrics.height - itemMetrics.bottom);
306  var verticalEndIndex = this.getHitIndex_(
307      bottom, itemMetrics.height, itemMetrics.marginTop);
308  for (var verticalIndex = verticalStartIndex;
309       verticalIndex < verticalEndIndex;
310       verticalIndex++) {
311    var indexBase = this.getFirstItemInRow(verticalIndex);
312    for (var horizontalIndex = horizontalStartIndex;
313         horizontalIndex < horizontalEndIndex;
314         horizontalIndex++) {
315      var index = indexBase + horizontalIndex;
316      if (0 <= index && index < this.dataModel.length)
317        currentSelection.push(index);
318    }
319  }
320  return currentSelection;
321};
322