equicord-companion
Version:
Equicord Companion is a vscode extension to test Equicord patches & webpack finds right from the comfort of your IDE
198 lines (167 loc) • 8.24 kB
text/typescript
/**
* Manages react webview panels
*/
import { format } from "@modules/format";
import { mkStringUri } from "@modules/util";
import { handleExtractPayload, sendAndGetData } from "@server";
import { EvaledPatch, ReporterData, WebviewMessage } from "@type/reporter";
import { extensionPath, extensionUri } from "./extension";
import { commands, Disposable, Uri, ViewColumn, WebviewPanel, window } from "vscode";
type Patch = { patch: EvaledPatch; };
type PluginName = { pluginName: string; };
type Diff = {
oldModule: string;
newModule: string;
};
// TODO: add persistant state
export class ReporterPanel {
/**
* Track the currently panel. Only allow a single panel to exist at a time.
*/
public static currentPanel: ReporterPanel | undefined;
private static readonly viewType = "equicordReporter";
private readonly _panel: WebviewPanel;
private readonly _extensionUri: Uri;
private readonly _extensionPath: string;
private _disposables: Disposable[] = [];
public static createOrShow(data: ReporterData) {
const column = window.activeTextEditor ? window.activeTextEditor.viewColumn : undefined;
// If we already have a panel, show it.
// Otherwise, create a new panel.
// dont create another panel with the same data
if (ReporterPanel.currentPanel && !data) {
ReporterPanel.currentPanel._panel.reveal(column);
} else {
ReporterPanel.currentPanel = new ReporterPanel(extensionUri, column || ViewColumn.One, data);
}
}
private constructor(extensionUri: Uri, column: ViewColumn, data: ReporterData) {
this._extensionUri = extensionUri;
this._extensionPath = extensionPath;
// Create and show a new webview panel
this._panel = window.createWebviewPanel(ReporterPanel.viewType, "Equicord Reporter", column, {
// Enable javascript in the webview
enableScripts: true,
// And restric the webview to only loading content from our extension's `media` directory.
localResourceRoots: [Uri.joinPath(extensionUri, "dist/webview")],
});
// Set the webview's initial html content
this._panel.webview.html = this._getHtmlForWebview(data);
// 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
this._panel.webview.onDidReceiveMessage(async (message: WebviewMessage) => {
try {
switch (message.type) {
case "disable": {
const { pluginName, enabled }: PluginName & { enabled: boolean; } = message.data;
// DISABLE PLUGIN
await sendAndGetData({
type: "disable",
data: {
pluginName,
enabled,
},
});
break;
}
case "jumpToPatch": {
const { patch }: Patch & PluginName = message.data;
// any attempt to get this to open without user interaction is a complete shitshow
// just use the builtin fuzzy finder and the patch find
// while there might be more than one find, the user can deal with that
commands.executeCommand("workbench.action.quickOpen", `%${patch.find}`);
break;
}
case "extract": {
const { patch }: Patch = message.data;
if (Number.isNaN(Number(patch.id))) {
window.showErrorMessage("Module ID is not a number");
return;
}
try {
const r = await sendAndGetData<"extract">({
type: "extract",
data: {
extractType: "id",
idOrSearch: Number(patch.id),
usePatched: null,
},
});
handleExtractPayload(r);
} catch (e) {
window.showErrorMessage(String(e));
}
break;
}
case "diff": {
const { oldModule, newModule, id }: Diff & EvaledPatch = message.data;
// we cant format code with syntax errors
let sourceUri, patchedUri;
try {
sourceUri = mkStringUri(await format(oldModule));
patchedUri = mkStringUri(await format(newModule));
} catch (error) {
window.showErrorMessage(`Failed to format code, probably a syntax error, ${error}`);
sourceUri = mkStringUri(oldModule);
patchedUri = mkStringUri(newModule);
}
commands.executeCommand("vscode.diff", sourceUri, patchedUri, `Patch Diff: ${id}`);
break;
}
default: {
window.showErrorMessage(`Unknown message type from webview, got : ${message.type}`);
break;
}
}
} catch (error) {
window.showErrorMessage(String(error));
}
}, null, this._disposables);
}
public dispose() {
ReporterPanel.currentPanel = undefined;
// Clean up our resources
this._panel.dispose();
while (this._disposables.length) {
const x = this._disposables.pop();
if (x) {
x.dispose();
}
}
}
private _getHtmlForWebview(data: ReporterData) {
const scriptPathOnDisk = Uri.joinPath(this._extensionUri, "dist/webview/index.js");
const scriptUri = this._panel.webview.asWebviewUri(scriptPathOnDisk);
const stylePathOnDisk = Uri.joinPath(this._extensionUri, "dist/webview/index.css");
const styleUri = this._panel.webview.asWebviewUri(stylePathOnDisk); // Use a nonce to whitelist which scripts can be run
const nonce = 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>
<script nonce="${nonce}">window.reporterData = ${JSON.stringify(data)}</script>
<link rel="stylesheet" type="text/css" href="${styleUri}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${this._panel.webview.cspSource} https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src ${this._panel.webview.cspSource} https:; script-src 'nonce-${nonce}';">
<base href="${this._panel.webview.asWebviewUri(Uri.joinPath(this._extensionUri, "dist/webview"))}/">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script defer nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;
}
}
function getNonce() {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}