Skip to main content
Glama
nrwl

Nx MCP Server

Official
by nrwl
new-project-details-preview.ts10.3 kB
import { handleGraphInteractionEventBase } from '@nx-console/vscode-graph-base'; import { onWorkspaceRefreshed } from '@nx-console/vscode-lsp-client'; import { getPDVData, getProjectByPath } from '@nx-console/vscode-nx-workspace'; import { getGraphWebviewManager } from '@nx-console/vscode-project-graph'; import { join } from 'path'; import { commands, ExtensionContext, Uri, ViewColumn, WebviewPanel, window, } from 'vscode'; import { createActor, fromPromise } from 'xstate'; import { machine } from './pdv-state-machine'; import { ProjectDetailsPreview } from './project-details-preview'; export class NewProjectDetailsPreview implements ProjectDetailsPreview { private webviewPanel: WebviewPanel; projectRoot: string | undefined; constructor( private path: string, private context: ExtensionContext, ) { this.webviewPanel = window.createWebviewPanel( 'nx-console-project-details', `Project Details`, ViewColumn.Beside, { enableScripts: true, }, ); const actor = createActor( machine.provide({ actors: { loadPDVData: fromPromise(async () => { return await getPDVData(path); }), }, actions: { renderLoading: () => this.renderLoading(), renderPDV: ({ context }) => this.renderPDV(context.pdvDataSerialized, context.graphBasePath), reRenderPDV: (_: unknown, params: { pdvData: string | undefined }) => this.reRenderPDV(params.pdvData), renderError: ({ context }) => this.renderError( context.errorsSerialized, context.errorMessage, context.graphBasePath, ), renderMultiPDV: ({ context }) => this.renderMultiPDV( context.pdvDataSerializedMulti, context.multiSelectedProject, context.graphBasePath, ), renderNoGraphError: ({ context }) => this.renderNoGraphError(context.errorMessage), }, }), ); const interactionEventListener = this.webviewPanel.webview.onDidReceiveMessage(async (event) => { if (event.type === 'projectSelected') { actor.send({ type: 'MULTI_PROJECT_SELECTED', project: event.project, }); return; } const handled = await handleGraphInteractionEventBase(event); if (handled) return; if (event.type === 'open-project-graph') { getGraphWebviewManager().focusProject(event.payload.projectName); return; } if (event.type === 'open-task-graph') { getGraphWebviewManager().focusTarget( event.payload.projectName, event.payload.targetName, ); return; } }); actor.start(); const workspaceRefreshListener = onWorkspaceRefreshed(() => { actor.send({ type: 'REFRESH' }); }); const viewStateListener = this.webviewPanel.onDidChangeViewState( ({ webviewPanel }) => { commands.executeCommand( 'setContext', 'projectDetailsViewVisible', webviewPanel.visible, ); }, ); this.webviewPanel.onDidDispose(() => { interactionEventListener.dispose(); viewStateListener.dispose(); workspaceRefreshListener?.dispose(); commands.executeCommand('setContext', 'projectDetailsViewVisible', false); }); // asynchronously load the project to get the root // this is used to match existing previews // even if they come from a different file in the same project getProjectByPath(path).then((project) => { this.projectRoot = project?.root; }); } private renderLoading() { this.webviewPanel.webview.html = `<html><body>Loading...</body></html>`; } private renderPDV( pdvData: string | undefined, graphBasePath: string | undefined, ) { if (pdvData === undefined || graphBasePath === undefined) { return; } let html = this.loadPDVHtmlBase(graphBasePath); html = html.replace( '</body>', /*html*/ ` <script> const data = ${pdvData} const pdvService = window.renderPDV(data) const vscode = acquireVsCodeApi() window.externalApi.graphInteractionEventListener = (message) => { vscode.postMessage(message); } // messages from the extension window.addEventListener('message', event => { const message = event.data; if(message.type === 'reload') { pdvService.send({ type: 'loadData', ...message.data, }); } }); </script> </body>`, ); this.webviewPanel.webview.html = html; } private reRenderPDV(pdvData: string | undefined) { if (pdvData === undefined) { return; } this.webviewPanel.webview.postMessage({ type: 'reload', data: JSON.parse(pdvData), }); } private renderError( errorsSerialized: string | undefined, errorMessage: string | undefined, graphBasePath: string | undefined, ) { if (errorsSerialized === undefined || graphBasePath === undefined) { return; } let html = this.loadPDVHtmlBase(graphBasePath); html = html.replace( '</body>', ` <script> const service = window.renderError({ message: "${errorMessage ?? ''}", errors: ${errorsSerialized} } ) </script> </body>`, ); this.webviewPanel.webview.html = html; } private renderMultiPDV( data: Record<string, string> | undefined, selectedProject: string | undefined, graphBasePath: string | undefined, ) { if ( data === undefined || graphBasePath === undefined || selectedProject === undefined ) { return; } const projects = Object.keys(data); const stringifiedData = JSON.stringify( Object.entries(data).reduce( (acc, [key, value]) => ({ ...acc, [key]: JSON.parse(value) }), {}, ), ); let html = this.loadPDVHtmlBase(graphBasePath); html = html.replace( '</head>', ` <script src="${this.webviewPanel.webview .asWebviewUri( Uri.joinPath( this.context.extensionUri, 'node_modules/@vscode-elements/elements/dist/bundled.js', ), ) .toString()}" type="module" ></script> </head> `, ); html = html.replace( '<body>', ` <body> <div style="display: flex; align-items: center; gap: 1rem; margin-top: 0.5rem;"> <p> Project: </p> <vscode-single-select id="project-select"> ${[ selectedProject, ...projects.filter((p) => p !== selectedProject), ].map((p) => `<vscode-option value="${p}">${p}</vscode-option>`)} </vscode-single-select> </div> <vscode-divider></vscode-divider> `, ); html = html.replace( '</body>', /*html*/ ` <script> const vscode = acquireVsCodeApi(); window.externalApi.graphInteractionEventListener = (message) => { vscode.postMessage(message); } const selectBox = document.getElementById('project-select') selectBox.addEventListener('change', (event) => { const selectedValue = event.target.value; vscode.postMessage({ type: 'projectSelected', project: selectedValue }) }); window.__pdvData = ${stringifiedData} const pdvService = window.renderPDV(window.__pdvData['${selectedProject}']) window.addEventListener('message', event => { const message = event.data; if(message.type === 'reload') { pdvService.send({ type: 'loadData', ...message.data, }); } }); </script> </body> `, ); this.webviewPanel.webview.html = html; } private renderNoGraphError(error?: string) { this.webviewPanel.webview.html = `<html><body> <h2>Nx Console could not load the Project Details View. </h2> <h4> This is most likely because local dependencies are not installed. <br/> Make sure to run npm/yarn/pnpm/bun install and refresh the workspace using the button in the editor toolbar. </h4> ${ error ? ` <h4> The following error occurred: <br/> <pre>${error}</pre> See idea.log for more details. </h4> ` : '' } </body></html>`; } onDispose(callback: () => void): void { this.webviewPanel.onDidDispose(() => { callback(); }); } reveal(column?: ViewColumn): void { this.webviewPanel.reveal(column); } private loadPDVHtmlBase(graphBasePath: string): string { const asWebviewUri = (path: string) => this.webviewPanel.webview .asWebviewUri(Uri.file(join(graphBasePath, path))) .toString(); return `<html> <head> <script src="${asWebviewUri('environment.js')}"></script> <link rel="stylesheet" href="${asWebviewUri('styles.css')}"> <style> body { background-color: var(--vscode-editor-background) !important; color: var(--vscode-editor-foreground) !important; } html { font-size: var(--vscode-font-size) !important; } </style> </head> <body> <script> window.__NX_RENDER_GRAPH__ = false; window.environment = "nx-console"; </script> <div style="padding: 0.5rem 0.5rem 0.5rem 0.5rem" id="app"></div> <script src="${asWebviewUri('runtime.js')}"></script> <script src="${asWebviewUri('styles.js')}"></script> <script src="${asWebviewUri('main.js')}"></script> </body> </html>`; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nrwl/nx-console'

If you have feedback or need assistance with the MCP directory API, please join our Discord server