Extract base functionality for WebviewViewProviders into an abstract class (#2895)

This commit is contained in:
Charis Kyriakou
2023-10-02 16:31:52 +01:00
committed by GitHub
parent 6e06e7934b
commit 558b9329c5
4 changed files with 124 additions and 66 deletions

View File

@@ -598,8 +598,7 @@ export type FromModelEditorMessage =
| SetModeledMethodMessage;
export type FromMethodModelingMessage =
| TelemetryMessage
| UnhandledErrorMessage
| CommonFromViewMessages
| SetModeledMethodMessage;
interface SetMethodMessage {

View File

@@ -0,0 +1,85 @@
import * as vscode from "vscode";
import { Uri, WebviewViewProvider } from "vscode";
import { WebviewKind, WebviewMessage, getHtmlForWebview } from "./webview-html";
import { Disposable } from "../disposable-object";
import { App } from "../app";
export abstract class AbstractWebviewViewProvider<
ToMessage extends WebviewMessage,
FromMessage extends WebviewMessage,
> implements WebviewViewProvider
{
protected webviewView: vscode.WebviewView | undefined = undefined;
private disposables: Disposable[] = [];
constructor(
private readonly app: App,
private readonly webviewKind: WebviewKind,
) {}
/**
* This is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*/
public resolveWebviewView(
webviewView: vscode.WebviewView,
_context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [Uri.file(this.app.extensionPath)],
};
const html = getHtmlForWebview(
this.app,
webviewView.webview,
this.webviewKind,
{
allowInlineStyles: true,
allowWasmEval: false,
},
);
webviewView.webview.html = html;
this.webviewView = webviewView;
webviewView.webview.onDidReceiveMessage(async (msg) => this.onMessage(msg));
webviewView.onDidDispose(() => this.dispose());
}
protected get isShowingView() {
return this.webviewView?.visible ?? false;
}
protected async postMessage(msg: ToMessage): Promise<void> {
await this.webviewView?.webview.postMessage(msg);
}
protected dispose() {
while (this.disposables.length > 0) {
const disposable = this.disposables.pop()!;
disposable.dispose();
}
this.webviewView = undefined;
}
protected push<T extends Disposable>(obj: T): T {
if (obj !== undefined) {
this.disposables.push(obj);
}
return obj;
}
protected abstract onMessage(msg: FromMessage): Promise<void>;
/**
* This is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*/
protected onWebViewLoaded(): void {
// Do nothing by default.
}
}

View File

@@ -12,7 +12,6 @@ export class MethodModelingPanel extends DisposableObject {
super();
this.provider = new MethodModelingViewProvider(app, modelingStore);
this.push(this.provider);
this.push(
window.registerWebviewViewProvider(
MethodModelingViewProvider.viewType,

View File

@@ -1,82 +1,51 @@
import * as vscode from "vscode";
import { Uri, WebviewViewProvider } from "vscode";
import { getHtmlForWebview } from "../../common/vscode/webview-html";
import { FromMethodModelingMessage } from "../../common/interface-types";
import {
FromMethodModelingMessage,
ToMethodModelingMessage,
} from "../../common/interface-types";
import { telemetryListener } from "../../common/vscode/telemetry";
import { showAndLogExceptionWithTelemetry } from "../../common/logging/notifications";
import { extLogger } from "../../common/logging/vscode/loggers";
import { App } from "../../common/app";
import { redactableError } from "../../common/errors";
import { Method } from "../method";
import { DisposableObject } from "../../common/disposable-object";
import { ModelingStore } from "../modeling-store";
import { AbstractWebviewViewProvider } from "../../common/vscode/abstract-webview-view-provider";
export class MethodModelingViewProvider
extends DisposableObject
implements WebviewViewProvider
{
export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
ToMethodModelingMessage,
FromMethodModelingMessage
> {
public static readonly viewType = "codeQLMethodModeling";
private webviewView: vscode.WebviewView | undefined = undefined;
private method: Method | undefined = undefined;
constructor(
private readonly app: App,
app: App,
private readonly modelingStore: ModelingStore,
) {
super();
super(app, "method-modeling");
}
/**
* This is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*/
public resolveWebviewView(
webviewView: vscode.WebviewView,
_context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [Uri.file(this.app.extensionPath)],
};
const html = getHtmlForWebview(
this.app,
webviewView.webview,
"method-modeling",
{
allowInlineStyles: true,
allowWasmEval: false,
},
);
webviewView.webview.html = html;
webviewView.webview.onDidReceiveMessage(async (msg) => this.onMessage(msg));
this.webviewView = webviewView;
this.setInitialState(webviewView);
protected override onWebViewLoaded(): void {
this.setInitialState();
this.registerToModelingStoreEvents();
}
public async setMethod(method: Method): Promise<void> {
this.method = method;
if (this.webviewView) {
await this.webviewView.webview.postMessage({
if (this.isShowingView) {
await this.postMessage({
t: "setMethod",
method,
});
}
}
private setInitialState(webviewView: vscode.WebviewView): void {
private setInitialState(): void {
const selectedMethod = this.modelingStore.getSelectedMethodDetails();
if (selectedMethod) {
void webviewView.webview.postMessage({
void this.postMessage({
t: "setSelectedMethod",
method: selectedMethod.method,
modeledMethod: selectedMethod.modeledMethod,
@@ -85,8 +54,28 @@ export class MethodModelingViewProvider
}
}
private async onMessage(msg: FromMethodModelingMessage): Promise<void> {
protected override async onMessage(
msg: FromMethodModelingMessage,
): Promise<void> {
switch (msg.t) {
case "viewLoaded":
this.onWebViewLoaded();
break;
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
case "unhandledError":
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError(
msg.error,
)`Unhandled error in method modeling view: ${msg.error.message}`,
);
break;
case "setModeledMethod": {
const activeState = this.modelingStore.getStateForActiveDb();
if (!activeState) {
@@ -98,20 +87,6 @@ export class MethodModelingViewProvider
);
break;
}
case "telemetry": {
telemetryListener?.sendUIInteraction(msg.action);
break;
}
case "unhandledError":
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError(
msg.error,
)`Unhandled error in method modeling view: ${msg.error.message}`,
);
break;
}
}