Skip to content

Commit

Permalink
feat: project tree view (#1390)
Browse files Browse the repository at this point in the history
Co-authored-by: Max Kless <[email protected]>
Co-authored-by: Jonathan Cammisuli <[email protected]>
  • Loading branch information
3 people authored Nov 1, 2022
1 parent 1d3ac22 commit ea9445d
Show file tree
Hide file tree
Showing 14 changed files with 1,414 additions and 157 deletions.
4 changes: 2 additions & 2 deletions apps/vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import {
NxCommandsTreeProvider,
} from '@nx-console/vscode/nx-commands-view';
import {
NxProjectTreeItem,
NxProjectTreeProvider,
NxTreeItem,
} from '@nx-console/vscode/nx-project-view';
import {
LOCATE_YOUR_WORKSPACE,
Expand Down Expand Up @@ -72,7 +72,7 @@ import {
} from '@nx-console/vscode/nx-workspace';

let runTargetTreeView: TreeView<RunTargetTreeItem>;
let nxProjectTreeView: TreeView<NxProjectTreeItem>;
let nxProjectTreeView: TreeView<NxTreeItem>;
let nxCommandsTreeView: TreeView<NxCommandsTreeItem>;
let nxHelpAndFeedbackTreeView: TreeView<NxHelpAndFeedbackTreeItem | TreeItem>;

Expand Down
13 changes: 11 additions & 2 deletions apps/vscode/src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,12 @@
"view/item/context": [
{
"command": "nxConsole.editWorkspaceJson",
"when": "view == nxProjects",
"when": "view == nxProjects && viewItem == project || viewItem == task",
"group": "inline"
},
{
"command": "nxConsole.revealInExplorer",
"when": "view == nxProjects && viewItem == project",
"when": "view == nxProjects && viewItem != task",
"group": "inline"
},
{
Expand Down Expand Up @@ -723,6 +723,15 @@
"default": true,
"description": "Enables the filter for listed generators with Nx Console."
},
"nxConsole.projectViewingStyle": {
"type": "string",
"default": "list",
"enum": [
"list",
"tree"
],
"description": "Define how the 'Projects' view shows the entries.\n\nlist: show the projects in an ordered list.\ntree: show the projects in the same folder structure as they are located in your repo."
},
"nxConsole.generatorAllowlist": {
"type": "array",
"default": [],
Expand Down
13 changes: 13 additions & 0 deletions libs/vscode/configuration/src/lib/configuration-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,21 @@ export const GLOBAL_CONFIG_KEYS = [
'generatorAllowlist',
'generatorBlocklist',
'enableTaskExecutionDryRunOnChange',
'projectViewingStyle',
] as const;

export type GlobalConfig = {
enableTelemetry: boolean;
enableGenerateFromContextMenu: boolean;
enableWorkspaceConfigCodeLens: boolean;
enableLibraryImports: boolean;
enableGeneratorFilters: boolean;
generatorAllowlist: string[];
generatorBlocklist: string[];
enableTaskExecutionDryRunOnChange: boolean;
projectViewingStyle: 'list' | 'tree';
};

/**
* configuration Keys used for NxConsole
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
Memento,
} from 'vscode';
import { Store } from '@nx-console/shared/schema';
import { GLOBAL_CONFIG_KEYS, GlobalConfigKeys } from './configuration-keys';
import {
GLOBAL_CONFIG_KEYS,
GlobalConfigKeys,
GlobalConfig,
} from './configuration-keys';

let CONFIG_STORE: GlobalConfigurationStore;

Expand All @@ -28,6 +32,8 @@ export class GlobalConfigurationStore implements Store {

private constructor(private readonly state: Memento) {}

get<T extends keyof GlobalConfig>(key: T): GlobalConfig[T] | null;
get<T>(key: GlobalConfigKeys, defaultValue?: T): T | null;
get<T>(key: GlobalConfigKeys, defaultValue?: T): T | null {
const value = this.storage(key).get(key, defaultValue);
return typeof value === 'undefined' ? defaultValue || null : value;
Expand Down
1 change: 0 additions & 1 deletion libs/vscode/nx-project-view/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './lib/nx-project-tree-provider';
export * from './lib/nx-project-tree-item';
20 changes: 0 additions & 20 deletions libs/vscode/nx-project-view/src/lib/nx-project-tree-item.ts

This file was deleted.

198 changes: 67 additions & 131 deletions libs/vscode/nx-project-view/src/lib/nx-project-tree-provider.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
import { GlobalConfigurationStore } from '@nx-console/vscode/configuration';
import { revealNxProject } from '@nx-console/vscode/nx-workspace';
import { CliTaskProvider } from '@nx-console/vscode/tasks';
import {
AbstractTreeProvider,
getOutputChannel,
} from '@nx-console/vscode/utils';
import { join } from 'path';
import { AbstractTreeProvider } from '@nx-console/vscode/utils';
import {
commands,
ExtensionContext,
ProviderResult,
TreeItem,
TreeItemCollapsibleState,
Uri,
} from 'vscode';
import { NxProject, NxProjectTreeItem } from './nx-project-tree-item';
import {
createListViewStrategy,
createTreeViewStrategy,
ListViewStrategy,
TreeViewStrategy,
} from './views';
import { ListViewItem, ViewItem } from './views/nx-project-base-view';

/**
* Provides data for the "Projects" tree view
*/
export class NxProjectTreeProvider extends AbstractTreeProvider<NxProjectTreeItem> {
export class NxProjectTreeProvider extends AbstractTreeProvider<NxTreeItem> {
private readonly listView: ListViewStrategy;
private readonly treeView: TreeViewStrategy;

constructor(
context: ExtensionContext,
private readonly cliTaskProvider: CliTaskProvider
Expand All @@ -29,140 +37,52 @@ export class NxProjectTreeProvider extends AbstractTreeProvider<NxProjectTreeIte
['revealInExplorer', this.revealInExplorer],
['runTask', this.runTask],
['refreshNxProjectsTree', this.refreshNxProjectsTree],
] as [string, (item: NxProjectTreeItem) => Promise<unknown>][]
] as const
).forEach(([commandSuffix, callback]) => {
context.subscriptions.push(
commands.registerCommand(`nxConsole.${commandSuffix}`, callback, this)
);
});
}

async getParent(
element: NxProjectTreeItem
): Promise<NxProjectTreeItem | null | undefined> {
const { project, target, root } = element.nxProject;

if (target) {
if (target.configuration) {
return this.createNxProjectTreeItem(
{ project, target: { name: target.name }, root },
target.name
);
} else {
return this.createNxProjectTreeItem({ project, root }, project);
}
} else {
return null;
}
this.listView = createListViewStrategy(this.cliTaskProvider);
this.treeView = createTreeViewStrategy(this.cliTaskProvider);
}

async createNxProjectTreeItem(
nxProject: NxProject,
treeItemLabel: string,
hasChildren?: boolean
) {
const item = new NxProjectTreeItem(
nxProject,
treeItemLabel,
hasChildren
? TreeItemCollapsibleState.Collapsed
: TreeItemCollapsibleState.None
);
if (!nxProject.target) {
const projectDef = (await this.cliTaskProvider.getProjects())[
nxProject.project
];
if (projectDef) {
if (projectDef.root === undefined) {
getOutputChannel().appendLine(
`Project ${nxProject.project} has no root. This could be because of an error loading the workspace configuration.`
);
}

item.resourceUri = Uri.file(
join(this.cliTaskProvider.getWorkspacePath(), projectDef.root ?? '')
);
}
item.contextValue = 'project';
} else {
item.contextValue = 'task';
}
getParent() {
// not implemented, because the reveal API is not needed for the projects view
return null;
}

return item;
getChildren(element?: NxTreeItem): ProviderResult<NxTreeItem[]> {
return this.getViewChildren(element?.item).then((items) => {
if (!items) return [];
return items.map((item) => new NxTreeItem(item));
});
}

async getChildren(
parent?: NxProjectTreeItem
): Promise<NxProjectTreeItem[] | undefined> {
if (!parent) {
const projects = await this.cliTaskProvider.getProjectEntries();
return Promise.all(
projects.map(
async ([name, def]): Promise<NxProjectTreeItem> =>
this.createNxProjectTreeItem(
{ project: name, root: def.root },
name,
Boolean(def.targets)
)
)
);
}

const { nxProject } = parent;
const { target, project } = nxProject;
const projectDef = (await this.cliTaskProvider.getProjects())[project];

if (!projectDef) {
return;
private async getViewChildren(viewItem?: ViewItem) {
if (this.isListViewElement(viewItem)) {
return this.listView.getChildren(viewItem);
}
return this.treeView.getChildren(viewItem);
}

if (!target) {
if (projectDef.targets) {
return Promise.all(
Object.keys(projectDef.targets).map(
async (name): Promise<NxProjectTreeItem> =>
this.createNxProjectTreeItem(
{ target: { name }, project, root: projectDef.root },
name,
Boolean(projectDef.targets?.[name].configurations)
)
)
);
}
} else {
const { configuration } = target;

if (configuration || !projectDef.targets) {
return;
}

const configurations = projectDef.targets
? projectDef.targets[target.name].configurations
: undefined;
if (!configurations) {
return;
}

return Promise.all(
Object.keys(configurations).map(async (name) =>
this.createNxProjectTreeItem(
{
target: { ...target, configuration: name },
project,
root: projectDef.root,
},
name
)
)
);
}
private isListViewElement(_?: ViewItem): _ is ListViewItem {
const config = GlobalConfigurationStore.instance.get('projectViewingStyle');
return config === 'list' || config === null;
}

private async runTask(selection: NxProjectTreeItem) {
const { target, project } = selection.nxProject;
if (!target) {
private async runTask(selection: NxTreeItem) {
const viewItem = selection.item;
if (
viewItem.contextValue === 'project' ||
viewItem.contextValue === 'folder'
) {
// can not run a task on a project
return;
}
const { project } = viewItem.nxProject;
const target = viewItem.nxTarget;

const flags = [];
if (target.configuration) {
Expand All @@ -176,21 +96,37 @@ export class NxProjectTreeProvider extends AbstractTreeProvider<NxProjectTreeIte
});
}

private async revealInExplorer(selection: NxProjectTreeItem) {
private async revealInExplorer(selection: NxTreeItem) {
if (selection.resourceUri) {
commands.executeCommand('revealInExplorer', selection.resourceUri);
}
}

private async editWorkspaceJson(selection: NxProjectTreeItem) {
return revealNxProject(
selection.nxProject.project,
selection.nxProject.root,
selection.nxProject.target
);
private async editWorkspaceJson(selection: NxTreeItem) {
const viewItem = selection.item;
if (viewItem.contextValue === 'folder') {
return;
}

const { project, root } = viewItem.nxProject;
if (viewItem.contextValue === 'project') {
return revealNxProject(project, root);
}
const target = viewItem.nxTarget;
return revealNxProject(project, root, target);
}

private async refreshNxProjectsTree() {
this.refresh();
}
}

export class NxTreeItem extends TreeItem {
constructor(public readonly item: ViewItem) {
super(item.label, TreeItemCollapsibleState[item.collapsible]);
this.contextValue = item.contextValue;
if (item.contextValue === 'folder' || item.contextValue === 'project') {
this.resourceUri = Uri.file(item.resource);
}
}
}
2 changes: 2 additions & 0 deletions libs/vscode/nx-project-view/src/lib/views/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './nx-project-list-view';
export * from './nx-project-tree-view';
Loading

0 comments on commit ea9445d

Please sign in to comment.