@@ -2,7 +2,10 @@ import {
22 getNxGraphServer ,
33 handleGraphInteractionEventBase ,
44 loadGraphBaseHtml ,
5+ loadGraphErrorHtml ,
56} from '@nx-console/vscode/graph-base' ;
7+ import { onWorkspaceRefreshed } from '@nx-console/vscode/lsp-client' ;
8+ import { getNxWorkspace } from '@nx-console/vscode/nx-workspace' ;
69import {
710 commands ,
811 ExtensionContext ,
@@ -16,11 +19,21 @@ export class GraphWebviewManager implements Disposable {
1619 private webviewPanel : WebviewPanel | undefined ;
1720 private currentPanelIsAffected = false ;
1821
19- constructor ( private context : ExtensionContext ) { }
22+ private lastGraphCommand : GraphCommand | undefined ;
23+
24+ constructor ( private context : ExtensionContext ) {
25+ onWorkspaceRefreshed ( ( ) => {
26+ if ( this . webviewPanel ) {
27+ this . loadGraphWebview ( this . currentPanelIsAffected ) ;
28+ this . rerunLastGraphCommand ( ) ;
29+ }
30+ } ) ;
31+ }
2032
2133 async showAllProjects ( ) {
2234 await this . openGraphWebview ( ) ;
2335 this . webviewPanel ?. webview . postMessage ( { type : 'show-all' } ) ;
36+ this . lastGraphCommand = { type : 'show-all' } ;
2437 }
2538
2639 async focusProject ( projectName : string ) {
@@ -29,6 +42,7 @@ export class GraphWebviewManager implements Disposable {
2942 type : 'focus-project' ,
3043 payload : { projectName } ,
3144 } ) ;
45+ this . lastGraphCommand = { type : 'focus-project' , projectName } ;
3246 }
3347
3448 async selectProject ( projectName : string ) {
@@ -37,6 +51,7 @@ export class GraphWebviewManager implements Disposable {
3751 type : 'select-project' ,
3852 payload : { projectName } ,
3953 } ) ;
54+ this . lastGraphCommand = { type : 'select-project' , projectName } ;
4055 }
4156
4257 async focusTarget ( projectName : string , targetName : string ) {
@@ -45,6 +60,7 @@ export class GraphWebviewManager implements Disposable {
4560 type : 'focus-target' ,
4661 payload : { projectName, targetName } ,
4762 } ) ;
63+ this . lastGraphCommand = { type : 'focus-target' , projectName, targetName } ;
4864 }
4965
5066 async showAllTargetsByName ( targetName : string ) {
@@ -53,11 +69,13 @@ export class GraphWebviewManager implements Disposable {
5369 type : 'show-all-targets-by-name' ,
5470 payload : { targetName } ,
5571 } ) ;
72+ this . lastGraphCommand = { type : 'show-all-targets-by-name' , targetName } ;
5673 }
5774
5875 async showAffectedProjects ( ) {
5976 await this . openGraphWebview ( true ) ;
6077 this . webviewPanel ?. webview . postMessage ( { type : 'show-affected-projects' } ) ;
78+ this . lastGraphCommand = { type : 'show-affected-projects' } ;
6179 }
6280
6381 async openGraphWebview ( affected = false ) {
@@ -70,59 +88,94 @@ export class GraphWebviewManager implements Disposable {
7088 this . webviewPanel . reveal ( ) ;
7189 return ;
7290 }
91+
92+ this . currentPanelIsAffected = affected ;
93+
94+ await this . loadGraphWebview ( affected ) ;
95+
96+ this . webviewPanel ?. reveal ( ) ;
97+ }
98+
99+ private async loadGraphWebview ( affected : boolean ) {
73100 const graphServer = getNxGraphServer ( this . context , affected ) ;
74101
102+ const viewColumn = this . webviewPanel ?. viewColumn || ViewColumn . Active ;
103+
75104 this . webviewPanel ?. dispose ( ) ;
105+
76106 this . webviewPanel = window . createWebviewPanel (
77107 'graph' ,
78108 `Nx Graph` ,
79- ViewColumn . Active ,
109+ viewColumn ,
80110 {
81111 enableScripts : true ,
82112 retainContextWhenHidden : true ,
83113 }
84114 ) ;
85- this . currentPanelIsAffected = affected ;
86115
87- let html = await loadGraphBaseHtml ( this . webviewPanel . webview ) ;
88-
89- html = html . replace (
90- '</head>' ,
91- /*html*/ `
92- <script>
93- window.addEventListener('message', async ({ data }) => {
94- await window.waitForRouter()
95-
96- const { type, payload } = data;
97- if(type === 'show-all') {
98- window.externalApi.selectAllProjects()
99- } else if (type === 'focus-project') {
100- window.externalApi.focusProject(payload.projectName);
101- } else if(type === 'select-project') {
102- window.externalApi.toggleSelectProject(payload.projectName);
103- } else if(type === 'focus-target') {
104- window.externalApi.focusTarget(payload.projectName, payload.targetName);
105- } else if(type === 'show-all-targets-by-name') {
106- window.externalApi.selectAllTargetsByName(payload.targetName)
107- } else if(type === 'show-affected-projects') {
108- window.externalApi.showAffectedProjects()
109- }
110- });
111- </script>
112- </head>
113- `
114- ) ;
116+ const nxWorkspace = await getNxWorkspace ( ) ;
117+ const workspaceErrors = nxWorkspace ?. errors ;
118+ const isPartial = nxWorkspace ?. isPartial ;
119+ const hasProjects =
120+ Object . keys ( nxWorkspace ?. workspace . projects ?? { } ) . length > 0 ;
121+ const hasProject =
122+ this . lastGraphCommand &&
123+ isCommandWithProjectName ( this . lastGraphCommand ) &&
124+ nxWorkspace ?. workspace . projects [ this . lastGraphCommand . projectName ] !==
125+ undefined ;
126+
127+ let html : string ;
128+ if (
129+ workspaceErrors &&
130+ ( ! isPartial ||
131+ ! hasProjects ||
132+ ! hasProject ||
133+ nxWorkspace . nxVersion . major < 19 )
134+ ) {
135+ html = loadGraphErrorHtml ( workspaceErrors ) ;
136+ } else {
137+ html = await loadGraphBaseHtml ( this . webviewPanel . webview ) ;
138+
139+ html = html . replace (
140+ '</head>' ,
141+ /*html*/ `
142+ <script>
143+ window.addEventListener('message', async ({ data }) => {
144+ await window.waitForRouter()
145+
146+ const { type, payload } = data;
147+ if(type === 'show-all') {
148+ window.externalApi.selectAllProjects()
149+ } else if (type === 'focus-project') {
150+ window.externalApi.focusProject(payload.projectName);
151+ } else if(type === 'select-project') {
152+ window.externalApi.toggleSelectProject(payload.projectName);
153+ } else if(type === 'focus-target') {
154+ window.externalApi.focusTarget(payload.projectName, payload.targetName);
155+ } else if(type === 'show-all-targets-by-name') {
156+ window.externalApi.selectAllTargetsByName(payload.targetName)
157+ } else if(type === 'show-affected-projects') {
158+ window.externalApi.showAffectedProjects()
159+ }
160+ });
161+ </script>
162+ </head>
163+ `
164+ ) ;
165+ }
115166
116167 this . webviewPanel . webview . html = html ;
117- this . webviewPanel . webview . onDidReceiveMessage ( async ( event ) => {
118- const handled = await handleGraphInteractionEventBase ( event ) ;
119- if ( handled ) return ;
120- if ( event . type . startsWith ( 'request' ) ) {
121- const response = await graphServer . handleWebviewRequest ( event ) ;
122- this . webviewPanel ?. webview . postMessage ( response ) ;
123- return ;
168+ const messageListener = this . webviewPanel . webview . onDidReceiveMessage (
169+ async ( event ) => {
170+ const handled = await handleGraphInteractionEventBase ( event ) ;
171+ if ( handled ) return ;
172+ if ( event . type . startsWith ( 'request' ) ) {
173+ const response = await graphServer . handleWebviewRequest ( event ) ;
174+ this . webviewPanel ?. webview . postMessage ( response ) ;
175+ return ;
176+ }
124177 }
125- } ) ;
178+ ) ;
126179
127180 const viewStateListener = this . webviewPanel . onDidChangeViewState (
128181 ( { webviewPanel } ) => {
@@ -137,15 +190,84 @@ export class GraphWebviewManager implements Disposable {
137190 this . webviewPanel . onDidDispose ( ( ) => {
138191 commands . executeCommand ( 'setContext' , 'graphWebviewVisible' , false ) ;
139192 viewStateListener . dispose ( ) ;
193+ messageListener . dispose ( ) ;
140194
141195 this . webviewPanel = undefined ;
142- this . currentPanelIsAffected = false ;
143196 } ) ;
197+ }
198+
199+ private rerunLastGraphCommand ( ) {
200+ if ( ! this . lastGraphCommand ) return ;
144201
145- this . webviewPanel . reveal ( ) ;
202+ switch ( this . lastGraphCommand . type ) {
203+ case 'show-all' :
204+ this . showAllProjects ( ) ;
205+ break ;
206+ case 'focus-project' :
207+ this . focusProject ( this . lastGraphCommand . projectName ) ;
208+ break ;
209+ case 'select-project' :
210+ this . selectProject ( this . lastGraphCommand . projectName ) ;
211+ break ;
212+ case 'focus-target' :
213+ this . focusTarget (
214+ this . lastGraphCommand . projectName ,
215+ this . lastGraphCommand . targetName
216+ ) ;
217+ break ;
218+ case 'show-all-targets-by-name' :
219+ this . showAllTargetsByName ( this . lastGraphCommand . targetName ) ;
220+ break ;
221+ case 'show-affected-projects' :
222+ this . showAffectedProjects ( ) ;
223+ break ;
224+ }
146225 }
147226
148227 dispose ( ) {
149228 this . webviewPanel ?. dispose ( ) ;
150229 }
151230}
231+
232+ type GraphCommand =
233+ | ShowAllCommand
234+ | FocusProjectCommand
235+ | SelectProjectCommand
236+ | FocusTargetCommand
237+ | ShowAllTargetsByNameCommand
238+ | ShowAffectedProjectsCommand ;
239+
240+ interface ShowAllCommand {
241+ type : 'show-all' ;
242+ }
243+
244+ interface FocusProjectCommand {
245+ type : 'focus-project' ;
246+ projectName : string ;
247+ }
248+
249+ interface SelectProjectCommand {
250+ type : 'select-project' ;
251+ projectName : string ;
252+ }
253+
254+ interface FocusTargetCommand {
255+ type : 'focus-target' ;
256+ projectName : string ;
257+ targetName : string ;
258+ }
259+
260+ interface ShowAllTargetsByNameCommand {
261+ type : 'show-all-targets-by-name' ;
262+ targetName : string ;
263+ }
264+
265+ interface ShowAffectedProjectsCommand {
266+ type : 'show-affected-projects' ;
267+ }
268+
269+ function isCommandWithProjectName (
270+ command : GraphCommand
271+ ) : command is FocusProjectCommand | SelectProjectCommand {
272+ return command . type === 'focus-project' || command . type === 'select-project' ;
273+ }
0 commit comments