Archify Diagram Viewer 0.1.0
VS Code extension that previews Archify diagrams from their JSON sources (live, as you type) and opens rendered Archify HTML in a viewer tab. Bundles the Archify 3.0.1 renderer and runs it on VS Code's Node runtime. Adds validation diagnostics, JSON schema help, source-link navigation, export saving, render-to-file and open-in-browser commands. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import path from 'node:path';
|
||||
import * as vscode from 'vscode';
|
||||
import { isArchifyHtml } from './detect';
|
||||
import { viewerTheme } from './runtime';
|
||||
import { handleWebviewMessage, WebviewMessage } from './webviewMessages';
|
||||
import { makeNonce, messagePage, prepareDiagramHtml } from './webviewHtml';
|
||||
|
||||
export const HTML_VIEW_TYPE = 'archify.htmlViewer';
|
||||
|
||||
class HtmlDocument implements vscode.CustomDocument {
|
||||
constructor(readonly uri: vscode.Uri) {}
|
||||
dispose(): void {}
|
||||
}
|
||||
|
||||
/** Read-only custom editor that shows rendered Archify HTML files. */
|
||||
export class HtmlViewerProvider implements vscode.CustomReadonlyEditorProvider<HtmlDocument>, vscode.Disposable {
|
||||
private readonly viewers = new Map<vscode.WebviewPanel, () => Promise<void>>();
|
||||
private active: { uri: vscode.Uri; panel: vscode.WebviewPanel } | undefined;
|
||||
private readonly disposables: vscode.Disposable[];
|
||||
|
||||
constructor() {
|
||||
this.disposables = [
|
||||
vscode.window.registerCustomEditorProvider(HTML_VIEW_TYPE, this, {
|
||||
webviewOptions: { retainContextWhenHidden: true, enableFindWidget: true },
|
||||
supportsMultipleEditorsPerDocument: true,
|
||||
}),
|
||||
vscode.window.onDidChangeActiveColorTheme(() => this.reloadAll()),
|
||||
vscode.workspace.onDidChangeConfiguration((event) => {
|
||||
if (event.affectsConfiguration('archify.theme')) this.reloadAll();
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/** File shown in the focused Archify HTML viewer, if any. */
|
||||
get activeUri(): vscode.Uri | undefined {
|
||||
return this.active?.uri;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
for (const disposable of this.disposables.splice(0)) disposable.dispose();
|
||||
}
|
||||
|
||||
openCustomDocument(uri: vscode.Uri): HtmlDocument {
|
||||
return new HtmlDocument(uri);
|
||||
}
|
||||
|
||||
async resolveCustomEditor(document: HtmlDocument, panel: vscode.WebviewPanel): Promise<void> {
|
||||
const uri = document.uri;
|
||||
let hash: string | undefined;
|
||||
panel.webview.options = { enableScripts: true, localResourceRoots: [] };
|
||||
|
||||
const reload = async () => {
|
||||
let text: string;
|
||||
try {
|
||||
text = new TextDecoder('utf-8').decode(await vscode.workspace.fs.readFile(uri));
|
||||
} catch (error) {
|
||||
panel.webview.html = messagePage(
|
||||
{ cspSource: panel.webview.cspSource, nonce: makeNonce() },
|
||||
'Cannot open file',
|
||||
uri.fsPath,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
return;
|
||||
}
|
||||
panel.webview.html = prepareDiagramHtml(text, {
|
||||
cspSource: panel.webview.cspSource,
|
||||
nonce: makeNonce(),
|
||||
theme: viewerTheme(),
|
||||
hash,
|
||||
notice: isArchifyHtml(text)
|
||||
? undefined
|
||||
: { kind: 'info', text: 'This file was not generated by Archify. It is shown in a sandbox without network access.' },
|
||||
});
|
||||
};
|
||||
|
||||
this.viewers.set(panel, reload);
|
||||
|
||||
const disposables: vscode.Disposable[] = [
|
||||
panel.webview.onDidReceiveMessage((message: WebviewMessage) => {
|
||||
if (message.type === 'hash') {
|
||||
hash = message.hash;
|
||||
return;
|
||||
}
|
||||
void handleWebviewMessage(message, uri, panel);
|
||||
}),
|
||||
panel.onDidChangeViewState(() => {
|
||||
if (panel.active) this.active = { uri, panel };
|
||||
else if (this.active?.panel === panel) this.active = undefined;
|
||||
}),
|
||||
];
|
||||
if (uri.scheme === 'file') {
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(
|
||||
new vscode.RelativePattern(vscode.Uri.file(path.dirname(uri.fsPath)), path.basename(uri.fsPath)),
|
||||
);
|
||||
let timer: NodeJS.Timeout | undefined;
|
||||
const onChange = () => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => void reload(), 300);
|
||||
};
|
||||
disposables.push(watcher, watcher.onDidChange(onChange), watcher.onDidCreate(onChange), {
|
||||
dispose: () => clearTimeout(timer),
|
||||
});
|
||||
}
|
||||
panel.onDidDispose(() => {
|
||||
this.viewers.delete(panel);
|
||||
if (this.active?.panel === panel) this.active = undefined;
|
||||
for (const disposable of disposables) disposable.dispose();
|
||||
});
|
||||
if (panel.active) this.active = { uri, panel };
|
||||
await reload();
|
||||
}
|
||||
|
||||
reloadAll(): void {
|
||||
for (const reload of this.viewers.values()) void reload();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user