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 |