blob: b4c6eabb5e1cd67d6e53b6c8f5c116d605f67635 [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
* OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @implements {SDK.TargetManager.Observer}
* @unrestricted
*/
Sources.NavigatorView = class extends UI.VBox {
constructor() {
super(true);
this.registerRequiredCSS('sources/navigatorView.css');
/** @type {?UI.Widget} */
this._placeholder = null;
this._scriptsTree = new UI.TreeOutlineInShadow();
this._scriptsTree.registerRequiredCSS('sources/navigatorTree.css');
this._scriptsTree.setComparator(Sources.NavigatorView._treeElementsCompare);
this.contentElement.appendChild(this._scriptsTree.element);
this.setDefaultFocusedElement(this._scriptsTree.element);
/** @type {!Platform.Multimap<!Workspace.UISourceCode, !Sources.NavigatorUISourceCodeTreeNode>} */
this._uiSourceCodeNodes = new Platform.Multimap();
/** @type {!Map.<string, !Sources.NavigatorFolderTreeNode>} */
this._subfolderNodes = new Map();
this._rootNode = new Sources.NavigatorRootTreeNode(this);
this._rootNode.populate();
/** @type {!Map.<!SDK.ResourceTreeFrame, !Sources.NavigatorGroupTreeNode>} */
this._frameNodes = new Map();
this.contentElement.addEventListener('contextmenu', this.handleContextMenu.bind(this), false);
UI.shortcutRegistry.addShortcutListener(
this.contentElement, 'sources.rename', this._renameShortcut.bind(this), true);
this._navigatorGroupByFolderSetting = Common.moduleSetting('navigatorGroupByFolder');
this._navigatorGroupByFolderSetting.addChangeListener(this._groupingChanged.bind(this));
this._initGrouping();
Persistence.persistence.addEventListener(
Persistence.Persistence.Events.BindingCreated, this._onBindingChanged, this);
Persistence.persistence.addEventListener(
Persistence.Persistence.Events.BindingRemoved, this._onBindingChanged, this);
SDK.targetManager.addEventListener(SDK.TargetManager.Events.NameChanged, this._targetNameChanged, this);
SDK.targetManager.observeTargets(this);
this._resetWorkspace(Workspace.workspace);
this._workspace.uiSourceCodes().forEach(this._addUISourceCode.bind(this));
Bindings.networkProjectManager.addEventListener(
Bindings.NetworkProjectManager.Events.FrameAttributionAdded, this._frameAttributionAdded, this);
Bindings.networkProjectManager.addEventListener(
Bindings.NetworkProjectManager.Events.FrameAttributionRemoved, this._frameAttributionRemoved, this);
}
/**
* @param {!UI.TreeElement} treeElement
*/
static _treeElementOrder(treeElement) {
if (treeElement._boostOrder) {
return 0;
}
if (!Sources.NavigatorView._typeOrders) {
const weights = {};
const types = Sources.NavigatorView.Types;
weights[types.Root] = 1;
weights[types.Domain] = 10;
weights[types.FileSystemFolder] = 1;
weights[types.NetworkFolder] = 1;
weights[types.SourceMapFolder] = 2;
weights[types.File] = 10;
weights[types.Frame] = 70;
weights[types.Worker] = 90;
weights[types.FileSystem] = 100;
Sources.NavigatorView._typeOrders = weights;
}
let order = Sources.NavigatorView._typeOrders[treeElement._nodeType];
if (treeElement._uiSourceCode) {
const contentType = treeElement._uiSourceCode.contentType();
if (contentType.isDocument()) {
order += 3;
} else if (contentType.isScript()) {
order += 5;
} else if (contentType.isStyleSheet()) {
order += 10;
} else {
order += 15;
}
}
return order;
}
/**
* @param {!UI.ContextMenu} contextMenu
* @param {string=} path
*/
static appendSearchItem(contextMenu, path) {
function searchPath() {
Sources.SearchSourcesView.openSearch(`file:${path.trim()}`);
}
let searchLabel = Common.UIString('Search in folder');
if (!path || !path.trim()) {
path = '*';
searchLabel = Common.UIString('Search in all files');
}
contextMenu.viewSection().appendItem(searchLabel, searchPath);
}
/**
* @param {!UI.TreeElement} treeElement1
* @param {!UI.TreeElement} treeElement2
* @return {number}
*/
static _treeElementsCompare(treeElement1, treeElement2) {
const typeWeight1 = Sources.NavigatorView._treeElementOrder(treeElement1);
const typeWeight2 = Sources.NavigatorView._treeElementOrder(treeElement2);
if (typeWeight1 > typeWeight2) {
return 1;
}
if (typeWeight1 < typeWeight2) {
return -1;
}
return treeElement1.titleAsText().compareTo(treeElement2.titleAsText());
}
/**
* @param {!UI.Widget} placeholder
*/
setPlaceholder(placeholder) {
console.assert(!this._placeholder, 'A placeholder widget was already set');
this._placeholder = placeholder;
placeholder.show(this.contentElement, this.contentElement.firstChild);
updateVisibility.call(this);
this._scriptsTree.addEventListener(UI.TreeOutline.Events.ElementAttached, updateVisibility.bind(this));
this._scriptsTree.addEventListener(UI.TreeOutline.Events.ElementsDetached, updateVisibility.bind(this));
/**
* @this {!Sources.NavigatorView}
*/
function updateVisibility() {
const showTree = this._scriptsTree.firstChild();
if (showTree) {
placeholder.hideWidget();
} else {
placeholder.showWidget();
}
this._scriptsTree.element.classList.toggle('hidden', !showTree);
}
}
/**
* @param {!Common.Event} event
*/
_onBindingChanged(event) {
const binding = /** @type {!Persistence.PersistenceBinding} */ (event.data);
// Update UISourceCode titles.
const networkNodes = this._uiSourceCodeNodes.get(binding.network);
for (const networkNode of networkNodes) {
networkNode.updateTitle();
}
const fileSystemNodes = this._uiSourceCodeNodes.get(binding.fileSystem);
for (const fileSystemNode of fileSystemNodes) {
fileSystemNode.updateTitle();
}
// Update folder titles.
const pathTokens = Persistence.FileSystemWorkspaceBinding.relativePath(binding.fileSystem);
let folderPath = '';
for (let i = 0; i < pathTokens.length - 1; ++i) {
folderPath += pathTokens[i];
const folderId =
this._folderNodeId(binding.fileSystem.project(), null, null, binding.fileSystem.origin(), folderPath);
const folderNode = this._subfolderNodes.get(folderId);
if (folderNode) {
folderNode.updateTitle();
}
folderPath += '/';
}
// Update fileSystem root title.
const fileSystemRoot = this._rootNode.child(binding.fileSystem.project().id());
if (fileSystemRoot) {
fileSystemRoot.updateTitle();
}
}
/**
* @override
*/
focus() {
this._scriptsTree.focus();
}
/**
* @param {!Workspace.Workspace} workspace
*/
_resetWorkspace(workspace) {
this._workspace = workspace;
this._workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
this._workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
this._workspace.addEventListener(Workspace.Workspace.Events.ProjectAdded, event => {
const project = /** @type {!Workspace.Project} */ (event.data);
this._projectAdded(project);
if (project.type() === Workspace.projectTypes.FileSystem) {
this._computeUniqueFileSystemProjectNames();
}
});
this._workspace.addEventListener(Workspace.Workspace.Events.ProjectRemoved, event => {
const project = /** @type {!Workspace.Project} */ (event.data);
this._removeProject(project);
if (project.type() === Workspace.projectTypes.FileSystem) {
this._computeUniqueFileSystemProjectNames();
}
});
this._workspace.projects().forEach(this._projectAdded.bind(this));
this._computeUniqueFileSystemProjectNames();
}
/**
* @return {!Workspace.Workspace}
* @protected
*/
workspace() {
return this._workspace;
}
/**
* @param {!Workspace.Project} project
* @return {boolean}
*/
acceptProject(project) {
return !project.isServiceProject();
}
/**
* @param {!Common.Event} event
*/
_frameAttributionAdded(event) {
const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data.uiSourceCode);
if (!this._acceptsUISourceCode(uiSourceCode)) {
return;
}
const addedFrame = /** @type {?SDK.ResourceTreeFrame} */ (event.data.frame);
// This event does not happen for UISourceCodes without initial attribution.
this._addUISourceCodeNode(uiSourceCode, addedFrame);
}
/**
* @param {!Common.Event} event
*/
_frameAttributionRemoved(event) {
const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data.uiSourceCode);
if (!this._acceptsUISourceCode(uiSourceCode)) {
return;
}
const removedFrame = /** @type {?SDK.ResourceTreeFrame} */ (event.data.frame);
const node = Array.from(this._uiSourceCodeNodes.get(uiSourceCode)).find(node => node.frame() === removedFrame);
this._removeUISourceCodeNode(node);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @return {boolean}
*/
_acceptsUISourceCode(uiSourceCode) {
return this.acceptProject(uiSourceCode.project());
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_addUISourceCode(uiSourceCode) {
if (!this._acceptsUISourceCode(uiSourceCode)) {
return;
}
const frames = Bindings.NetworkProject.framesForUISourceCode(uiSourceCode);
if (frames.length) {
for (const frame of frames) {
this._addUISourceCodeNode(uiSourceCode, frame);
}
} else {
this._addUISourceCodeNode(uiSourceCode, null);
}
this.uiSourceCodeAdded(uiSourceCode);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {?SDK.ResourceTreeFrame} frame
*/
_addUISourceCodeNode(uiSourceCode, frame) {
const isFromSourceMap = uiSourceCode.contentType().isFromSourceMap();
let path;
if (uiSourceCode.project().type() === Workspace.projectTypes.FileSystem) {
path = Persistence.FileSystemWorkspaceBinding.relativePath(uiSourceCode).slice(0, -1);
} else {
path = Common.ParsedURL.extractPath(uiSourceCode.url()).split('/').slice(1, -1);
}
const project = uiSourceCode.project();
const target = Bindings.NetworkProject.targetForUISourceCode(uiSourceCode);
const folderNode =
this._folderNode(uiSourceCode, project, target, frame, uiSourceCode.origin(), path, isFromSourceMap);
const uiSourceCodeNode = new Sources.NavigatorUISourceCodeTreeNode(this, uiSourceCode, frame);
folderNode.appendChild(uiSourceCodeNode);
this._uiSourceCodeNodes.set(uiSourceCode, uiSourceCodeNode);
this._selectDefaultTreeNode();
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
uiSourceCodeAdded(uiSourceCode) {
}
/**
* @param {!Common.Event} event
*/
_uiSourceCodeAdded(event) {
const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data);
this._addUISourceCode(uiSourceCode);
}
/**
* @param {!Common.Event} event
*/
_uiSourceCodeRemoved(event) {
const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data);
this._removeUISourceCode(uiSourceCode);
}
/**
* @protected
* @param {!Workspace.Project} project
*/
tryAddProject(project) {
this._projectAdded(project);
project.uiSourceCodes().forEach(this._addUISourceCode.bind(this));
}
/**
* @param {!Workspace.Project} project
*/
_projectAdded(project) {
if (!this.acceptProject(project) || project.type() !== Workspace.projectTypes.FileSystem ||
Snippets.isSnippetsProject(project) || this._rootNode.child(project.id())) {
return;
}
this._rootNode.appendChild(new Sources.NavigatorGroupTreeNode(
this, project, project.id(), Sources.NavigatorView.Types.FileSystem, project.displayName()));
this._selectDefaultTreeNode();
}
// TODO(einbinder) remove this code after crbug.com/964075 is fixed
_selectDefaultTreeNode() {
const children = this._rootNode.children();
if (children.length && !this._scriptsTree.selectedTreeElement) {
children[0].treeNode().select(true /* omitFocus */, false /* selectedByUser */);
}
}
_computeUniqueFileSystemProjectNames() {
const fileSystemProjects = this._workspace.projectsForType(Workspace.projectTypes.FileSystem);
if (!fileSystemProjects.length) {
return;
}
const encoder = new Persistence.PathEncoder();
const reversedPaths = fileSystemProjects.map(project => {
const fileSystem = /** @type {!Persistence.FileSystemWorkspaceBinding.FileSystem} */ (project);
return encoder.encode(fileSystem.fileSystemPath()).reverse();
});
const reversedIndex = new Common.Trie();
for (const reversedPath of reversedPaths) {
reversedIndex.add(reversedPath);
}
for (let i = 0; i < fileSystemProjects.length; ++i) {
const reversedPath = reversedPaths[i];
const project = fileSystemProjects[i];
reversedIndex.remove(reversedPath);
const commonPrefix = reversedIndex.longestPrefix(reversedPath, false /* fullWordOnly */);
reversedIndex.add(reversedPath);
const path = encoder.decode(reversedPath.substring(0, commonPrefix.length + 1).reverse());
const fileSystemNode = this._rootNode.child(project.id());
if (fileSystemNode) {
fileSystemNode.setTitle(path);
}
}
}
/**
* @param {!Workspace.Project} project
*/
_removeProject(project) {
const uiSourceCodes = project.uiSourceCodes();
for (let i = 0; i < uiSourceCodes.length; ++i) {
this._removeUISourceCode(uiSourceCodes[i]);
}
if (project.type() !== Workspace.projectTypes.FileSystem) {
return;
}
const fileSystemNode = this._rootNode.child(project.id());
if (!fileSystemNode) {
return;
}
this._rootNode.removeChild(fileSystemNode);
}
/**
* @param {!Workspace.Project} project
* @param {?SDK.Target} target
* @param {?SDK.ResourceTreeFrame} frame
* @param {string} projectOrigin
* @param {string} path
* @return {string}
*/
_folderNodeId(project, target, frame, projectOrigin, path) {
const targetId = target ? target.id() : '';
const projectId = project.type() === Workspace.projectTypes.FileSystem ? project.id() : '';
const frameId = this._groupByFrame && frame ? frame.id : '';
return targetId + ':' + projectId + ':' + frameId + ':' + projectOrigin + ':' + path;
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {!Workspace.Project} project
* @param {?SDK.Target} target
* @param {?SDK.ResourceTreeFrame} frame
* @param {string} projectOrigin
* @param {!Array<string>} path
* @param {boolean} fromSourceMap
* @return {!Sources.NavigatorTreeNode}
*/
_folderNode(uiSourceCode, project, target, frame, projectOrigin, path, fromSourceMap) {
if (Snippets.isSnippetsUISourceCode(uiSourceCode)) {
return this._rootNode;
}
if (target && !this._groupByFolder && !fromSourceMap) {
return this._domainNode(uiSourceCode, project, target, frame, projectOrigin);
}
const folderPath = path.join('/');
const folderId = this._folderNodeId(project, target, frame, projectOrigin, folderPath);
let folderNode = this._subfolderNodes.get(folderId);
if (folderNode) {
return folderNode;
}
if (!path.length) {
if (target) {
return this._domainNode(uiSourceCode, project, target, frame, projectOrigin);
}
return /** @type {!Sources.NavigatorTreeNode} */ (this._rootNode.child(project.id()));
}
const parentNode =
this._folderNode(uiSourceCode, project, target, frame, projectOrigin, path.slice(0, -1), fromSourceMap);
let type = fromSourceMap ? Sources.NavigatorView.Types.SourceMapFolder : Sources.NavigatorView.Types.NetworkFolder;
if (project.type() === Workspace.projectTypes.FileSystem) {
type = Sources.NavigatorView.Types.FileSystemFolder;
}
const name = path[path.length - 1];
folderNode = new Sources.NavigatorFolderTreeNode(this, project, folderId, type, folderPath, name);
this._subfolderNodes.set(folderId, folderNode);
parentNode.appendChild(folderNode);
return folderNode;
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {!Workspace.Project} project
* @param {!SDK.Target} target
* @param {?SDK.ResourceTreeFrame} frame
* @param {string} projectOrigin
* @return {!Sources.NavigatorTreeNode}
*/
_domainNode(uiSourceCode, project, target, frame, projectOrigin) {
const frameNode = this._frameNode(project, target, frame);
if (!this._groupByDomain) {
return frameNode;
}
let domainNode = frameNode.child(projectOrigin);
if (domainNode) {
return domainNode;
}
domainNode = new Sources.NavigatorGroupTreeNode(
this, project, projectOrigin, Sources.NavigatorView.Types.Domain,
this._computeProjectDisplayName(target, projectOrigin));
if (frame && projectOrigin === Common.ParsedURL.extractOrigin(frame.url)) {
domainNode.treeNode()._boostOrder = true;
}
frameNode.appendChild(domainNode);
return domainNode;
}
/**
* @param {!Workspace.Project} project
* @param {!SDK.Target} target
* @param {?SDK.ResourceTreeFrame} frame
* @return {!Sources.NavigatorTreeNode}
*/
_frameNode(project, target, frame) {
if (!this._groupByFrame || !frame) {
return this._targetNode(project, target);
}
let frameNode = this._frameNodes.get(frame);
if (frameNode) {
return frameNode;
}
frameNode = new Sources.NavigatorGroupTreeNode(
this, project, target.id() + ':' + frame.id, Sources.NavigatorView.Types.Frame, frame.displayName());
frameNode.setHoverCallback(hoverCallback);
this._frameNodes.set(frame, frameNode);
const parentFrame = frame.parentFrame || frame.crossTargetParentFrame();
this._frameNode(project, parentFrame ? parentFrame.resourceTreeModel().target() : target, parentFrame)
.appendChild(frameNode);
if (!parentFrame) {
frameNode.treeNode()._boostOrder = true;
frameNode.treeNode().expand();
}
/**
* @param {boolean} hovered
*/
function hoverCallback(hovered) {
if (hovered) {
const overlayModel = target.model(SDK.OverlayModel);
if (overlayModel) {
overlayModel.highlightFrame(frame.id);
}
} else {
SDK.OverlayModel.hideDOMNodeHighlight();
}
}
return frameNode;
}
/**
* @param {!Workspace.Project} project
* @param {!SDK.Target} target
* @return {!Sources.NavigatorTreeNode}
*/
_targetNode(project, target) {
if (target === SDK.targetManager.mainTarget()) {
return this._rootNode;
}
let targetNode = this._rootNode.child('target:' + target.id());
if (!targetNode) {
targetNode = new Sources.NavigatorGroupTreeNode(
this, project, 'target:' + target.id(),
target.type() === SDK.Target.Type.Frame ? Sources.NavigatorView.Types.Frame :
Sources.NavigatorView.Types.Worker,
target.name());
this._rootNode.appendChild(targetNode);
}
return targetNode;
}
/**
* @param {!SDK.Target} target
* @param {string} projectOrigin
* @return {string}
*/
_computeProjectDisplayName(target, projectOrigin) {
const runtimeModel = target.model(SDK.RuntimeModel);
const executionContexts = runtimeModel ? runtimeModel.executionContexts() : [];
for (const context of executionContexts) {
if (context.name && context.origin && projectOrigin.startsWith(context.origin)) {
return context.name;
}
}
if (!projectOrigin) {
return Common.UIString('(no domain)');
}
const parsedURL = new Common.ParsedURL(projectOrigin);
const prettyURL = parsedURL.isValid ? parsedURL.host + (parsedURL.port ? (':' + parsedURL.port) : '') : '';
return (prettyURL || projectOrigin);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {boolean=} select
* @return {?Sources.NavigatorUISourceCodeTreeNode}
*/
revealUISourceCode(uiSourceCode, select) {
const nodes = this._uiSourceCodeNodes.get(uiSourceCode);
const node = nodes.firstValue();
if (!node) {
return null;
}
if (this._scriptsTree.selectedTreeElement) {
this._scriptsTree.selectedTreeElement.deselect();
}
this._lastSelectedUISourceCode = uiSourceCode;
// TODO(dgozman): figure out revealing multiple.
node.reveal(select);
return node;
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {boolean} focusSource
*/
_sourceSelected(uiSourceCode, focusSource) {
this._lastSelectedUISourceCode = uiSourceCode;
Common.Revealer.reveal(uiSourceCode, !focusSource);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_removeUISourceCode(uiSourceCode) {
const nodes = this._uiSourceCodeNodes.get(uiSourceCode);
for (const node of nodes) {
this._removeUISourceCodeNode(node);
}
}
/**
* @param {!Sources.NavigatorUISourceCodeTreeNode} node
*/
_removeUISourceCodeNode(node) {
const uiSourceCode = node.uiSourceCode();
this._uiSourceCodeNodes.delete(uiSourceCode, node);
const project = uiSourceCode.project();
const target = Bindings.NetworkProject.targetForUISourceCode(uiSourceCode);
const frame = node.frame();
let parentNode = node.parent;
parentNode.removeChild(node);
node = parentNode;
while (node) {
parentNode = node.parent;
if (!parentNode || !node.isEmpty()) {
break;
}
if (parentNode === this._rootNode && project.type() === Workspace.projectTypes.FileSystem) {
break;
}
if (!(node instanceof Sources.NavigatorGroupTreeNode || node instanceof Sources.NavigatorFolderTreeNode)) {
break;
}
if (node._type === Sources.NavigatorView.Types.Frame) {
this._discardFrame(/** @type {!SDK.ResourceTreeFrame} */ (frame));
break;
}
const folderId = this._folderNodeId(project, target, frame, uiSourceCode.origin(), node._folderPath);
this._subfolderNodes.delete(folderId);
parentNode.removeChild(node);
node = parentNode;
}
}
reset() {
for (const node of this._uiSourceCodeNodes.valuesArray()) {
node.dispose();
}
this._scriptsTree.removeChildren();
this._uiSourceCodeNodes.clear();
this._subfolderNodes.clear();
this._frameNodes.clear();
this._rootNode.reset();
}
/**
* @param {!Event} event
*/
handleContextMenu(event) {
}
/**
* @return {boolean}
*/
_renameShortcut() {
const node = this._scriptsTree.selectedTreeElement && this._scriptsTree.selectedTreeElement._node;
if (!node || !node._uiSourceCode || !node._uiSourceCode.canRename()) {
return false;
}
this.rename(node, false);
return true;
}
/**
* @param {!Workspace.Project} project
* @param {string} path
* @param {!Workspace.UISourceCode=} uiSourceCode
*/
_handleContextMenuCreate(project, path, uiSourceCode) {
if (uiSourceCode) {
const relativePath = Persistence.FileSystemWorkspaceBinding.relativePath(uiSourceCode);
relativePath.pop();
path = relativePath.join('/');
}
this.create(project, path, uiSourceCode);
}
/**
* @param {!Sources.NavigatorUISourceCodeTreeNode} node
*/
_handleContextMenuRename(node) {
this.rename(node, false);
}
/**
* @param {!Workspace.Project} project
* @param {string} path
*/
_handleContextMenuExclude(project, path) {
const shouldExclude = window.confirm(Common.UIString('Are you sure you want to exclude this folder?'));
if (shouldExclude) {
UI.startBatchUpdate();
project.excludeFolder(Persistence.FileSystemWorkspaceBinding.completeURL(project, path));
UI.endBatchUpdate();
}
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_handleContextMenuDelete(uiSourceCode) {
const shouldDelete = window.confirm(Common.UIString('Are you sure you want to delete this file?'));
if (shouldDelete) {
uiSourceCode.project().deleteFile(uiSourceCode);
}
}
/**
* @param {!Event} event
* @param {!Sources.NavigatorUISourceCodeTreeNode} node
*/
handleFileContextMenu(event, node) {
const uiSourceCode = node.uiSourceCode();
const contextMenu = new UI.ContextMenu(event);
contextMenu.appendApplicableItems(uiSourceCode);
const project = uiSourceCode.project();
if (project.type() === Workspace.projectTypes.FileSystem) {
contextMenu.editSection().appendItem(
Common.UIString('Rename\u2026'), this._handleContextMenuRename.bind(this, node));
contextMenu.editSection().appendItem(
Common.UIString('Make a copy\u2026'), this._handleContextMenuCreate.bind(this, project, '', uiSourceCode));
contextMenu.editSection().appendItem(
Common.UIString('Delete'), this._handleContextMenuDelete.bind(this, uiSourceCode));
}
contextMenu.show();
}
/**
* @param {!Event} event
* @param {!Sources.NavigatorTreeNode} node
*/
handleFolderContextMenu(event, node) {
const path = node._folderPath || '';
const project = node._project;
const contextMenu = new UI.ContextMenu(event);
if (project.type() === Workspace.projectTypes.FileSystem) {
Sources.NavigatorView.appendSearchItem(contextMenu, path);
const folderPath = Common.ParsedURL.urlToPlatformPath(
Persistence.FileSystemWorkspaceBinding.completeURL(project, path), Host.isWin());
contextMenu.revealSection().appendItem(
Common.UIString('Open folder'), () => Host.InspectorFrontendHost.showItemInFolder(folderPath));
if (project.canCreateFile()) {
contextMenu.defaultSection().appendItem(
Common.UIString('New file'), this._handleContextMenuCreate.bind(this, project, path));
}
}
if (project.canExcludeFolder(path)) {
contextMenu.defaultSection().appendItem(
Common.UIString('Exclude folder'), this._handleContextMenuExclude.bind(this, project, path));
}
function removeFolder() {
const shouldRemove = window.confirm(Common.UIString('Are you sure you want to remove this folder?'));
if (shouldRemove) {
project.remove();
}
}
if (project.type() === Workspace.projectTypes.FileSystem) {
contextMenu.defaultSection().appendAction('sources.add-folder-to-workspace', undefined, true);
if (node instanceof Sources.NavigatorGroupTreeNode) {
contextMenu.defaultSection().appendItem(Common.UIString('Remove folder from workspace'), removeFolder);
}
}
contextMenu.show();
}
/**
* @param {!Sources.NavigatorUISourceCodeTreeNode} node
* @param {boolean} creatingNewUISourceCode
* @protected
*/
rename(node, creatingNewUISourceCode) {
const uiSourceCode = node.uiSourceCode();
node.rename(callback.bind(this));
/**
* @this {Sources.NavigatorView}
* @param {boolean} committed
*/
function callback(committed) {
if (!creatingNewUISourceCode) {
return;
}
if (!committed) {
uiSourceCode.remove();
} else if (node._treeElement.listItemElement.hasFocus()) {
this._sourceSelected(uiSourceCode, true);
}
}
}
/**
* @param {!Workspace.Project} project
* @param {string} path
* @param {!Workspace.UISourceCode=} uiSourceCodeToCopy
*/
async create(project, path, uiSourceCodeToCopy) {
let content = '';
if (uiSourceCodeToCopy) {
content = (await uiSourceCodeToCopy.requestContent()).content || '';
}
const uiSourceCode = await project.createFile(path, null, content);
if (!uiSourceCode) {
return;
}
this._sourceSelected(uiSourceCode, false);
const node = this.revealUISourceCode(uiSourceCode, true);
if (node) {
this.rename(node, true);
}
}
_groupingChanged() {
this.reset();
this._initGrouping();
this._workspace.uiSourceCodes().forEach(this._addUISourceCode.bind(this));
}
_initGrouping() {
this._groupByFrame = true;
this._groupByDomain = this._navigatorGroupByFolderSetting.get();
this._groupByFolder = this._groupByDomain;
}
_resetForTest() {
this.reset();
this._workspace.uiSourceCodes().forEach(this._addUISourceCode.bind(this));
}
/**
* @param {!SDK.ResourceTreeFrame} frame
*/
_discardFrame(frame) {
const node = this._frameNodes.get(frame);
if (!node) {
return;
}
if (node.parent) {
node.parent.removeChild(node);
}
this._frameNodes.delete(frame);
for (const child of frame.childFrames) {
this._discardFrame(child);
}
}
/**
* @override
* @param {!SDK.Target} target
*/
targetAdded(target) {
}
/**
* @override
* @param {!SDK.Target} target
*/
targetRemoved(target) {
const targetNode = this._rootNode.child('target:' + target.id());
if (targetNode) {
this._rootNode.removeChild(targetNode);
}
}
/**
* @param {!Common.Event} event
*/
_targetNameChanged(event) {
const target = /** @type {!SDK.Target} */ (event.data);
const targetNode = this._rootNode.child('target:' + target.id());
if (targetNode) {
targetNode.setTitle(target.name());
}
}
};
Sources.NavigatorView.Types = {
Domain: 'domain',
File: 'file',
FileSystem: 'fs',
FileSystemFolder: 'fs-folder',
Frame: 'frame',
NetworkFolder: 'nw-folder',
Root: 'root',
SourceMapFolder: 'sm-folder',
Worker: 'worker'
};
/**
* @unrestricted
*/
Sources.NavigatorFolderTreeElement = class extends UI.TreeElement {
/**
* @param {!Sources.NavigatorView} navigatorView
* @param {string} type
* @param {string} title
* @param {function(boolean)=} hoverCallback
*/
constructor(navigatorView, type, title, hoverCallback) {
super('', true);
this.listItemElement.classList.add('navigator-' + type + '-tree-item', 'navigator-folder-tree-item');
this._nodeType = type;
this.title = title;
this.tooltip = title;
this._navigatorView = navigatorView;
this._hoverCallback = hoverCallback;
let iconType = 'largeicon-navigator-folder';
if (type === Sources.NavigatorView.Types.Domain) {
iconType = 'largeicon-navigator-domain';
} else if (type === Sources.NavigatorView.Types.Frame) {
iconType = 'largeicon-navigator-frame';
} else if (type === Sources.NavigatorView.Types.Worker) {
iconType = 'largeicon-navigator-worker';
}
this.setLeadingIcons([UI.Icon.create(iconType, 'icon')]);
}
/**
* @override
* @returns {!Promise}
*/
async onpopulate() {
this._node.populate();
}
/**
* @override
*/
onattach() {
this.collapse();
this._node.onattach();
this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), false);
this.listItemElement.addEventListener('mousemove', this._mouseMove.bind(this), false);
this.listItemElement.addEventListener('mouseleave', this._mouseLeave.bind(this), false);
}
/**
* @param {!Sources.NavigatorTreeNode} node
*/
setNode(node) {
this._node = node;
const paths = [];
while (node && !node.isRoot()) {
paths.push(node._title);
node = node.parent;
}
paths.reverse();
this.tooltip = paths.join('/');
}
/**
* @param {!Event} event
*/
_handleContextMenuEvent(event) {
if (!this._node) {
return;
}
this.select();
this._navigatorView.handleFolderContextMenu(event, this._node);
}
/**
* @param {!Event} event
*/
_mouseMove(event) {
if (this._hovered || !this._hoverCallback) {
return;
}
this._hovered = true;
this._hoverCallback(true);
}
/**
* @param {!Event} event
*/
_mouseLeave(event) {
if (!this._hoverCallback) {
return;
}
this._hovered = false;
this._hoverCallback(false);
}
};
/**
* @unrestricted
*/
Sources.NavigatorSourceTreeElement = class extends UI.TreeElement {
/**
* @param {!Sources.NavigatorView} navigatorView
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {string} title
* @param {!Sources.NavigatorUISourceCodeTreeNode} node
*/
constructor(navigatorView, uiSourceCode, title, node) {
super('', false);
this._nodeType = Sources.NavigatorView.Types.File;
this._node = node;
this.title = title;
this.listItemElement.classList.add(
'navigator-' + uiSourceCode.contentType().name() + '-tree-item', 'navigator-file-tree-item');
this.tooltip = uiSourceCode.url();
this._navigatorView = navigatorView;
this._uiSourceCode = uiSourceCode;
this.updateIcon();
}
updateIcon() {
const binding = Persistence.persistence.binding(this._uiSourceCode);
if (binding) {
const container = createElementWithClass('span', 'icon-stack');
let iconType = 'largeicon-navigator-file-sync';
if (Snippets.isSnippetsUISourceCode(binding.fileSystem)) {
iconType = 'largeicon-navigator-snippet';
}
const icon = UI.Icon.create(iconType, 'icon');
const badge = UI.Icon.create('badge-navigator-file-sync', 'icon-badge');
// TODO(allada) This does not play well with dark theme. Add an actual icon and use it.
if (Persistence.networkPersistenceManager.project() === binding.fileSystem.project()) {
badge.style.filter = 'hue-rotate(160deg)';
}
container.appendChild(icon);
container.appendChild(badge);
container.title = Persistence.PersistenceUtils.tooltipForUISourceCode(this._uiSourceCode);
this.setLeadingIcons([container]);
} else {
let iconType = 'largeicon-navigator-file';
if (Snippets.isSnippetsUISourceCode(this._uiSourceCode)) {
iconType = 'largeicon-navigator-snippet';
}
const defaultIcon = UI.Icon.create(iconType, 'icon');
this.setLeadingIcons([defaultIcon]);
}
}
/**
* @return {!Workspace.UISourceCode}
*/
get uiSourceCode() {
return this._uiSourceCode;
}
/**
* @override
*/
onattach() {
this.listItemElement.draggable = true;
this.listItemElement.addEventListener('click', this._onclick.bind(this), false);
this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), false);
this.listItemElement.addEventListener('dragstart', this._ondragstart.bind(this), false);
}
_shouldRenameOnMouseDown() {
if (!this._uiSourceCode.canRename()) {
return false;
}
const isSelected = this === this.treeOutline.selectedTreeElement;
return isSelected && this.treeOutline.element.hasFocus() && !UI.isBeingEdited(this.treeOutline.element);
}
/**
* @override
*/
selectOnMouseDown(event) {
if (event.which !== 1 || !this._shouldRenameOnMouseDown()) {
super.selectOnMouseDown(event);
return;
}
setTimeout(rename.bind(this), 300);
/**
* @this {Sources.NavigatorSourceTreeElement}
*/
function rename() {
if (this._shouldRenameOnMouseDown()) {
this._navigatorView.rename(this._node, false);
}
}
}
/**
* @param {!DragEvent} event
*/
_ondragstart(event) {
event.dataTransfer.setData('text/plain', this._uiSourceCode.url());
event.dataTransfer.effectAllowed = 'copy';
}
/**
* @override
* @return {boolean}
*/
onspace() {
this._navigatorView._sourceSelected(this.uiSourceCode, true);
return true;
}
/**
* @param {!Event} event
*/
_onclick(event) {
this._navigatorView._sourceSelected(this.uiSourceCode, false);
}
/**
* @override
* @return {boolean}
*/
ondblclick(event) {
const middleClick = event.button === 1;
this._navigatorView._sourceSelected(this.uiSourceCode, !middleClick);
return false;
}
/**
* @override
* @return {boolean}
*/
onenter() {
this._navigatorView._sourceSelected(this.uiSourceCode, true);
return true;
}
/**
* @override
* @return {boolean}
*/
ondelete() {
return true;
}
/**
* @param {!Event} event
*/
_handleContextMenuEvent(event) {
this.select();
this._navigatorView.handleFileContextMenu(event, this._node);
}
};
/**
* @unrestricted
*/
Sources.NavigatorTreeNode = class {
/**
* @param {string} id
* @param {string} type
*/
constructor(id, type) {
this.id = id;
this._type = type;
/** @type {!Map.<string, !Sources.NavigatorTreeNode>} */
this._children = new Map();
}
/**
* @return {!UI.TreeElement}
*/
treeNode() {
throw 'Not implemented';
}
dispose() {
}
/**
* @return {boolean}
*/
isRoot() {
return false;
}
/**
* @return {boolean}
*/
hasChildren() {
return true;
}
onattach() {
}
/**
* @param {string} title
*/
setTitle(title) {
throw 'Not implemented';
}
populate() {
if (this.isPopulated()) {
return;
}
if (this.parent) {
this.parent.populate();
}
this._populated = true;
this.wasPopulated();
}
wasPopulated() {
const children = this.children();
for (let i = 0; i < children.length; ++i) {
this.treeNode().appendChild(/** @type {!UI.TreeElement} */ (children[i].treeNode()));
}
}
/**
* @param {!Sources.NavigatorTreeNode} node
*/
didAddChild(node) {
if (this.isPopulated()) {
this.treeNode().appendChild(/** @type {!UI.TreeElement} */ (node.treeNode()));
}
}
/**
* @param {!Sources.NavigatorTreeNode} node
*/
willRemoveChild(node) {
if (this.isPopulated()) {
this.treeNode().removeChild(/** @type {!UI.TreeElement} */ (node.treeNode()));
}
}
/**
* @return {boolean}
*/
isPopulated() {
return this._populated;
}
/**
* @return {boolean}
*/
isEmpty() {
return !this._children.size;
}
/**
* @return {!Array.<!Sources.NavigatorTreeNode>}
*/
children() {
return this._children.valuesArray();
}
/**
* @param {string} id
* @return {?Sources.NavigatorTreeNode}
*/
child(id) {
return this._children.get(id) || null;
}
/**
* @param {!Sources.NavigatorTreeNode} node
*/
appendChild(node) {
this._children.set(node.id, node);
node.parent = this;
this.didAddChild(node);
}
/**
* @param {!Sources.NavigatorTreeNode} node
*/
removeChild(node) {
this.willRemoveChild(node);
this._children.remove(node.id);
delete node.parent;
node.dispose();
}
reset() {
this._children.clear();
}
};
/**
* @unrestricted
*/
Sources.NavigatorRootTreeNode = class extends Sources.NavigatorTreeNode {
/**
* @param {!Sources.NavigatorView} navigatorView
*/
constructor(navigatorView) {
super('', Sources.NavigatorView.Types.Root);
this._navigatorView = navigatorView;
}
/**
* @override
* @return {boolean}
*/
isRoot() {
return true;
}
/**
* @override
* @return {!UI.TreeElement}
*/
treeNode() {
return this._navigatorView._scriptsTree.rootElement();
}
};
/**
* @unrestricted
*/
Sources.NavigatorUISourceCodeTreeNode = class extends Sources.NavigatorTreeNode {
/**
* @param {!Sources.NavigatorView} navigatorView
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {?SDK.ResourceTreeFrame} frame
*/
constructor(navigatorView, uiSourceCode, frame) {
super(uiSourceCode.project().id() + ':' + uiSourceCode.url(), Sources.NavigatorView.Types.File);
this._navigatorView = navigatorView;
this._uiSourceCode = uiSourceCode;
this._treeElement = null;
this._eventListeners = [];
this._frame = frame;
}
/**
* @return {?SDK.ResourceTreeFrame}
*/
frame() {
return this._frame;
}
/**
* @return {!Workspace.UISourceCode}
*/
uiSourceCode() {
return this._uiSourceCode;
}
/**
* @override
* @return {!UI.TreeElement}
*/
treeNode() {
if (this._treeElement) {
return this._treeElement;
}
this._treeElement = new Sources.NavigatorSourceTreeElement(this._navigatorView, this._uiSourceCode, '', this);
this.updateTitle();
const updateTitleBound = this.updateTitle.bind(this, undefined);
this._eventListeners = [
this._uiSourceCode.addEventListener(Workspace.UISourceCode.Events.TitleChanged, updateTitleBound),
this._uiSourceCode.addEventListener(Workspace.UISourceCode.Events.WorkingCopyChanged, updateTitleBound),
this._uiSourceCode.addEventListener(Workspace.UISourceCode.Events.WorkingCopyCommitted, updateTitleBound)
];
return this._treeElement;
}
/**
* @param {boolean=} ignoreIsDirty
*/
updateTitle(ignoreIsDirty) {
if (!this._treeElement) {
return;
}
let titleText = this._uiSourceCode.displayName();
if (!ignoreIsDirty && this._uiSourceCode.isDirty()) {
titleText = '*' + titleText;
}
this._treeElement.title = titleText;
this._treeElement.updateIcon();
let tooltip = this._uiSourceCode.url();
if (this._uiSourceCode.contentType().isFromSourceMap()) {
tooltip = Common.UIString('%s (from source map)', this._uiSourceCode.displayName());
}
this._treeElement.tooltip = tooltip;
}
/**
* @override
* @return {boolean}
*/
hasChildren() {
return false;
}
/**
* @override
*/
dispose() {
Common.EventTarget.removeEventListeners(this._eventListeners);
}
/**
* @param {boolean=} select
*/
reveal(select) {
this.parent.populate();
this.parent.treeNode().expand();
this._treeElement.reveal(true);
if (select) {
this._treeElement.select(true);
}
}
/**
* @param {function(boolean)=} callback
*/
rename(callback) {
if (!this._treeElement) {
return;
}
this._treeElement.listItemElement.focus();
// Tree outline should be marked as edited as well as the tree element to prevent search from starting.
const treeOutlineElement = this._treeElement.treeOutline.element;
UI.markBeingEdited(treeOutlineElement, true);
/**
* @param {!Element} element
* @param {string} newTitle
* @param {string} oldTitle
* @this {Sources.NavigatorUISourceCodeTreeNode}
*/
function commitHandler(element, newTitle, oldTitle) {
if (newTitle !== oldTitle) {
this._treeElement.title = newTitle;
this._uiSourceCode.rename(newTitle).then(renameCallback.bind(this));
return;
}
afterEditing.call(this, true);
}
/**
* @param {boolean} success
* @this {Sources.NavigatorUISourceCodeTreeNode}
*/
function renameCallback(success) {
if (!success) {
UI.markBeingEdited(treeOutlineElement, false);
this.updateTitle();
this.rename(callback);
return;
}
afterEditing.call(this, true);
}
/**
* @param {boolean} committed
* @this {Sources.NavigatorUISourceCodeTreeNode}
*/
function afterEditing(committed) {
UI.markBeingEdited(treeOutlineElement, false);
this.updateTitle();
if (callback) {
callback(committed);
}
}
this.updateTitle(true);
this._treeElement.startEditingTitle(
new UI.InplaceEditor.Config(commitHandler.bind(this), afterEditing.bind(this, false)));
}
};
/**
* @unrestricted
*/
Sources.NavigatorFolderTreeNode = class extends Sources.NavigatorTreeNode {
/**
* @param {!Sources.NavigatorView} navigatorView
* @param {?Workspace.Project} project
* @param {string} id
* @param {string} type
* @param {string} folderPath
* @param {string} title
*/
constructor(navigatorView, project, id, type, folderPath, title) {
super(id, type);
this._navigatorView = navigatorView;
this._project = project;
this._folderPath = folderPath;
this._title = title;
}
/**
* @override
* @return {!UI.TreeElement}
*/
treeNode() {
if (this._treeElement) {
return this._treeElement;
}
this._treeElement = this._createTreeElement(this._title, this);
this.updateTitle();
return this._treeElement;
}
updateTitle() {
if (!this._treeElement || this._project.type() !== Workspace.projectTypes.FileSystem) {
return;
}
const absoluteFileSystemPath =
Persistence.FileSystemWorkspaceBinding.fileSystemPath(this._project.id()) + '/' + this._folderPath;
const hasMappedFiles = Persistence.persistence.filePathHasBindings(absoluteFileSystemPath);
this._treeElement.listItemElement.classList.toggle('has-mapped-files', hasMappedFiles);
}
/**
* @return {!UI.TreeElement}
*/
_createTreeElement(title, node) {
if (this._project.type() !== Workspace.projectTypes.FileSystem) {
try {
title = decodeURI(title);
} catch (e) {
}
}
const treeElement = new Sources.NavigatorFolderTreeElement(this._navigatorView, this._type, title);
treeElement.setNode(node);
return treeElement;
}
/**
* @override
*/
wasPopulated() {
if (!this._treeElement || this._treeElement._node !== this) {
return;
}
this._addChildrenRecursive();
}
_addChildrenRecursive() {
const children = this.children();
for (let i = 0; i < children.length; ++i) {
const child = children[i];
this.didAddChild(child);
if (child instanceof Sources.NavigatorFolderTreeNode) {
child._addChildrenRecursive();
}
}
}
_shouldMerge(node) {
return this._type !== Sources.NavigatorView.Types.Domain && node instanceof Sources.NavigatorFolderTreeNode;
}
/**
* @param {!Sources.NavigatorTreeNode} node
* @override
*/
didAddChild(node) {
function titleForNode(node) {
return node._title;
}
if (!this._treeElement) {
return;
}
let children = this.children();
if (children.length === 1 && this._shouldMerge(node)) {
node._isMerged = true;
this._treeElement.title = this._treeElement.title + '/' + node._title;
node._treeElement = this._treeElement;
this._treeElement.setNode(node);
return;
}
let oldNode;
if (children.length === 2) {
oldNode = children[0] !== node ? children[0] : children[1];
}
if (oldNode && oldNode._isMerged) {
delete oldNode._isMerged;
const mergedToNodes = [];
mergedToNodes.push(this);
let treeNode = this;
while (treeNode._isMerged) {
treeNode = treeNode.parent;
mergedToNodes.push(treeNode);
}
mergedToNodes.reverse();
const titleText = mergedToNodes.map(titleForNode).join('/');
const nodes = [];
treeNode = oldNode;
do {
nodes.push(treeNode);
children = treeNode.children();
treeNode = children.length === 1 ? children[0] : null;
} while (treeNode && treeNode._isMerged);
if (!this.isPopulated()) {
this._treeElement.title = titleText;
this._treeElement.setNode(this);
for (let i = 0; i < nodes.length; ++i) {
delete nodes[i]._treeElement;
delete nodes[i]._isMerged;
}
return;
}
const oldTreeElement = this._treeElement;
const treeElement = this._createTreeElement(titleText, this);
for (let i = 0; i < mergedToNodes.length; ++i) {
mergedToNodes[i]._treeElement = treeElement;
}
oldTreeElement.parent.appendChild(treeElement);
oldTreeElement.setNode(nodes[nodes.length - 1]);
oldTreeElement.title = nodes.map(titleForNode).join('/');
oldTreeElement.parent.removeChild(oldTreeElement);
this._treeElement.appendChild(oldTreeElement);
if (oldTreeElement.expanded) {
treeElement.expand();
}
}
if (this.isPopulated()) {
this._treeElement.appendChild(node.treeNode());
}
}
/**
* @override
* @param {!Sources.NavigatorTreeNode} node
*/
willRemoveChild(node) {
if (node._isMerged || !this.isPopulated()) {
return;
}
this._treeElement.removeChild(node._treeElement);
}
};
/**
* @unrestricted
*/
Sources.NavigatorGroupTreeNode = class extends Sources.NavigatorTreeNode {
/**
* @param {!Sources.NavigatorView} navigatorView
* @param {!Workspace.Project} project
* @param {string} id
* @param {string} type
* @param {string} title
*/
constructor(navigatorView, project, id, type, title) {
super(id, type);
this._project = project;
this._navigatorView = navigatorView;
this._title = title;
this.populate();
}
/**
* @param {function(boolean)} hoverCallback
*/
setHoverCallback(hoverCallback) {
this._hoverCallback = hoverCallback;
}
/**
* @override
* @return {!UI.TreeElement}
*/
treeNode() {
if (this._treeElement) {
return this._treeElement;
}
this._treeElement =
new Sources.NavigatorFolderTreeElement(this._navigatorView, this._type, this._title, this._hoverCallback);
this._treeElement.setNode(this);
return this._treeElement;
}
/**
* @override
*/
onattach() {
this.updateTitle();
}
updateTitle() {
if (!this._treeElement || this._project.type() !== Workspace.projectTypes.FileSystem) {
return;
}
const fileSystemPath = Persistence.FileSystemWorkspaceBinding.fileSystemPath(this._project.id());
const wasActive = this._treeElement.listItemElement.classList.contains('has-mapped-files');
const isActive = Persistence.persistence.filePathHasBindings(fileSystemPath);
if (wasActive === isActive) {
return;
}
this._treeElement.listItemElement.classList.toggle('has-mapped-files', isActive);
if (this._treeElement.childrenListElement.hasFocus()) {
return;
}
if (isActive) {
this._treeElement.expand();
} else {
this._treeElement.collapse();
}
}
/**
* @param {string} title
* @override
*/
setTitle(title) {
this._title = title;
if (this._treeElement) {
this._treeElement.title = this._title;
}
}
};