@@ -9,121 +9,131 @@ import { Uri } from "vscode";
99import { EXTENSION_URI } from "../extension" ;
1010import { StepZenResponse } from "../types" ;
1111import { UI } from "../utils/constants" ;
12-
13- /** The singleton results panel instance */
14- let panel : vscode . WebviewPanel | undefined ;
12+ import { BaseWebviewPanel } from "./BaseWebviewPanel" ;
1513
1614/**
17- * Opens or reveals the results panel and displays the payload
18- *
19- * @param payload The GraphQL response data to display
15+ * Results panel implementation extending BaseWebviewPanel
16+ * Displays GraphQL response data with tabs for data, errors, debug, and trace
2017 */
21- export async function openResultsPanel ( payload : StepZenResponse ) {
22- const extensionUri = EXTENSION_URI ;
23- if ( ! panel ) {
24- panel = vscode . window . createWebviewPanel (
25- UI . RESULTS_PANEL_VIEW_TYPE ,
26- UI . RESULTS_PANEL_TITLE ,
27- vscode . ViewColumn . Beside ,
28- {
29- enableScripts : true ,
30- localResourceRoots : [ Uri . joinPath ( extensionUri , "webview" ) ] ,
31- }
32- ) ;
33- panel . onDidDispose ( ( ) => ( panel = undefined ) ) ;
18+ class ResultsPanel extends BaseWebviewPanel {
19+ private static instance : ResultsPanel | undefined ;
20+
21+ private constructor ( extensionUri : Uri ) {
22+ super ( extensionUri ) ;
3423 }
3524
36- panel . webview . html = getHtml ( panel . webview , extensionUri , payload ) ;
37- panel . reveal ( ) ;
38- }
25+ /**
26+ * Gets or creates the singleton results panel instance
27+ */
28+ public static getInstance ( ) : ResultsPanel {
29+ if ( ! ResultsPanel . instance ) {
30+ ResultsPanel . instance = new ResultsPanel ( EXTENSION_URI ) ;
31+ }
32+ return ResultsPanel . instance ;
33+ }
3934
40- /**
41- * Clears the results panel by disposing the webview panel
42- * Used when clearing results or when the extension is deactivated
43- */
44- export function clearResultsPanel ( ) : void {
45- if ( panel ) {
46- panel . dispose ( ) ;
47- panel = undefined ;
35+ /**
36+ * Opens or reveals the results panel and displays the payload
37+ */
38+ public async openWithPayload ( payload : StepZenResponse ) : Promise < void > {
39+ if ( ! this . panel ) {
40+ this . panel = this . createWebviewPanel (
41+ UI . RESULTS_PANEL_VIEW_TYPE ,
42+ UI . RESULTS_PANEL_TITLE ,
43+ vscode . ViewColumn . Beside
44+ ) ;
45+ }
46+
47+ this . panel . webview . html = this . generateHtml ( this . panel . webview , payload ) ;
48+ this . reveal ( ) ;
49+ }
50+
51+ /**
52+ * Clears the results panel by disposing it
53+ */
54+ public clear ( ) : void {
55+ this . dispose ( ) ;
56+ }
57+
58+ protected onDispose ( ) : void {
59+ super . onDispose ( ) ;
60+ ResultsPanel . instance = undefined ;
61+ }
62+
63+ protected generateHtml ( webview : vscode . Webview , payload : StepZenResponse ) : string {
64+ const nonce = this . nonce ( ) ;
65+ const payloadJs = JSON . stringify ( payload ) ;
66+ const hasErrors = Array . isArray ( payload ?. errors ) && payload . errors . length > 0 ;
67+
68+ return /* html */ `
69+ <!DOCTYPE html>
70+ <html lang="en">
71+ <head>
72+ <meta charset="UTF-8">
73+ <meta http-equiv="Content-Security-Policy" content="${ this . csp ( webview , nonce ) } ">
74+ <title>${ UI . RESULTS_PANEL_TITLE } </title>
75+
76+ <!-- Link to CSS file instead of inline styles -->
77+ <link rel="stylesheet" href="${ this . getWebviewUri ( webview , [ 'css' , 'results-panel.css' ] ) } ">
78+ </head>
79+ <body>
80+ <div class="tabs">
81+ <div class="tab active" data-id="data">Data</div>
82+ ${ hasErrors ? '<div class="tab" data-id="errors">Errors</div>' : '' }
83+ <div class="tab" data-id="debug">Debug</div>
84+ <div class="tab" data-id="trace">Trace View</div>
85+ </div>
86+
87+ <div id="pane-data" class="panel"></div>
88+ ${ hasErrors ? '<div id="pane-errors" class="panel" hidden></div>' : '' }
89+ <div id="pane-debug" class="panel" hidden></div>
90+ <div id="pane-trace" class="panel" hidden></div>
91+
92+ <!-- Load React libraries -->
93+ <script nonce="${ nonce } " src="${ this . getWebviewUri ( webview , [ 'libs' , 'react.production.min.js' ] ) } "></script>
94+ <script nonce="${ nonce } " src="${ this . getWebviewUri ( webview , [ 'libs' , 'react-dom.production.min.js' ] ) } "></script>
95+ <script nonce="${ nonce } " src="${ this . getWebviewUri ( webview , [ 'libs' , 'react-json-view.min.js' ] ) } "></script>
96+
97+ <!-- Load our custom scripts -->
98+ <script nonce="${ nonce } " src="${ this . getWebviewUri ( webview , [ 'js' , 'trace-viewer.js' ] ) } "></script>
99+ <script nonce="${ nonce } " src="${ this . getWebviewUri ( webview , [ 'js' , 'results-panel.js' ] ) } "></script>
100+
101+ <!-- Initialize the panel -->
102+ <script nonce="${ nonce } ">
103+ // Initialize when the DOM is ready
104+ document.addEventListener('DOMContentLoaded', () => {
105+ const payload = ${ payloadJs } ;
106+ window.ResultsPanel.initResultsPanel(payload);
107+ });
108+ </script>
109+ </body>
110+ </html>
111+ ` ;
48112 }
49113}
50114
51- /*─────────────────────────────────────────────────────────*/
115+ /** The singleton results panel instance */
116+ let resultsPanel : ResultsPanel | undefined ;
52117
53118/**
54- * Generates the HTML content for the results panel webview
119+ * Opens or reveals the results panel and displays the payload
55120 *
56- * @param webview The webview to generate HTML for
57- * @param extUri The extension URI for resource loading
58121 * @param payload The GraphQL response data to display
59- * @returns HTML string for the webview
60122 */
61- function getHtml (
62- webview : vscode . Webview ,
63- extUri : Uri ,
64- payload : StepZenResponse
65- ) : string {
66- // Helper to get webview URIs
67- const getUri = ( pathList : string [ ] ) => {
68- return webview . asWebviewUri ( Uri . joinPath ( extUri , "webview" , ...pathList ) ) ;
69- } ;
70-
71- const nonce = getNonce ( ) ;
72- const payloadJs = JSON . stringify ( payload ) ;
73- const hasErrors = Array . isArray ( payload ?. errors ) && payload . errors . length > 0 ;
74-
75- return /* html */ `
76- <!DOCTYPE html>
77- <html lang="en">
78- <head>
79- <meta charset="UTF-8">
80- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${ webview . cspSource } ; style-src ${ webview . cspSource } ; script-src 'nonce-${ nonce } ' ${ webview . cspSource } ;">
81- <title>${ UI . RESULTS_PANEL_TITLE } </title>
82-
83- <!-- Link to CSS file instead of inline styles -->
84- <link rel="stylesheet" href="${ getUri ( [ 'css' , 'results-panel.css' ] ) } ">
85- </head>
86- <body>
87- <div class="tabs">
88- <div class="tab active" data-id="data">Data</div>
89- ${ hasErrors ? '<div class="tab" data-id="errors">Errors</div>' : '' }
90- <div class="tab" data-id="debug">Debug</div>
91- <div class="tab" data-id="trace">Trace View</div>
92- </div>
93-
94- <div id="pane-data" class="panel"></div>
95- ${ hasErrors ? '<div id="pane-errors" class="panel" hidden></div>' : '' }
96- <div id="pane-debug" class="panel" hidden></div>
97- <div id="pane-trace" class="panel" hidden></div>
98-
99- <!-- Load React libraries -->
100- <script nonce="${ nonce } " src="${ getUri ( [ 'libs' , 'react.production.min.js' ] ) } "></script>
101- <script nonce="${ nonce } " src="${ getUri ( [ 'libs' , 'react-dom.production.min.js' ] ) } "></script>
102- <script nonce="${ nonce } " src="${ getUri ( [ 'libs' , 'react-json-view.min.js' ] ) } "></script>
103-
104- <!-- Load our custom scripts -->
105- <script nonce="${ nonce } " src="${ getUri ( [ 'js' , 'trace-viewer.js' ] ) } "></script>
106- <script nonce="${ nonce } " src="${ getUri ( [ 'js' , 'results-panel.js' ] ) } "></script>
107-
108- <!-- Initialize the panel -->
109- <script nonce="${ nonce } ">
110- // Initialize when the DOM is ready
111- document.addEventListener('DOMContentLoaded', () => {
112- const payload = ${ payloadJs } ;
113- window.ResultsPanel.initResultsPanel(payload);
114- });
115- </script>
116- </body>
117- </html>
118- ` ;
123+ export async function openResultsPanel ( payload : StepZenResponse ) {
124+ if ( ! resultsPanel ) {
125+ resultsPanel = ResultsPanel . getInstance ( ) ;
126+ }
127+ await resultsPanel . openWithPayload ( payload ) ;
119128}
120129
121130/**
122- * Generates a random nonce for Content Security Policy
123- * Used to secure inline scripts in the webview
124- *
125- * @returns A random string to use as a nonce
131+ * Clears the results panel by disposing the webview panel
132+ * Used when clearing results or when the extension is deactivated
126133 */
127- function getNonce ( ) {
128- return [ ...Array ( 16 ) ] . map ( ( ) => Math . random ( ) . toString ( 36 ) [ 2 ] ) . join ( "" ) ;
134+ export function clearResultsPanel ( ) : void {
135+ if ( resultsPanel ) {
136+ resultsPanel . clear ( ) ;
137+ resultsPanel = undefined ;
138+ }
129139}
0 commit comments