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