Commit 611f8753 authored by Luciano Pacheco's avatar Luciano Pacheco Committed by Commit Bot

[Files app] Convert directory_tree.js to ES6 class

Converted with lebab:
$ lebab --replace tree.js  --transform class

Then manually:

1. Move @extends {cr.ui.TreeItem} to actual ES6 extends.
2. call super() in the ctor.
3. Move Closure markup from class to its ctor.
4. Remove return from ctor.
5. Rename item.* to this.* in ctor.
6. Check FunctionName.prototype = { __proto__: ... } move getters/
setters to class body, removing trailing commas.
7. Fix closure errors, mostly declaring attributes in the ctor.
8. Run clang format in the file, because lebab format style is very
different.

$ buildtools/linux64/clang-format -i \
   ui/file_manager/file_manager/foreground/js/ui/directory_tree.js

Test: No change in behavior, so current tests already covers it.
Bug: 778674
Change-Id: I2f11ea6d85ef9546fc3b54e5b0a5e187e8b3e90b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1539479
Commit-Queue: Luciano Pacheco <lucmult@chromium.org>
Reviewed-by: default avatarAlex Danilo <adanilo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#644689}
parent 9f743848
...@@ -62,8 +62,8 @@ DirectoryItemTreeBaseMethods.getItemByEntry = function(entry) { ...@@ -62,8 +62,8 @@ DirectoryItemTreeBaseMethods.getItemByEntry = function(entry) {
* Finds a parent directory of the {@code entry} in {@code this}, and * Finds a parent directory of the {@code entry} in {@code this}, and
* invokes the DirectoryItem.selectByEntry() of the found directory. * invokes the DirectoryItem.selectByEntry() of the found directory.
* *
* @param {!DirectoryEntry|!FakeEntry} entry The entry to be searched for. Can * @param {!DirectoryEntry|!FilesAppDirEntry} entry The entry to be searched
* be a fake. * for. Can be a fake.
* @return {boolean} True if the parent item is found. * @return {boolean} True if the parent item is found.
* @this {(DirectoryItem|VolumeItem|DirectoryTree)} * @this {(DirectoryItem|VolumeItem|DirectoryTree)}
*/ */
...@@ -140,57 +140,54 @@ const TREE_ITEM_INNER_HTML = '<div class="tree-row">' + ...@@ -140,57 +140,54 @@ const TREE_ITEM_INNER_HTML = '<div class="tree-row">' +
/** /**
* An expandable directory in the tree. Each element represents one folder (sub * An expandable directory in the tree. Each element represents one folder (sub
* directory) or one volume (root directory). * directory) or one volume (root directory).
* */
class DirectoryItem extends cr.ui.TreeItem {
/**
* @param {string} label Label for this item. * @param {string} label Label for this item.
* @param {DirectoryTree} tree Current tree, which contains this item. * @param {DirectoryTree} tree Current tree, which contains this item.
* @extends {cr.ui.TreeItem}
* @constructor
*/ */
function DirectoryItem(label, tree) { constructor(label, tree) {
const item = /** @type {DirectoryItem} */ (new cr.ui.TreeItem()); super();
// Get the original label id defined by TreeItem, before overwriting // Get the original label id defined by TreeItem, before overwriting
// prototype. // prototype.
const labelId = item.labelElement.id; const labelId = this.labelElement.id;
item.__proto__ = DirectoryItem.prototype; this.__proto__ = DirectoryItem.prototype;
if (window.IN_TEST) { if (window.IN_TEST) {
item.setAttribute('dir-type', 'DirectoryItem'); this.setAttribute('dir-type', 'DirectoryItem');
item.setAttribute('entry-label', label); this.setAttribute('entry-label', label);
} }
item.parentTree_ = tree; this.parentTree_ = tree;
item.directoryModel_ = tree.directoryModel; this.directoryModel_ = tree.directoryModel;
item.fileFilter_ = tree.directoryModel.getFileFilter(); this.fileFilter_ = tree.directoryModel.getFileFilter();
item.innerHTML = TREE_ITEM_INNER_HTML; this.innerHTML = TREE_ITEM_INNER_HTML;
item.labelElement.id = labelId; this.labelElement.id = labelId;
item.addEventListener('expand', item.onExpand_.bind(item), false); this.addEventListener('expand', this.onExpand_.bind(this), false);
// Listen for collapse because for the delayed expansion case all // Listen for collapse because for the delayed expansion case all
// children are also collapsed. // children are also collapsed.
item.addEventListener('collapse', item.onCollapse_.bind(item), false); this.addEventListener('collapse', this.onCollapse_.bind(this), false);
// Default delayExpansion to false. Volumes will set it to true for // Default delayExpansion to false. Volumes will set it to true for
// provided file systems. SubDirectories will inherit from their // provided file systems. SubDirectories will inherit from their
// parent. // parent.
item.delayExpansion = false; this.delayExpansion = false;
// Sets hasChildren=false tentatively. This will be overridden after // Sets hasChildren=false tentatively. This will be overridden after
// scanning sub-directories in updateSubElementsFromList(). // scanning sub-directories in updateSubElementsFromList().
item.hasChildren = false; this.hasChildren = false;
item.label = label; this.label = label;
// @type {!Array<Entry>} Filled after updateSubDirectories read entries. // @type {!Array<Entry>} Filled after updateSubDirectories read entries.
item.entries_ = []; this.entries_ = [];
// @type {function()=} onMetadataUpdated_ bound to |this| used to listen // @type {function()=} onMetadataUpdated_ bound to |this| used to listen
// metadata update events. // metadata update events.
item.onMetadataUpdateBound_ = undefined; this.onMetadataUpdateBound_ = undefined;
}
return item;
}
DirectoryItem.prototype = {
__proto__: cr.ui.TreeItem.prototype,
/** /**
* The DirectoryEntry corresponding to this DirectoryItem. This may be * The DirectoryEntry corresponding to this DirectoryItem. This may be
...@@ -199,7 +196,7 @@ DirectoryItem.prototype = { ...@@ -199,7 +196,7 @@ DirectoryItem.prototype = {
*/ */
get entry() { get entry() {
return null; return null;
}, }
/** /**
* The element containing the label text and the icon. * The element containing the label text and the icon.
...@@ -208,7 +205,7 @@ DirectoryItem.prototype = { ...@@ -208,7 +205,7 @@ DirectoryItem.prototype = {
*/ */
get labelElement() { get labelElement() {
return this.firstElementChild.querySelector('.label'); return this.firstElementChild.querySelector('.label');
}, }
/** /**
* Returns true if this item is inside any part of My Drive. * Returns true if this item is inside any part of My Drive.
...@@ -223,7 +220,7 @@ DirectoryItem.prototype = { ...@@ -223,7 +220,7 @@ DirectoryItem.prototype = {
this.parentTree_.volumeManager.getLocationInfo(this.entry); this.parentTree_.volumeManager.getLocationInfo(this.entry);
return locationInfo && return locationInfo &&
locationInfo.rootType === VolumeManagerCommon.RootType.DRIVE; locationInfo.rootType === VolumeManagerCommon.RootType.DRIVE;
}, }
/** /**
* Returns true if this item is inside any part of Computers. * Returns true if this item is inside any part of Computers.
...@@ -240,7 +237,7 @@ DirectoryItem.prototype = { ...@@ -240,7 +237,7 @@ DirectoryItem.prototype = {
(locationInfo.rootType === (locationInfo.rootType ===
VolumeManagerCommon.RootType.COMPUTERS_GRAND_ROOT || VolumeManagerCommon.RootType.COMPUTERS_GRAND_ROOT ||
locationInfo.rootType === VolumeManagerCommon.RootType.COMPUTER); locationInfo.rootType === VolumeManagerCommon.RootType.COMPUTER);
}, }
/** /**
* Returns true if this item is inside any part of Drive, including Team * Returns true if this item is inside any part of Drive, including Team
...@@ -267,7 +264,7 @@ DirectoryItem.prototype = { ...@@ -267,7 +264,7 @@ DirectoryItem.prototype = {
VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME || VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
locationInfo.rootType === locationInfo.rootType ===
VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT); VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT);
}, }
/** /**
* If the this directory supports 'shared' feature, as in displays shared * If the this directory supports 'shared' feature, as in displays shared
...@@ -277,15 +274,14 @@ DirectoryItem.prototype = { ...@@ -277,15 +274,14 @@ DirectoryItem.prototype = {
*/ */
get supportDriveSpecificIcons() { get supportDriveSpecificIcons() {
return this.insideMyDrive || this.insideComputers; return this.insideMyDrive || this.insideComputers;
}, }
};
/** /**
* Handles the Metadata update event. It updates the shared icon of this item * Handles the Metadata update event. It updates the shared icon of this item
* sub-folders. * sub-folders.
* @param {Event} event Metadata update event. * @param {Event} event Metadata update event.
*/ */
DirectoryItem.prototype.onMetadataUpdated_ = function(event) { onMetadataUpdated_(event) {
if (!this.supportDriveSpecificIcons) { if (!this.supportDriveSpecificIcons) {
return; return;
} }
...@@ -306,9 +302,9 @@ DirectoryItem.prototype.onMetadataUpdated_ = function(event) { ...@@ -306,9 +302,9 @@ DirectoryItem.prototype.onMetadataUpdated_ = function(event) {
index++; index++;
} }
}; }
/** /**
* Updates sub-elements of {@code this} reading {@code DirectoryEntry}. * Updates sub-elements of {@code this} reading {@code DirectoryEntry}.
* The list of {@code DirectoryEntry} are not updated by this method. * The list of {@code DirectoryEntry} are not updated by this method.
* *
...@@ -317,16 +313,16 @@ DirectoryItem.prototype.onMetadataUpdated_ = function(event) { ...@@ -317,16 +313,16 @@ DirectoryItem.prototype.onMetadataUpdated_ = function(event) {
* only immediate child directories without arrows. * only immediate child directories without arrows.
* @this {DirectoryItem} * @this {DirectoryItem}
*/ */
DirectoryItem.prototype.updateSubElementsFromList = function(recursive) { updateSubElementsFromList(recursive) {
let index = 0; let index = 0;
const tree = this.parentTree_; const tree = this.parentTree_;
let item; let item;
while (this.entries_[index]) { while (this.entries_[index]) {
const currentEntry = this.entries_[index]; const currentEntry = this.entries_[index];
const currentElement = this.items[index]; const currentElement = this.items[index];
const label = const label = util.getEntryLabel(
util.getEntryLabel( tree.volumeManager_.getLocationInfo(currentEntry),
tree.volumeManager_.getLocationInfo(currentEntry), currentEntry) || currentEntry) ||
''; '';
if (index >= this.items.length) { if (index >= this.items.length) {
...@@ -382,38 +378,39 @@ DirectoryItem.prototype.updateSubElementsFromList = function(recursive) { ...@@ -382,38 +378,39 @@ DirectoryItem.prototype.updateSubElementsFromList = function(recursive) {
} else { } else {
this.hasChildren = true; this.hasChildren = true;
} }
}; }
/** /**
* Calls DirectoryItemTreeBaseMethods.getItemByEntry(). * Calls DirectoryItemTreeBaseMethods.getItemByEntry().
* @param {!Entry} entry * @param {!Entry} entry
* @return {DirectoryItem} * @return {DirectoryItem}
*/ */
DirectoryItem.prototype.getItemByEntry = function(entry) { getItemByEntry(entry) {
return DirectoryItemTreeBaseMethods.getItemByEntry.call(this, entry); return DirectoryItemTreeBaseMethods.getItemByEntry.call(this, entry);
}; }
/** /**
* Calls DirectoryItemTreeBaseMethods.updateSubElementsFromList(). * Calls DirectoryItemTreeBaseMethods.updateSubElementsFromList().
* *
* @param {!DirectoryEntry|!FakeEntry} entry The entry to be searched for. Can * @param {!DirectoryEntry|!FilesAppDirEntry} entry The entry to be searched
* be a fake. * for. Can be a fake.
* @return {boolean} True if the parent item is found. * @return {boolean} True if the parent item is found.
*/ */
DirectoryItem.prototype.searchAndSelectByEntry = function(entry) { searchAndSelectByEntry(entry) {
return DirectoryItemTreeBaseMethods.searchAndSelectByEntry.call(this, entry); return DirectoryItemTreeBaseMethods.searchAndSelectByEntry.call(
}; this, entry);
}
/** /**
* Overrides WebKit's scrollIntoViewIfNeeded, which doesn't work well with * Overrides WebKit's scrollIntoViewIfNeeded, which doesn't work well with
* a complex layout. This call is not necessary, so we are ignoring it. * a complex layout. This call is not necessary, so we are ignoring it.
* *
* @param {boolean=} opt_unused Unused. * @param {boolean=} opt_unused Unused.
* @override * @override
*/ */
DirectoryItem.prototype.scrollIntoViewIfNeeded = opt_unused => {}; scrollIntoViewIfNeeded(opt_unused) {}
/** /**
* Removes the child node, but without selecting the parent item, to avoid * Removes the child node, but without selecting the parent item, to avoid
* unintended changing of directories. Removing is done externally, and other * unintended changing of directories. Removing is done externally, and other
* code will navigate to another directory. * code will navigate to another directory.
...@@ -421,29 +418,29 @@ DirectoryItem.prototype.scrollIntoViewIfNeeded = opt_unused => {}; ...@@ -421,29 +418,29 @@ DirectoryItem.prototype.scrollIntoViewIfNeeded = opt_unused => {};
* @param {!cr.ui.TreeItem=} child The tree item child to remove. * @param {!cr.ui.TreeItem=} child The tree item child to remove.
* @override * @override
*/ */
DirectoryItem.prototype.remove = function(child) { remove(child) {
this.lastElementChild.removeChild(/** @type {!cr.ui.TreeItem} */ (child)); this.lastElementChild.removeChild(/** @type {!cr.ui.TreeItem} */ (child));
if (this.items.length == 0) { if (this.items.length == 0) {
this.hasChildren = false; this.hasChildren = false;
} }
}; }
/** /**
* Removes the has-children attribute which allows returning * Removes the has-children attribute which allows returning
* to the ambiguous may-have-children state. * to the ambiguous may-have-children state.
*/ */
DirectoryItem.prototype.clearHasChildren = function() { clearHasChildren() {
const rowItem = this.firstElementChild; const rowItem = this.firstElementChild;
this.removeAttribute('has-children'); this.removeAttribute('has-children');
rowItem.removeAttribute('has-children'); rowItem.removeAttribute('has-children');
}; }
/** /**
* Invoked when the item is being expanded. * Invoked when the item is being expanded.
* @param {!Event} e Event. * @param {!Event} e Event.
* @private * @private
*/ */
DirectoryItem.prototype.onExpand_ = function(e) { onExpand_(e) {
if (this.supportDriveSpecificIcons && !this.onMetadataUpdateBound_) { if (this.supportDriveSpecificIcons && !this.onMetadataUpdateBound_) {
this.onMetadataUpdateBound_ = this.onMetadataUpdated_.bind(this); this.onMetadataUpdateBound_ = this.onMetadataUpdated_.bind(this);
this.parentTree_.metadataModel_.addEventListener( this.parentTree_.metadataModel_.addEventListener(
...@@ -464,14 +461,14 @@ DirectoryItem.prototype.onExpand_ = function(e) { ...@@ -464,14 +461,14 @@ DirectoryItem.prototype.onExpand_ = function(e) {
}); });
e.stopPropagation(); e.stopPropagation();
}; }
/** /**
* Invoked when the item is being collapsed. * Invoked when the item is being collapsed.
* @param {!Event} e Event. * @param {!Event} e Event.
* @private * @private
*/ */
DirectoryItem.prototype.onCollapse_ = function(e) { onCollapse_(e) {
if (this.onMetadataUpdateBound_) { if (this.onMetadataUpdateBound_) {
this.parentTree_.metadataModel_.removeEventListener( this.parentTree_.metadataModel_.removeEventListener(
'update', this.onMetadataUpdateBound_); 'update', this.onMetadataUpdateBound_);
...@@ -493,15 +490,15 @@ DirectoryItem.prototype.onCollapse_ = function(e) { ...@@ -493,15 +490,15 @@ DirectoryItem.prototype.onCollapse_ = function(e) {
} }
e.stopPropagation(); e.stopPropagation();
}; }
/** /**
* Invoked when the tree item is clicked. * Invoked when the tree item is clicked.
* *
* @param {Event} e Click event. * @param {Event} e Click event.
* @override * @override
*/ */
DirectoryItem.prototype.handleClick = function(e) { handleClick(e) {
cr.ui.TreeItem.prototype.handleClick.call(this, e); cr.ui.TreeItem.prototype.handleClick.call(this, e);
if (!this.entry || e.button === 2) { if (!this.entry || e.button === 2) {
...@@ -518,27 +515,26 @@ DirectoryItem.prototype.handleClick = function(e) { ...@@ -518,27 +515,26 @@ DirectoryItem.prototype.handleClick = function(e) {
DirectoryItemTreeBaseMethods.recordUMASelectedEntry.call( DirectoryItemTreeBaseMethods.recordUMASelectedEntry.call(
this, e, location.rootType, location.isRootEntry); this, e, location.rootType, location.isRootEntry);
} }
}; }
/** /**
* Default sorting for DirectoryItem sub-dirrectories. * Default sorting for DirectoryItem sub-dirrectories.
* @param {!Array<!Entry>} entries Entries to be sorted. * @param {!Array<!Entry>} entries Entries to be sorted.
* @returns {!Array<!Entry>} * @returns {!Array<!Entry>}
*/ */
DirectoryItem.prototype.sortEntries = function(entries) { sortEntries(entries) {
entries.sort(util.compareName); entries.sort(util.compareName);
const filter = this.fileFilter_.filter.bind(this.fileFilter_); const filter = this.fileFilter_.filter.bind(this.fileFilter_);
return entries.filter(filter); return entries.filter(filter);
}; }
/** /**
* Retrieves the latest subdirectories and update them on the tree. * Retrieves the latest subdirectories and update them on the tree.
* @param {boolean} recursive True if the update is recursively. * @param {boolean} recursive True if the update is recursively.
* @param {function()=} opt_successCallback Callback called on success. * @param {function()=} opt_successCallback Callback called on success.
* @param {function()=} opt_errorCallback Callback called on error. * @param {function()=} opt_errorCallback Callback called on error.
*/ */
DirectoryItem.prototype.updateSubDirectories = function( updateSubDirectories(recursive, opt_successCallback, opt_errorCallback) {
recursive, opt_successCallback, opt_errorCallback) {
if (!this.entry || this.entry.createReader === undefined) { if (!this.entry || this.entry.createReader === undefined) {
opt_errorCallback && opt_errorCallback(); opt_errorCallback && opt_errorCallback();
return; return;
...@@ -566,16 +562,16 @@ DirectoryItem.prototype.updateSubDirectories = function( ...@@ -566,16 +562,16 @@ DirectoryItem.prototype.updateSubDirectories = function(
}); });
}; };
readEntry(); readEntry();
}; }
/** /**
* Searches for the changed directory in the current subtree, and if it is found * Searches for the changed directory in the current subtree, and if it is
* then updates it. * found then updates it.
* *
* @param {!DirectoryEntry} changedDirectoryEntry The entry ot the changed * @param {!DirectoryEntry} changedDirectoryEntry The entry of the changed
* directory. * directory.
*/ */
DirectoryItem.prototype.updateItemByEntry = function(changedDirectoryEntry) { updateItemByEntry(changedDirectoryEntry) {
if (util.isSameEntry(changedDirectoryEntry, this.entry)) { if (util.isSameEntry(changedDirectoryEntry, this.entry)) {
this.updateSubDirectories(false /* recursive */); this.updateSubDirectories(false /* recursive */);
return; return;
...@@ -593,19 +589,19 @@ DirectoryItem.prototype.updateItemByEntry = function(changedDirectoryEntry) { ...@@ -593,19 +589,19 @@ DirectoryItem.prototype.updateItemByEntry = function(changedDirectoryEntry) {
break; break;
} }
} }
}; }
/** /**
* Update the icon based on whether the folder is shared on Drive. * Update the icon based on whether the folder is shared on Drive.
*/ */
DirectoryItem.prototype.updateDriveSpecificIcons = () => {}; updateDriveSpecificIcons() {}
/** /**
* Select the item corresponding to the given {@code entry}. * Select the item corresponding to the given {@code entry}.
* @param {!DirectoryEntry|!FakeEntry} entry The entry to be selected. Can be a * @param {!DirectoryEntry|!FilesAppDirEntry} entry The entry to be selected.
* fake. * Can be a fake.
*/ */
DirectoryItem.prototype.selectByEntry = function(entry) { selectByEntry(entry) {
if (util.isSameEntry(entry, this.entry)) { if (util.isSameEntry(entry, this.entry)) {
this.selected = true; this.selected = true;
return; return;
...@@ -618,30 +614,30 @@ DirectoryItem.prototype.selectByEntry = function(entry) { ...@@ -618,30 +614,30 @@ DirectoryItem.prototype.selectByEntry = function(entry) {
// If the entry doesn't exist, updates sub directories and tries again. // If the entry doesn't exist, updates sub directories and tries again.
this.updateSubDirectories( this.updateSubDirectories(
false /* recursive */, this.searchAndSelectByEntry.bind(this, entry)); false /* recursive */, this.searchAndSelectByEntry.bind(this, entry));
}; }
/** /**
* Executes the assigned action as a drop target. * Executes the assigned action as a drop target.
*/ */
DirectoryItem.prototype.doDropTargetAction = function() { doDropTargetAction() {
this.expanded = true; this.expanded = true;
}; }
/** /**
* Change current directory to the entry of this item. * Change current directory to the entry of this item.
*/ */
DirectoryItem.prototype.activate = function() { activate() {
if (this.entry) { if (this.entry) {
this.parentTree_.directoryModel.activateDirectoryEntry(this.entry); this.parentTree_.directoryModel.activateDirectoryEntry(this.entry);
} }
}; }
/** /**
* Set up eject button if needed. * Set up eject button if needed.
* @param {HTMLElement} rowElement The parent element for eject button. * @param {HTMLElement} rowElement The parent element for eject button.
* @private * @private
*/ */
DirectoryItem.prototype.setupEjectButton_ = function(rowElement) { setupEjectButton_(rowElement) {
const ejectButton = cr.doc.createElement('button'); const ejectButton = cr.doc.createElement('button');
// Block other mouse handlers. // Block other mouse handlers.
ejectButton.addEventListener('mouseup', (event) => { ejectButton.addEventListener('mouseup', (event) => {
...@@ -674,16 +670,17 @@ DirectoryItem.prototype.setupEjectButton_ = function(rowElement) { ...@@ -674,16 +670,17 @@ DirectoryItem.prototype.setupEjectButton_ = function(rowElement) {
ripple.setAttribute('fit', ''); ripple.setAttribute('fit', '');
ripple.className = 'circle recenteringTouch'; ripple.className = 'circle recenteringTouch';
ejectButton.appendChild(ripple); ejectButton.appendChild(ripple);
}; }
/** /**
* Set up the context menu for directory items. * Set up the context menu for directory items.
* @param {!cr.ui.Menu} menu Menu to be set. * @param {!cr.ui.Menu} menu Menu to be set.
* @private * @private
*/ */
DirectoryItem.prototype.setContextMenu_ = function(menu) { setContextMenu_(menu) {
cr.ui.contextMenuHandler.setContextMenu(this, menu); cr.ui.contextMenuHandler.setContextMenu(this, menu);
}; }
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// SubDirectoryItem // SubDirectoryItem
...@@ -691,40 +688,41 @@ DirectoryItem.prototype.setContextMenu_ = function(menu) { ...@@ -691,40 +688,41 @@ DirectoryItem.prototype.setContextMenu_ = function(menu) {
/** /**
* A sub directory in the tree. Each element represents a directory which is not * A sub directory in the tree. Each element represents a directory which is not
* a volume's root. * a volume's root.
* */
class SubDirectoryItem extends DirectoryItem {
/**
* @param {string} label Label for this item. * @param {string} label Label for this item.
* @param {DirectoryEntry} dirEntry DirectoryEntry of this item. * @param {DirectoryEntry} dirEntry DirectoryEntry of this item.
* @param {DirectoryItem|ShortcutItem|DirectoryTree} parentDirItem * @param {DirectoryItem|ShortcutItem|DirectoryTree} parentDirItem
* Parent of this item. * Parent of this item.
* @param {DirectoryTree} tree Current tree, which contains this item. * @param {DirectoryTree} tree Current tree, which contains this item.
* @extends {DirectoryItem}
* @constructor
*/ */
function SubDirectoryItem(label, dirEntry, parentDirItem, tree) { constructor(label, dirEntry, parentDirItem, tree) {
const item = new DirectoryItem(label, tree); super(label, tree);
item.__proto__ = SubDirectoryItem.prototype; this.__proto__ = SubDirectoryItem.prototype;
if (window.IN_TEST) { if (window.IN_TEST) {
item.setAttribute('dir-type', 'SubDirectoryItem'); this.setAttribute('dir-type', 'SubDirectoryItem');
} }
item.entry = dirEntry; this.dirEntry_ = dirEntry;
item.delayExpansion = parentDirItem.delayExpansion; this.entry = dirEntry;
this.delayExpansion = parentDirItem.delayExpansion;
if (item.delayExpansion) { if (this.delayExpansion) {
item.clearHasChildren(); this.clearHasChildren();
item.mayHaveChildren_ = true; this.mayHaveChildren_ = true;
} }
// Sets up icons of the item. // Sets up icons of the item.
const icon = item.querySelector('.icon'); const icon = this.querySelector('.icon');
icon.classList.add('item-icon'); icon.classList.add('item-icon');
const location = tree.volumeManager.getLocationInfo(item.entry); const location = tree.volumeManager.getLocationInfo(this.entry);
if (location && location.rootType && location.isRootEntry) { if (location && location.rootType && location.isRootEntry) {
icon.setAttribute('volume-type-icon', location.rootType); icon.setAttribute('volume-type-icon', location.rootType);
if (window.IN_TEST && location.volumeInfo) { if (window.IN_TEST && location.volumeInfo) {
item.setAttribute( this.setAttribute(
'volume-type-for-testing', location.volumeInfo.volumeType); 'volume-type-for-testing', location.volumeInfo.volumeType);
item.setAttribute('drive-label', location.volumeInfo.driveLabel); this.setAttribute('drive-label', location.volumeInfo.driveLabel);
} }
} else { } else {
const rootType = location && location.rootType ? location.rootType : null; const rootType = location && location.rootType ? location.rootType : null;
...@@ -735,44 +733,25 @@ function SubDirectoryItem(label, dirEntry, parentDirItem, tree) { ...@@ -735,44 +733,25 @@ function SubDirectoryItem(label, dirEntry, parentDirItem, tree) {
icon.setAttribute('volume-type-icon', iconOverride); icon.setAttribute('volume-type-icon', iconOverride);
} }
icon.setAttribute('file-type-icon', iconOverride || 'folder'); icon.setAttribute('file-type-icon', iconOverride || 'folder');
item.updateDriveSpecificIcons(); this.updateDriveSpecificIcons();
} }
// Sets up context menu of the item. // Sets up context menu of the item.
if (tree.contextMenuForSubitems) { if (tree.contextMenuForSubitems) {
item.setContextMenu_(tree.contextMenuForSubitems); this.setContextMenu_(tree.contextMenuForSubitems);
} }
// Populates children now if needed. // Populates children now if needed.
if (parentDirItem.expanded) { if (parentDirItem.expanded) {
item.updateSubDirectories(false /* recursive */); this.updateSubDirectories(false /* recursive */);
}
return item;
}
SubDirectoryItem.prototype = {
__proto__: DirectoryItem.prototype,
get entry() {
return this.dirEntry_;
},
set entry(value) {
this.dirEntry_ = value;
// Set helper attribute for testing.
if (window.IN_TEST) {
this.setAttribute('full-path-for-testing', this.dirEntry_.fullPath);
} }
} }
};
/** /**
* Update the icon based on whether the folder is shared on Drive. * Update the icon based on whether the folder is shared on Drive.
* @override * @override
*/ */
SubDirectoryItem.prototype.updateDriveSpecificIcons = function() { updateDriveSpecificIcons() {
const icon = this.querySelector('.icon'); const icon = this.querySelector('.icon');
const metadata = this.parentTree_.metadataModel.getCache( const metadata = this.parentTree_.metadataModel.getCache(
[this.dirEntry_], ['shared', 'isMachineRoot', 'isExternalMedia']); [this.dirEntry_], ['shared', 'isMachineRoot', 'isExternalMedia']);
...@@ -785,50 +764,64 @@ SubDirectoryItem.prototype.updateDriveSpecificIcons = function() { ...@@ -785,50 +764,64 @@ SubDirectoryItem.prototype.updateDriveSpecificIcons = function() {
icon.setAttribute( icon.setAttribute(
'volume-type-icon', VolumeManagerCommon.RootType.EXTERNAL_MEDIA); 'volume-type-icon', VolumeManagerCommon.RootType.EXTERNAL_MEDIA);
} }
}; }
get entry() {
return this.dirEntry_;
}
set entry(value) {
this.dirEntry_ = value;
// Set helper attribute for testing.
if (window.IN_TEST) {
this.setAttribute('full-path-for-testing', this.dirEntry_.fullPath);
}
}
}
/** /**
* A directory of entries. Each element represents an entry. * A directory of entries. Each element represents an entry.
* */
class EntryListItem extends DirectoryItem {
/**
* @param {VolumeManagerCommon.RootType} rootType The root type to record. * @param {VolumeManagerCommon.RootType} rootType The root type to record.
* @param {!NavigationModelFakeItem} modelItem NavigationModelItem of this * @param {!NavigationModelFakeItem} modelItem NavigationModelItem of this
* volume. * volume.
* @param {DirectoryTree} tree Current tree, which contains this item. * @param {DirectoryTree} tree Current tree, which contains this item.
* @extends {DirectoryItem}
* @constructor
*/ */
function EntryListItem(rootType, modelItem, tree) { constructor(rootType, modelItem, tree) {
const item = super(modelItem.label, tree);
/** @type {EntryListItem} */ (new DirectoryItem(modelItem.label, tree)); this.__proto__ = EntryListItem.prototype;
item.__proto__ = EntryListItem.prototype;
if (window.IN_TEST) { if (window.IN_TEST) {
item.setAttribute('dir-type', 'EntryListItem'); this.setAttribute('dir-type', 'EntryListItem');
} }
item.entries_ = []; this.entries_ = [];
item.rootType_ = rootType; this.rootType_ = rootType;
item.modelItem_ = modelItem; this.modelItem_ = modelItem;
item.dirEntry_ = modelItem.entry; this.dirEntry_ = modelItem.entry;
item.parentTree_ = tree; this.parentTree_ = tree;
if (rootType === VolumeManagerCommon.RootType.REMOVABLE) { if (rootType === VolumeManagerCommon.RootType.REMOVABLE) {
item.setupEjectButton_(item.rowElement); this.setupEjectButton_(this.rowElement);
// For removable add menus for roots to be able to unmount, format, etc. // For removable add menus for roots to be able to unmount, format, etc.
if (tree.contextMenuForRootItems) { if (tree.contextMenuForRootItems) {
item.setContextMenu_(tree.contextMenuForRootItems); this.setContextMenu_(tree.contextMenuForRootItems);
} }
} else { } else {
// For MyFiles allow normal file operations menus. // For MyFiles allow normal file operations menus.
if (tree.contextMenuForSubitems) { if (tree.contextMenuForSubitems) {
item.setContextMenu_(tree.contextMenuForSubitems); this.setContextMenu_(tree.contextMenuForSubitems);
} }
} }
const icon = queryRequiredElement('.icon', item); const icon = queryRequiredElement('.icon', this);
if (window.IN_TEST && item.entry && item.entry.volumeInfo) { if (window.IN_TEST && this.entry && this.entry.volumeInfo) {
item.setAttribute( this.setAttribute(
'volume-type-for-testing', item.entry.volumeInfo.volumeType); 'volume-type-for-testing', this.entry.volumeInfo.volumeType);
// TODO(crbug.com/880130) Remove volume-type-icon from here once // TODO(crbug.com/880130) Remove volume-type-icon from here once
// MyFilesVolume flag is removed. // MyFilesVolume flag is removed.
icon.setAttribute('volume-type-icon', rootType); icon.setAttribute('volume-type-icon', rootType);
...@@ -838,50 +831,19 @@ function EntryListItem(rootType, modelItem, tree) { ...@@ -838,50 +831,19 @@ function EntryListItem(rootType, modelItem, tree) {
// MyFiles shows expanded by default. // MyFiles shows expanded by default.
if (rootType === VolumeManagerCommon.RootType.MY_FILES) { if (rootType === VolumeManagerCommon.RootType.MY_FILES) {
item.mayHaveChildren_ = true; this.mayHaveChildren_ = true;
item.expanded = true; this.expanded = true;
} }
// Populate children of this volume. // Populate children of this volume.
item.updateSubDirectories(false /* recursive */); this.updateSubDirectories(false /* recursive */);
return item;
}
EntryListItem.prototype = {
__proto__: DirectoryItem.prototype,
/**
* The DirectoryEntry corresponding to this DirectoryItem. This may be
* a dummy DirectoryEntry.
* @type {DirectoryEntry|Object}
*/
get entry() {
return this.dirEntry_;
},
/**
* The element containing the label text and the icon.
* @type {!HTMLElement}
* @override
*/
get labelElement() {
return this.firstElementChild.querySelector('.label');
},
/**
* @type {!NavigationModelVolumeItem}
*/
get modelItem() {
return this.modelItem_;
} }
};
/** /**
* Default sorting for DirectoryItem sub-dirrectories. * Default sorting for DirectoryItem sub-dirrectories.
* @param {!Array<!Entry>} entries Entries to be sorted. * @param {!Array<!Entry>} entries Entries to be sorted.
* @returns {!Array<!Entry>} * @returns {!Array<!Entry>}
*/ */
EntryListItem.prototype.sortEntries = function(entries) { sortEntries(entries) {
if (!entries.length) { if (!entries.length) {
return []; return [];
} }
...@@ -904,17 +866,16 @@ EntryListItem.prototype.sortEntries = function(entries) { ...@@ -904,17 +866,16 @@ EntryListItem.prototype.sortEntries = function(entries) {
const filter = this.fileFilter_.filter.bind(this.fileFilter_); const filter = this.fileFilter_.filter.bind(this.fileFilter_);
return entries.filter(filter).sort(compareFunction); return entries.filter(filter).sort(compareFunction);
}; }
/** /**
* Retrieves the subdirectories and update them on the tree. Runs synchronously, * Retrieves the subdirectories and update them on the tree. Runs
* since EntryList has its subdirectories already in memory. * synchronously, since EntryList has its subdirectories already in memory.
* @param {boolean} recursive True if the update is recursively. * @param {boolean} recursive True if the update is recursively.
* @param {function()=} opt_successCallback Callback called on success. * @param {function()=} opt_successCallback Callback called on success.
* @param {function()=} opt_errorCallback Callback called on error. * @param {function()=} opt_errorCallback Callback called on error.
*/ */
EntryListItem.prototype.updateSubDirectories = function( updateSubDirectories(recursive, opt_successCallback, opt_errorCallback) {
recursive, opt_successCallback, opt_errorCallback) {
if (!this.entry || this.entry.createReader === undefined) { if (!this.entry || this.entry.createReader === undefined) {
opt_errorCallback && opt_errorCallback(); opt_errorCallback && opt_errorCallback();
return; return;
...@@ -943,7 +904,33 @@ EntryListItem.prototype.updateSubDirectories = function( ...@@ -943,7 +904,33 @@ EntryListItem.prototype.updateSubDirectories = function(
}); });
}; };
readEntry(); readEntry();
}; }
/**
* The DirectoryEntry corresponding to this DirectoryItem. This may be
* a dummy DirectoryEntry.
* @type {DirectoryEntry|Object}
*/
get entry() {
return this.dirEntry_;
}
/**
* The element containing the label text and the icon.
* @type {!HTMLElement}
* @override
*/
get labelElement() {
return this.firstElementChild.querySelector('.label');
}
/**
* @type {!NavigationModelVolumeItem}
*/
get modelItem() {
return this.modelItem_;
}
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// VolumeItem // VolumeItem
...@@ -951,33 +938,32 @@ EntryListItem.prototype.updateSubDirectories = function( ...@@ -951,33 +938,32 @@ EntryListItem.prototype.updateSubDirectories = function(
/** /**
* A TreeItem which represents a volume. Volume items are displayed as * A TreeItem which represents a volume. Volume items are displayed as
* top-level children of DirectoryTree. * top-level children of DirectoryTree.
* */
class VolumeItem extends DirectoryItem {
/**
* @param {!NavigationModelVolumeItem} modelItem NavigationModelItem of this * @param {!NavigationModelVolumeItem} modelItem NavigationModelItem of this
* volume. * volume.
* @param {!DirectoryTree} tree Current tree, which contains this item. * @param {!DirectoryTree} tree Current tree, which contains this item.
* @extends {DirectoryItem}
* @constructor
*/ */
function VolumeItem(modelItem, tree) { constructor(modelItem, tree) {
const item = /** @type {VolumeItem} */ ( super(modelItem.volumeInfo.label, tree);
new DirectoryItem(modelItem.volumeInfo.label, tree)); this.__proto__ = VolumeItem.prototype;
item.__proto__ = VolumeItem.prototype;
item.modelItem_ = modelItem; this.modelItem_ = modelItem;
item.volumeInfo_ = modelItem.volumeInfo; this.volumeInfo_ = modelItem.volumeInfo;
// Provided volumes should delay the expansion of child nodes // Provided volumes should delay the expansion of child nodes
// for performance reasons. // for performance reasons.
item.delayExpansion = (item.volumeInfo.volumeType === 'provided'); this.delayExpansion = (this.volumeInfo.volumeType === 'provided');
// Set helper attribute for testing. // Set helper attribute for testing.
if (window.IN_TEST) { if (window.IN_TEST) {
item.setAttribute('volume-type-for-testing', item.volumeInfo_.volumeType); this.setAttribute('volume-type-for-testing', this.volumeInfo_.volumeType);
item.setAttribute('dir-type', 'VolumeItem'); this.setAttribute('dir-type', 'VolumeItem');
item.setAttribute('drive-label', item.volumeInfo_.driveLabel); this.setAttribute('drive-label', this.volumeInfo_.driveLabel);
} }
item.setupIcon_(item.querySelector('.icon'), item.volumeInfo_); this.setupIcon_(this.querySelector('.icon'), this.volumeInfo_);
// Attach a placeholder for rename input text box and the eject icon if the // Attach a placeholder for rename input text box and the eject icon if the
// volume is ejectable // volume is ejectable
...@@ -987,71 +973,44 @@ function VolumeItem(modelItem, tree) { ...@@ -987,71 +973,44 @@ function VolumeItem(modelItem, tree) {
modelItem.volumeInfo_.source === VolumeManagerCommon.Source.FILE) { modelItem.volumeInfo_.source === VolumeManagerCommon.Source.FILE) {
// This placeholder is added to allow to put textbox before eject button // This placeholder is added to allow to put textbox before eject button
// while executing renaming action on external drive. // while executing renaming action on external drive.
item.setupRenamePlaceholder_(item.rowElement); this.setupRenamePlaceholder_(this.rowElement);
item.setupEjectButton_(item.rowElement); this.setupEjectButton_(this.rowElement);
} }
// Sets up context menu of the item. // Sets up context menu of the item.
if (tree.contextMenuForRootItems) { if (tree.contextMenuForRootItems) {
item.setContextMenu_(tree.contextMenuForRootItems); this.setContextMenu_(tree.contextMenuForRootItems);
} }
// Populate children of this volume using resolved display root. For SMB // Populate children of this volume using resolved display root. For SMB
// shares, avoid prefetching sub directories to delay authentication. // shares, avoid prefetching sub directories to delay authentication.
if (modelItem.volumeInfo_.providerId !== '@smb') { if (modelItem.volumeInfo_.providerId !== '@smb') {
item.volumeInfo_.resolveDisplayRoot((displayRoot) => { this.volumeInfo_.resolveDisplayRoot((displayRoot) => {
item.updateSubDirectories(false /* recursive */); this.updateSubDirectories(false /* recursive */);
}); });
} }
return item;
}
VolumeItem.prototype = {
__proto__: DirectoryItem.prototype,
/**
* Directory entry for the display root, whose initial value is null.
* @type {DirectoryEntry}
* @override
*/
get entry() {
return this.volumeInfo_.displayRoot;
},
/**
* @type {!VolumeInfo}
*/
get volumeInfo() {
return this.volumeInfo_;
},
/**
* @type {!NavigationModelVolumeItem}
*/
get modelItem() {
return this.modelItem_;
} }
};
/** /**
* @override * @override
*/ */
VolumeItem.prototype.updateSubDirectories = function( updateSubDirectories(recursive, opt_successCallback, opt_errorCallback) {
recursive, opt_successCallback, opt_errorCallback) {
if (this.volumeInfo.volumeType === if (this.volumeInfo.volumeType ===
VolumeManagerCommon.VolumeType.MEDIA_VIEW) { VolumeManagerCommon.VolumeType.MEDIA_VIEW) {
// If this is a media-view volume, we don't show child directories. // If this is a media-view volume, we don't show child directories.
// (Instead, we provide flattend files in the file list.) // (Instead, we provide flattened files in the file list.)
opt_successCallback && opt_successCallback(); opt_successCallback && opt_successCallback();
} else { } else {
DirectoryItem.prototype.updateSubDirectories.call( DirectoryItem.prototype.updateSubDirectories.call(
this, recursive, opt_successCallback, opt_errorCallback); this, recursive, opt_successCallback, opt_errorCallback);
} }
}; }
/** /**
* Change current entry to this volume's root directory. * Change current entry to this volume's root directory.
* @override * @override
*/ */
VolumeItem.prototype.activate = function() { activate() {
const directoryModel = this.parentTree_.directoryModel; const directoryModel = this.parentTree_.directoryModel;
const onEntryResolved = (entry) => { const onEntryResolved = (entry) => {
// Changes directory to the model item's root directory if needed. // Changes directory to the model item's root directory if needed.
...@@ -1068,15 +1027,15 @@ VolumeItem.prototype.activate = function() { ...@@ -1068,15 +1027,15 @@ VolumeItem.prototype.activate = function() {
// Error, the display root is not available. It may happen on Drive. // Error, the display root is not available. It may happen on Drive.
this.parentTree_.dataModel.onItemNotFoundError(this.modelItem); this.parentTree_.dataModel.onItemNotFoundError(this.modelItem);
}); });
}; }
/** /**
* Set up icon of this volume item. * Set up icon of this volume item.
* @param {Element} icon Icon element to be setup. * @param {Element} icon Icon element to be setup.
* @param {VolumeInfo} volumeInfo VolumeInfo determines the icon type. * @param {VolumeInfo} volumeInfo VolumeInfo determines the icon type.
* @private * @private
*/ */
VolumeItem.prototype.setupIcon_ = (icon, volumeInfo) => { setupIcon_(icon, volumeInfo) {
icon.classList.add('item-icon'); icon.classList.add('item-icon');
const backgroundImage = const backgroundImage =
util.iconSetToCSSBackgroundImageValue(volumeInfo.iconSet); util.iconSetToCSSBackgroundImageValue(volumeInfo.iconSet);
...@@ -1094,18 +1053,42 @@ VolumeItem.prototype.setupIcon_ = (icon, volumeInfo) => { ...@@ -1094,18 +1053,42 @@ VolumeItem.prototype.setupIcon_ = (icon, volumeInfo) => {
} else { } else {
icon.setAttribute('volume-subtype', volumeInfo.deviceType || ''); icon.setAttribute('volume-subtype', volumeInfo.deviceType || '');
} }
}; }
/** /**
* Set up rename input textbox placeholder if needed. * Set up rename input textbox placeholder if needed.
* @param {HTMLElement} rowElement The parent element for placeholder. * @param {HTMLElement} rowElement The parent element for placeholder.
* @private * @private
*/ */
VolumeItem.prototype.setupRenamePlaceholder_ = rowElement => { setupRenamePlaceholder_(rowElement) {
const placeholder = cr.doc.createElement('span'); const placeholder = cr.doc.createElement('span');
placeholder.className = 'rename-placeholder'; placeholder.className = 'rename-placeholder';
rowElement.appendChild(placeholder); rowElement.appendChild(placeholder);
}; }
/**
* Directory entry for the display root, whose initial value is null.
* @type {DirectoryEntry}
* @override
*/
get entry() {
return this.volumeInfo_.displayRoot;
}
/**
* @type {!VolumeInfo}
*/
get volumeInfo() {
return this.volumeInfo_;
}
/**
* @type {!NavigationModelVolumeItem}
*/
get modelItem() {
return this.modelItem_;
}
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// DriveVolumeItem // DriveVolumeItem
...@@ -1113,71 +1096,53 @@ VolumeItem.prototype.setupRenamePlaceholder_ = rowElement => { ...@@ -1113,71 +1096,53 @@ VolumeItem.prototype.setupRenamePlaceholder_ = rowElement => {
/** /**
* A TreeItem which represents a Drive volume. Drive volume has fake entries * A TreeItem which represents a Drive volume. Drive volume has fake entries
* such as Shared Drives, Shared with me, and Offline in it. * such as Shared Drives, Shared with me, and Offline in it.
* */
class DriveVolumeItem extends VolumeItem {
/**
* @param {!NavigationModelVolumeItem} modelItem NavigationModelItem of this * @param {!NavigationModelVolumeItem} modelItem NavigationModelItem of this
* volume. * volume.
* @param {!DirectoryTree} tree Current tree, which contains this item. * @param {!DirectoryTree} tree Current tree, which contains this item.
* @extends {VolumeItem}
* @constructor
*/ */
function DriveVolumeItem(modelItem, tree) { constructor(modelItem, tree) {
const item = new VolumeItem(modelItem, tree); super(modelItem, tree);
item.__proto__ = DriveVolumeItem.prototype; this.__proto__ = DriveVolumeItem.prototype;
item.classList.add('drive-volume');
if (window.IN_TEST) {
item.setAttribute('dir-type', 'DriveVolumeItem');
}
return item;
}
DriveVolumeItem.prototype = { this.classList.add('drive-volume');
__proto__: VolumeItem.prototype, if (window.IN_TEST) {
// Overrides the property 'expanded' to prevent Drive volume from shrinking. this.setAttribute('dir-type', 'DriveVolumeItem');
get expanded() {
return Object.getOwnPropertyDescriptor(cr.ui.TreeItem.prototype, 'expanded')
.get.call(this);
},
set expanded(b) {
Object.getOwnPropertyDescriptor(cr.ui.TreeItem.prototype, 'expanded')
.set.call(this, b);
// When Google Drive is expanded while it is selected, select the My Drive.
if (b) {
if (this.selected && this.entry) {
this.selectByEntry(this.entry);
}
} }
} }
};
/** /**
* Invoked when the tree item is clicked. * Invoked when the tree item is clicked.
* *
* @param {Event} e Click event. * @param {Event} e Click event.
* @override * @override
*/ */
DriveVolumeItem.prototype.handleClick = function(e) { handleClick(e) {
VolumeItem.prototype.handleClick.call(this, e); VolumeItem.prototype.handleClick.call(this, e);
this.selectDisplayRoot_(e.target); this.selectDisplayRoot_(e.target);
DirectoryItemTreeBaseMethods.recordUMASelectedEntry.call( DirectoryItemTreeBaseMethods.recordUMASelectedEntry.call(
this, e, VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT, true); this, e, VolumeManagerCommon.RootType.DRIVE_FAKE_ROOT, true);
}; }
/** /**
* Creates Shared Drives root if there is any team drive, if there is no team * Creates Shared Drives root if there is any team drive, if there is no team
* drive, then it removes the root. * drive, then it removes the root.
* *
* Since we don't currently support any functionality with just the grand root * Since we don't currently support any functionality with just the grand root
* (e.g. you can't create a new team drive from the root yet), remove/don't * (e.g. you can't create a new team drive from the root yet), remove/don't
* create the grand root so it can't be reached via keyboard. * create the grand root so it can't be reached via keyboard.
* If there is at least one Shared Drive, add/show the Shared Drives grand root. * If there is at least one Shared Drive, add/show the Shared Drives grand
* root.
* *
* @return {!Promise<SubDirectoryItem>} Resolved with Shared Drive Grand Root * @return {!Promise<SubDirectoryItem>} Resolved with Shared Drive Grand Root
* SubDirectoryItem instance, or undefined when it shouldn't exist. * SubDirectoryItem instance, or undefined when it shouldn't exist.
* @private * @private
*/ */
DriveVolumeItem.prototype.createSharedDrivesGrandRoot_ = function() { createSharedDrivesGrandRoot_() {
return new Promise((resolve) => { return new Promise((resolve) => {
const sharedDriveGrandRoot = this.volumeInfo_.sharedDriveDisplayRoot; const sharedDriveGrandRoot = this.volumeInfo_.sharedDriveDisplayRoot;
if (!sharedDriveGrandRoot) { if (!sharedDriveGrandRoot) {
...@@ -1228,9 +1193,9 @@ DriveVolumeItem.prototype.createSharedDrivesGrandRoot_ = function() { ...@@ -1228,9 +1193,9 @@ DriveVolumeItem.prototype.createSharedDrivesGrandRoot_ = function() {
} }
}); });
}); });
}; }
/** /**
* Creates Computers root if there is any computer. If there is no computer, * Creates Computers root if there is any computer. If there is no computer,
* then it removes the root. * then it removes the root.
* *
...@@ -1243,7 +1208,7 @@ DriveVolumeItem.prototype.createSharedDrivesGrandRoot_ = function() { ...@@ -1243,7 +1208,7 @@ DriveVolumeItem.prototype.createSharedDrivesGrandRoot_ = function() {
* SubDirectoryItem instance, or undefined when it shouldn't exist. * SubDirectoryItem instance, or undefined when it shouldn't exist.
* @private * @private
*/ */
DriveVolumeItem.prototype.createComputersGrandRoot_ = function() { createComputersGrandRoot_() {
return new Promise((resolve) => { return new Promise((resolve) => {
const computerGrandRoot = this.volumeInfo_.computersDisplayRoot; const computerGrandRoot = this.volumeInfo_.computersDisplayRoot;
if (!computerGrandRoot) { if (!computerGrandRoot) {
...@@ -1298,21 +1263,21 @@ DriveVolumeItem.prototype.createComputersGrandRoot_ = function() { ...@@ -1298,21 +1263,21 @@ DriveVolumeItem.prototype.createComputersGrandRoot_ = function() {
} }
}); });
}); });
}; }
/** /**
* Change current entry to the entry corresponding to My Drive. * Change current entry to the entry corresponding to My Drive.
*/ */
DriveVolumeItem.prototype.activate = function() { activate() {
VolumeItem.prototype.activate.call(this); VolumeItem.prototype.activate.call(this);
this.selectDisplayRoot_(this); this.selectDisplayRoot_(this);
}; }
/** /**
* Select Drive's display root. * Select Drive's display root.
* @param {EventTarget} target The event target. * @param {EventTarget} target The event target.
*/ */
DriveVolumeItem.prototype.selectDisplayRoot_ = function(target) { selectDisplayRoot_(target) {
if (!target.classList.contains('expand-icon')) { if (!target.classList.contains('expand-icon')) {
// If the Drive volume is clicked, select one of the children instead of // If the Drive volume is clicked, select one of the children instead of
// this item itself. // this item itself.
...@@ -1320,14 +1285,14 @@ DriveVolumeItem.prototype.selectDisplayRoot_ = function(target) { ...@@ -1320,14 +1285,14 @@ DriveVolumeItem.prototype.selectDisplayRoot_ = function(target) {
this.searchAndSelectByEntry(displayRoot); this.searchAndSelectByEntry(displayRoot);
}); });
} }
}; }
/** /**
* Retrieves the latest subdirectories and update them on the tree. * Retrieves the latest subdirectories and update them on the tree.
* @param {boolean} recursive True if the update is recursively. * @param {boolean} recursive True if the update is recursively.
* @override * @override
*/ */
DriveVolumeItem.prototype.updateSubDirectories = function(recursive) { updateSubDirectories(recursive) {
if (!this.entry || this.hasChildren) { if (!this.entry || this.hasChildren) {
return; return;
} }
...@@ -1370,28 +1335,29 @@ DriveVolumeItem.prototype.updateSubDirectories = function(recursive) { ...@@ -1370,28 +1335,29 @@ DriveVolumeItem.prototype.updateSubDirectories = function(recursive) {
} else { } else {
const label = const label =
util.getEntryLabel( util.getEntryLabel(
this.parentTree_.volumeManager_.getLocationInfo(entry), entry) || this.parentTree_.volumeManager_.getLocationInfo(entry),
entry) ||
''; '';
const item = new SubDirectoryItem(label, entry, this, this.parentTree_); const item = new SubDirectoryItem(label, entry, this, this.parentTree_);
this.add(item); this.add(item);
item.updateSubDirectories(false); item.updateSubDirectories(false);
} }
} }
}; }
/** /**
* Searches for the changed directory in the current subtree, and if it is found * Searches for the changed directory in the current subtree, and if it is
* then updates it. * found then updates it.
* *
* @param {!DirectoryEntry} changedDirectoryEntry The entry ot the changed * @param {!DirectoryEntry} changedDirectoryEntry The entry of the changed
* directory. * directory.
* @override * @override
*/ */
DriveVolumeItem.prototype.updateItemByEntry = function(changedDirectoryEntry) { updateItemByEntry(changedDirectoryEntry) {
const isTeamDriveChild = util.isSharedDriveEntry(changedDirectoryEntry); const isTeamDriveChild = util.isSharedDriveEntry(changedDirectoryEntry);
// If Shared Drive grand root has been removed and we receive an update for an // If Shared Drive grand root has been removed and we receive an update for
// team drive, we need to create the Shared Drive grand root. // an team drive, we need to create the Shared Drive grand root.
if (isTeamDriveChild) { if (isTeamDriveChild) {
this.createSharedDrivesGrandRoot_().then((sharedDriveGrandRootItem) => { this.createSharedDrivesGrandRoot_().then((sharedDriveGrandRootItem) => {
if (sharedDriveGrandRootItem) { if (sharedDriveGrandRootItem) {
...@@ -1414,24 +1380,24 @@ DriveVolumeItem.prototype.updateItemByEntry = function(changedDirectoryEntry) { ...@@ -1414,24 +1380,24 @@ DriveVolumeItem.prototype.updateItemByEntry = function(changedDirectoryEntry) {
} }
// Must be under "My Drive", which is always the first item. // Must be under "My Drive", which is always the first item.
this.items[0].updateItemByEntry(changedDirectoryEntry); this.items[0].updateItemByEntry(changedDirectoryEntry);
}; }
/** /**
* Select the item corresponding to the given entry. * Select the item corresponding to the given entry.
* @param {!DirectoryEntry|!FakeEntry} entry The directory entry to be selected. * @param {!DirectoryEntry|!FilesAppDirEntry} entry The directory entry to be
* Can be a fake. * selected. Can be a fake.
* @override * @override
*/ */
DriveVolumeItem.prototype.selectByEntry = function(entry) { selectByEntry(entry) {
// Find the item to be selected among children. // Find the item to be selected among children.
this.searchAndSelectByEntry(entry); this.searchAndSelectByEntry(entry);
}; }
/** /**
* Return the index where we want to display the "Computers" root. * Return the index where we want to display the "Computers" root.
* @private * @private
*/ */
DriveVolumeItem.prototype.computersIndexPosition_ = function() { computersIndexPosition_() {
// We want the order to be // We want the order to be
// - My Drive // - My Drive
// - Shared Drives (if the user has any) // - Shared Drives (if the user has any)
...@@ -1448,7 +1414,24 @@ DriveVolumeItem.prototype.computersIndexPosition_ = function() { ...@@ -1448,7 +1414,24 @@ DriveVolumeItem.prototype.computersIndexPosition_ = function() {
} }
} }
return 1; return 1;
}; }
// Overrides the property 'expanded' to prevent Drive volume from shrinking.
get expanded() {
return Object.getOwnPropertyDescriptor(cr.ui.TreeItem.prototype, 'expanded')
.get.call(this);
}
set expanded(b) {
Object.getOwnPropertyDescriptor(cr.ui.TreeItem.prototype, 'expanded')
.set.call(this, b);
// When Google Drive is expanded while it is selected, select the My Drive.
if (b) {
if (this.selected && this.entry) {
this.selectByEntry(this.entry);
}
}
}
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// ShortcutItem // ShortcutItem
...@@ -1456,77 +1439,63 @@ DriveVolumeItem.prototype.computersIndexPosition_ = function() { ...@@ -1456,77 +1439,63 @@ DriveVolumeItem.prototype.computersIndexPosition_ = function() {
/** /**
* A TreeItem which represents a shortcut for Drive folder. * A TreeItem which represents a shortcut for Drive folder.
* Shortcut items are displayed as top-level children of DirectoryTree. * Shortcut items are displayed as top-level children of DirectoryTree.
* */
class ShortcutItem extends cr.ui.TreeItem {
/**
* @param {!NavigationModelShortcutItem} modelItem NavigationModelItem of this * @param {!NavigationModelShortcutItem} modelItem NavigationModelItem of this
* volume. * volume.
* @param {!DirectoryTree} tree Current tree, which contains this item. * @param {!DirectoryTree} tree Current tree, which contains this item.
* @extends {cr.ui.TreeItem}
* @constructor
*/ */
function ShortcutItem(modelItem, tree) { constructor(modelItem, tree) {
const item = /** @type {ShortcutItem} */ (new cr.ui.TreeItem()); super();
// Get the original label id defined by TreeItem, before overwriting // Get the original label id defined by TreeItem, before overwriting
// prototype. // prototype.
const labelId = item.labelElement.id; const labelId = this.labelElement.id;
item.__proto__ = ShortcutItem.prototype; this.__proto__ = ShortcutItem.prototype;
item.parentTree_ = tree; this.parentTree_ = tree;
item.dirEntry_ = modelItem.entry; this.dirEntry_ = modelItem.entry;
item.modelItem_ = modelItem; this.modelItem_ = modelItem;
item.innerHTML = TREE_ITEM_INNER_HTML; this.innerHTML = TREE_ITEM_INNER_HTML;
item.labelElement.id = labelId; this.labelElement.id = labelId;
const icon = item.querySelector('.icon'); const icon = this.querySelector('.icon');
icon.classList.add('item-icon'); icon.classList.add('item-icon');
icon.setAttribute('volume-type-icon', 'shortcut'); icon.setAttribute('volume-type-icon', 'shortcut');
if (tree.contextMenuForRootItems) { if (tree.contextMenuForRootItems) {
item.setContextMenu_(tree.contextMenuForRootItems); this.setContextMenu_(tree.contextMenuForRootItems);
} }
item.label = modelItem.entry.name; this.label = modelItem.entry.name;
if (window.IN_TEST) { if (window.IN_TEST) {
item.setAttribute('dir-type', 'ShortcutItem'); this.setAttribute('dir-type', 'ShortcutItem');
item.setAttribute('entry-label', item.label); this.setAttribute('entry-label', this.label);
} }
return item;
}
ShortcutItem.prototype = {
__proto__: cr.ui.TreeItem.prototype,
get entry() {
return this.dirEntry_;
},
get modelItem() {
return this.modelItem_;
},
get labelElement() {
return this.firstElementChild.querySelector('.label');
} }
};
/** /**
* Finds a parent directory of the {@code entry} in {@code this}, and * Finds a parent directory of the {@code entry} in {@code this}, and
* invokes the DirectoryItem.selectByEntry() of the found directory. * invokes the DirectoryItem.selectByEntry() of the found directory.
* *
* @param {!DirectoryEntry|!FakeEntry} entry The entry to be searched for. Can * @param {!DirectoryEntry|!FilesAppDirEntry} entry The entry to be searched
* be a fake. * for. Can be a fake.
* @return {boolean} True if the parent item is found. * @return {boolean} True if the parent item is found.
*/ */
ShortcutItem.prototype.searchAndSelectByEntry = entry => { searchAndSelectByEntry(entry) {
// Always false as shortcuts have no children. // Always false as shortcuts have no children.
return false; return false;
}; }
/** /**
* Invoked when the tree item is clicked. * Invoked when the tree item is clicked.
* *
* @param {Event} e Click event. * @param {Event} e Click event.
* @override * @override
*/ */
ShortcutItem.prototype.handleClick = function(e) { handleClick(e) {
cr.ui.TreeItem.prototype.handleClick.call(this, e); cr.ui.TreeItem.prototype.handleClick.call(this, e);
// Do not activate with right click. // Do not activate with right click.
...@@ -1541,31 +1510,31 @@ ShortcutItem.prototype.handleClick = function(e) { ...@@ -1541,31 +1510,31 @@ ShortcutItem.prototype.handleClick = function(e) {
const location = this.tree.volumeManager.getLocationInfo(this.entry); const location = this.tree.volumeManager.getLocationInfo(this.entry);
DirectoryItemTreeBaseMethods.recordUMASelectedEntry.call( DirectoryItemTreeBaseMethods.recordUMASelectedEntry.call(
this, e, location.rootType, location.isRootEntry); this, e, location.rootType, location.isRootEntry);
}; }
/** /**
* Select the item corresponding to the given entry. * Select the item corresponding to the given entry.
* @param {!DirectoryEntry} entry The directory entry to be selected. * @param {!DirectoryEntry} entry The directory entry to be selected.
*/ */
ShortcutItem.prototype.selectByEntry = function(entry) { selectByEntry(entry) {
if (util.isSameEntry(entry, this.entry)) { if (util.isSameEntry(entry, this.entry)) {
this.selected = true; this.selected = true;
} }
}; }
/** /**
* Sets the context menu for shortcut items. * Sets the context menu for shortcut items.
* @param {!cr.ui.Menu} menu Menu to be set. * @param {!cr.ui.Menu} menu Menu to be set.
* @private * @private
*/ */
ShortcutItem.prototype.setContextMenu_ = function(menu) { setContextMenu_(menu) {
cr.ui.contextMenuHandler.setContextMenu(this, menu); cr.ui.contextMenuHandler.setContextMenu(this, menu);
}; }
/** /**
* Change current entry to the entry corresponding to this shortcut. * Change current entry to the entry corresponding to this shortcut.
*/ */
ShortcutItem.prototype.activate = function() { activate() {
const directoryModel = this.parentTree_.directoryModel; const directoryModel = this.parentTree_.directoryModel;
const onEntryResolved = (entry) => { const onEntryResolved = (entry) => {
// Changes directory to the model item's root directory if needed. // Changes directory to the model item's root directory if needed.
...@@ -1584,106 +1553,114 @@ ShortcutItem.prototype.activate = function() { ...@@ -1584,106 +1553,114 @@ ShortcutItem.prototype.activate = function() {
// initialization. // initialization.
this.parentTree_.dataModel.onItemNotFoundError(this.modelItem); this.parentTree_.dataModel.onItemNotFoundError(this.modelItem);
}); });
}; }
get entry() {
return this.dirEntry_;
}
get modelItem() {
return this.modelItem_;
}
get labelElement() {
return this.firstElementChild.querySelector('.label');
}
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// FakeItem // FakeItem
/** /**
* FakeItem is used by Recent and Linux files. * FakeItem is used by Recent and Linux files.
*/
class FakeItem extends cr.ui.TreeItem {
/**
* @param {!VolumeManagerCommon.RootType} rootType root type. * @param {!VolumeManagerCommon.RootType} rootType root type.
* @param {!NavigationModelFakeItem} modelItem * @param {!NavigationModelFakeItem} modelItem
* @param {!DirectoryTree} tree Current tree, which contains this item. * @param {!DirectoryTree} tree Current tree, which contains this item.
* @extends {cr.ui.TreeItem}
* @constructor
*/ */
function FakeItem(rootType, modelItem, tree) { constructor(rootType, modelItem, tree) {
const item = new cr.ui.TreeItem(); super();
// Get the original label id defined by TreeItem, before overwriting // Get the original label id defined by TreeItem, before overwriting
// prototype. // prototype.
const labelId = item.labelElement.id; const labelId = this.labelElement.id;
item.__proto__ = FakeItem.prototype; this.__proto__ = FakeItem.prototype;
if (window.IN_TEST) { if (window.IN_TEST) {
item.setAttribute('dir-type', 'FakeItem'); this.setAttribute('dir-type', 'FakeItem');
item.setAttribute('entry-label', modelItem.label); this.setAttribute('entry-label', modelItem.label);
} }
item.rootType_ = rootType; this.rootType_ = rootType;
item.parentTree_ = tree; this.parentTree_ = tree;
item.modelItem_ = modelItem; this.modelItem_ = modelItem;
item.dirEntry_ = modelItem.entry; this.dirEntry_ = modelItem.entry;
item.innerHTML = TREE_ITEM_INNER_HTML; this.innerHTML = TREE_ITEM_INNER_HTML;
item.labelElement.id = labelId; this.labelElement.id = labelId;
item.label = modelItem.label; this.label = modelItem.label;
item.directoryModel_ = tree.directoryModel; this.directoryModel_ = tree.directoryModel;
const icon = queryRequiredElement('.icon', item); const icon = queryRequiredElement('.icon', this);
icon.classList.add('item-icon'); icon.classList.add('item-icon');
icon.setAttribute('root-type-icon', rootType); icon.setAttribute('root-type-icon', rootType);
return item;
}
FakeItem.prototype = {
__proto__: cr.ui.TreeItem.prototype,
get entry() {
return this.dirEntry_;
},
get modelItem() {
return this.modelItem_;
},
get labelElement() {
return this.firstElementChild.querySelector('.label');
} }
};
/** /**
* @param {!DirectoryEntry|!FakeEntry} entry * @param {!DirectoryEntry|!FilesAppDirEntry} entry
* @return {boolean} True if the parent item is found. * @return {boolean} True if the parent item is found.
*/ */
FakeItem.prototype.searchAndSelectByEntry = entry => { searchAndSelectByEntry(entry) {
return false; return false;
}; }
/** /**
* @override * @override
*/ */
FakeItem.prototype.handleClick = function(e) { handleClick(e) {
this.activate(); this.activate();
DirectoryItemTreeBaseMethods.recordUMASelectedEntry.call( DirectoryItemTreeBaseMethods.recordUMASelectedEntry.call(
this, e, this.rootType_, true); this, e, this.rootType_, true);
}; }
/** /**
* @param {!DirectoryEntry} entry * @param {!DirectoryEntry} entry
*/ */
FakeItem.prototype.selectByEntry = function(entry) { selectByEntry(entry) {
if (util.isSameEntry(entry, this.entry)) { if (util.isSameEntry(entry, this.entry)) {
this.selected = true; this.selected = true;
} }
}; }
/** /**
* Executes the command. * Executes the command.
*/ */
FakeItem.prototype.activate = function() { activate() {
this.parentTree_.directoryModel.activateDirectoryEntry(this.entry); this.parentTree_.directoryModel.activateDirectoryEntry(this.entry);
}; }
/** /**
* FakeItem doesn't really have sub-directories, it's defined here only to have * FakeItem doesn't really have sub-directories, it's defined here only to
* the same API of other Items on this file. * have the same API of other Items on this file.
*/ */
FakeItem.prototype.updateSubDirectories = updateSubDirectories(recursive, opt_successCallback, opt_errorCallback) {
(recursive, opt_successCallback, opt_errorCallback) => {
return opt_successCallback && opt_successCallback(); return opt_successCallback && opt_successCallback();
}; }
/** /**
* FakeItem doesn't really have shared status/icon so we define here as no-op. * FakeItem doesn't really have shared status/icon so we define here as no-op.
*/ */
FakeItem.prototype.updateDriveSpecificIcons = () => {}; updateDriveSpecificIcons() {}
get entry() {
return this.dirEntry_;
}
get modelItem() {
return this.modelItem_;
}
get labelElement() {
return this.firstElementChild.querySelector('.label');
}
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// DirectoryTree // DirectoryTree
...@@ -1691,157 +1668,93 @@ FakeItem.prototype.updateDriveSpecificIcons = () => {}; ...@@ -1691,157 +1668,93 @@ FakeItem.prototype.updateDriveSpecificIcons = () => {};
/** /**
* Tree of directories on the middle bar. This element is also the root of * Tree of directories on the middle bar. This element is also the root of
* items, in other words, this is the parent of the top-level items. * items, in other words, this is the parent of the top-level items.
*
* @constructor
* @extends {cr.ui.Tree}
*/ */
function DirectoryTree() {} class DirectoryTree extends cr.ui.Tree {
constructor() {
super();
/** /** @type {NavigationListModel} */
this.dataModel_ = null;
/** @type {number} */
this.sequence_ = 0;
/** @type {DirectoryModel} */
this.directoryModel_ = null;
/** @type {VolumeManager} this is set in decorate() */
this.volumeManager_ = null;
/** @type {MetadataModel} */
this.metadataModel_ = null;
/** @type {FileFilter} */
this.fileFilter_ = null;
/** @type {?function(*)} */
this.onListContentChangedBound_ = null;
/** @type {?function(!chrome.fileManagerPrivate.FileWatchEvent)} */
this.privateOnDirectoryChangedBound_ = null;
}
/**
* Decorates an element. * Decorates an element.
* @param {HTMLElement} el Element to be DirectoryTree.
* @param {!DirectoryModel} directoryModel Current DirectoryModel. * @param {!DirectoryModel} directoryModel Current DirectoryModel.
* @param {!VolumeManager} volumeManager VolumeManager of the system. * @param {!VolumeManager} volumeManager VolumeManager of the system.
* @param {!MetadataModel} metadataModel Shared MetadataModel instance. * @param {!MetadataModel} metadataModel Shared MetadataModel instance.
* @param {!FileOperationManager} fileOperationManager * @param {!FileOperationManager} fileOperationManager
* @param {boolean} fakeEntriesVisible True if it should show the fakeEntries. * @param {boolean} fakeEntriesVisible True if it should show the fakeEntries.
*/ */
DirectoryTree.decorate = decorateDirectoryTree(
(el, directoryModel, volumeManager, metadataModel, fileOperationManager,
fakeEntriesVisible) => {
el.__proto__ = DirectoryTree.prototype;
/** @type {DirectoryTree} */ (el).decorateDirectoryTree(
directoryModel, volumeManager, metadataModel, fileOperationManager, directoryModel, volumeManager, metadataModel, fileOperationManager,
fakeEntriesVisible); fakeEntriesVisible) {
}; cr.ui.Tree.prototype.decorate.call(this);
DirectoryTree.prototype = { this.sequence_ = 0;
__proto__: cr.ui.Tree.prototype, this.directoryModel_ = directoryModel;
this.volumeManager_ = volumeManager;
this.metadataModel_ = metadataModel;
// DirectoryTree is always expanded. this.fileFilter_ = this.directoryModel_.getFileFilter();
get expanded() { this.fileFilter_.addEventListener(
return true; 'changed', this.onFilterChanged_.bind(this));
},
/**
* @param {boolean} value Not used.
*/
set expanded(value) {},
/** this.directoryModel_.addEventListener(
* The DirectoryEntry corresponding to this DirectoryItem. This may be 'directory-changed', this.onCurrentDirectoryChanged_.bind(this));
* a dummy DirectoryEntry.
* @type {DirectoryEntry|Object}
*/
get entry() {
return this.dirEntry_;
},
/** util.addEventListenerToBackgroundComponent(
* The DirectoryModel this tree corresponds to. fileOperationManager, 'entries-changed',
* @type {DirectoryModel} this.onEntriesChanged_.bind(this));
*/
get directoryModel() {
return this.directoryModel_;
},
/** this.addEventListener('click', (event) => {
* The VolumeManager instance of the system. // Chromevox triggers |click| without switching focus, we force the focus
* @type {VolumeManager} // here so we can handle further keyboard/mouse events to expand/collapse
*/ // directories.
get volumeManager() { if (document.activeElement === document.body) {
return this.volumeManager_; this.focus();
},
/**
* The reference to shared MetadataModel instance.
* @type {!MetadataModel}
*/
get metadataModel() {
return this.metadataModel_;
},
set dataModel(dataModel) {
if (!this.onListContentChangedBound_) {
this.onListContentChangedBound_ = this.onListContentChanged_.bind(this);
}
if (this.dataModel_) {
this.dataModel_.removeEventListener(
'change', this.onListContentChangedBound_);
this.dataModel_.removeEventListener(
'permuted', this.onListContentChangedBound_);
}
this.dataModel_ = dataModel;
dataModel.addEventListener('change', this.onListContentChangedBound_);
dataModel.addEventListener('permuted', this.onListContentChangedBound_);
},
get dataModel() {
return this.dataModel_;
} }
}; });
cr.defineProperty(DirectoryTree, 'contextMenuForSubitems', cr.PropertyKind.JS); this.privateOnDirectoryChangedBound_ =
cr.defineProperty(DirectoryTree, 'contextMenuForRootItems', cr.PropertyKind.JS); this.onDirectoryContentChanged_.bind(this);
chrome.fileManagerPrivate.onDirectoryChanged.addListener(
this.privateOnDirectoryChangedBound_);
/** /**
* Creates a new DirectoryItem based on |modelItem|. * Flag to show fake entries in the tree.
* @param {NavigationModelItem} modelItem, model that will determine the type of * @type {boolean}
* DirectoryItem to be created. * @private
* @param {!DirectoryTree} tree The tree to add the new DirectoryItem to.
* @return {!cr.ui.TreeItem} a newly created instance of a
* DirectoryItem type.
*/ */
DirectoryTree.createDirectoryItem = (modelItem, tree) => { this.fakeEntriesVisible_ = fakeEntriesVisible;
switch (modelItem.type) {
case NavigationModelItemType.VOLUME:
const volumeModelItem =
/** @type {NavigationModelVolumeItem} */ (modelItem);
if (volumeModelItem.volumeInfo.volumeType ===
VolumeManagerCommon.VolumeType.DRIVE) {
return new DriveVolumeItem(volumeModelItem, tree);
} else {
return new VolumeItem(volumeModelItem, tree);
}
break;
case NavigationModelItemType.SHORTCUT:
return new ShortcutItem(
/** @type {!NavigationModelShortcutItem} */ (modelItem), tree);
break;
case NavigationModelItemType.RECENT:
return new FakeItem(
VolumeManagerCommon.RootType.RECENT,
/** @type {!NavigationModelFakeItem} */ (modelItem), tree);
break;
case NavigationModelItemType.CROSTINI:
return new FakeItem(
VolumeManagerCommon.RootType.CROSTINI,
/** @type {!NavigationModelFakeItem} */ (modelItem), tree);
break;
case NavigationModelItemType.DRIVE:
return new FakeItem(
VolumeManagerCommon.RootType.DRIVE,
/** @type {!NavigationModelFakeItem} */ (modelItem), tree);
break;
case NavigationModelItemType.ENTRY_LIST:
const rootType = modelItem.section === NavigationSection.REMOVABLE ?
VolumeManagerCommon.RootType.REMOVABLE :
VolumeManagerCommon.RootType.MY_FILES;
return new EntryListItem(
rootType,
/** @type {!NavigationModelFakeItem} */ (modelItem), tree);
break;
} }
assertNotReached(`No DirectoryItem model: "${modelItem.type}"`);
};
/** /**
* Updates and selects new directory. * Updates and selects new directory.
* @param {!DirectoryEntry} parentDirectory Parent directory of new directory. * @param {!DirectoryEntry} parentDirectory Parent directory of new directory.
* @param {!DirectoryEntry} newDirectory * @param {!DirectoryEntry} newDirectory
*/ */
DirectoryTree.prototype.updateAndSelectNewDirectory = function( updateAndSelectNewDirectory(parentDirectory, newDirectory) {
parentDirectory, newDirectory) {
// Expand parent directory. // Expand parent directory.
const parentItem = const parentItem =
DirectoryItemTreeBaseMethods.getItemByEntry.call(this, parentDirectory); DirectoryItemTreeBaseMethods.getItemByEntry.call(this, parentDirectory);
...@@ -1868,16 +1781,16 @@ DirectoryTree.prototype.updateAndSelectNewDirectory = function( ...@@ -1868,16 +1781,16 @@ DirectoryTree.prototype.updateAndSelectNewDirectory = function(
parentItem.addAt(newDirectoryItem, addAt); parentItem.addAt(newDirectoryItem, addAt);
this.selectedItem = newDirectoryItem; this.selectedItem = newDirectoryItem;
}; }
/** /**
* Calls DirectoryItemTreeBaseMethods.updateSubElementsFromList(). * Calls DirectoryItemTreeBaseMethods.updateSubElementsFromList().
* *
* @param {boolean} recursive True if the all visible sub-directories are * @param {boolean} recursive True if the all visible sub-directories are
* updated recursively including left arrows. If false, the update walks * updated recursively including left arrows. If false, the update walks
* only immediate child directories without arrows. * only immediate child directories without arrows.
*/ */
DirectoryTree.prototype.updateSubElementsFromList = function(recursive) { updateSubElementsFromList(recursive) {
// First, current items which is not included in the dataModel should be // First, current items which is not included in the dataModel should be
// removed. // removed.
for (let i = 0; i < this.items.length;) { for (let i = 0; i < this.items.length;) {
...@@ -1919,8 +1832,8 @@ DirectoryTree.prototype.updateSubElementsFromList = function(recursive) { ...@@ -1919,8 +1832,8 @@ DirectoryTree.prototype.updateSubElementsFromList = function(recursive) {
currentItem.updateSubDirectories(true); currentItem.updateSubDirectories(true);
} }
// EntryListItem can contain volumes that might have been updated: ask // EntryListItem can contain volumes that might have been updated: ask
// them to re-draw. Updates recursively so any created or removed children // them to re-draw. Updates recursively so any created or removed
// folder can be reflected on directory tree. // children folder can be reflected on directory tree.
if (currentItem instanceof EntryListItem) { if (currentItem instanceof EntryListItem) {
currentItem.updateSubDirectories(true); currentItem.updateSubDirectories(true);
} }
...@@ -1941,24 +1854,24 @@ DirectoryTree.prototype.updateSubElementsFromList = function(recursive) { ...@@ -1941,24 +1854,24 @@ DirectoryTree.prototype.updateSubElementsFromList = function(recursive) {
modelIndex++; modelIndex++;
} }
if (itemIndex !== 0) { // if (itemIndex !== 0) {
this.hasChildren = true; // this.hasChildren = true;
//}
} }
};
/** /**
* Finds a parent directory of the {@code entry} in {@code this}, and * Finds a parent directory of the {@code entry} in {@code this}, and
* invokes the DirectoryItem.selectByEntry() of the found directory. * invokes the DirectoryItem.selectByEntry() of the found directory.
* *
* @param {!DirectoryEntry|!FakeEntry} entry The entry to be searched for. Can * @param {!DirectoryEntry|!FilesAppDirEntry} entry The entry to be searched
* be a fake. * for. Can be a fake.
* @return {boolean} True if the parent item is found. * @return {boolean} True if the parent item is found.
*/ */
DirectoryTree.prototype.searchAndSelectByEntry = function(entry) { searchAndSelectByEntry(entry) {
// If the |entry| is same as one of volumes or shortcuts, select it. // If the |entry| is same as one of volumes or shortcuts, select it.
for (let i = 0; i < this.items.length; i++) { for (let i = 0; i < this.items.length; i++) {
// Skips the Drive root volume. For Drive entries, one of children of Drive // Skips the Drive root volume. For Drive entries, one of children of
// root or shortcuts should be selected. // Drive root or shortcuts should be selected.
const item = this.items[i]; const item = this.items[i];
if (item instanceof DriveVolumeItem) { if (item instanceof DriveVolumeItem) {
continue; continue;
...@@ -1973,66 +1886,14 @@ DirectoryTree.prototype.searchAndSelectByEntry = function(entry) { ...@@ -1973,66 +1886,14 @@ DirectoryTree.prototype.searchAndSelectByEntry = function(entry) {
const found = const found =
DirectoryItemTreeBaseMethods.searchAndSelectByEntry.call(this, entry); DirectoryItemTreeBaseMethods.searchAndSelectByEntry.call(this, entry);
return found; return found;
};
/**
* Decorates an element.
* @param {!DirectoryModel} directoryModel Current DirectoryModel.
* @param {!VolumeManager} volumeManager VolumeManager of the system.
* @param {!MetadataModel} metadataModel Shared MetadataModel instance.
* @param {!FileOperationManager} fileOperationManager
* @param {boolean} fakeEntriesVisible True if it should show the fakeEntries.
*/
DirectoryTree.prototype.decorateDirectoryTree = function(
directoryModel, volumeManager, metadataModel, fileOperationManager,
fakeEntriesVisible) {
cr.ui.Tree.prototype.decorate.call(this);
this.sequence_ = 0;
this.directoryModel_ = directoryModel;
this.volumeManager_ = volumeManager;
this.metadataModel_ = metadataModel;
this.models_ = [];
this.fileFilter_ = this.directoryModel_.getFileFilter();
this.fileFilter_.addEventListener(
'changed', this.onFilterChanged_.bind(this));
this.directoryModel_.addEventListener(
'directory-changed', this.onCurrentDirectoryChanged_.bind(this));
util.addEventListenerToBackgroundComponent(
fileOperationManager, 'entries-changed',
this.onEntriesChanged_.bind(this));
this.addEventListener('click', (event) => {
// Chromevox triggers |click| without switching focus, we force the focus
// here so we can handle further keyboard/mouse events to expand/collapse
// directories.
if (document.activeElement === document.body) {
this.focus();
} }
});
this.privateOnDirectoryChangedBound_ =
this.onDirectoryContentChanged_.bind(this);
chrome.fileManagerPrivate.onDirectoryChanged.addListener(
this.privateOnDirectoryChangedBound_);
/** /**
* Flag to show fake entries in the tree.
* @type {boolean}
* @private
*/
this.fakeEntriesVisible_ = fakeEntriesVisible;
};
/**
* Handles entries changed event. * Handles entries changed event.
* @param {!Event} event * @param {!Event} event
* @private * @private
*/ */
DirectoryTree.prototype.onEntriesChanged_ = function(event) { onEntriesChanged_(event) {
const directories = event.entries.filter((entry) => entry.isDirectory); const directories = event.entries.filter((entry) => entry.isDirectory);
if (directories.length === 0) { if (directories.length === 0) {
...@@ -2044,10 +1905,12 @@ DirectoryTree.prototype.onEntriesChanged_ = function(event) { ...@@ -2044,10 +1905,12 @@ DirectoryTree.prototype.onEntriesChanged_ = function(event) {
// Handle as change event of parent entry. // Handle as change event of parent entry.
Promise Promise
.all(directories.map( .all(directories.map(
(directory) => new Promise(directory.getParent.bind(directory)))) (directory) =>
new Promise(directory.getParent.bind(directory))))
.then((parentDirectories) => { .then((parentDirectories) => {
parentDirectories.forEach( parentDirectories.forEach(
(parentDirectory) => this.updateTreeByEntry_(parentDirectory)); (parentDirectory) =>
this.updateTreeByEntry_(parentDirectory));
}); });
break; break;
case util.EntryChangedKind.DELETED: case util.EntryChangedKind.DELETED:
...@@ -2056,14 +1919,14 @@ DirectoryTree.prototype.onEntriesChanged_ = function(event) { ...@@ -2056,14 +1919,14 @@ DirectoryTree.prototype.onEntriesChanged_ = function(event) {
default: default:
assertNotReached(); assertNotReached();
} }
}; }
/** /**
* Select the item corresponding to the given entry. * Select the item corresponding to the given entry.
* @param {!DirectoryEntry|!FakeEntry} entry The directory entry to be selected. * @param {!DirectoryEntry|!FilesAppDirEntry} entry The directory entry to be
* Can be a fake. * selected. Can be a fake.
*/ */
DirectoryTree.prototype.selectByEntry = function(entry) { selectByEntry(entry) {
if (this.selectedItem && util.isSameEntry(entry, this.selectedItem.entry)) { if (this.selectedItem && util.isSameEntry(entry, this.selectedItem.entry)) {
return; return;
} }
...@@ -2086,14 +1949,14 @@ DirectoryTree.prototype.selectByEntry = function(entry) { ...@@ -2086,14 +1949,14 @@ DirectoryTree.prototype.selectByEntry = function(entry) {
this.selectedItem = null; this.selectedItem = null;
} }
}); });
}; }
/** /**
* Activates the volume or the shortcut corresponding to the given index. * Activates the volume or the shortcut corresponding to the given index.
* @param {number} index 0-based index of the target top-level item. * @param {number} index 0-based index of the target top-level item.
* @return {boolean} True if one of the volume items is selected. * @return {boolean} True if one of the volume items is selected.
*/ */
DirectoryTree.prototype.activateByIndex = function(index) { activateByIndex(index) {
if (index < 0 || index >= this.items.length) { if (index < 0 || index >= this.items.length) {
return false; return false;
} }
...@@ -2101,65 +1964,64 @@ DirectoryTree.prototype.activateByIndex = function(index) { ...@@ -2101,65 +1964,64 @@ DirectoryTree.prototype.activateByIndex = function(index) {
this.items[index].selected = true; this.items[index].selected = true;
this.items[index].activate(); this.items[index].activate();
return true; return true;
}; }
/** /**
* Retrieves the latest subdirectories and update them on the tree. * Retrieves the latest subdirectories and update them on the tree.
* *
* @param {boolean} recursive True if the update is recursively. * @param {boolean} recursive True if the update is recursively.
* @param {function()=} opt_callback Called when subdirectories are fully * @param {function()=} opt_callback Called when subdirectories are fully
* updated. * updated.
*/ */
DirectoryTree.prototype.updateSubDirectories = function( updateSubDirectories(recursive, opt_callback) {
recursive, opt_callback) {
this.redraw(recursive); this.redraw(recursive);
if (opt_callback) { if (opt_callback) {
opt_callback(); opt_callback();
} }
}; }
/** /**
* Redraw the list. * Redraw the list.
* @param {boolean} recursive True if the update is recursively. False if the * @param {boolean} recursive True if the update is recursively. False if the
* only root items are updated. * only root items are updated.
*/ */
DirectoryTree.prototype.redraw = function(recursive) { redraw(recursive) {
this.updateSubElementsFromList(recursive); this.updateSubElementsFromList(recursive);
}; }
/** /**
* Invoked when the filter is changed. * Invoked when the filter is changed.
* @private * @private
*/ */
DirectoryTree.prototype.onFilterChanged_ = function() { onFilterChanged_() {
// Returns immediately, if the tree is hidden. // Returns immediately, if the tree is hidden.
if (this.hidden) { if (this.hidden) {
return; return;
} }
this.redraw(true /* recursive */); this.redraw(true /* recursive */);
}; }
/** /**
* Invoked when a directory is changed. * Invoked when a directory is changed.
* @param {!chrome.fileManagerPrivate.FileWatchEvent} event Event. * @param {!chrome.fileManagerPrivate.FileWatchEvent} event Event.
* @private * @private
*/ */
DirectoryTree.prototype.onDirectoryContentChanged_ = function(event) { onDirectoryContentChanged_(event) {
if (event.eventType !== 'changed' || !event.entry) { if (event.eventType !== 'changed' || !event.entry) {
return; return;
} }
this.updateTreeByEntry_(/** @type{!Entry} */ (event.entry)); this.updateTreeByEntry_(/** @type{!Entry} */ (event.entry));
}; }
/** /**
* Updates tree by entry. * Updates tree by entry.
* @param {!Entry} entry A changed entry. Deleted entry is passed when watched * @param {!Entry} entry A changed entry. Deleted entry is passed when watched
* directory is deleted. * directory is deleted.
* @private * @private
*/ */
DirectoryTree.prototype.updateTreeByEntry_ = function(entry) { updateTreeByEntry_(entry) {
entry.getDirectory( entry.getDirectory(
entry.fullPath, {create: false}, entry.fullPath, {create: false},
() => { () => {
...@@ -2173,9 +2035,9 @@ DirectoryTree.prototype.updateTreeByEntry_ = function(entry) { ...@@ -2173,9 +2035,9 @@ DirectoryTree.prototype.updateTreeByEntry_ = function(entry) {
} }
}, },
() => { () => {
// If entry does not exist, try to get parent and update the subtree by // If entry does not exist, try to get parent and update the subtree
// it. // by it. e.g. /a/b is deleted while watching /a/b. Try to update /a
// e.g. /a/b is deleted while watching /a/b. Try to update /a in this // in this
// case. // case.
entry.getParent( entry.getParent(
(parentEntry) => { (parentEntry) => {
...@@ -2183,7 +2045,8 @@ DirectoryTree.prototype.updateTreeByEntry_ = function(entry) { ...@@ -2183,7 +2045,8 @@ DirectoryTree.prototype.updateTreeByEntry_ = function(entry) {
}, },
(error) => { (error) => {
// If it fails to get parent, update the subtree by volume. // If it fails to get parent, update the subtree by volume.
// e.g. /a/b is deleted while watching /a/b/c. getParent of /a/b/c // e.g. /a/b is deleted while watching /a/b/c. getParent of
// /a/b/c
// fails in this case. We falls back to volume update. // fails in this case. We falls back to volume update.
// //
// TODO(yawano): Try to get parent path also in this case by // TODO(yawano): Try to get parent path also in this case by
...@@ -2201,27 +2064,27 @@ DirectoryTree.prototype.updateTreeByEntry_ = function(entry) { ...@@ -2201,27 +2064,27 @@ DirectoryTree.prototype.updateTreeByEntry_ = function(entry) {
} }
}); });
}); });
}; }
/** /**
* Invoked when the current directory is changed. * Invoked when the current directory is changed.
* @param {!Event} event Event. * @param {!Event} event Event.
* @private * @private
*/ */
DirectoryTree.prototype.onCurrentDirectoryChanged_ = function(event) { onCurrentDirectoryChanged_(event) {
this.selectByEntry(event.newDirEntry); this.selectByEntry(event.newDirEntry);
this.updateSubDirectories(false /* recursive */, () => {}); this.updateSubDirectories(false /* recursive */, () => {});
}; }
/** /**
* Invoked when the volume list or shortcut list is changed. * Invoked when the volume list or shortcut list is changed.
* @private * @private
*/ */
DirectoryTree.prototype.onListContentChanged_ = function() { onListContentChanged_() {
this.updateSubDirectories(false, () => { this.updateSubDirectories(false, () => {
// If no item is selected now, try to select the item corresponding to // If no item is selected now, try to select the item corresponding to
// current directory because the current directory might have been populated // current directory because the current directory might have been
// in this tree in previous updateSubDirectories(). // populated in this tree in previous updateSubDirectories().
if (!this.selectedItem) { if (!this.selectedItem) {
const currentDir = this.directoryModel_.getCurrentDirEntry(); const currentDir = this.directoryModel_.getCurrentDirEntry();
if (currentDir) { if (currentDir) {
...@@ -2229,11 +2092,137 @@ DirectoryTree.prototype.onListContentChanged_ = function() { ...@@ -2229,11 +2092,137 @@ DirectoryTree.prototype.onListContentChanged_ = function() {
} }
} }
}); });
}; }
/** /**
* Updates the UI after the layout has changed. * Updates the UI after the layout has changed.
*/ */
DirectoryTree.prototype.relayout = function() { relayout() {
cr.dispatchSimpleEvent(this, 'relayout', true); cr.dispatchSimpleEvent(this, 'relayout', true);
}
// DirectoryTree is always expanded.
get expanded() {
return true;
}
/**
* @param {boolean} value Not used.
*/
set expanded(value) {}
/**
* The DirectoryModel this tree corresponds to.
* @type {DirectoryModel}
*/
get directoryModel() {
return this.directoryModel_;
}
/**
* The VolumeManager instance of the system.
* @type {VolumeManager}
*/
get volumeManager() {
return this.volumeManager_;
}
/**
* The reference to shared MetadataModel instance.
* @type {!MetadataModel}
*/
get metadataModel() {
return this.metadataModel_;
}
set dataModel(dataModel) {
if (!this.onListContentChangedBound_) {
this.onListContentChangedBound_ = this.onListContentChanged_.bind(this);
}
if (this.dataModel_) {
this.dataModel_.removeEventListener(
'change', this.onListContentChangedBound_);
this.dataModel_.removeEventListener(
'permuted', this.onListContentChangedBound_);
}
this.dataModel_ = dataModel;
dataModel.addEventListener('change', this.onListContentChangedBound_);
dataModel.addEventListener('permuted', this.onListContentChangedBound_);
}
get dataModel() {
return this.dataModel_;
}
}
/**
* Decorates an element.
* @param {HTMLElement} el Element to be DirectoryTree.
* @param {!DirectoryModel} directoryModel Current DirectoryModel.
* @param {!VolumeManager} volumeManager VolumeManager of the system.
* @param {!MetadataModel} metadataModel Shared MetadataModel instance.
* @param {!FileOperationManager} fileOperationManager
* @param {boolean} fakeEntriesVisible True if it should show the fakeEntries.
*/
DirectoryTree.decorate =
(el, directoryModel, volumeManager, metadataModel, fileOperationManager,
fakeEntriesVisible) => {
el.__proto__ = DirectoryTree.prototype;
/** @type {DirectoryTree} */ (el).decorateDirectoryTree(
directoryModel, volumeManager, metadataModel, fileOperationManager,
fakeEntriesVisible);
};
cr.defineProperty(DirectoryTree, 'contextMenuForSubitems', cr.PropertyKind.JS);
cr.defineProperty(DirectoryTree, 'contextMenuForRootItems', cr.PropertyKind.JS);
/**
* Creates a new DirectoryItem based on |modelItem|.
* @param {NavigationModelItem} modelItem, model that will determine the type of
* DirectoryItem to be created.
* @param {!DirectoryTree} tree The tree to add the new DirectoryItem to.
* @return {!cr.ui.TreeItem} a newly created instance of a
* DirectoryItem type.
*/
DirectoryTree.createDirectoryItem = (modelItem, tree) => {
switch (modelItem.type) {
case NavigationModelItemType.VOLUME:
const volumeModelItem =
/** @type {NavigationModelVolumeItem} */ (modelItem);
if (volumeModelItem.volumeInfo.volumeType ===
VolumeManagerCommon.VolumeType.DRIVE) {
return new DriveVolumeItem(volumeModelItem, tree);
} else {
return new VolumeItem(volumeModelItem, tree);
}
break;
case NavigationModelItemType.SHORTCUT:
return new ShortcutItem(
/** @type {!NavigationModelShortcutItem} */ (modelItem), tree);
break;
case NavigationModelItemType.RECENT:
return new FakeItem(
VolumeManagerCommon.RootType.RECENT,
/** @type {!NavigationModelFakeItem} */ (modelItem), tree);
break;
case NavigationModelItemType.CROSTINI:
return new FakeItem(
VolumeManagerCommon.RootType.CROSTINI,
/** @type {!NavigationModelFakeItem} */ (modelItem), tree);
break;
case NavigationModelItemType.DRIVE:
return new FakeItem(
VolumeManagerCommon.RootType.DRIVE,
/** @type {!NavigationModelFakeItem} */ (modelItem), tree);
break;
case NavigationModelItemType.ENTRY_LIST:
const rootType = modelItem.section === NavigationSection.REMOVABLE ?
VolumeManagerCommon.RootType.REMOVABLE :
VolumeManagerCommon.RootType.MY_FILES;
return new EntryListItem(
rootType,
/** @type {!NavigationModelFakeItem} */ (modelItem), tree);
break;
}
assertNotReached(`No DirectoryItem model: "${modelItem.type}"`);
}; };
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment