| OLD | NEW |
| (Empty) |
| 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 */ | |
| 10 var 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 */ | |
| 19 function 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 */ | |
| 29 FileTableColumnModel.RESIZABLE_LENGTH_ = 4; | |
| 30 | |
| 31 /** | |
| 32 * Inherits from cr.ui.TableColumnModel. | |
| 33 */ | |
| 34 FileTableColumnModel.prototype.__proto__ = | |
| 35 cr.ui.table.TableColumnModel.prototype; | |
| 36 | |
| 37 /** | |
| 38 * Minimum width of column. | |
| 39 * @const | |
| 40 * @type {number} | |
| 41 * @private | |
| 42 */ | |
| 43 FileTableColumnModel.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 */ | |
| 53 FileTableColumnModel.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 */ | |
| 78 FileTableColumnModel.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 */ | |
| 103 FileTableColumnModel.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 */ | |
| 113 FileTableColumnModel.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 */ | |
| 122 FileTableColumnModel.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 */ | |
| 163 var FileTableSplitter = cr.ui.define('div'); | |
| 164 | |
| 165 /** | |
| 166 * Inherits from cr.ui.TableSplitter. | |
| 167 */ | |
| 168 FileTableSplitter.prototype.__proto__ = cr.ui.TableSplitter.prototype; | |
| 169 | |
| 170 /** | |
| 171 * Handles the drag start event. | |
| 172 */ | |
| 173 FileTableSplitter.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 */ | |
| 182 FileTableSplitter.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 */ | |
| 191 FileTableSplitter.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 */ | |
| 200 function FileTable() { | |
| 201 throw new Error('Designed to decorate elements'); | |
| 202 } | |
| 203 | |
| 204 /** | |
| 205 * Inherits from cr.ui.Table. | |
| 206 */ | |
| 207 FileTable.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 */ | |
| 216 FileTable.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.relayoutAggregation_ = | |
| 309 new AsyncUtil.Aggregation(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 */ | |
| 356 FileTable.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 */ | |
| 375 FileTable.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 */ | |
| 428 FileTable.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 */ | |
| 450 FileTable.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 */ | |
| 468 FileTable.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 */ | |
| 485 FileTable.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 */ | |
| 507 FileTable.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 */ | |
| 523 FileTable.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 */ | |
| 539 FileTable.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 (modTime >= today && | |
| 558 modTime < today.getTime() + MILLISECONDS_IN_DAY) { | |
| 559 div.textContent = strf('TIME_TODAY', this.timeFormatter_.format(modTime)); | |
| 560 } else if (modTime >= today - MILLISECONDS_IN_DAY && modTime < today) { | |
| 561 div.textContent = strf('TIME_YESTERDAY', | |
| 562 this.timeFormatter_.format(modTime)); | |
| 563 } else { | |
| 564 div.textContent = | |
| 565 this.dateFormatter_.format(filesystemProps.modificationTime); | |
| 566 } | |
| 567 }; | |
| 568 | |
| 569 /** | |
| 570 * Updates the file metadata in the table item. | |
| 571 * | |
| 572 * @param {Element} item Table item. | |
| 573 * @param {Entry} entry File entry. | |
| 574 */ | |
| 575 FileTable.prototype.updateFileMetadata = function(item, entry) { | |
| 576 var props = this.metadataCache_.getCached(entry, 'filesystem'); | |
| 577 this.updateDate_(item.querySelector('.date'), props); | |
| 578 this.updateSize_(item.querySelector('.size'), entry, props); | |
| 579 }; | |
| 580 | |
| 581 /** | |
| 582 * Updates list items 'in place' on metadata change. | |
| 583 * @param {string} type Type of metadata change. | |
| 584 * @param {Object.<string, Object>} propsMap Map from entry URLs to metadata | |
| 585 * properties. | |
| 586 */ | |
| 587 FileTable.prototype.updateListItemsMetadata = function(type, propsMap) { | |
| 588 var forEachCell = function(selector, callback) { | |
| 589 var cells = this.querySelectorAll(selector); | |
| 590 for (var i = 0; i < cells.length; i++) { | |
| 591 var cell = cells[i]; | |
| 592 var listItem = this.list_.getListItemAncestor(cell); | |
| 593 var entry = this.dataModel.item(listItem.listIndex); | |
| 594 if (entry) { | |
| 595 var props = propsMap[entry.toURL()]; | |
| 596 if (props) | |
| 597 callback.call(this, cell, entry, props, listItem); | |
| 598 } | |
| 599 } | |
| 600 }.bind(this); | |
| 601 if (type === 'filesystem') { | |
| 602 forEachCell('.table-row-cell > .date', function(item, entry, props) { | |
| 603 this.updateDate_(item, props); | |
| 604 }); | |
| 605 forEachCell('.table-row-cell > .size', function(item, entry, props) { | |
| 606 this.updateSize_(item, entry, props); | |
| 607 }); | |
| 608 } else if (type === 'drive') { | |
| 609 // The cell name does not matter as the entire list item is needed. | |
| 610 forEachCell('.table-row-cell > .date', | |
| 611 function(item, entry, props, listItem) { | |
| 612 filelist.updateListItemDriveProps(listItem, props); | |
| 613 }); | |
| 614 } | |
| 615 }; | |
| 616 | |
| 617 /** | |
| 618 * Compare by mtime first, then by name. | |
| 619 * @param {Entry} a First entry. | |
| 620 * @param {Entry} b Second entry. | |
| 621 * @return {number} Compare result. | |
| 622 * @private | |
| 623 */ | |
| 624 FileTable.prototype.compareName_ = function(a, b) { | |
| 625 return this.collator_.compare(a.name, b.name); | |
| 626 }; | |
| 627 | |
| 628 /** | |
| 629 * Compare by mtime first, then by name. | |
| 630 * @param {Entry} a First entry. | |
| 631 * @param {Entry} b Second entry. | |
| 632 * @return {number} Compare result. | |
| 633 * @private | |
| 634 */ | |
| 635 FileTable.prototype.compareMtime_ = function(a, b) { | |
| 636 var aCachedFilesystem = this.metadataCache_.getCached(a, 'filesystem'); | |
| 637 var aTime = aCachedFilesystem ? aCachedFilesystem.modificationTime : 0; | |
| 638 | |
| 639 var bCachedFilesystem = this.metadataCache_.getCached(b, 'filesystem'); | |
| 640 var bTime = bCachedFilesystem ? bCachedFilesystem.modificationTime : 0; | |
| 641 | |
| 642 if (aTime > bTime) | |
| 643 return 1; | |
| 644 | |
| 645 if (aTime < bTime) | |
| 646 return -1; | |
| 647 | |
| 648 return this.collator_.compare(a.name, b.name); | |
| 649 }; | |
| 650 | |
| 651 /** | |
| 652 * Compare by size first, then by name. | |
| 653 * @param {Entry} a First entry. | |
| 654 * @param {Entry} b Second entry. | |
| 655 * @return {number} Compare result. | |
| 656 * @private | |
| 657 */ | |
| 658 FileTable.prototype.compareSize_ = function(a, b) { | |
| 659 var aCachedFilesystem = this.metadataCache_.getCached(a, 'filesystem'); | |
| 660 var aSize = aCachedFilesystem ? aCachedFilesystem.size : 0; | |
| 661 | |
| 662 var bCachedFilesystem = this.metadataCache_.getCached(b, 'filesystem'); | |
| 663 var bSize = bCachedFilesystem ? bCachedFilesystem.size : 0; | |
| 664 | |
| 665 if (aSize !== bSize) return aSize - bSize; | |
| 666 return this.collator_.compare(a.name, b.name); | |
| 667 }; | |
| 668 | |
| 669 /** | |
| 670 * Compare by type first, then by subtype and then by name. | |
| 671 * @param {Entry} a First entry. | |
| 672 * @param {Entry} b Second entry. | |
| 673 * @return {number} Compare result. | |
| 674 * @private | |
| 675 */ | |
| 676 FileTable.prototype.compareType_ = function(a, b) { | |
| 677 // Directories precede files. | |
| 678 if (a.isDirectory !== b.isDirectory) | |
| 679 return Number(b.isDirectory) - Number(a.isDirectory); | |
| 680 | |
| 681 var aType = FileType.typeToString(FileType.getType(a)); | |
| 682 var bType = FileType.typeToString(FileType.getType(b)); | |
| 683 | |
| 684 var result = this.collator_.compare(aType, bType); | |
| 685 if (result !== 0) | |
| 686 return result; | |
| 687 | |
| 688 return this.collator_.compare(a.name, b.name); | |
| 689 }; | |
| 690 | |
| 691 /** | |
| 692 * Renders table row. | |
| 693 * @param {function(Entry, cr.ui.Table)} baseRenderFunction Base renderer. | |
| 694 * @param {Entry} entry Corresponding entry. | |
| 695 * @return {HTMLLiElement} Created element. | |
| 696 * @private | |
| 697 */ | |
| 698 FileTable.prototype.renderTableRow_ = function(baseRenderFunction, entry) { | |
| 699 var item = baseRenderFunction(entry, this); | |
| 700 filelist.decorateListItem(item, entry, this.metadataCache_); | |
| 701 return item; | |
| 702 }; | |
| 703 | |
| 704 /** | |
| 705 * Render the type column of the detail table. | |
| 706 * | |
| 707 * Invoked by cr.ui.Table when a file needs to be rendered. | |
| 708 * | |
| 709 * @param {Entry} entry The Entry object to render. | |
| 710 * @param {string} columnId The id of the column to be rendered. | |
| 711 * @param {cr.ui.Table} table The table doing the rendering. | |
| 712 * @return {HTMLDivElement} Created element. | |
| 713 * @private | |
| 714 */ | |
| 715 FileTable.prototype.renderIconType_ = function(entry, columnId, table) { | |
| 716 var icon = this.ownerDocument.createElement('div'); | |
| 717 icon.className = 'detail-icon'; | |
| 718 icon.setAttribute('file-type-icon', FileType.getIcon(entry)); | |
| 719 return icon; | |
| 720 }; | |
| 721 | |
| 722 /** | |
| 723 * Sets the margin height for the transparent preview panel at the bottom. | |
| 724 * @param {number} margin Margin to be set in px. | |
| 725 */ | |
| 726 FileTable.prototype.setBottomMarginForPanel = function(margin) { | |
| 727 this.list_.style.paddingBottom = margin + 'px'; | |
| 728 this.scrollBar_.setBottomMarginForPanel(margin); | |
| 729 }; | |
| 730 | |
| 731 /** | |
| 732 * Redraws the UI. Skips multiple consecutive calls. | |
| 733 */ | |
| 734 FileTable.prototype.relayout = function() { | |
| 735 this.relayoutAggregation_.run(); | |
| 736 }; | |
| 737 | |
| 738 /** | |
| 739 * Redraws the UI immediately. | |
| 740 * @private | |
| 741 */ | |
| 742 FileTable.prototype.relayoutImmediately_ = function() { | |
| 743 if (this.clientWidth > 0) | |
| 744 this.normalizeColumns(); | |
| 745 this.redraw(); | |
| 746 cr.dispatchSimpleEvent(this.list, 'relayout'); | |
| 747 }; | |
| 748 | |
| 749 /** | |
| 750 * Common item decoration for table's and grid's items. | |
| 751 * @param {ListItem} li List item. | |
| 752 * @param {Entry} entry The entry. | |
| 753 * @param {MetadataCache} metadataCache Cache to retrieve metadada. | |
| 754 */ | |
| 755 filelist.decorateListItem = function(li, entry, metadataCache) { | |
| 756 li.classList.add(entry.isDirectory ? 'directory' : 'file'); | |
| 757 // The metadata may not yet be ready. In that case, the list item will be | |
| 758 // updated when the metadata is ready via updateListItemsMetadata. For files | |
| 759 // not on Drive, driveProps is not available. | |
| 760 var driveProps = metadataCache.getCached(entry, 'drive'); | |
| 761 if (driveProps) | |
| 762 filelist.updateListItemDriveProps(li, driveProps); | |
| 763 | |
| 764 // Overriding the default role 'list' to 'listbox' for better | |
| 765 // accessibility on ChromeOS. | |
| 766 li.setAttribute('role', 'option'); | |
| 767 | |
| 768 Object.defineProperty(li, 'selected', { | |
| 769 /** | |
| 770 * @this {ListItem} | |
| 771 * @return {boolean} True if the list item is selected. | |
| 772 */ | |
| 773 get: function() { | |
| 774 return this.hasAttribute('selected'); | |
| 775 }, | |
| 776 | |
| 777 /** | |
| 778 * @this {ListItem} | |
| 779 */ | |
| 780 set: function(v) { | |
| 781 if (v) | |
| 782 this.setAttribute('selected', ''); | |
| 783 else | |
| 784 this.removeAttribute('selected'); | |
| 785 } | |
| 786 }); | |
| 787 }; | |
| 788 | |
| 789 /** | |
| 790 * Render filename label for grid and list view. | |
| 791 * @param {HTMLDocument} doc Owner document. | |
| 792 * @param {Entry} entry The Entry object to render. | |
| 793 * @return {HTMLDivElement} The label. | |
| 794 */ | |
| 795 filelist.renderFileNameLabel = function(doc, entry) { | |
| 796 // Filename need to be in a '.filename-label' container for correct | |
| 797 // work of inplace renaming. | |
| 798 var box = doc.createElement('div'); | |
| 799 box.className = 'filename-label'; | |
| 800 var fileName = doc.createElement('span'); | |
| 801 fileName.textContent = entry.name; | |
| 802 box.appendChild(fileName); | |
| 803 | |
| 804 return box; | |
| 805 }; | |
| 806 | |
| 807 /** | |
| 808 * Updates grid item or table row for the driveProps. | |
| 809 * @param {cr.ui.ListItem} li List item. | |
| 810 * @param {Object} driveProps Metadata. | |
| 811 */ | |
| 812 filelist.updateListItemDriveProps = function(li, driveProps) { | |
| 813 if (li.classList.contains('file')) { | |
| 814 if (driveProps.availableOffline) | |
| 815 li.classList.remove('dim-offline'); | |
| 816 else | |
| 817 li.classList.add('dim-offline'); | |
| 818 // TODO(mtomasz): Consider adding some vidual indication for files which | |
| 819 // are not cached on LTE. Currently we show them as normal files. | |
| 820 // crbug.com/246611. | |
| 821 } | |
| 822 | |
| 823 if (driveProps.customIconUrl) { | |
| 824 var iconDiv = li.querySelector('.detail-icon'); | |
| 825 if (!iconDiv) | |
| 826 return; | |
| 827 iconDiv.style.backgroundImage = 'url(' + driveProps.customIconUrl + ')'; | |
| 828 } | |
| 829 }; | |
| OLD | NEW |