Expand Up @@ -6,152 +6,159 @@ import * as path from 'path' * Manages React webview panels */ class ReactWebView { //@ts -ignore private panel :vscode . WebviewPanel private extensionPath :string private disposables :vscode . Disposable [ ] = [ ] private onReceive :any // TODO: properly type public constructor ( extensionPath :string ) { this . extensionPath = extensionPath // Create and show a new webview panel this . panel = this . createWebviewPanel ( vscode . ViewColumn . Two ) // Set the webview's initial html content this . panel . webview . html = this . getHtmlForWebview ( ) // Listen for when the panel is disposed // This happens when the user closes the panel or when the panel is closed programatically this . panel . onDidDispose ( ( ) => this . dispose ( ) , null , this . disposables ) // Handle messages from the webview const onReceive = ( action :string | CR . Action ) => vscode . commands . executeCommand ( 'coderoad.receive_action' , action ) this . panel . webview . onDidReceiveMessage ( onReceive , null , this . disposables ) // update panel on changes const updateWindows = ( ) => { vscode . commands . executeCommand ( 'vscode.setEditorLayout' , { orientation :0 , groups :[ { groups :[ { } ] , size :0.6 } , { groups :[ { } ] , size :0.4 } ] } ) this . panel . reveal ( vscode . ViewColumn . Two ) } // prevents new panels from going ontop of coderoad panel vscode . window . onDidChangeActiveTextEditor ( ( param ) => { if ( ! param || param . viewColumn !== vscode . ViewColumn . Two ) { updateWindows ( ) } } ) // prevents moving coderoad panel on top of left panel vscode . window . onDidChangeVisibleTextEditors ( ( param ) => { updateWindows ( ) } ) // TODO: prevent window from moving to the left when no windows remain on rights //@ts -ignore private panel :vscode . WebviewPanel private extensionPath :string private disposables :vscode . Disposable [ ] = [ ] private onReceive :any // TODO: properly type public constructor ( extensionPath :string ) { this . extensionPath = extensionPath // Create and show a new webview panel this . panel = this . createWebviewPanel ( vscode . ViewColumn . Two ) // Set the webview's initial html content this . panel . webview . html = this . getHtmlForWebview ( ) // Listen for when the panel is disposed // This happens when the user closes the panel or when the panel is closed programatically // this.panel.onDidDispose(() => this.dispose(), null, this.disposables) // Handle messages from the webview const onReceive = ( action :string | CR . Action ) => vscode . commands . executeCommand ( 'coderoad.receive_action' , action ) this . panel . webview . onDidReceiveMessage ( onReceive , null , this . disposables ) // update panel on changes const updateWindows = ( ) => { vscode . commands . executeCommand ( 'vscode.setEditorLayout' , { orientation :0 , groups :[ { groups :[ { } ] , size :0.6 } , { groups :[ { } ] , size :0.4 } ] , } ) this . panel . reveal ( vscode . ViewColumn . Two ) } public createOrShow ( column :number ) :void { // If we already have a panel, show it. // Otherwise, create a new panel. if ( this . panel && this . panel . webview ) { console . log ( 'reveal' ) this . panel . reveal ( column ) } else { console . log ( 'make new panel' ) this . panel = this . createWebviewPanel ( column ) } this . panel . onDidDispose ( ( ) => { updateWindows ( ) } ) // this.panel.onDidChangeViewState(() => { // console.log('onDidChangeViewState') // updateWindows() // }) // prevents new panels from going ontop of coderoad panel vscode . window . onDidChangeActiveTextEditor ( param => { if ( ! param || param . viewColumn !== vscode . ViewColumn . Two ) { updateWindows ( ) } } ) // // prevents moving coderoad panel on top of left panel vscode . window . onDidChangeVisibleTextEditors ( param => { updateWindows ( ) } ) // TODO: prevent window from moving to the left when no windows remain on rights } public createOrShow ( column :number ) :void { // If we already have a panel, show it. // Otherwise, create a new panel. if ( this . panel && this . panel . webview ) { this . panel . reveal ( column ) } else { this . panel = this . createWebviewPanel ( column ) } private createWebviewPanel ( column :number ) :vscode . WebviewPanel { const viewType = 'CodeRoad' const title = 'CodeRoad' const config = { // Enable javascript in the webview enableScripts :true , // And restric the webview to only loading content from our extension's `media` directory. localResourceRoots :[ vscode . Uri . file ( path . join ( this . extensionPath , 'build' ) ) ] , // prevents destroying the window when it is in the background retainContextWhenHidden :true , } return vscode . window . createWebviewPanel ( viewType , title , column , config ) } private createWebviewPanel ( column :number ) :vscode . WebviewPanel { const viewType = 'CodeRoad' const title = 'CodeRoad' const config = { // Enable javascript in the webview enableScripts :true , // And restric the webview to only loading content from our extension's `media` directory. localResourceRoots :[ vscode . Uri . file ( path . join ( this . extensionPath , 'build' ) ) ] , // prevents destroying the window when it is in the background retainContextWhenHidden :true , } private getNonce ( ) : string { let text = '' const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' for ( let i = 0 ; i < 32 ; i ++ ) { text += possible . charAt ( Math . floor ( Math . random ( ) * possible . length ) ) } return text return vscode . window . createWebviewPanel ( viewType , title , column , config ) } private getNonce ( ) : string { let text = '' const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' for ( let i = 0 ; i < 32 ; i ++ ) { text += possible . charAt ( Math . floor ( Math . random ( ) * possible . length ) ) } public async postMessage ( action :CR . Action ) :Promise < void > { console . log ( 'webview postMessage' ) console . log ( action ) // Send a message to the webview webview. // You can send any JSON serializable data. const success = await this . panel . webview . postMessage ( action ) if ( ! success ) { throw new Error ( `Message post failure:${ JSON . stringify ( action ) } ` ) } console . log ( 'postMessage sent' ) return text } public async postMessage ( action :CR . Action ) :Promise < void > { // Send a message to the webview webview. // You can send any JSON serializable data. const success = await this . panel . webview . postMessage ( action ) if ( ! success ) { throw new Error ( `Message post failure:${ JSON . stringify ( action ) } ` ) } } public dispose ( ) :void { // Clean up our resources this . panel . dispose ( ) public dispose ( ) :void { // Clean up our resources this . panel . dispose ( ) while ( this . disposables . length ) { const x = this . disposables . pop ( ) if ( x ) { x . dispose ( ) } } while ( this . disposables . length ) { const x = this . disposables . pop ( ) if ( x ) { x . dispose ( ) } } private getHtmlForWebview ( ) :string { // eslint-disable-next-line const manifest = require ( path . join ( this . extensionPath , 'build' , 'asset-manifest.json' ) ) const mainScript = manifest . files [ 'main.js' ] // grab first chunk const chunk = Object . keys ( manifest . files ) . filter ( f => f . match ( / ^ s t a t i c \/ j s \/ .+ \. j s $ / ) ) [ 0 ] const chunkScript = manifest . files [ chunk ] const mainStyle = manifest . files [ 'main.css' ] const scriptPathOnDisk = vscode . Uri . file ( path . join ( this . extensionPath , 'build' , mainScript ) ) const scriptUri = scriptPathOnDisk . with ( { scheme :'vscode-resource' } ) const chunkPathOnDisk = vscode . Uri . file ( path . join ( this . extensionPath , 'build' , chunkScript ) ) const chunkUri = chunkPathOnDisk . with ( { scheme :'vscode-resource' } ) const stylePathOnDisk = vscode . Uri . file ( path . join ( this . extensionPath , 'build' , mainStyle ) ) const styleUri = stylePathOnDisk . with ( { scheme :'vscode-resource' } ) // Use a nonce to whitelist which scripts can be run const nonce = this . getNonce ( ) const nonce2 = this . getNonce ( ) const nonce3 = this . getNonce ( ) return `<!DOCTYPE html> } private getHtmlForWebview ( ) :string { // eslint-disable-next-line const manifest = require ( path . join ( this . extensionPath , 'build' , 'asset-manifest.json' ) ) const mainScript = manifest . files [ 'main.js' ] // grab first chunk const chunk = Object . keys ( manifest . files ) . filter ( f => f . match ( / ^ s t a t i c \/ j s \/ .+ \. j s $ / ) ) [ 0 ] const chunkScript = manifest . files [ chunk ] const mainStyle = manifest . files [ 'main.css' ] const scriptPathOnDisk = vscode . Uri . file ( path . join ( this . extensionPath , 'build' , mainScript ) ) const scriptUri = scriptPathOnDisk . with ( { scheme :'vscode-resource' } ) const chunkPathOnDisk = vscode . Uri . file ( path . join ( this . extensionPath , 'build' , chunkScript ) ) const chunkUri = chunkPathOnDisk . with ( { scheme :'vscode-resource' } ) const stylePathOnDisk = vscode . Uri . file ( path . join ( this . extensionPath , 'build' , mainStyle ) ) const styleUri = stylePathOnDisk . with ( { scheme :'vscode-resource' } ) // Use a nonce to whitelist which scripts can be run const [ n1 , n2 , n3 ] = [ 1 , 2 , 3 ] . map ( this . getNonce ) return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"> <meta name="theme-color" content="#000000"> <title>React App</title> <link rel="manifest" href="./manifest.json" /> <link rel="stylesheet" type="text/css" href="${ styleUri } "> <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${ nonce } ' 'nonce-${ nonce2 } ' 'nonce-${ nonce3 } '; style-src vscode-resource: 'unsafe-inline' http: https: data:;"> <base href="${ vscode . Uri . file ( path . join ( this . extensionPath , 'build' ) ) . with ( { scheme :'vscode-resource' } ) } /"> <style></style> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root">Loading...</div> <script nonce=${ nonce } src="./webpackBuild.js"></script> <script nonce=${ nonce2 } src="${ chunkUri } "></script> <script nonce="${ nonce3 } " src="${ scriptUri } "></script> </body> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"> <meta name="theme-color" content="#000000"> <title>React App</title> <link rel="manifest" href="./manifest.json" /> <link rel="stylesheet" type="text/css" href="${ styleUri } "> <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${ n1 } ' 'nonce-${ n2 } ' 'nonce-${ n3 } '; style-src vscode-resource: 'unsafe-inline' http: https: data:;"> <base href="${ vscode . Uri . file ( path . join ( this . extensionPath , 'build' ) ) . with ( { scheme :'vscode-resource' , } ) } /"> <style></style> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root">Loading...</div> <script nonce=${ n1 } src="./webpackBuild.js"></script> <script nonce=${ n2 } src="${ chunkUri } "></script> <script nonce="${ n3 } " src="${ scriptUri } "></script> </body> </html>` } } } export default ReactWebView