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