Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: project tree view #1390

Merged
merged 27 commits into from
Nov 1, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
69f6fdf
refactor: separate getChildren into sub functions
NicoVogel Oct 21, 2022
70ac7a9
refactor: move private function below public functions
NicoVogel Oct 21, 2022
e483700
refactor: separate project tree provider in preparation for tree view
NicoVogel Oct 21, 2022
975ca8a
feat: implement project tree view
NicoVogel Oct 21, 2022
925e476
refactor: make path handling platform independent
NicoVogel Oct 22, 2022
c1f0056
refactor: only show reveal in explorer for folders
NicoVogel Oct 22, 2022
5252b7d
refactor: move view code into view folder
NicoVogel Oct 22, 2022
cc9bcf9
refactor: separate general logic into utils
NicoVogel Oct 22, 2022
4b2bc99
feat: change enableProjectTreeView to projectViewingStyle
NicoVogel Oct 22, 2022
c823a59
refactor: fix linting
NicoVogel Oct 24, 2022
3815ba9
style: fix typos & add typing
MaxKless Oct 24, 2022
7ce8c77
refactor: remove not needed getParent functionality
NicoVogel Oct 25, 2022
7e2ef3b
refactor: remove not needed Promise.all calls
NicoVogel Oct 25, 2022
234415d
style: improve readability by renaming and splitting createFolders fu…
NicoVogel Oct 25, 2022
6b3fe9d
refactor: implement tests for path logic
NicoVogel Oct 25, 2022
f19727a
refactor: make PathHelper bullet proof
NicoVogel Oct 25, 2022
f22b477
fix: add missing return for createTargetFromProject
NicoVogel Oct 25, 2022
9943936
refactor: show at least folders if node_modules are not installed
NicoVogel Oct 25, 2022
d99eaf9
refactor: fix filename typo
MaxKless Oct 25, 2022
559c1f8
Revert "refactor: show at least folders if node_modules are not insta…
NicoVogel Oct 28, 2022
56685fe
fix: tree view for angular projects
NicoVogel Oct 28, 2022
0dbf2d1
refactor: add return type to detailedDirs
NicoVogel Oct 28, 2022
6a8359d
refactor: lint files
NicoVogel Oct 28, 2022
e2e1add
refactor: make code unit testable
NicoVogel Nov 1, 2022
c6956de
fix: add tests and fix angular projects without root value
NicoVogel Nov 1, 2022
ba16bf8
fix: show children of root placeholder
NicoVogel Nov 1, 2022
2ee8d50
Merge remote-tracking branch 'origin/master' into pr/NicoVogel/1390
Cammisuli Nov 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -69,7 +69,7 @@ import {
import { WorkspaceCodeLensProvider } 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 @@ -120,12 +120,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 @@ -722,6 +722,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
53 changes: 45 additions & 8 deletions libs/vscode/nx-project-view/src/lib/nx-project-tree-item.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,57 @@
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { join } from 'node:path';
import { TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';

export interface NxProject {
project: string;
root: string;
}

export interface NxTarget {
name: string;
configuration?: string;
}

export type NxListViewItem = NxProjectTreeItem | NxTargetTreeItem;
export type NxTreeViewItem =
| NxProjectTreeItem
| NxTargetTreeItem
| NxFolderTreeItem;
export type NxTreeItem = NxListViewItem | NxTreeViewItem;

export class NxFolderTreeItem extends TreeItem {
constructor(
public path: string,
workspacePath: string,
treeItemLabel: string,
collapsibleState?: TreeItemCollapsibleState | undefined
) {
super(treeItemLabel, collapsibleState);
this.resourceUri = Uri.file(join(workspacePath, path));
this.contextValue = 'folder';
}
}

export class NxProjectTreeItem extends TreeItem {
constructor(
public nxProject: NxProject,
workspacePath: string,
treeItemLabel: string,
collapsibleState?: TreeItemCollapsibleState | undefined
) {
super(treeItemLabel, collapsibleState);
this.resourceUri = Uri.file(join(workspacePath, nxProject.root ?? ''));
this.contextValue = 'project';
}
}

export interface NxProject {
project: string;
root: string;
target?: {
name: string;
configuration?: string;
};
export class NxTargetTreeItem extends TreeItem {
constructor(
public nxProject: NxProject,
public nxTarget: NxTarget,
treeItemLabel: string,
collapsibleState?: TreeItemCollapsibleState | undefined
) {
super(treeItemLabel, collapsibleState);
this.contextValue = 'task';
}
}
187 changes: 51 additions & 136 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,28 @@
import { GlobalConfigurationStore } from '@nx-console/vscode/configuration';
import { revealNxProject } from '@nx-console/vscode/nx-workspace';
import { CliTaskProvider } from '@nx-console/vscode/tasks';
import { AbstractTreeProvider } from '@nx-console/vscode/utils';
import { commands, ExtensionContext } from 'vscode';
import {
AbstractTreeProvider,
getOutputChannel,
} from '@nx-console/vscode/utils';
import { join } from 'path';
NxFolderTreeItem,
NxListViewItem,
NxProjectTreeItem,
NxTreeItem,
} from './nx-project-tree-item';
import {
commands,
ExtensionContext,
TreeItemCollapsibleState,
Uri,
} from 'vscode';
import { NxProject, NxProjectTreeItem } from './nx-project-tree-item';
createListViewStrategy,
createTreeViewStrategy,
ListViewStrategy,
TreeViewStrategy,
} from './views';

/**
* 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 +35,44 @@ 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';
}

return item;
getParent() {
// not implemented, because the reveal API is not needed for the projects view
return null;
}

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)
)
)
);
getChildren(element?: NxTreeItem) {
if (this.isListViewElement(element)) {
return this.listView.getChildren(element);
}
return this.treeView.getChildren(element);
}

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

if (!projectDef) {
return;
}

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(_?: NxTreeItem): _ is NxListViewItem {
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) {
if (
selection instanceof NxProjectTreeItem ||
selection instanceof NxFolderTreeItem
) {
// can not run a task on a project
return;
}
const { project } = selection.nxProject;
const target = selection.nxTarget;

const flags = [];
if (target.configuration) {
Expand All @@ -176,18 +86,23 @@ 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) {
if (selection instanceof NxFolderTreeItem) {
return;
}

const { project, root } = selection.nxProject;
if (selection instanceof NxProjectTreeItem) {
return revealNxProject(project, root);
}
const target = selection.nxTarget;
return revealNxProject(project, root, target);
}

private async refreshNxProjectsTree() {
Expand Down
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