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 * Namespace for utility functions. 9 */ 10var filelist = {}; 11 12/** 13 * Custom column model for advanced auto-resizing. 14 * 15 * @param {Array.<cr.ui.table.TableColumn>} tableColumns Table columns. 16 * @extends {cr.ui.table.TableColumnModel} 17 * @constructor 18 */ 19function FileTableColumnModel(tableColumns) { 20 cr.ui.table.TableColumnModel.call(this, tableColumns); 21} 22 23/** 24 * The columns whose index is less than the constant are resizable. 25 * @const 26 * @type {number} 27 * @private 28 */ 29FileTableColumnModel.RESIZABLE_LENGTH_ = 4; 30 31/** 32 * Inherits from cr.ui.TableColumnModel. 33 */ 34FileTableColumnModel.prototype.__proto__ = 35 cr.ui.table.TableColumnModel.prototype; 36 37/** 38 * Minimum width of column. 39 * @const 40 * @type {number} 41 * @private 42 */ 43FileTableColumnModel.MIN_WIDTH_ = 10; 44 45/** 46 * Sets column width so that the column dividers move to the specified position. 47 * This function also check the width of each column and keep the width larger 48 * than MIN_WIDTH_. 49 * 50 * @private 51 * @param {Array.<number>} newPos Positions of each column dividers. 52 */ 53FileTableColumnModel.prototype.applyColumnPositions_ = function(newPos) { 54 // Check the minimum width and adjust the positions. 55 for (var i = 0; i < newPos.length - 2; i++) { 56 if (newPos[i + 1] - newPos[i] < FileTableColumnModel.MIN_WIDTH_) { 57 newPos[i + 1] = newPos[i] + FileTableColumnModel.MIN_WIDTH_; 58 } 59 } 60 for (var i = newPos.length - 1; i >= 2; i--) { 61 if (newPos[i] - newPos[i - 1] < FileTableColumnModel.MIN_WIDTH_) { 62 newPos[i - 1] = newPos[i] - FileTableColumnModel.MIN_WIDTH_; 63 } 64 } 65 // Set the new width of columns 66 for (var i = 0; i < FileTableColumnModel.RESIZABLE_LENGTH_; i++) { 67 this.columns_[i].width = newPos[i + 1] - newPos[i]; 68 } 69}; 70 71/** 72 * Normalizes widths to make their sum 100% if possible. Uses the proportional 73 * approach with some additional constraints. 74 * 75 * @param {number} contentWidth Target width. 76 * @override 77 */ 78FileTableColumnModel.prototype.normalizeWidths = function(contentWidth) { 79 var totalWidth = 0; 80 var fixedWidth = 0; 81 // Some columns have fixed width. 82 for (var i = 0; i < this.columns_.length; i++) { 83 if (i < FileTableColumnModel.RESIZABLE_LENGTH_) 84 totalWidth += this.columns_[i].width; 85 else 86 fixedWidth += this.columns_[i].width; 87 } 88 var newTotalWidth = Math.max(contentWidth - fixedWidth, 0); 89 var positions = [0]; 90 var sum = 0; 91 for (var i = 0; i < FileTableColumnModel.RESIZABLE_LENGTH_; i++) { 92 var column = this.columns_[i]; 93 sum += column.width; 94 // Faster alternative to Math.floor for non-negative numbers. 95 positions[i + 1] = ~~(newTotalWidth * sum / totalWidth); 96 } 97 this.applyColumnPositions_(positions); 98}; 99 100/** 101 * Handles to the start of column resizing by splitters. 102 */ 103FileTableColumnModel.prototype.handleSplitterDragStart = function() { 104 this.columnPos_ = [0]; 105 for (var i = 0; i < this.columns_.length; i++) { 106 this.columnPos_[i + 1] = this.columns_[i].width + this.columnPos_[i]; 107 } 108}; 109 110/** 111 * Handles to the end of column resizing by splitters. 112 */ 113FileTableColumnModel.prototype.handleSplitterDragEnd = function() { 114 this.columnPos_ = null; 115}; 116 117/** 118 * Sets the width of column with keeping the total width of table. 119 * @param {number} columnIndex Index of column that is resized. 120 * @param {number} columnWidth New width of the column. 121 */ 122FileTableColumnModel.prototype.setWidthAndKeepTotal = function( 123 columnIndex, columnWidth) { 124 // Skip to resize 'selection' column 125 if (columnIndex < 0 || 126 columnIndex >= FileTableColumnModel.RESIZABLE_LENGTH_ || 127 !this.columnPos_) { 128 return; 129 } 130 131 // Calculate new positions of column splitters. 132 var newPosStart = 133 this.columnPos_[columnIndex] + Math.max(columnWidth, 134 FileTableColumnModel.MIN_WIDTH_); 135 var newPos = []; 136 var posEnd = this.columnPos_[FileTableColumnModel.RESIZABLE_LENGTH_]; 137 for (var i = 0; i < columnIndex + 1; i++) { 138 newPos[i] = this.columnPos_[i]; 139 } 140 for (var i = columnIndex + 1; 141 i < FileTableColumnModel.RESIZABLE_LENGTH_; 142 i++) { 143 var posStart = this.columnPos_[columnIndex + 1]; 144 newPos[i] = (posEnd - newPosStart) * 145 (this.columnPos_[i] - posStart) / 146 (posEnd - posStart) + 147 newPosStart; 148 // Faster alternative to Math.floor for non-negative numbers. 149 newPos[i] = ~~newPos[i]; 150 } 151 newPos[columnIndex] = this.columnPos_[columnIndex]; 152 newPos[FileTableColumnModel.RESIZABLE_LENGTH_] = posEnd; 153 this.applyColumnPositions_(newPos); 154 155 // Notifiy about resizing 156 cr.dispatchSimpleEvent(this, 'resize'); 157}; 158 159/** 160 * Custom splitter that resizes column with retaining the sum of all the column 161 * width. 162 */ 163var FileTableSplitter = cr.ui.define('div'); 164 165/** 166 * Inherits from cr.ui.TableSplitter. 167 */ 168FileTableSplitter.prototype.__proto__ = cr.ui.TableSplitter.prototype; 169 170/** 171 * Handles the drag start event. 172 */ 173FileTableSplitter.prototype.handleSplitterDragStart = function() { 174 cr.ui.TableSplitter.prototype.handleSplitterDragStart.call(this); 175 this.table_.columnModel.handleSplitterDragStart(); 176}; 177 178/** 179 * Handles the drag move event. 180 * @param {number} deltaX Horizontal mouse move offset. 181 */ 182FileTableSplitter.prototype.handleSplitterDragMove = function(deltaX) { 183 this.table_.columnModel.setWidthAndKeepTotal(this.columnIndex, 184 this.columnWidth_ + deltaX, 185 true); 186}; 187 188/** 189 * Handles the drag end event. 190 */ 191FileTableSplitter.prototype.handleSplitterDragEnd = function() { 192 cr.ui.TableSplitter.prototype.handleSplitterDragEnd.call(this); 193 this.table_.columnModel.handleSplitterDragEnd(); 194}; 195 196/** 197 * File list Table View. 198 * @constructor 199 */ 200function FileTable() { 201 throw new Error('Designed to decorate elements'); 202} 203 204/** 205 * Inherits from cr.ui.Table. 206 */ 207FileTable.prototype.__proto__ = cr.ui.Table.prototype; 208 209/** 210 * Decorates the element. 211 * @param {HTMLElement} self Table to decorate. 212 * @param {MetadataCache} metadataCache To retrieve metadata. 213 * @param {boolean} fullPage True if it's full page File Manager, 214 * False if a file open/save dialog. 215 */ 216FileTable.decorate = function(self, metadataCache, fullPage) { 217 cr.ui.Table.decorate(self); 218 self.__proto__ = FileTable.prototype; 219 self.metadataCache_ = metadataCache; 220 self.collator_ = Intl.Collator([], {numeric: true, sensitivity: 'base'}); 221 222 var columns = [ 223 new cr.ui.table.TableColumn('name', str('NAME_COLUMN_LABEL'), 224 fullPage ? 386 : 324), 225 new cr.ui.table.TableColumn('size', str('SIZE_COLUMN_LABEL'), 226 110, true), 227 new cr.ui.table.TableColumn('type', str('TYPE_COLUMN_LABEL'), 228 fullPage ? 110 : 110), 229 new cr.ui.table.TableColumn('modificationTime', 230 str('DATE_COLUMN_LABEL'), 231 fullPage ? 150 : 210) 232 ]; 233 234 columns[0].renderFunction = self.renderName_.bind(self); 235 columns[1].renderFunction = self.renderSize_.bind(self); 236 columns[1].defaultOrder = 'desc'; 237 columns[2].renderFunction = self.renderType_.bind(self); 238 columns[3].renderFunction = self.renderDate_.bind(self); 239 columns[3].defaultOrder = 'desc'; 240 241 var tableColumnModelClass; 242 tableColumnModelClass = FileTableColumnModel; 243 244 var columnModel = Object.create(tableColumnModelClass.prototype, { 245 /** 246 * The number of columns. 247 * @type {number} 248 */ 249 size: { 250 /** 251 * @this {FileTableColumnModel} 252 * @return {number} Number of columns. 253 */ 254 get: function() { 255 return this.totalSize; 256 } 257 }, 258 259 /** 260 * The number of columns. 261 * @type {number} 262 */ 263 totalSize: { 264 /** 265 * @this {FileTableColumnModel} 266 * @return {number} Number of columns. 267 */ 268 get: function() { 269 return columns.length; 270 } 271 }, 272 273 /** 274 * Obtains a column by the specified horizontal position. 275 */ 276 getHitColumn: { 277 /** 278 * @this {FileTableColumnModel} 279 * @param {number} x Horizontal position. 280 * @return {object} The object that contains column index, column width, 281 * and hitPosition where the horizontal position is hit in the column. 282 */ 283 value: function(x) { 284 for (var i = 0; x >= this.columns_[i].width; i++) { 285 x -= this.columns_[i].width; 286 } 287 if (i >= this.columns_.length) 288 return null; 289 return {index: i, hitPosition: x, width: this.columns_[i].width}; 290 } 291 } 292 }); 293 294 tableColumnModelClass.call(columnModel, columns); 295 self.columnModel = columnModel; 296 self.setDateTimeFormat(true); 297 self.setRenderFunction(self.renderTableRow_.bind(self, 298 self.getRenderFunction())); 299 300 self.scrollBar_ = MainPanelScrollBar(); 301 self.scrollBar_.initialize(self, self.list); 302 // Keep focus on the file list when clicking on the header. 303 self.header.addEventListener('mousedown', function(e) { 304 self.list.focus(); 305 e.preventDefault(); 306 }); 307 308 self.relayoutRateLimiter_ = 309 new AsyncUtil.RateLimiter(self.relayoutImmediately_.bind(self)); 310 311 // Override header#redraw to use FileTableSplitter. 312 self.header_.redraw = function() { 313 this.__proto__.redraw.call(this); 314 // Extend table splitters 315 var splitters = this.querySelectorAll('.table-header-splitter'); 316 for (var i = 0; i < splitters.length; i++) { 317 if (splitters[i] instanceof FileTableSplitter) 318 continue; 319 FileTableSplitter.decorate(splitters[i]); 320 } 321 }; 322 323 // Save the last selection. This is used by shouldStartDragSelection. 324 self.list.addEventListener('mousedown', function(e) { 325 this.lastSelection_ = this.selectionModel.selectedIndexes; 326 }.bind(self), true); 327 self.list.shouldStartDragSelection = 328 self.shouldStartDragSelection_.bind(self); 329 330 /** 331 * Obtains the index list of elements that are hit by the point or the 332 * rectangle. 333 * 334 * @param {number} x X coordinate value. 335 * @param {number} y Y coordinate value. 336 * @param {=number} opt_width Width of the coordinate. 337 * @param {=number} opt_height Height of the coordinate. 338 * @return {Array.<number>} Index list of hit elements. 339 */ 340 self.list.getHitElements = function(x, y, opt_width, opt_height) { 341 var currentSelection = []; 342 var bottom = y + (opt_height || 0); 343 for (var i = 0; i < this.selectionModel_.length; i++) { 344 var itemMetrics = this.getHeightsForIndex_(i); 345 if (itemMetrics.top < bottom && itemMetrics.top + itemMetrics.height >= y) 346 currentSelection.push(i); 347 } 348 return currentSelection; 349 }; 350}; 351 352/** 353 * Sets date and time format. 354 * @param {boolean} use12hourClock True if 12 hours clock, False if 24 hours. 355 */ 356FileTable.prototype.setDateTimeFormat = function(use12hourClock) { 357 this.timeFormatter_ = Intl.DateTimeFormat( 358 [] /* default locale */, 359 {hour: 'numeric', minute: 'numeric', 360 hour12: use12hourClock}); 361 this.dateFormatter_ = Intl.DateTimeFormat( 362 [] /* default locale */, 363 {year: 'numeric', month: 'short', day: 'numeric', 364 hour: 'numeric', minute: 'numeric', 365 hour12: use12hourClock}); 366}; 367 368/** 369 * Obtains if the drag selection should be start or not by referring the mouse 370 * event. 371 * @param {MouseEvent} event Drag start event. 372 * @return {boolean} True if the mouse is hit to the background of the list. 373 * @private 374 */ 375FileTable.prototype.shouldStartDragSelection_ = function(event) { 376 // If the shift key is pressed, it should starts drag selection. 377 if (event.shiftKey) 378 return true; 379 380 // If the position values are negative, it points the out of list. 381 // It should start the drag selection. 382 var pos = DragSelector.getScrolledPosition(this.list, event); 383 if (!pos) 384 return false; 385 if (pos.x < 0 || pos.y < 0) 386 return true; 387 388 // If the item index is out of range, it should start the drag selection. 389 var itemHeight = this.list.measureItem().height; 390 // Faster alternative to Math.floor for non-negative numbers. 391 var itemIndex = ~~(pos.y / itemHeight); 392 if (itemIndex >= this.list.dataModel.length) 393 return true; 394 395 // If the pointed item is already selected, it should not start the drag 396 // selection. 397 if (this.lastSelection_.indexOf(itemIndex) !== -1) 398 return false; 399 400 // If the horizontal value is not hit to column, it should start the drag 401 // selection. 402 var hitColumn = this.columnModel.getHitColumn(pos.x); 403 if (!hitColumn) 404 return true; 405 406 // Check if the point is on the column contents or not. 407 var item = this.list.getListItemByIndex(itemIndex); 408 switch (this.columnModel.columns_[hitColumn.index].id) { 409 case 'name': 410 var spanElement = item.querySelector('.filename-label span'); 411 var spanRect = spanElement.getBoundingClientRect(); 412 // The this.list.cachedBounds_ object is set by 413 // DragSelector.getScrolledPosition. 414 if (!this.list.cachedBounds) 415 return true; 416 var textRight = 417 spanRect.left - this.list.cachedBounds.left + spanRect.width; 418 return textRight <= hitColumn.hitPosition; 419 default: 420 return true; 421 } 422}; 423 424/** 425 * Prepares the data model to be sorted by columns. 426 * @param {cr.ui.ArrayDataModel} dataModel Data model to prepare. 427 */ 428FileTable.prototype.setupCompareFunctions = function(dataModel) { 429 dataModel.setCompareFunction('name', 430 this.compareName_.bind(this)); 431 dataModel.setCompareFunction('modificationTime', 432 this.compareMtime_.bind(this)); 433 dataModel.setCompareFunction('size', 434 this.compareSize_.bind(this)); 435 dataModel.setCompareFunction('type', 436 this.compareType_.bind(this)); 437}; 438 439/** 440 * Render the Name column of the detail table. 441 * 442 * Invoked by cr.ui.Table when a file needs to be rendered. 443 * 444 * @param {Entry} entry The Entry object to render. 445 * @param {string} columnId The id of the column to be rendered. 446 * @param {cr.ui.Table} table The table doing the rendering. 447 * @return {HTMLDivElement} Created element. 448 * @private 449 */ 450FileTable.prototype.renderName_ = function(entry, columnId, table) { 451 var label = this.ownerDocument.createElement('div'); 452 label.appendChild(this.renderIconType_(entry, columnId, table)); 453 label.entry = entry; 454 label.className = 'detail-name'; 455 label.appendChild(filelist.renderFileNameLabel(this.ownerDocument, entry)); 456 return label; 457}; 458 459/** 460 * Render the Size column of the detail table. 461 * 462 * @param {Entry} entry The Entry object to render. 463 * @param {string} columnId The id of the column to be rendered. 464 * @param {cr.ui.Table} table The table doing the rendering. 465 * @return {HTMLDivElement} Created element. 466 * @private 467 */ 468FileTable.prototype.renderSize_ = function(entry, columnId, table) { 469 var div = this.ownerDocument.createElement('div'); 470 div.className = 'size'; 471 this.updateSize_( 472 div, entry, this.metadataCache_.getCached(entry, 'filesystem')); 473 474 return div; 475}; 476 477/** 478 * Sets up or updates the size cell. 479 * 480 * @param {HTMLDivElement} div The table cell. 481 * @param {Entry} entry The corresponding entry. 482 * @param {Object} filesystemProps Metadata. 483 * @private 484 */ 485FileTable.prototype.updateSize_ = function(div, entry, filesystemProps) { 486 if (!filesystemProps) { 487 div.textContent = '...'; 488 } else if (filesystemProps.size === -1) { 489 div.textContent = '--'; 490 } else if (filesystemProps.size === 0 && 491 FileType.isHosted(entry)) { 492 div.textContent = '--'; 493 } else { 494 div.textContent = util.bytesToString(filesystemProps.size); 495 } 496}; 497 498/** 499 * Render the Type column of the detail table. 500 * 501 * @param {Entry} entry The Entry object to render. 502 * @param {string} columnId The id of the column to be rendered. 503 * @param {cr.ui.Table} table The table doing the rendering. 504 * @return {HTMLDivElement} Created element. 505 * @private 506 */ 507FileTable.prototype.renderType_ = function(entry, columnId, table) { 508 var div = this.ownerDocument.createElement('div'); 509 div.className = 'type'; 510 div.textContent = FileType.typeToString(FileType.getType(entry)); 511 return div; 512}; 513 514/** 515 * Render the Date column of the detail table. 516 * 517 * @param {Entry} entry The Entry object to render. 518 * @param {string} columnId The id of the column to be rendered. 519 * @param {cr.ui.Table} table The table doing the rendering. 520 * @return {HTMLDivElement} Created element. 521 * @private 522 */ 523FileTable.prototype.renderDate_ = function(entry, columnId, table) { 524 var div = this.ownerDocument.createElement('div'); 525 div.className = 'date'; 526 527 this.updateDate_(div, 528 this.metadataCache_.getCached(entry, 'filesystem')); 529 return div; 530}; 531 532/** 533 * Sets up or updates the date cell. 534 * 535 * @param {HTMLDivElement} div The table cell. 536 * @param {Object} filesystemProps Metadata. 537 * @private 538 */ 539FileTable.prototype.updateDate_ = function(div, filesystemProps) { 540 if (!filesystemProps) { 541 div.textContent = '...'; 542 return; 543 } 544 545 var modTime = filesystemProps.modificationTime; 546 var today = new Date(); 547 today.setHours(0); 548 today.setMinutes(0); 549 today.setSeconds(0); 550 today.setMilliseconds(0); 551 552 /** 553 * Number of milliseconds in a day. 554 */ 555 var MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; 556 557 if (isNaN(modTime.getTime())) { 558 // In case of 'Invalid Date'. 559 div.textContent = '--'; 560 } else if (modTime >= today && 561 modTime < today.getTime() + MILLISECONDS_IN_DAY) { 562 div.textContent = strf('TIME_TODAY', this.timeFormatter_.format(modTime)); 563 } else if (modTime >= today - MILLISECONDS_IN_DAY && modTime < today) { 564 div.textContent = strf('TIME_YESTERDAY', 565 this.timeFormatter_.format(modTime)); 566 } else { 567 div.textContent = this.dateFormatter_.format(modTime); 568 } 569}; 570 571/** 572 * Updates the file metadata in the table item. 573 * 574 * @param {Element} item Table item. 575 * @param {Entry} entry File entry. 576 */ 577FileTable.prototype.updateFileMetadata = function(item, entry) { 578 var props = this.metadataCache_.getCached(entry, 'filesystem'); 579 this.updateDate_(item.querySelector('.date'), props); 580 this.updateSize_(item.querySelector('.size'), entry, props); 581}; 582 583/** 584 * Updates list items 'in place' on metadata change. 585 * @param {string} type Type of metadata change. 586 * @param {Object.<string, Object>} propsMap Map from entry URLs to metadata 587 * properties. 588 */ 589FileTable.prototype.updateListItemsMetadata = function(type, propsMap) { 590 var forEachCell = function(selector, callback) { 591 var cells = this.querySelectorAll(selector); 592 for (var i = 0; i < cells.length; i++) { 593 var cell = cells[i]; 594 var listItem = this.list_.getListItemAncestor(cell); 595 var entry = this.dataModel.item(listItem.listIndex); 596 if (entry) { 597 var props = propsMap[entry.toURL()]; 598 if (props) 599 callback.call(this, cell, entry, props, listItem); 600 } 601 } 602 }.bind(this); 603 if (type === 'filesystem') { 604 forEachCell('.table-row-cell > .date', function(item, entry, props) { 605 this.updateDate_(item, props); 606 }); 607 forEachCell('.table-row-cell > .size', function(item, entry, props) { 608 this.updateSize_(item, entry, props); 609 }); 610 } else if (type === 'drive') { 611 // The cell name does not matter as the entire list item is needed. 612 forEachCell('.table-row-cell > .date', 613 function(item, entry, props, listItem) { 614 filelist.updateListItemDriveProps(listItem, props); 615 }); 616 } 617}; 618 619/** 620 * Compare by mtime first, then by name. 621 * @param {Entry} a First entry. 622 * @param {Entry} b Second entry. 623 * @return {number} Compare result. 624 * @private 625 */ 626FileTable.prototype.compareName_ = function(a, b) { 627 return this.collator_.compare(a.name, b.name); 628}; 629 630/** 631 * Compare by mtime first, then by name. 632 * @param {Entry} a First entry. 633 * @param {Entry} b Second entry. 634 * @return {number} Compare result. 635 * @private 636 */ 637FileTable.prototype.compareMtime_ = function(a, b) { 638 var aCachedFilesystem = this.metadataCache_.getCached(a, 'filesystem'); 639 var aTime = aCachedFilesystem ? aCachedFilesystem.modificationTime : 0; 640 641 var bCachedFilesystem = this.metadataCache_.getCached(b, 'filesystem'); 642 var bTime = bCachedFilesystem ? bCachedFilesystem.modificationTime : 0; 643 644 if (aTime > bTime) 645 return 1; 646 647 if (aTime < bTime) 648 return -1; 649 650 return this.collator_.compare(a.name, b.name); 651}; 652 653/** 654 * Compare by size first, then by name. 655 * @param {Entry} a First entry. 656 * @param {Entry} b Second entry. 657 * @return {number} Compare result. 658 * @private 659 */ 660FileTable.prototype.compareSize_ = function(a, b) { 661 var aCachedFilesystem = this.metadataCache_.getCached(a, 'filesystem'); 662 var aSize = aCachedFilesystem ? aCachedFilesystem.size : 0; 663 664 var bCachedFilesystem = this.metadataCache_.getCached(b, 'filesystem'); 665 var bSize = bCachedFilesystem ? bCachedFilesystem.size : 0; 666 667 if (aSize !== bSize) return aSize - bSize; 668 return this.collator_.compare(a.name, b.name); 669}; 670 671/** 672 * Compare by type first, then by subtype and then by name. 673 * @param {Entry} a First entry. 674 * @param {Entry} b Second entry. 675 * @return {number} Compare result. 676 * @private 677 */ 678FileTable.prototype.compareType_ = function(a, b) { 679 // Directories precede files. 680 if (a.isDirectory !== b.isDirectory) 681 return Number(b.isDirectory) - Number(a.isDirectory); 682 683 var aType = FileType.typeToString(FileType.getType(a)); 684 var bType = FileType.typeToString(FileType.getType(b)); 685 686 var result = this.collator_.compare(aType, bType); 687 if (result !== 0) 688 return result; 689 690 return this.collator_.compare(a.name, b.name); 691}; 692 693/** 694 * Renders table row. 695 * @param {function(Entry, cr.ui.Table)} baseRenderFunction Base renderer. 696 * @param {Entry} entry Corresponding entry. 697 * @return {HTMLLiElement} Created element. 698 * @private 699 */ 700FileTable.prototype.renderTableRow_ = function(baseRenderFunction, entry) { 701 var item = baseRenderFunction(entry, this); 702 filelist.decorateListItem(item, entry, this.metadataCache_); 703 return item; 704}; 705 706/** 707 * Render the type column of the detail table. 708 * 709 * Invoked by cr.ui.Table when a file needs to be rendered. 710 * 711 * @param {Entry} entry The Entry object to render. 712 * @param {string} columnId The id of the column to be rendered. 713 * @param {cr.ui.Table} table The table doing the rendering. 714 * @return {HTMLDivElement} Created element. 715 * @private 716 */ 717FileTable.prototype.renderIconType_ = function(entry, columnId, table) { 718 var icon = this.ownerDocument.createElement('div'); 719 icon.className = 'detail-icon'; 720 icon.setAttribute('file-type-icon', FileType.getIcon(entry)); 721 return icon; 722}; 723 724/** 725 * Sets the margin height for the transparent preview panel at the bottom. 726 * @param {number} margin Margin to be set in px. 727 */ 728FileTable.prototype.setBottomMarginForPanel = function(margin) { 729 this.list_.style.paddingBottom = margin + 'px'; 730 this.scrollBar_.setBottomMarginForPanel(margin); 731}; 732 733/** 734 * Redraws the UI. Skips multiple consecutive calls. 735 */ 736FileTable.prototype.relayout = function() { 737 this.relayoutRateLimiter_.run(); 738}; 739 740/** 741 * Redraws the UI immediately. 742 * @private 743 */ 744FileTable.prototype.relayoutImmediately_ = function() { 745 if (this.clientWidth > 0) 746 this.normalizeColumns(); 747 this.redraw(); 748 cr.dispatchSimpleEvent(this.list, 'relayout'); 749}; 750 751/** 752 * Common item decoration for table's and grid's items. 753 * @param {ListItem} li List item. 754 * @param {Entry} entry The entry. 755 * @param {MetadataCache} metadataCache Cache to retrieve metadada. 756 */ 757filelist.decorateListItem = function(li, entry, metadataCache) { 758 li.classList.add(entry.isDirectory ? 'directory' : 'file'); 759 // The metadata may not yet be ready. In that case, the list item will be 760 // updated when the metadata is ready via updateListItemsMetadata. For files 761 // not on Drive, driveProps is not available. 762 var driveProps = metadataCache.getCached(entry, 'drive'); 763 if (driveProps) 764 filelist.updateListItemDriveProps(li, driveProps); 765 766 // Overriding the default role 'list' to 'listbox' for better 767 // accessibility on ChromeOS. 768 li.setAttribute('role', 'option'); 769 770 Object.defineProperty(li, 'selected', { 771 /** 772 * @this {ListItem} 773 * @return {boolean} True if the list item is selected. 774 */ 775 get: function() { 776 return this.hasAttribute('selected'); 777 }, 778 779 /** 780 * @this {ListItem} 781 */ 782 set: function(v) { 783 if (v) 784 this.setAttribute('selected', ''); 785 else 786 this.removeAttribute('selected'); 787 } 788 }); 789}; 790 791/** 792 * Render filename label for grid and list view. 793 * @param {HTMLDocument} doc Owner document. 794 * @param {Entry} entry The Entry object to render. 795 * @return {HTMLDivElement} The label. 796 */ 797filelist.renderFileNameLabel = function(doc, entry) { 798 // Filename need to be in a '.filename-label' container for correct 799 // work of inplace renaming. 800 var box = doc.createElement('div'); 801 box.className = 'filename-label'; 802 var fileName = doc.createElement('span'); 803 fileName.className = 'entry-name'; 804 fileName.textContent = entry.name; 805 box.appendChild(fileName); 806 807 return box; 808}; 809 810/** 811 * Updates grid item or table row for the driveProps. 812 * @param {cr.ui.ListItem} li List item. 813 * @param {Object} driveProps Metadata. 814 */ 815filelist.updateListItemDriveProps = function(li, driveProps) { 816 if (li.classList.contains('file')) { 817 if (driveProps.availableOffline) 818 li.classList.remove('dim-offline'); 819 else 820 li.classList.add('dim-offline'); 821 // TODO(mtomasz): Consider adding some vidual indication for files which 822 // are not cached on LTE. Currently we show them as normal files. 823 // crbug.com/246611. 824 } 825 826 var iconDiv = li.querySelector('.detail-icon'); 827 if (!iconDiv) 828 return; 829 830 if (driveProps.customIconUrl) 831 iconDiv.style.backgroundImage = 'url(' + driveProps.customIconUrl + ')'; 832 833 if (li.classList.contains('directory')) 834 iconDiv.classList.toggle('shared', driveProps.shared); 835}; 836