Skip to content

Commit 558b932

Browse files
authored
Extract base functionality for WebviewViewProviders into an abstract class (#2895)
1 parent 6e06e79 commit 558b932

File tree

4 files changed

+122
-64
lines changed

4 files changed

+122
-64
lines changed

extensions/ql-vscode/src/common/interface-types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,8 +598,7 @@ export type FromModelEditorMessage =
598598
| SetModeledMethodMessage;
599599

600600
export type FromMethodModelingMessage =
601-
| TelemetryMessage
602-
| UnhandledErrorMessage
601+
| CommonFromViewMessages
603602
| SetModeledMethodMessage;
604603

605604
interface SetMethodMessage {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import * as vscode from "vscode";
2+
import { Uri, WebviewViewProvider } from "vscode";
3+
import { WebviewKind, WebviewMessage, getHtmlForWebview } from "./webview-html";
4+
import { Disposable } from "../disposable-object";
5+
import { App } from "../app";
6+
7+
export abstract class AbstractWebviewViewProvider<
8+
ToMessage extends WebviewMessage,
9+
FromMessage extends WebviewMessage,
10+
> implements WebviewViewProvider
11+
{
12+
protected webviewView: vscode.WebviewView | undefined = undefined;
13+
private disposables: Disposable[] = [];
14+
15+
constructor(
16+
private readonly app: App,
17+
private readonly webviewKind: WebviewKind,
18+
) {}
19+
20+
/**
21+
* This is called when a view first becomes visible. This may happen when the view is
22+
* first loaded or when the user hides and then shows a view again.
23+
*/
24+
public resolveWebviewView(
25+
webviewView: vscode.WebviewView,
26+
_context: vscode.WebviewViewResolveContext,
27+
_token: vscode.CancellationToken,
28+
) {
29+
webviewView.webview.options = {
30+
enableScripts: true,
31+
localResourceRoots: [Uri.file(this.app.extensionPath)],
32+
};
33+
34+
const html = getHtmlForWebview(
35+
this.app,
36+
webviewView.webview,
37+
this.webviewKind,
38+
{
39+
allowInlineStyles: true,
40+
allowWasmEval: false,
41+
},
42+
);
43+
44+
webviewView.webview.html = html;
45+
46+
this.webviewView = webviewView;
47+
48+
webviewView.webview.onDidReceiveMessage(async (msg) => this.onMessage(msg));
49+
webviewView.onDidDispose(() => this.dispose());
50+
}
51+
52+
protected get isShowingView() {
53+
return this.webviewView?.visible ?? false;
54+
}
55+
56+
protected async postMessage(msg: ToMessage): Promise<void> {
57+
await this.webviewView?.webview.postMessage(msg);
58+
}
59+
60+
protected dispose() {
61+
while (this.disposables.length > 0) {
62+
const disposable = this.disposables.pop()!;
63+
disposable.dispose();
64+
}
65+
66+
this.webviewView = undefined;
67+
}
68+
69+
protected push<T extends Disposable>(obj: T): T {
70+
if (obj !== undefined) {
71+
this.disposables.push(obj);
72+
}
73+
return obj;
74+
}
75+
76+
protected abstract onMessage(msg: FromMessage): Promise<void>;
77+
78+
/**
79+
* This is called when a view first becomes visible. This may happen when the view is
80+
* first loaded or when the user hides and then shows a view again.
81+
*/
82+
protected onWebViewLoaded(): void {
83+
// Do nothing by default.
84+
}
85+
}

extensions/ql-vscode/src/model-editor/method-modeling/method-modeling-panel.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export class MethodModelingPanel extends DisposableObject {
1212
super();
1313

1414
this.provider = new MethodModelingViewProvider(app, modelingStore);
15-
this.push(this.provider);
1615
this.push(
1716
window.registerWebviewViewProvider(
1817
MethodModelingViewProvider.viewType,

extensions/ql-vscode/src/model-editor/method-modeling/method-modeling-view-provider.ts

Lines changed: 36 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,51 @@
1-
import * as vscode from "vscode";
2-
import { Uri, WebviewViewProvider } from "vscode";
3-
import { getHtmlForWebview } from "../../common/vscode/webview-html";
4-
import { FromMethodModelingMessage } from "../../common/interface-types";
1+
import {
2+
FromMethodModelingMessage,
3+
ToMethodModelingMessage,
4+
} from "../../common/interface-types";
55
import { telemetryListener } from "../../common/vscode/telemetry";
66
import { showAndLogExceptionWithTelemetry } from "../../common/logging/notifications";
77
import { extLogger } from "../../common/logging/vscode/loggers";
88
import { App } from "../../common/app";
99
import { redactableError } from "../../common/errors";
1010
import { Method } from "../method";
11-
import { DisposableObject } from "../../common/disposable-object";
1211
import { ModelingStore } from "../modeling-store";
12+
import { AbstractWebviewViewProvider } from "../../common/vscode/abstract-webview-view-provider";
1313

14-
export class MethodModelingViewProvider
15-
extends DisposableObject
16-
implements WebviewViewProvider
17-
{
14+
export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
15+
ToMethodModelingMessage,
16+
FromMethodModelingMessage
17+
> {
1818
public static readonly viewType = "codeQLMethodModeling";
1919

20-
private webviewView: vscode.WebviewView | undefined = undefined;
21-
2220
private method: Method | undefined = undefined;
2321

2422
constructor(
25-
private readonly app: App,
23+
app: App,
2624
private readonly modelingStore: ModelingStore,
2725
) {
28-
super();
26+
super(app, "method-modeling");
2927
}
3028

31-
/**
32-
* This is called when a view first becomes visible. This may happen when the view is
33-
* first loaded or when the user hides and then shows a view again.
34-
*/
35-
public resolveWebviewView(
36-
webviewView: vscode.WebviewView,
37-
_context: vscode.WebviewViewResolveContext,
38-
_token: vscode.CancellationToken,
39-
) {
40-
webviewView.webview.options = {
41-
enableScripts: true,
42-
localResourceRoots: [Uri.file(this.app.extensionPath)],
43-
};
44-
45-
const html = getHtmlForWebview(
46-
this.app,
47-
webviewView.webview,
48-
"method-modeling",
49-
{
50-
allowInlineStyles: true,
51-
allowWasmEval: false,
52-
},
53-
);
54-
55-
webviewView.webview.html = html;
56-
57-
webviewView.webview.onDidReceiveMessage(async (msg) => this.onMessage(msg));
58-
59-
this.webviewView = webviewView;
60-
61-
this.setInitialState(webviewView);
29+
protected override onWebViewLoaded(): void {
30+
this.setInitialState();
6231
this.registerToModelingStoreEvents();
6332
}
6433

6534
public async setMethod(method: Method): Promise<void> {
6635
this.method = method;
6736

68-
if (this.webviewView) {
69-
await this.webviewView.webview.postMessage({
37+
if (this.isShowingView) {
38+
await this.postMessage({
7039
t: "setMethod",
7140
method,
7241
});
7342
}
7443
}
7544

76-
private setInitialState(webviewView: vscode.WebviewView): void {
45+
private setInitialState(): void {
7746
const selectedMethod = this.modelingStore.getSelectedMethodDetails();
7847
if (selectedMethod) {
79-
void webviewView.webview.postMessage({
48+
void this.postMessage({
8049
t: "setSelectedMethod",
8150
method: selectedMethod.method,
8251
modeledMethod: selectedMethod.modeledMethod,
@@ -85,24 +54,18 @@ export class MethodModelingViewProvider
8554
}
8655
}
8756

88-
private async onMessage(msg: FromMethodModelingMessage): Promise<void> {
57+
protected override async onMessage(
58+
msg: FromMethodModelingMessage,
59+
): Promise<void> {
8960
switch (msg.t) {
90-
case "setModeledMethod": {
91-
const activeState = this.modelingStore.getStateForActiveDb();
92-
if (!activeState) {
93-
throw new Error("No active state found in modeling store");
94-
}
95-
this.modelingStore.updateModeledMethod(
96-
activeState.databaseItem,
97-
msg.method,
98-
);
61+
case "viewLoaded":
62+
this.onWebViewLoaded();
9963
break;
100-
}
10164

102-
case "telemetry": {
65+
case "telemetry":
10366
telemetryListener?.sendUIInteraction(msg.action);
10467
break;
105-
}
68+
10669
case "unhandledError":
10770
void showAndLogExceptionWithTelemetry(
10871
extLogger,
@@ -112,6 +75,18 @@ export class MethodModelingViewProvider
11275
)`Unhandled error in method modeling view: ${msg.error.message}`,
11376
);
11477
break;
78+
79+
case "setModeledMethod": {
80+
const activeState = this.modelingStore.getStateForActiveDb();
81+
if (!activeState) {
82+
throw new Error("No active state found in modeling store");
83+
}
84+
this.modelingStore.updateModeledMethod(
85+
activeState.databaseItem,
86+
msg.method,
87+
);
88+
break;
89+
}
11590
}
11691
}
11792

0 commit comments

Comments
 (0)