Day27 | 导入WebviewPanel

大家好,我是韦恩,今天是铁人赛的二十六天,让我们在专案的现有架构里导入WebViewPanel元件吧!


CodeManager当前专案架构


在昨天,我们初步建立了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

专案的资料夹根据不同功能的划分为各个模组,分为以下几类

  • common: 放置常用的vscode工具元件,如input、dropdown等等。
  • data: MVVM的model层,处理data-access的逻辑,如读写档案、http request等等。
  • model: 定义专案的ts类型,view-model、model和其他模组会使用到的interface与各种type。
  • view: MVVM的View层,这里会放置展示资料与互动的元件,如TreeView和WebviewPanel。
  • view-model: MVVM的ViewModel层,目前已经定义好的viewModel为储存snippet用资料的workspace类别。

这些档案模组会在extension.ts中主程序的进入点active function中初始化,并加以使用。

导入WebviewPanel与WebView


上面的树状图让我们对专案的个功能一目了然,现在,我们在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到专案里,明天也会继续有更多实作。

我们明天见,谢谢大家。


<<:  [Day 27] 损失视觉化 Loss Visualization

>>:  第27天:实作档案上传功能(4)

Flutter基础介绍与实作-Day23 旅游笔记的实作(4)

今天就接续来讲中部地区的制作吧! 资料夹建立 lib/scareens/food_Middle/fo...

Day 10 Prototype 制作 - 版面、字体、间距、图示设定小技巧分享

今天用这个阳春的版面来介绍版面、字体、图示还有间距的设定。刚刚跟接案的客户谈完流程,也收了头款,大家...

更新网格交易机器人

使用存到文字档的log取代print 解决相对网格里面,分母部位过小的问题 ...

[Day 08] 原形设计的样板参考

由於不是设计师,对於UI/UX相关的东西懂得可能就跟完全没接触过的人一样, 所以在想做一个自己的Ap...

(29) 试着学 Hexo - 奇淫技巧 - 快速上传你的图片到 imgur

前言 接下来来讲讲如何透过 VSCode 快速上传图片到 imgur 吧! imgur 前面章节「(...