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