Index: ui/file_manager/file_manager/foreground/js/ui/file_table_list.js |
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js b/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js |
index 609f8d7a277a07736e99fbda511f431dd8ec790c..fc26203f0715b4df38a73a1c30ed2d3716e27a74 100644 |
--- a/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js |
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_table_list.js |
@@ -3,12 +3,19 @@ |
// found in the LICENSE file. |
/** |
+ * Namespace for utility functions. |
+ */ |
+var filelist = {}; |
+ |
+/** |
* File table list. |
* @constructor |
* @struct |
* @extends {cr.ui.table.TableList} |
*/ |
-function FileTableList() {} |
+function FileTableList() { |
+ throw new Error('Designed to decorate elements'); |
+} |
/** |
* Decorates TableList as FileTableList. |
@@ -20,6 +27,20 @@ FileTableList.decorate = function(self) { |
FileTableList.prototype.__proto__ = cr.ui.table.TableList.prototype; |
+/** |
+ * @type {?function(number, number)} |
+ */ |
+FileTableList.prototype.onMergeItems_ = null; |
+ |
+/** |
+ * @param {function(number, number)} onMergeItems callback called from |
+ * |mergeItems| with the parameters |beginIndex| and |endIndex|. |
+ */ |
+FileTableList.prototype.setOnMergeItems = function(onMergeItems) { |
+ assert(!this.onMergeItems_); |
+ this.onMergeItems_ = onMergeItems; |
+}; |
+ |
/** @override */ |
FileTableList.prototype.mergeItems = function(beginIndex, endIndex) { |
cr.ui.table.TableList.prototype.mergeItems.call(this, beginIndex, endIndex); |
@@ -36,7 +57,9 @@ FileTableList.prototype.mergeItems = function(beginIndex, endIndex) { |
item.selected = isSelected; |
} |
- this.table.updateHighPriorityRange(beginIndex, endIndex); |
+ if (this.onMergeItems_) { |
+ this.onMergeItems_(beginIndex, endIndex); |
+ } |
} |
/** @override */ |
@@ -69,3 +92,324 @@ FileListSelectionController.prototype.handlePointerDownUp = function(e, index) { |
FileListSelectionController.prototype.handleKeyDown = function(e) { |
filelist.handleKeyDown.call(this, e); |
}; |
+ |
+/** |
+ * Common item decoration for table's and grid's items. |
+ * @param {cr.ui.ListItem} li List item. |
+ * @param {Entry} entry The entry. |
+ * @param {!MetadataModel} metadataModel Cache to |
+ * retrieve metadada. |
+ */ |
+filelist.decorateListItem = function(li, entry, metadataModel) { |
+ li.classList.add(entry.isDirectory ? 'directory' : 'file'); |
+ // The metadata may not yet be ready. In that case, the list item will be |
+ // updated when the metadata is ready via updateListItemsMetadata. For files |
+ // not on an external backend, externalProps is not available. |
+ var externalProps = metadataModel.getCache( |
+ [entry], ['hosted', 'availableOffline', 'customIconUrl', 'shared'])[0]; |
+ filelist.updateListItemExternalProps(li, externalProps); |
+ |
+ // Overriding the default role 'list' to 'listbox' for better |
+ // accessibility on ChromeOS. |
+ li.setAttribute('role', 'option'); |
+ |
+ Object.defineProperty(li, 'selected', { |
+ /** |
+ * @this {cr.ui.ListItem} |
+ * @return {boolean} True if the list item is selected. |
+ */ |
+ get: function() { |
+ return this.hasAttribute('selected'); |
+ }, |
+ |
+ /** |
+ * @this {cr.ui.ListItem} |
+ */ |
+ set: function(v) { |
+ if (v) |
+ this.setAttribute('selected', ''); |
+ else |
+ this.removeAttribute('selected'); |
+ } |
+ }); |
+}; |
+ |
+/** |
+ * Render the type column of the detail table. |
+ * @param {!Document} doc Owner document. |
+ * @param {!Entry} entry The Entry object to render. |
+ * @param {string=} opt_mimeType Optional mime type for the file. |
+ * @return {!HTMLDivElement} Created element. |
+ */ |
+filelist.renderFileTypeIcon = function(doc, entry, opt_mimeType) { |
+ var icon = /** @type {!HTMLDivElement} */ (doc.createElement('div')); |
+ icon.className = 'detail-icon'; |
+ icon.setAttribute('file-type-icon', FileType.getIcon(entry, opt_mimeType)); |
+ return icon; |
+}; |
+ |
+/** |
+ * Render filename label for grid and list view. |
+ * @param {!Document} doc Owner document. |
+ * @param {!Entry} entry The Entry object to render. |
+ * @return {!HTMLDivElement} The label. |
+ */ |
+filelist.renderFileNameLabel = function(doc, entry) { |
+ // Filename need to be in a '.filename-label' container for correct |
+ // work of inplace renaming. |
+ var box = /** @type {!HTMLDivElement} */ (doc.createElement('div')); |
+ box.className = 'filename-label'; |
+ var fileName = doc.createElement('span'); |
+ fileName.className = 'entry-name'; |
+ fileName.textContent = entry.name; |
+ box.appendChild(fileName); |
+ |
+ return box; |
+}; |
+ |
+/** |
+ * Updates grid item or table row for the externalProps. |
+ * @param {cr.ui.ListItem} li List item. |
+ * @param {Object} externalProps Metadata. |
+ */ |
+filelist.updateListItemExternalProps = function(li, externalProps) { |
+ if (li.classList.contains('file')) { |
+ if (externalProps.availableOffline) |
+ li.classList.remove('dim-offline'); |
+ else |
+ li.classList.add('dim-offline'); |
+ // TODO(mtomasz): Consider adding some vidual indication for files which |
+ // are not cached on LTE. Currently we show them as normal files. |
+ // crbug.com/246611. |
+ } |
+ |
+ var iconDiv = li.querySelector('.detail-icon'); |
+ if (!iconDiv) |
+ return; |
+ |
+ if (externalProps.customIconUrl) |
+ iconDiv.style.backgroundImage = 'url(' + externalProps.customIconUrl + ')'; |
+ else |
+ iconDiv.style.backgroundImage = ''; // Back to the default image. |
+ |
+ if (li.classList.contains('directory')) |
+ iconDiv.classList.toggle('shared', !!externalProps.shared); |
+}; |
+ |
+/** |
+ * Handles mouseup/mousedown events on file list to change the selection state. |
+ * |
+ * Basically the content of this function is identical to |
+ * cr.ui.ListSelectionController's handlePointerDownUp(), but following |
+ * handlings are inserted to control the check-select mode. |
+ * |
+ * 1) When checkmark area is clicked, toggle item selection and enable the |
+ * check-select mode. |
+ * 2) When non-checkmark area is clicked in check-select mode, disable the |
+ * check-select mode. |
+ * |
+ * @param {!Event} e The browser mouse event. |
+ * @param {number} index The index that was under the mouse pointer, -1 if |
+ * none. |
+ * @this {cr.ui.ListSelectionController} |
+ */ |
+filelist.handlePointerDownUp = function(e, index) { |
+ var sm = /** @type {!FileListSelectionModel|!FileListSingleSelectionModel} */ |
+ (this.selectionModel); |
+ var anchorIndex = sm.anchorIndex; |
+ var isDown = (e.type == 'mousedown'); |
+ |
+ var isTargetCheckmark = e.target.classList.contains('detail-checkmark') || |
+ e.target.classList.contains('checkmark'); |
+ // If multiple selection is allowed and the checkmark is clicked without |
+ // modifiers(Ctrl/Shift), the click should toggle the item's selection. |
+ // (i.e. same behavior as Ctrl+Click) |
+ var isClickOnCheckmark = isTargetCheckmark && sm.multiple && index != -1 && |
+ !e.shiftKey && !e.ctrlKey && e.button == 0; |
+ |
+ sm.beginChange(); |
+ |
+ if (index == -1) { |
+ sm.leadIndex = sm.anchorIndex = -1; |
+ sm.unselectAll(); |
+ } else { |
+ if (sm.multiple && (e.ctrlKey || isClickOnCheckmark) && !e.shiftKey) { |
+ // Selection is handled at mouseUp. |
+ if (!isDown) { |
+ // 1) When checkmark area is clicked, toggle item selection and enable |
+ // the check-select mode. |
+ if (isClickOnCheckmark) { |
+ // If a selected item's checkmark is clicked when the selection mode |
+ // is not check-select, we should avoid toggling(unselecting) the |
+ // item. It is done here by toggling the selection twice. |
+ if (!sm.getCheckSelectMode() && sm.getIndexSelected(index)) |
+ sm.setIndexSelected(index, !sm.getIndexSelected(index)); |
+ // Always enables check-select mode on clicks on checkmark. |
+ sm.setCheckSelectMode(true); |
+ } |
+ // Toggle the current one and make it anchor index. |
+ sm.setIndexSelected(index, !sm.getIndexSelected(index)); |
+ sm.leadIndex = index; |
+ sm.anchorIndex = index; |
+ } |
+ } else if (e.shiftKey && anchorIndex != -1 && anchorIndex != index) { |
+ // Shift is done in mousedown. |
+ if (isDown) { |
+ sm.unselectAll(); |
+ sm.leadIndex = index; |
+ if (sm.multiple) |
+ sm.selectRange(anchorIndex, index); |
+ else |
+ sm.setIndexSelected(index, true); |
+ } |
+ } else { |
+ // Right click for a context menu needs to not clear the selection. |
+ var isRightClick = e.button == 2; |
+ |
+ // If the index is selected this is handled in mouseup. |
+ var indexSelected = sm.getIndexSelected(index); |
+ if ((indexSelected && !isDown || !indexSelected && isDown) && |
+ !(indexSelected && isRightClick)) { |
+ // 2) When non-checkmark area is clicked in check-select mode, disable |
+ // the check-select mode. |
+ if (sm.getCheckSelectMode()) { |
+ // Unselect all items once to ensure that the check-select mode is |
+ // terminated. |
+ sm.endChange(); |
+ sm.unselectAll(); |
+ sm.beginChange(); |
+ } |
+ sm.selectedIndex = index; |
+ } |
+ } |
+ } |
+ sm.endChange(); |
+}; |
+ |
+/** |
+ * Handles key events on file list to change the selection state. |
+ * |
+ * Basically the content of this function is identical to |
+ * cr.ui.ListSelectionController's handleKeyDown(), but following handlings is |
+ * inserted to control the check-select mode. |
+ * |
+ * 1) When pressing direction key results in a single selection, the |
+ * check-select mode should be terminated. |
+ * |
+ * @param {Event} e The keydown event. |
+ * @this {cr.ui.ListSelectionController} |
+ */ |
+filelist.handleKeyDown = function(e) { |
+ var SPACE_KEY_CODE = 32; |
+ var tagName = e.target.tagName; |
+ |
+ // If focus is in an input field of some kind, only handle navigation keys |
+ // that aren't likely to conflict with input interaction (e.g., text |
+ // editing, or changing the value of a checkbox or select). |
+ if (tagName == 'INPUT') { |
+ var inputType = e.target.type; |
+ // Just protect space (for toggling) for checkbox and radio. |
+ if (inputType == 'checkbox' || inputType == 'radio') { |
+ if (e.keyCode == SPACE_KEY_CODE) |
+ return; |
+ // Protect all but the most basic navigation commands in anything else. |
+ } else if (e.key != 'ArrowUp' && e.key != 'ArrowDown') { |
+ return; |
+ } |
+ } |
+ // Similarly, don't interfere with select element handling. |
+ if (tagName == 'SELECT') |
+ return; |
+ |
+ var sm = /** @type {!FileListSelectionModel|!FileListSingleSelectionModel} */ |
+ (this.selectionModel); |
+ var newIndex = -1; |
+ var leadIndex = sm.leadIndex; |
+ var prevent = true; |
+ |
+ // Ctrl/Meta+A |
+ if (sm.multiple && e.keyCode == 65 && |
+ (cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey)) { |
+ sm.selectAll(); |
+ e.preventDefault(); |
+ return; |
+ } |
+ |
+ // Esc |
+ if (e.keyCode === 27 && !e.ctrlKey && !e.shiftKey) { |
+ sm.unselectAll(); |
+ e.preventDefault(); |
+ return; |
+ } |
+ |
+ // Space |
+ if (e.keyCode == SPACE_KEY_CODE) { |
+ if (leadIndex != -1) { |
+ var selected = sm.getIndexSelected(leadIndex); |
+ if (e.ctrlKey || !selected) { |
+ sm.setIndexSelected(leadIndex, !selected || !sm.multiple); |
+ return; |
+ } |
+ } |
+ } |
+ |
+ switch (e.key) { |
+ case 'Home': |
+ newIndex = this.getFirstIndex(); |
+ break; |
+ case 'End': |
+ newIndex = this.getLastIndex(); |
+ break; |
+ case 'ArrowUp': |
+ newIndex = leadIndex == -1 ? |
+ this.getLastIndex() : this.getIndexAbove(leadIndex); |
+ break; |
+ case 'ArrowDown': |
+ newIndex = leadIndex == -1 ? |
+ this.getFirstIndex() : this.getIndexBelow(leadIndex); |
+ break; |
+ case 'ArrowLeft': |
+ case 'MediaTrackPrevious': |
+ newIndex = leadIndex == -1 ? |
+ this.getLastIndex() : this.getIndexBefore(leadIndex); |
+ break; |
+ case 'ArrowRight': |
+ case 'MediaTrackNext': |
+ newIndex = leadIndex == -1 ? |
+ this.getFirstIndex() : this.getIndexAfter(leadIndex); |
+ break; |
+ default: |
+ prevent = false; |
+ } |
+ |
+ if (newIndex >= 0 && newIndex < sm.length) { |
+ sm.beginChange(); |
+ |
+ sm.leadIndex = newIndex; |
+ if (e.shiftKey) { |
+ var anchorIndex = sm.anchorIndex; |
+ if (sm.multiple) |
+ sm.unselectAll(); |
+ if (anchorIndex == -1) { |
+ sm.setIndexSelected(newIndex, true); |
+ sm.anchorIndex = newIndex; |
+ } else { |
+ sm.selectRange(anchorIndex, newIndex); |
+ } |
+ } else { |
+ // 1) When pressing direction key results in a single selection, the |
+ // check-select mode should be terminated. |
+ sm.setCheckSelectMode(false); |
+ |
+ if (sm.multiple) |
+ sm.unselectAll(); |
+ sm.setIndexSelected(newIndex, true); |
+ sm.anchorIndex = newIndex; |
+ } |
+ |
+ sm.endChange(); |
+ |
+ if (prevent) |
+ e.preventDefault(); |
+ } |
+}; |