大家好,我是韋恩,今天是鐵人賽的二十六天,讓我們在專案的現有架構裡導入WebViewPanel元件吧!
在昨天,我們初步建立了View-ViewModel-Model的資料流架構。
現在我們的專案的src資料夾結構如下
./src
├── extension.ts
├── main
│ ├── common
│ │ └── control.ts
│ ├── data
│ │ └── snippets
│ │ ├── index.ts
│ │ └── snippetfile.ts
│ ├── model
│ │ ├── index.ts
│ │ └── snippet.ts
│ ├── view
│ │ └── treeview.ts
│ └── view-model
│ └── workspace
│ ├── index.ts
│ ├── initialize.ts
│ └── workspace.ts
└── test
├── runTest.ts
└── suite
├── extension.test.ts
└── index.ts
專案的資料夾根據不同功能的劃分為各個模組,分為以下幾類
這些檔案模組會在extension.ts中主程式的進入點active function中初始化,並加以使用。
上面的樹狀圖讓我們對專案的個功能一目了然,現在,我們在view的資料夾新增一個webview-panel.ts,並實作WebViewPanel的類別。
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
type ObjectLike = { [key: string]: any };
interface Action<T extends ObjectLike> {
type: string;
payload: T
}
export class WebviewPanel {
private static _instance: WebviewPanel | null;
public static getInstance(context: vscode.ExtensionContext) {
if (WebviewPanel._instance) {
return WebviewPanel._instance;
}
return new WebviewPanel(context);
}
public static createOrShow(context: vscode.ExtensionContext) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
if (WebviewPanel._instance) {
WebviewPanel._instance._panel.reveal(column);
return this._instance;
} else {
WebviewPanel._instance = new WebviewPanel(context);
return this._instance;
}
}
public readonly viewType = 'CodeManagerWebViewPanel';
private _panel: vscode.WebviewPanel;
private get webview() {
return this._panel.webview;
}
private _disposables: vscode.Disposable[] = [];
private _eventEmitter = new vscode.EventEmitter();
public get onDidReceiveMessage() {
return this._eventEmitter.event;
}
private constructor(private context: vscode.ExtensionContext) {
this._panel = this.createPanel(context
this.listenLifeCycleChanges();
this.listenWebViewMessage();
this.webview.html = this.loadWebviewContent();
}
private createPanel(context: vscode.ExtensionContext) {
return vscode.window.createWebviewPanel(
this.viewType,
'SnippetManager',
vscode.ViewColumn.Beside,
{
enableScripts: true,
localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, 'out', 'build'),
vscode.Uri.joinPath(context.extensionUri, 'out', 'build', 'assets')
]
}
);
}
private _dispose() {
this._panel.dispose();
this._disposables.forEach((d: vscode.Disposable) => d.dispose());
this._disposables = [];
this._eventEmitter.dispose();
WebviewPanel._instance = null;
}
private listenLifeCycleChanges() {
this._panel.onDidDispose(() => this._dispose(), null, this._disposables);
this._panel.onDidChangeViewState(
e => {},
null,
this._disposables
);
}
private listenWebViewMessage() {
this._panel.webview.onDidReceiveMessage(
message => {
this._eventEmitter.fire(e);
},
null,
this._disposables
);
}
public sendMessage<T extends ObjectLike>(action: Action<T>) {
this.webview.postMessage(action);
}
private webviewUri(relativePath: string) {
const uri = vscode.Uri.parse(
path.join(this.context.extensionPath,'out','build', relativePath)
);
return this.webview.asWebviewUri(uri);
}
public loadWebviewContent() {
const mainfest = readJSON(path.join(this.context.extensionPath, 'out/build/asset-manifest.json'));
const entrypointsJs = mainfest['entrypoints'].filter((p: string) => p.includes('static/js'));
console.log(entrypointsJs);
const nonce = getNonce();
return `<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" href="${this.webviewUri('./favicon.ico')}"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="theme-color" content="#000000"/>
<meta name="description" content="Web site created using create-react-app"/>
<title>React App</title>
<base href="${this.webviewUri('/')}">
<link rel="stylesheet" type="text/css" href="${this.webviewUri(mainfest.files['main.css'])}">
<link rel="apple-touch-icon" href="${this.webviewUri('./logo192.png')}"/>
<link rel="manifest" href="${this.webviewUri('./manifest.json')}"/>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="${this.webviewUri(entrypointsJs[0])}"></script>
<script src="${this.webviewUri(entrypointsJs[1])}"></script>
<script src="${this.webviewUri(entrypointsJs[2])}"></script>
</body>
</html>`;
}
}
function readJSON(path: string) {
const jsonString = fs.readFileSync(path, 'utf-8');
return JSON.parse(jsonString);
}
實作的方法大致與我們之前WebView(四)時提到的結構相同。
小有不同的是我們改變了WebView顯示的位置,我們會將其顯示於側邊的editor。
private createPanel(context: vscode.ExtensionContext) {
return vscode.window.createWebviewPanel(
this.viewType,
'SnippetManager',
vscode.ViewColumn.Beside,
{
enableScripts: true,
localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, 'out', 'build'),
vscode.Uri.joinPath(context.extensionUri, 'out', 'build', 'assets')
]
}
);
}
對外我們也會暴露onDidReceiveMessage
方法,讓外部也可以監聽收到Webview傳來的資料,進行對應處理。
...
public get onDidReceiveMessage() {
return this._eventEmitter.event;
}
...
完成WebviewPanel之後,我們一樣照先前教學將web專案命名為webview,並讓webview資料夾與src資料夾一樣位於根目錄。
啟動Webview後,我們會在當前編輯中的文件旁另開一個editor,如下所示。
好啦,今天我們開始導入WebviewPanel到專案裡,明天也會繼續有更多實作。
我們明天見,謝謝大家。