Several people edit one model at once through a server you run yourself (archify-vscode-ext-sync); the files stay ordinary files on each disk. Sign-in through the browser, offline-first with later merge, teammates' cursors in the Source view and text editors, and a Shared section on the model page with people, versions, Compare and Restore. Without a server nothing changes: the sync code is a separate bundle that loads only when a model is shared, after a sign-in, or when a sync command or invite link is used.
116 lines
4.3 KiB
TypeScript
116 lines
4.3 KiB
TypeScript
import path from 'node:path';
|
|
import * as vscode from 'vscode';
|
|
import { repoRootFor } from '../archify/runtime';
|
|
import { log } from '../util/log';
|
|
import type { LiveAction } from '../sync/liveBridge';
|
|
|
|
export type WebviewMessage =
|
|
| { type: 'hash'; hash: string }
|
|
| { type: 'save'; filename: string; base64: string }
|
|
| { type: 'openExternal'; href: string }
|
|
| { type: 'openRelative'; href: string }
|
|
| { type: 'openSource'; path: string; line?: number; endLine?: number; href?: string }
|
|
| { type: 'openDiagram'; path: string }
|
|
| { type: 'openRegistry'; file: string; line?: number }
|
|
| { type: 'openDecision'; file: string }
|
|
| { type: 'showGraph'; query?: string }
|
|
| { type: 'showModel' }
|
|
| { type: 'liveReady' }
|
|
| { type: 'liveAction'; action: LiveAction }
|
|
| { type: 'error'; message: string };
|
|
|
|
/** Column for opening files next to a webview without covering it. */
|
|
function besideColumn(panel: vscode.WebviewPanel): vscode.ViewColumn {
|
|
return panel.viewColumn === vscode.ViewColumn.One ? vscode.ViewColumn.Two : vscode.ViewColumn.One;
|
|
}
|
|
|
|
async function exists(uri: vscode.Uri): Promise<boolean> {
|
|
try {
|
|
await vscode.workspace.fs.stat(uri);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Opens a diagram's source evidence in the editor. The path is relative to
|
|
* the repository root; the diagram's checkout and the workspace folders are
|
|
* tried in turn before falling back to the web link.
|
|
*/
|
|
async function openSource(
|
|
message: Extract<WebviewMessage, { type: 'openSource' }>,
|
|
context: vscode.Uri,
|
|
panel: vscode.WebviewPanel,
|
|
): Promise<void> {
|
|
const relative = message.path.replace(/^\/+/, '');
|
|
const roots = new Set<string>();
|
|
const repoRoot = await repoRootFor(context);
|
|
if (repoRoot) roots.add(repoRoot);
|
|
for (const folder of vscode.workspace.workspaceFolders ?? []) roots.add(folder.uri.fsPath);
|
|
|
|
for (const root of roots) {
|
|
const candidate = vscode.Uri.file(path.join(root, relative));
|
|
if (relative && (await exists(candidate))) {
|
|
const start = Math.max((message.line ?? 1) - 1, 0);
|
|
const end = Math.max((message.endLine ?? message.line ?? 1) - 1, start);
|
|
const selection = new vscode.Range(start, 0, end, Number.MAX_SAFE_INTEGER);
|
|
await vscode.window.showTextDocument(candidate, { viewColumn: besideColumn(panel), selection });
|
|
return;
|
|
}
|
|
}
|
|
if (message.href && /^https?:/i.test(message.href)) {
|
|
await vscode.env.openExternal(vscode.Uri.parse(message.href));
|
|
return;
|
|
}
|
|
vscode.window.showWarningMessage(`Archify: source file "${message.path}" was not found in the workspace.`);
|
|
}
|
|
|
|
export async function handleWebviewMessage(
|
|
message: WebviewMessage,
|
|
context: vscode.Uri,
|
|
panel: vscode.WebviewPanel,
|
|
): Promise<void> {
|
|
try {
|
|
switch (message.type) {
|
|
case 'openExternal':
|
|
await vscode.env.openExternal(vscode.Uri.parse(message.href));
|
|
break;
|
|
case 'openRelative': {
|
|
const [file] = message.href.split('#');
|
|
if (!file) break;
|
|
const target = vscode.Uri.joinPath(context, '..', decodeURIComponent(file));
|
|
await vscode.commands.executeCommand('vscode.open', target, besideColumn(panel));
|
|
break;
|
|
}
|
|
case 'openSource':
|
|
await openSource(message, context, panel);
|
|
break;
|
|
case 'save': {
|
|
const name = path.basename(message.filename || 'archify-export');
|
|
const defaultUri = vscode.Uri.joinPath(context, '..', name);
|
|
const target = await vscode.window.showSaveDialog({ defaultUri, saveLabel: 'Export' });
|
|
if (!target) break;
|
|
await vscode.workspace.fs.writeFile(target, Buffer.from(message.base64, 'base64'));
|
|
const choice = await vscode.window.showInformationMessage(`Exported ${path.basename(target.fsPath)}`, 'Reveal');
|
|
if (choice === 'Reveal') await vscode.commands.executeCommand('revealFileInOS', target);
|
|
break;
|
|
}
|
|
case 'error':
|
|
vscode.window.showErrorMessage(`Archify: ${message.message}`);
|
|
break;
|
|
case 'hash':
|
|
case 'openDiagram':
|
|
case 'showGraph':
|
|
case 'showModel':
|
|
case 'liveReady':
|
|
case 'liveAction':
|
|
// Handled by the view, which knows the model root.
|
|
break;
|
|
}
|
|
} catch (error) {
|
|
log().error(error instanceof Error ? error : String(error));
|
|
vscode.window.showErrorMessage(`Archify: ${error instanceof Error ? error.message : String(error)}`);
|
|
}
|
|
}
|