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