From 97b3b5f6856eb3f6554ba6ad415514b93707cfb6 Mon Sep 17 00:00:00 2001 From: Root At Skic Date: Sun, 11 Oct 2026 00:54:00 +0300 Subject: [PATCH] Add a 3D view of every diagram; organise source by job; 0.6.0 3D view: a "3D" menu beside Export switches the diagram to a three.js scene built from the live Archify SVG. Nodes become blocks topped with the node as Archify draws it, relationships become lit pipes along Archify's routes, and the canvas, frames, lanes and legend form the floor. Clicks and hover go to the SVG nodes, so focus, the Semantic Passport and the intent trace work as in 2D; Archify's own animations (trace, node pulse, passport/path/lens flows) are mirrored live. The menu also offers flat or stacked levels, reset view and a top view; Export gains "3D view" (PNG). Trackpad scrolling pans, pinch and the mouse wheel zoom, and the zoom buttons work beyond the 2D limits. The scene draws only when something moves. New setting archify.defaultView. Source: src/ is split by job (commands, views, webview, archify, sources, language, util, viewer3d); behaviour unchanged. --- CHANGELOG.md | 10 + README.md | 42 +- docs/preview-3d.png | Bin 0 -> 242934 bytes esbuild.mjs | 29 +- package-lock.json | 69 +- package.json | 23 +- src/{renderer.ts => archify/cli.ts} | 3 +- src/{ => archify}/runtime.ts | 26 +- src/commands/context.ts | 38 + src/commands/contextKeys.ts | 34 + src/commands/newDiagram.ts | 59 + src/commands/renderCommands.ts | 90 ++ src/commands/viewCommands.ts | 52 + src/extension.ts | 264 +---- src/{ => language}/brandCompletion.ts | 4 +- src/{ => language}/brands.ts | 0 src/{ => language}/diagnostics.ts | 7 +- src/{ => language}/jsonPointer.ts | 0 src/{ => language}/templates.ts | 2 +- src/{ => sources}/detect.ts | 30 - src/sources/evidence.ts | 31 + src/util/files.ts | 8 + src/util/log.ts | 8 + src/util/settings.ts | 11 + src/viewer3d/index.ts | 558 +++++++++ src/viewer3d/raster.ts | 126 ++ src/viewer3d/stage.ts | 1019 +++++++++++++++++ src/viewer3d/style.ts | 45 + src/viewer3d/svgModel.ts | 181 +++ src/{preview.ts => views/diagramView.ts} | 23 +- src/{ => views}/htmlViewer.ts | 14 +- .../messages.ts} | 3 +- src/{webviewHtml.ts => webview/page.ts} | 29 +- src/webview/viewer3d.ts | 15 + test/brands.test.ts | 4 +- test/detect.test.ts | 7 +- test/{webviewHtml.test.ts => page.test.ts} | 30 +- test/renderer.test.ts | 4 +- test/templates.test.ts | 6 +- tsconfig.json | 2 +- 40 files changed, 2553 insertions(+), 353 deletions(-) create mode 100644 docs/preview-3d.png rename src/{renderer.ts => archify/cli.ts} (98%) rename src/{ => archify}/runtime.ts (66%) create mode 100644 src/commands/context.ts create mode 100644 src/commands/contextKeys.ts create mode 100644 src/commands/newDiagram.ts create mode 100644 src/commands/renderCommands.ts create mode 100644 src/commands/viewCommands.ts rename src/{ => language}/brandCompletion.ts (95%) rename src/{ => language}/brands.ts (100%) rename src/{ => language}/diagnostics.ts (95%) rename src/{ => language}/jsonPointer.ts (100%) rename src/{ => language}/templates.ts (98%) rename src/{ => sources}/detect.ts (74%) create mode 100644 src/sources/evidence.ts create mode 100644 src/util/files.ts create mode 100644 src/util/log.ts create mode 100644 src/util/settings.ts create mode 100644 src/viewer3d/index.ts create mode 100644 src/viewer3d/raster.ts create mode 100644 src/viewer3d/stage.ts create mode 100644 src/viewer3d/style.ts create mode 100644 src/viewer3d/svgModel.ts rename src/{preview.ts => views/diagramView.ts} (92%) rename src/{ => views}/htmlViewer.ts (88%) rename src/{webviewMessages.ts => webview/messages.ts} (97%) rename src/{webviewHtml.ts => webview/page.ts} (88%) create mode 100644 src/webview/viewer3d.ts rename test/{webviewHtml.test.ts => page.test.ts} (51%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97f35e0..038add8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.6.0 — 2026-10-11 + +- **3D view:** a **3D ▾** menu in the diagram toolbar, styled like Archify's own menus, switches any diagram to 3D. Nodes become raised blocks topped with the node as Archify draws it, relationships become lit pipes along Archify's routes, and the canvas, frames, lanes and legend form the floor. Works for all five diagram kinds, in every theme and style. +- The menu also holds **Levels** (*Flat*, or *Stacked* by kind) and **Camera** (*Reset view*, *From above*). +- **Animation follows 2D:** the 3D view mirrors Archify's own animations live: the trace when the diagram opens (replayed when 3D is chosen), node pulses, the hover trace, and the Semantic Passport, Path and Lens flows. *Live* off and reduced motion keep it still. +- In 3D, clicking a block focuses it as in 2D (Semantic Passport, fading); hover shows the one-hop trace; trackpad scrolling pans, pinch and the mouse wheel zoom; the zoom buttons and +/-/0 move the camera; **Export → 3D view** saves a PNG. +- **Performance:** the 3D view draws only when something moves (no GPU use while idle), uses lighter textures and pixel density, and the hidden 2D diagram is no longer painted. Hover and animations never rebuild textures. +- New setting `archify.defaultView` (`2d` or `3d`). Each open diagram remembers its view, camera and levels. +- The source code is reorganised by job (`commands/`, `views/`, `webview/`, `archify/`, `sources/`, `language/`, `util/`); no change in behaviour. + ## 0.5.2 — 2026-10-10 - **Right-click menus:** the Archify commands (Archify | New Diagram…, Archify | Open Preview, Archify | Render to HTML File…, Archify | Open in HTML Viewer) now sit in their own section at the very bottom of the Explorer and editor-tab menus, instead of between New File, New Folder and the other built-in entries. diff --git a/README.md b/README.md index 6269714..373b507 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,31 @@ The Archify CLI and the AI agents that use it write diagrams as JSON, named `nam - **Export**: the diagram's own Export menu (PNG, SVG, share card, …) saves through a VS Code save dialog. - **Commands**: *Archify | New Diagram…*, *Archify | Render to HTML File…*, *Archify | Open in Browser*, *Archify | Refresh Preview*, *Archify | Show Source*. +## 3D view + +Every diagram can also be seen in 3D. Open the **3D ▾** menu next to **Export** in the diagram's toolbar and choose **3D**; choose **2D** to go back. The button wears a cyan ring while the 3D view is on. + +![The same diagram in the 3D view](docs/preview-3d.png) + +The 3D view is built from the diagram Archify draws, so it looks the same: each node becomes a raised block whose top is the node as Archify draws it, relationships become pipes along Archify's routes with their colours, dashes and arrowheads, and the canvas, frames, lanes and legend form the floor. It works for all five diagram kinds, in every theme and style (Classic, Signal Flow, Blueprint, Editorial). + +The menu has three groups: + +- **View**: 2D or 3D. +- **Levels**: *Flat* keeps every node on the floor; *Stacked* lifts nodes into levels by kind: clients and external systems on top, security below them, services in the middle, data on the floor. +- **Camera**: *Reset view* (or 0) flies back to the whole diagram, wherever you have moved to; *From above* looks straight down, like 2D. + +Moving around: + +- Drag to turn, right-drag to move. On a trackpad, scroll with two fingers to move in any direction and pinch to zoom; a mouse wheel zooms. The diagram's zoom buttons and the + and - keys work too, beyond the 2D limits. +- Click a block to focus it, as in 2D: the Semantic Passport opens and unrelated parts fade. Hover a block to see its one-hop trace. Double-click a block to fly to it, or empty space to show everything. + +Animation follows 2D exactly, because the 3D view reads Archify's own animations as they play: the trace (relationships drawing in, nodes pulsing) when the diagram opens or 3D is chosen, the hover trace, and the flows from the Semantic Passport, Path and Lens. *Live* off, or reduced motion, keeps it still. + +**Export → 3D view** saves the current 3D view as a PNG. Each open diagram remembers whether it was in 3D, its camera and its levels; set `archify.defaultView` to `3d` to open diagrams in 3D. + +The 3D view draws only when something moves, so an idle diagram costs no GPU time. + ## Source evidence A diagram that declares `meta.repository` and `sources` is verified against a local Git checkout whose `origin` and pinned commit match. The extension uses the Git top-level directory of the diagram file, or `archify.repoRoot` when set. @@ -81,6 +106,7 @@ When verification fails, the default (`archify.sourceEvidence: "fallback"`) stil | Setting | Default | Description | |---|---|---| | `archify.theme` | `vscode` | `vscode` follows the color theme; `dark` or `light` force one; `diagram` keeps the diagram's own choice. | +| `archify.defaultView` | `2d` | `3d` opens diagrams in the 3D view. Each open diagram remembers its own choice. | | `archify.preview.liveUpdate` | `true` | Re-render while typing; when off, re-render on save. | | `archify.preview.debounceMs` | `400` | Delay before re-rendering after an edit. | | `archify.validation.enabled` | `true` | Report validation results in Problems. | @@ -91,7 +117,7 @@ When verification fails, the default (`archify.sourceEvidence: "fallback"`) stil ## Security -Diagrams run in a webview with a strict content security policy. Only the page's own inline scripts run (via a per-render nonce), there is no network access for scripts or fetches, and links open through VS Code. +Diagrams run in a webview with a strict content security policy. Only the page's own inline scripts and the extension's bundled 3D viewer run (via a per-render nonce), there is no network access for scripts or fetches, and links open through VS Code. ## Development @@ -105,6 +131,20 @@ npm run package # build archify-vscode-ext-.vsix Press F5 to launch an Extension Development Host with the test fixtures. +### Code layout + +| Folder | What it holds | +|---|---| +| `src/extension.ts` | Activation: creates the views and registers everything | +| `src/commands/` | Command handlers, grouped by job, and the editor context keys | +| `src/views/` | The diagram view (editor for `.archify`, side preview for JSON) and the HTML viewer | +| `src/webview/` | The page the views show: CSP, the bridge script, messages from the page | +| `src/archify/` | Running the bundled Archify CLI: render, validate, runtime settings | +| `src/sources/` | Reading diagram files: type detection, YAML/JSON, source evidence | +| `src/language/` | Editor help: Problems, brand completion, templates | +| `src/util/` | Logging, settings, file reading | +| `src/viewer3d/` | The 3D view; runs inside the Archify page and is bundled to `dist/viewer3d.js` with three.js | + ### Updating the bundled renderer `vendor/archify/` and `schemas/` are generated from an Archify checkout: diff --git a/docs/preview-3d.png b/docs/preview-3d.png new file mode 100644 index 0000000000000000000000000000000000000000..57cc3ecd7442fd0aeb40aa7a7ff7d91e2791dcc6 GIT binary patch literal 242934 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%0D4Ug`DbFxJF`` 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 z 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 literal 0 HcmV?d00001 diff --git a/esbuild.mjs b/esbuild.mjs index 9ce0143..ba1b66e 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -33,16 +33,27 @@ if (tests) { sourcemap: true, }); } else { - const ctx = await esbuild.context({ - ...common, - entryPoints: ['src/extension.ts'], - outfile: 'dist/extension.js', - external: ['vscode'], - }); + const contexts = await Promise.all([ + esbuild.context({ + ...common, + entryPoints: ['src/extension.ts'], + outfile: 'dist/extension.js', + external: ['vscode'], + }), + // Runs inside the Archify page in the webview; three.js is bundled in. + esbuild.context({ + ...common, + format: 'iife', + platform: 'browser', + target: 'es2022', + entryPoints: ['src/viewer3d/index.ts'], + outfile: 'dist/viewer3d.js', + }), + ]); if (watch) { - await ctx.watch(); + await Promise.all(contexts.map((ctx) => ctx.watch())); } else { - await ctx.rebuild(); - await ctx.dispose(); + await Promise.all(contexts.map((ctx) => ctx.rebuild())); + await Promise.all(contexts.map((ctx) => ctx.dispose())); } } diff --git a/package-lock.json b/package-lock.json index 347c168..8f87824 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,20 @@ { "name": "archify-vscode-ext", - "version": "0.5.2", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "archify-vscode-ext", - "version": "0.5.2", + "version": "0.6.0", "license": "MIT", "dependencies": { + "three": "^0.186.1", "yaml": "^2.9.1" }, "devDependencies": { "@types/node": "^22.0.0", + "@types/three": "^0.186.0", "@types/vscode": "~1.100.0", "@vscode/test-electron": "^3.1.0", "@vscode/vsce": "^3.0.0", @@ -246,6 +248,13 @@ "node": ">=6.9.0" } }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -999,6 +1008,13 @@ "@textlint/ast-node-types": "15.8.0" } }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.20.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.4.tgz", @@ -1023,6 +1039,28 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.186.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.186.0.tgz", + "integrity": "sha512-mxYSBpDC+D0pLfSP6sW4WZTcT+nrtmZcimMqnVmy36Hte3XpeYSrvgg4TRdaM1GemGog1AWzI5qL2VoIfMXbJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": ">=0.5.17", + "fflate": "~0.8.3", + "meshoptimizer": "~1.1.1" + } + }, "node_modules/@types/vscode": { "version": "1.100.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.100.0.tgz", @@ -1030,6 +1068,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "dev": true, + "license": "MIT" + }, "node_modules/@typespec/ts-http-runtime": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.9.tgz", @@ -2162,6 +2207,13 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3097,6 +3149,13 @@ "node": ">= 8" } }, + "node_modules/meshoptimizer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz", + "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==", + "dev": true, + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4423,6 +4482,12 @@ "url": "https://bevry.me/fund" } }, + "node_modules/three": { + "version": "0.186.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.186.1.tgz", + "integrity": "sha512-blFeqb49wRCSGUGj7gtpfnSGHy2lwDk94RhUmS1c/hTby70kvChbWpkJ4Pm1390LqzzvTmzgXKHPEafJwCb8jA==", + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", diff --git a/package.json b/package.json index 244fd96..7a1b28b 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "archify-vscode-ext", "displayName": "Archify Diagram Viewer", - "description": "Archify diagrams in VS Code. Write diagrams as .archify files in YAML: they open as the interactive diagram, with live updates, validation, templates and completion. Also previews Archify's own JSON files (*.architecture.json, \u2026) and opens rendered Archify HTML.", - "version": "0.5.2", + "description": "Archify diagrams in VS Code. Write diagrams as .archify files in YAML: they open as the interactive diagram, with live updates, validation, templates and completion. Also previews Archify's own JSON files (*.architecture.json, …) and opens rendered Archify HTML.", + "version": "0.6.0", "publisher": "root-at-skic", "license": "MIT", "icon": "media/icon.png", @@ -41,7 +41,7 @@ "commands": [ { "command": "archify.newDiagram", - "title": "Archify | New Diagram\u2026", + "title": "Archify | New Diagram…", "icon": "$(new-file)" }, { @@ -61,7 +61,7 @@ }, { "command": "archify.renderToHtml", - "title": "Archify | Render to HTML File\u2026", + "title": "Archify | Render to HTML File…", "icon": "$(file-code)" }, { @@ -257,6 +257,19 @@ "default": "vscode", "description": "Theme used when showing Archify diagrams." }, + "archify.defaultView": { + "type": "string", + "enum": [ + "2d", + "3d" + ], + "enumDescriptions": [ + "Open diagrams as Archify draws them.", + "Open diagrams in the 3D view. The 3D button in the diagram toolbar switches back." + ], + "default": "2d", + "description": "How diagrams open. Each open diagram remembers its own choice." + }, "archify.preview.liveUpdate": { "type": "boolean", "default": true, @@ -340,6 +353,7 @@ }, "devDependencies": { "@types/node": "^22.0.0", + "@types/three": "^0.186.0", "@types/vscode": "~1.100.0", "@vscode/test-electron": "^3.1.0", "@vscode/vsce": "^3.0.0", @@ -353,6 +367,7 @@ }, "homepage": "https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext#readme", "dependencies": { + "three": "^0.186.1", "yaml": "^2.9.1" } } diff --git a/src/renderer.ts b/src/archify/cli.ts similarity index 98% rename from src/renderer.ts rename to src/archify/cli.ts index add4c06..808f3c0 100644 --- a/src/renderer.ts +++ b/src/archify/cli.ts @@ -1,7 +1,8 @@ import { spawn } from 'node:child_process'; import fs from 'node:fs/promises'; import path from 'node:path'; -import { DiagramSource, DiagramType, hasSourceEvidence, stripSourceEvidence } from './detect'; +import { DiagramSource, DiagramType } from '../sources/detect'; +import { hasSourceEvidence, stripSourceEvidence } from '../sources/evidence'; export interface RuntimeOptions { /** Absolute path to vendor/archify/bin/archify.mjs. */ diff --git a/src/runtime.ts b/src/archify/runtime.ts similarity index 66% rename from src/runtime.ts rename to src/archify/runtime.ts index b5286ac..41841b8 100644 --- a/src/runtime.ts +++ b/src/archify/runtime.ts @@ -1,15 +1,8 @@ import { execFile } from 'node:child_process'; import path from 'node:path'; import * as vscode from 'vscode'; -import { RuntimeOptions } from './renderer'; -import { ViewerTheme } from './webviewHtml'; - -let output: vscode.LogOutputChannel | undefined; - -export function log(): vscode.LogOutputChannel { - output ??= vscode.window.createOutputChannel('Archify', { log: true }); - return output; -} +import { log } from '../util/log'; +import { RuntimeOptions } from './cli'; const gitRoots = new Map>(); @@ -60,18 +53,3 @@ export async function runtimeFor(context: vscode.ExtensionContext, uri: vscode.U export function evidenceMode(uri: vscode.Uri | undefined): 'fallback' | 'strict' { return vscode.workspace.getConfiguration('archify', uri).get<'fallback' | 'strict'>('sourceEvidence', 'fallback'); } - -export function viewerTheme(): ViewerTheme { - const setting = vscode.workspace.getConfiguration('archify').get('theme', 'vscode'); - if (setting === 'dark' || setting === 'light') return setting; - if (setting === 'diagram') return undefined; - const kind = vscode.window.activeColorTheme.kind; - return kind === vscode.ColorThemeKind.Light || kind === vscode.ColorThemeKind.HighContrastLight ? 'light' : 'dark'; -} - -/** Current text of a document: the open editor buffer, else the file on disk. */ -export async function readText(uri: vscode.Uri): Promise { - const open = vscode.workspace.textDocuments.find((doc) => doc.uri.toString() === uri.toString()); - if (open) return open.getText(); - return new TextDecoder('utf-8').decode(await vscode.workspace.fs.readFile(uri)); -} diff --git a/src/commands/context.ts b/src/commands/context.ts new file mode 100644 index 0000000..5909150 --- /dev/null +++ b/src/commands/context.ts @@ -0,0 +1,38 @@ +import * as vscode from 'vscode'; +import { HTML_VIEW_TYPE, HtmlViewerProvider } from '../views/htmlViewer'; +import { DiagramViewManager } from '../views/diagramView'; + +/** What command handlers share: the extension context and the open views. */ +export interface CommandContext { + extension: vscode.ExtensionContext; + views: DiagramViewManager; + htmlViewer: HtmlViewerProvider; +} + +export function isHtmlUri(uri: vscode.Uri): boolean { + return /\.html?$/i.test(uri.path); +} + +/** The file a command applies to: explicit argument, focused preview or viewer, then the text editor. */ +export function targetUri(ctx: CommandContext, arg?: unknown): vscode.Uri | undefined { + if (arg instanceof vscode.Uri) return arg; + return ctx.views.activePreview?.uri ?? ctx.htmlViewer.activeUri ?? vscode.window.activeTextEditor?.document.uri; +} + +export function sourceUri(ctx: CommandContext, arg?: unknown): vscode.Uri | undefined { + const uri = targetUri(ctx, arg); + if (!uri) vscode.window.showWarningMessage('Archify: open a .archify diagram first.'); + return uri; +} + +export const openHtmlViewer = (uri: vscode.Uri, viewColumn?: vscode.ViewColumn) => + vscode.commands.executeCommand('vscode.openWith', uri, HTML_VIEW_TYPE, viewColumn); + +export async function exists(uri: vscode.Uri): Promise { + try { + await vscode.workspace.fs.stat(uri); + return true; + } catch { + return false; + } +} diff --git a/src/commands/contextKeys.ts b/src/commands/contextKeys.ts new file mode 100644 index 0000000..6aaf8b9 --- /dev/null +++ b/src/commands/contextKeys.ts @@ -0,0 +1,34 @@ +import * as vscode from 'vscode'; +import { clearGitRootCache } from '../archify/runtime'; +import { isArchifyFile, isArchifyHtml, isDiagramFileName, readDiagramSource } from '../sources/detect'; + +const JSON_LANGUAGES = new Set(['json', 'jsonc']); + +/** Keeps the archify.* context keys (editor title buttons, keybindings) up to date. */ +export function registerContextKeys(context: vscode.ExtensionContext): void { + let contextTimer: NodeJS.Timeout | undefined; + const updateContext = () => { + const doc = vscode.window.activeTextEditor?.document; + const diagram = Boolean( + doc && + (isArchifyFile(doc.fileName) || + (JSON_LANGUAGES.has(doc.languageId) && (isDiagramFileName(doc.fileName) || readDiagramSource(doc.getText(), doc.fileName)))), + ); + const html = Boolean(doc && doc.languageId === 'html' && isArchifyHtml(doc.getText())); + void vscode.commands.executeCommand('setContext', 'archify.isDiagramSource', diagram); + void vscode.commands.executeCommand('setContext', 'archify.isArchifyHtml', html); + }; + context.subscriptions.push( + vscode.window.onDidChangeActiveTextEditor(updateContext), + vscode.workspace.onDidChangeTextDocument((event) => { + if (event.document !== vscode.window.activeTextEditor?.document) return; + clearTimeout(contextTimer); + contextTimer = setTimeout(updateContext, 300); + }), + vscode.workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration('archify.repoRoot')) clearGitRootCache(); + }), + { dispose: () => clearTimeout(contextTimer) }, + ); + updateContext(); +} diff --git a/src/commands/newDiagram.ts b/src/commands/newDiagram.ts new file mode 100644 index 0000000..dd45852 --- /dev/null +++ b/src/commands/newDiagram.ts @@ -0,0 +1,59 @@ +import * as vscode from 'vscode'; +import { diagramFromTemplate, diagramName, invalidNameReason, TEMPLATES } from '../language/templates'; +import { diagramBaseName, isArchifyFile } from '../sources/detect'; +import { readText } from '../util/files'; +import { DIAGRAM_EDITOR_VIEW_TYPE } from '../views/diagramView'; +import { CommandContext, exists } from './context'; + +/** Folder for a new diagram: the clicked folder (or a file's folder), else a workspace folder. */ +async function newDiagramFolder(arg?: unknown): Promise { + if (arg instanceof vscode.Uri) { + const stat = await vscode.workspace.fs.stat(arg); + return stat.type & vscode.FileType.Directory ? arg : vscode.Uri.joinPath(arg, '..'); + } + const folders = vscode.workspace.workspaceFolders ?? []; + if (folders.length <= 1) return folders[0]?.uri; + return (await vscode.window.showWorkspaceFolderPick({ placeHolder: 'Folder for the new diagram' }))?.uri; +} + +export function newDiagramCommands(ctx: CommandContext): vscode.Disposable[] { + return [ + vscode.commands.registerCommand('archify.newDiagram', async (arg?: unknown) => { + const template = await vscode.window.showQuickPick( + TEMPLATES.map((item) => ({ label: item.label, description: item.description, template: item })), + { title: 'Archify | New Diagram', placeHolder: 'Kind of diagram' }, + ); + if (!template) return; + const { file, defaultName } = template.template; + + const folder = await newDiagramFolder(arg); + let target: vscode.Uri | undefined; + if (folder) { + const name = await vscode.window.showInputBox({ + title: 'Archify | New Diagram', + prompt: `File name in ${vscode.workspace.asRelativePath(folder, true)}`, + value: defaultName, + validateInput: async (value) => + invalidNameReason(value) ?? + ((await exists(vscode.Uri.joinPath(folder, `${diagramName(value)}.archify`))) ? `${diagramName(value)}.archify already exists.` : undefined), + }); + if (name === undefined) return; + target = vscode.Uri.joinPath(folder, `${diagramName(name)}.archify`); + } else { + // No workspace: let the user choose where to save. + target = await vscode.window.showSaveDialog({ + title: 'Archify | New Diagram', + filters: { 'Archify Diagram': ['archify'] }, + saveLabel: 'Create', + }); + if (!target) return; + if (!isArchifyFile(target.path)) target = target.with({ path: `${target.path}.archify` }); + } + + const templateText = await readText(vscode.Uri.joinPath(ctx.extension.extensionUri, 'templates', file)); + const text = diagramFromTemplate(templateText, diagramBaseName(target.path)); + await vscode.workspace.fs.writeFile(target, Buffer.from(text, 'utf8')); + await vscode.commands.executeCommand('vscode.openWith', target, DIAGRAM_EDITOR_VIEW_TYPE); + }), + ]; +} diff --git a/src/commands/renderCommands.ts b/src/commands/renderCommands.ts new file mode 100644 index 0000000..deb0f44 --- /dev/null +++ b/src/commands/renderCommands.ts @@ -0,0 +1,90 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import * as vscode from 'vscode'; +import { isEvidenceError, renderDiagram, renderToFile } from '../archify/cli'; +import { evidenceMode, runtimeFor } from '../archify/runtime'; +import { diagramBaseName, readDiagramSource } from '../sources/detect'; +import { log } from '../util/log'; +import { readText } from '../util/files'; +import { CommandContext, isHtmlUri, openHtmlViewer, sourceUri } from './context'; + +export function renderCommands(ctx: CommandContext): vscode.Disposable[] { + return [ + vscode.commands.registerCommand('archify.renderToHtml', async (arg?: unknown) => { + const uri = sourceUri(ctx, arg); + if (!uri) return; + const text = await readText(uri); + const source = readDiagramSource(text, uri.path); + if (!source) { + vscode.window.showWarningMessage(`Archify: ${path.basename(uri.path)} has no "diagram_type" (architecture, workflow, sequence, dataflow or lifecycle).`); + return; + } + const meta = (source.diagram.meta ?? {}) as { output?: unknown }; + const authored = typeof meta.output === 'string' && !/[\\/]/.test(meta.output) ? meta.output : undefined; + const fileName = authored ?? `${diagramBaseName(uri.path)}.html`; + const defaultUri = uri.scheme === 'file' ? vscode.Uri.joinPath(uri, '..', fileName) : undefined; + const target = await vscode.window.showSaveDialog({ + defaultUri, + filters: { HTML: ['html'] }, + saveLabel: 'Render', + }); + if (!target) return; + + const runtime = await runtimeFor(ctx.extension, uri); + let result = await vscode.window.withProgress( + { location: vscode.ProgressLocation.Notification, title: 'Archify: rendering…' }, + () => renderToFile(runtime, source, target.fsPath), + ); + if (!result.ok && isEvidenceError(result.error)) { + const choice = await vscode.window.showWarningMessage( + 'Archify could not verify the diagram\'s source evidence against a local checkout.', + { modal: true, detail: result.error }, + 'Render Without Source Links', + ); + if (choice) result = await renderToFile(runtime, source, target.fsPath, true); + else return; + } + if (!result.ok) { + log().error(result.error ?? 'render failed'); + vscode.window.showErrorMessage('Archify: render failed.', { modal: true, detail: result.error }); + return; + } + const choice = await vscode.window.showInformationMessage( + `Rendered ${path.basename(target.fsPath)}`, + 'Open in Viewer', + 'Open in Browser', + ); + if (choice === 'Open in Viewer') await openHtmlViewer(target); + if (choice === 'Open in Browser') await vscode.env.openExternal(target); + }), + + vscode.commands.registerCommand('archify.openInBrowser', async (arg?: unknown) => { + const uri = sourceUri(ctx, arg); + if (!uri) return; + if (isHtmlUri(uri)) { + await vscode.env.openExternal(uri); + return; + } + const text = await readText(uri); + const source = readDiagramSource(text, uri.path); + if (!source) { + vscode.window.showWarningMessage(`Archify: ${path.basename(uri.path)} has no "diagram_type" (architecture, workflow, sequence, dataflow or lifecycle).`); + return; + } + const runtime = await runtimeFor(ctx.extension, uri); + const result = await vscode.window.withProgress( + { location: vscode.ProgressLocation.Notification, title: 'Archify: rendering…' }, + () => renderDiagram(runtime, source, evidenceMode(uri)), + ); + if (!result.ok || !result.html) { + vscode.window.showErrorMessage('Archify: render failed.', { modal: true, detail: result.error }); + return; + } + const dir = path.join(ctx.extension.globalStorageUri.fsPath, 'browser'); + await fs.mkdir(dir, { recursive: true }); + const file = path.join(dir, `${diagramBaseName(uri.path)}.html`); + await fs.writeFile(file, result.html, 'utf8'); + await vscode.env.openExternal(vscode.Uri.file(file)); + }), + ]; +} diff --git a/src/commands/viewCommands.ts b/src/commands/viewCommands.ts new file mode 100644 index 0000000..78af58e --- /dev/null +++ b/src/commands/viewCommands.ts @@ -0,0 +1,52 @@ +import * as vscode from 'vscode'; +import { isArchifyFile } from '../sources/detect'; +import { DIAGRAM_EDITOR_VIEW_TYPE } from '../views/diagramView'; +import { CommandContext, isHtmlUri, openHtmlViewer, sourceUri } from './context'; + +export function viewCommands(ctx: CommandContext): vscode.Disposable[] { + return [ + vscode.commands.registerCommand('archify.showPreview', (arg?: unknown) => { + const uri = sourceUri(ctx, arg); + if (!uri) return; + if (isHtmlUri(uri)) return openHtmlViewer(uri); + if (isArchifyFile(uri.path)) return vscode.commands.executeCommand('vscode.openWith', uri, DIAGRAM_EDITOR_VIEW_TYPE); + ctx.views.show(uri, vscode.window.activeTextEditor?.viewColumn ?? vscode.ViewColumn.Active); + }), + + vscode.commands.registerCommand('archify.showPreviewToSide', (arg?: unknown) => { + const uri = sourceUri(ctx, arg); + if (!uri) return; + if (isHtmlUri(uri)) return openHtmlViewer(uri, vscode.ViewColumn.Beside); + if (isArchifyFile(uri.path)) { + return vscode.commands.executeCommand('vscode.openWith', uri, DIAGRAM_EDITOR_VIEW_TYPE, { + viewColumn: vscode.ViewColumn.Beside, + preserveFocus: true, + }); + } + ctx.views.show(uri, vscode.ViewColumn.Beside, true); + }), + + vscode.commands.registerCommand('archify.openHtmlViewer', (arg?: unknown) => { + const uri = sourceUri(ctx, arg); + if (uri) return openHtmlViewer(uri); + }), + + vscode.commands.registerCommand('archify.refreshPreview', () => ctx.views.activePreview?.update(true)), + + vscode.commands.registerCommand('archify.showSource', async () => { + const preview = ctx.views.activePreview; + if (preview?.isEditor) { + // The diagram is the editor; open the YAML text beside it. + await vscode.commands.executeCommand('vscode.openWith', preview.uri, 'default', vscode.ViewColumn.Beside); + return; + } + if (preview) { + const column = preview.panel.viewColumn === vscode.ViewColumn.One ? vscode.ViewColumn.Two : vscode.ViewColumn.One; + await vscode.window.showTextDocument(preview.uri, { viewColumn: column }); + return; + } + const html = ctx.htmlViewer.activeUri; + if (html) await vscode.commands.executeCommand('vscode.openWith', html, 'default'); + }), + ]; +} diff --git a/src/extension.ts b/src/extension.ts index 55d8a0c..8d0d029 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,254 +1,32 @@ -import fs from 'node:fs/promises'; -import path from 'node:path'; import * as vscode from 'vscode'; -import { BRAND_COMPLETION_SELECTOR, BrandCompletionProvider } from './brandCompletion'; -import { diagramBaseName, isArchifyFile, isArchifyHtml, isDiagramFileName, readDiagramSource } from './detect'; -import { DiagnosticsController } from './diagnostics'; -import { HTML_VIEW_TYPE, HtmlViewerProvider } from './htmlViewer'; -import { DIAGRAM_EDITOR_VIEW_TYPE, PreviewManager } from './preview'; -import { isEvidenceError, renderDiagram, renderToFile } from './renderer'; -import { clearGitRootCache, evidenceMode, log, readText, runtimeFor } from './runtime'; -import { diagramFromTemplate, diagramName, invalidNameReason, TEMPLATES } from './templates'; - -const JSON_LANGUAGES = new Set(['json', 'jsonc']); - -function isHtmlUri(uri: vscode.Uri): boolean { - return /\.html?$/i.test(uri.path); -} +import { CommandContext } from './commands/context'; +import { registerContextKeys } from './commands/contextKeys'; +import { newDiagramCommands } from './commands/newDiagram'; +import { renderCommands } from './commands/renderCommands'; +import { viewCommands } from './commands/viewCommands'; +import { BRAND_COMPLETION_SELECTOR, BrandCompletionProvider } from './language/brandCompletion'; +import { DiagnosticsController } from './language/diagnostics'; +import { log } from './util/log'; +import { HtmlViewerProvider } from './views/htmlViewer'; +import { DiagramViewManager } from './views/diagramView'; export function activate(context: vscode.ExtensionContext): void { - const previews = new PreviewManager(context); - const htmlViewer = new HtmlViewerProvider(); + const ctx: CommandContext = { + extension: context, + views: new DiagramViewManager(context), + htmlViewer: new HtmlViewerProvider(context.extensionUri), + }; context.subscriptions.push( - previews, - htmlViewer, + ctx.views, + ctx.htmlViewer, new DiagnosticsController(context), log(), vscode.languages.registerCompletionItemProvider(BRAND_COMPLETION_SELECTOR, new BrandCompletionProvider(context.extensionPath), ' ', '"'), + ...viewCommands(ctx), + ...newDiagramCommands(ctx), + ...renderCommands(ctx), ); - - /** The file a command applies to: explicit argument, focused preview or viewer, then the text editor. */ - function targetUri(arg?: unknown): vscode.Uri | undefined { - if (arg instanceof vscode.Uri) return arg; - return previews.activePreview?.uri ?? htmlViewer.activeUri ?? vscode.window.activeTextEditor?.document.uri; - } - - function sourceUri(arg?: unknown): vscode.Uri | undefined { - const uri = targetUri(arg); - if (!uri) vscode.window.showWarningMessage('Archify: open a .archify diagram first.'); - return uri; - } - - // Context keys for editor title buttons and keybindings. - let contextTimer: NodeJS.Timeout | undefined; - const updateContext = () => { - const doc = vscode.window.activeTextEditor?.document; - const diagram = Boolean( - doc && - (isArchifyFile(doc.fileName) || - (JSON_LANGUAGES.has(doc.languageId) && (isDiagramFileName(doc.fileName) || readDiagramSource(doc.getText(), doc.fileName)))), - ); - const html = Boolean(doc && doc.languageId === 'html' && isArchifyHtml(doc.getText())); - void vscode.commands.executeCommand('setContext', 'archify.isDiagramSource', diagram); - void vscode.commands.executeCommand('setContext', 'archify.isArchifyHtml', html); - }; - context.subscriptions.push( - vscode.window.onDidChangeActiveTextEditor(updateContext), - vscode.workspace.onDidChangeTextDocument((event) => { - if (event.document !== vscode.window.activeTextEditor?.document) return; - clearTimeout(contextTimer); - contextTimer = setTimeout(updateContext, 300); - }), - vscode.workspace.onDidChangeConfiguration((event) => { - if (event.affectsConfiguration('archify.repoRoot')) clearGitRootCache(); - }), - { dispose: () => clearTimeout(contextTimer) }, - ); - updateContext(); - - const openHtmlViewer = (uri: vscode.Uri, viewColumn?: vscode.ViewColumn) => - vscode.commands.executeCommand('vscode.openWith', uri, HTML_VIEW_TYPE, viewColumn); - - const exists = async (uri: vscode.Uri) => { - try { - await vscode.workspace.fs.stat(uri); - return true; - } catch { - return false; - } - }; - - /** Folder for a new diagram: the clicked folder (or a file's folder), else a workspace folder. */ - async function newDiagramFolder(arg?: unknown): Promise { - if (arg instanceof vscode.Uri) { - const stat = await vscode.workspace.fs.stat(arg); - return stat.type & vscode.FileType.Directory ? arg : vscode.Uri.joinPath(arg, '..'); - } - const folders = vscode.workspace.workspaceFolders ?? []; - if (folders.length <= 1) return folders[0]?.uri; - return (await vscode.window.showWorkspaceFolderPick({ placeHolder: 'Folder for the new diagram' }))?.uri; - } - - context.subscriptions.push( - vscode.commands.registerCommand('archify.showPreview', (arg?: unknown) => { - const uri = sourceUri(arg); - if (!uri) return; - if (isHtmlUri(uri)) return openHtmlViewer(uri); - if (isArchifyFile(uri.path)) return vscode.commands.executeCommand('vscode.openWith', uri, DIAGRAM_EDITOR_VIEW_TYPE); - previews.show(uri, vscode.window.activeTextEditor?.viewColumn ?? vscode.ViewColumn.Active); - }), - - vscode.commands.registerCommand('archify.showPreviewToSide', (arg?: unknown) => { - const uri = sourceUri(arg); - if (!uri) return; - if (isHtmlUri(uri)) return openHtmlViewer(uri, vscode.ViewColumn.Beside); - if (isArchifyFile(uri.path)) { - return vscode.commands.executeCommand('vscode.openWith', uri, DIAGRAM_EDITOR_VIEW_TYPE, { - viewColumn: vscode.ViewColumn.Beside, - preserveFocus: true, - }); - } - previews.show(uri, vscode.ViewColumn.Beside, true); - }), - - vscode.commands.registerCommand('archify.openHtmlViewer', (arg?: unknown) => { - const uri = sourceUri(arg); - if (uri) return openHtmlViewer(uri); - }), - - vscode.commands.registerCommand('archify.newDiagram', async (arg?: unknown) => { - const template = await vscode.window.showQuickPick( - TEMPLATES.map((item) => ({ label: item.label, description: item.description, template: item })), - { title: 'Archify | New Diagram', placeHolder: 'Kind of diagram' }, - ); - if (!template) return; - const { file, defaultName } = template.template; - - const folder = await newDiagramFolder(arg); - let target: vscode.Uri | undefined; - if (folder) { - const name = await vscode.window.showInputBox({ - title: 'Archify | New Diagram', - prompt: `File name in ${vscode.workspace.asRelativePath(folder, true)}`, - value: defaultName, - validateInput: async (value) => - invalidNameReason(value) ?? - ((await exists(vscode.Uri.joinPath(folder, `${diagramName(value)}.archify`))) ? `${diagramName(value)}.archify already exists.` : undefined), - }); - if (name === undefined) return; - target = vscode.Uri.joinPath(folder, `${diagramName(name)}.archify`); - } else { - // No workspace: let the user choose where to save. - target = await vscode.window.showSaveDialog({ - title: 'Archify | New Diagram', - filters: { 'Archify Diagram': ['archify'] }, - saveLabel: 'Create', - }); - if (!target) return; - if (!isArchifyFile(target.path)) target = target.with({ path: `${target.path}.archify` }); - } - - const templateText = await readText(vscode.Uri.joinPath(context.extensionUri, 'templates', file)); - const text = diagramFromTemplate(templateText, diagramBaseName(target.path)); - await vscode.workspace.fs.writeFile(target, Buffer.from(text, 'utf8')); - await vscode.commands.executeCommand('vscode.openWith', target, DIAGRAM_EDITOR_VIEW_TYPE); - }), - - vscode.commands.registerCommand('archify.refreshPreview', () => previews.activePreview?.update(true)), - - vscode.commands.registerCommand('archify.showSource', async () => { - const preview = previews.activePreview; - if (preview?.isEditor) { - // The diagram is the editor; open the YAML text beside it. - await vscode.commands.executeCommand('vscode.openWith', preview.uri, 'default', vscode.ViewColumn.Beside); - return; - } - if (preview) { - const column = preview.panel.viewColumn === vscode.ViewColumn.One ? vscode.ViewColumn.Two : vscode.ViewColumn.One; - await vscode.window.showTextDocument(preview.uri, { viewColumn: column }); - return; - } - const html = htmlViewer.activeUri; - if (html) await vscode.commands.executeCommand('vscode.openWith', html, 'default'); - }), - - vscode.commands.registerCommand('archify.renderToHtml', async (arg?: unknown) => { - const uri = sourceUri(arg); - if (!uri) return; - const text = await readText(uri); - const source = readDiagramSource(text, uri.path); - if (!source) { - vscode.window.showWarningMessage(`Archify: ${path.basename(uri.path)} has no "diagram_type" (architecture, workflow, sequence, dataflow or lifecycle).`); - return; - } - const meta = (source.diagram.meta ?? {}) as { output?: unknown }; - const authored = typeof meta.output === 'string' && !/[\\/]/.test(meta.output) ? meta.output : undefined; - const fileName = authored ?? `${diagramBaseName(uri.path)}.html`; - const defaultUri = uri.scheme === 'file' ? vscode.Uri.joinPath(uri, '..', fileName) : undefined; - const target = await vscode.window.showSaveDialog({ - defaultUri, - filters: { HTML: ['html'] }, - saveLabel: 'Render', - }); - if (!target) return; - - const runtime = await runtimeFor(context, uri); - let result = await vscode.window.withProgress( - { location: vscode.ProgressLocation.Notification, title: 'Archify: rendering…' }, - () => renderToFile(runtime, source, target.fsPath), - ); - if (!result.ok && isEvidenceError(result.error)) { - const choice = await vscode.window.showWarningMessage( - 'Archify could not verify the diagram\'s source evidence against a local checkout.', - { modal: true, detail: result.error }, - 'Render Without Source Links', - ); - if (choice) result = await renderToFile(runtime, source, target.fsPath, true); - else return; - } - if (!result.ok) { - log().error(result.error ?? 'render failed'); - vscode.window.showErrorMessage('Archify: render failed.', { modal: true, detail: result.error }); - return; - } - const choice = await vscode.window.showInformationMessage( - `Rendered ${path.basename(target.fsPath)}`, - 'Open in Viewer', - 'Open in Browser', - ); - if (choice === 'Open in Viewer') await openHtmlViewer(target); - if (choice === 'Open in Browser') await vscode.env.openExternal(target); - }), - - vscode.commands.registerCommand('archify.openInBrowser', async (arg?: unknown) => { - const uri = sourceUri(arg); - if (!uri) return; - if (isHtmlUri(uri)) { - await vscode.env.openExternal(uri); - return; - } - const text = await readText(uri); - const source = readDiagramSource(text, uri.path); - if (!source) { - vscode.window.showWarningMessage(`Archify: ${path.basename(uri.path)} has no "diagram_type" (architecture, workflow, sequence, dataflow or lifecycle).`); - return; - } - const runtime = await runtimeFor(context, uri); - const result = await vscode.window.withProgress( - { location: vscode.ProgressLocation.Notification, title: 'Archify: rendering…' }, - () => renderDiagram(runtime, source, evidenceMode(uri)), - ); - if (!result.ok || !result.html) { - vscode.window.showErrorMessage('Archify: render failed.', { modal: true, detail: result.error }); - return; - } - const dir = path.join(context.globalStorageUri.fsPath, 'browser'); - await fs.mkdir(dir, { recursive: true }); - const file = path.join(dir, `${diagramBaseName(uri.path)}.html`); - await fs.writeFile(file, result.html, 'utf8'); - await vscode.env.openExternal(vscode.Uri.file(file)); - }), - ); + registerContextKeys(context); } export function deactivate(): void {} diff --git a/src/brandCompletion.ts b/src/language/brandCompletion.ts similarity index 95% rename from src/brandCompletion.ts rename to src/language/brandCompletion.ts index 7d59bda..98c30c8 100644 --- a/src/brandCompletion.ts +++ b/src/language/brandCompletion.ts @@ -1,8 +1,8 @@ import path from 'node:path'; import * as vscode from 'vscode'; +import { isArchifyFile } from '../sources/detect'; +import { log } from '../util/log'; import { Brand, brandValueAt, loadBrandCatalog } from './brands'; -import { isArchifyFile } from './detect'; -import { log } from './runtime'; const JSON_SOURCES = '**/*.{architecture,workflow,sequence,dataflow,lifecycle}.json'; diff --git a/src/brands.ts b/src/language/brands.ts similarity index 100% rename from src/brands.ts rename to src/language/brands.ts diff --git a/src/diagnostics.ts b/src/language/diagnostics.ts similarity index 95% rename from src/diagnostics.ts rename to src/language/diagnostics.ts index 5097945..5376286 100644 --- a/src/diagnostics.ts +++ b/src/language/diagnostics.ts @@ -1,9 +1,10 @@ import { parse, ParseError } from 'jsonc-parser'; import * as vscode from 'vscode'; -import { isArchifyFile, readDiagramSource } from './detect'; +import { ArchifyDiagnostic, isEvidenceError, validate } from '../archify/cli'; +import { evidenceMode, runtimeFor } from '../archify/runtime'; +import { isArchifyFile, readDiagramSource } from '../sources/detect'; +import { log } from '../util/log'; import { diagnosticPointer, rangeForPointer, rangeForPointerYaml, yamlSyntaxProblems } from './jsonPointer'; -import { ArchifyDiagnostic, isEvidenceError, validate } from './renderer'; -import { evidenceMode, log, runtimeFor } from './runtime'; const JSON_LANGUAGES = new Set(['json', 'jsonc']); diff --git a/src/jsonPointer.ts b/src/language/jsonPointer.ts similarity index 100% rename from src/jsonPointer.ts rename to src/language/jsonPointer.ts diff --git a/src/templates.ts b/src/language/templates.ts similarity index 98% rename from src/templates.ts rename to src/language/templates.ts index 79c2517..761141e 100644 --- a/src/templates.ts +++ b/src/language/templates.ts @@ -1,5 +1,5 @@ import { isMap, parseDocument } from 'yaml'; -import { DiagramType } from './detect'; +import { DiagramType } from '../sources/detect'; export interface DiagramTemplate { /** File name under templates/. */ diff --git a/src/detect.ts b/src/sources/detect.ts similarity index 74% rename from src/detect.ts rename to src/sources/detect.ts index 85ccfa9..c7bc343 100644 --- a/src/detect.ts +++ b/src/sources/detect.ts @@ -4,15 +4,6 @@ import { parse as parseYaml } from 'yaml'; export const DIAGRAM_TYPES = ['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle'] as const; export type DiagramType = (typeof DIAGRAM_TYPES)[number]; -/** Node collection that may carry `sources` evidence, per diagram type. */ -export const EVIDENCE_COLLECTIONS: Record = { - architecture: 'components', - workflow: 'nodes', - sequence: 'participants', - dataflow: 'nodes', - lifecycle: 'states', -}; - const FILENAME_RE = /\.(architecture|workflow|sequence|dataflow|lifecycle)\.json$/i; const ARCHIFY_EXT_RE = /\.archify$/i; const MAX_SNIFF_BYTES = 8 * 1024 * 1024; @@ -92,24 +83,3 @@ export function isArchifyHtml(text: string): boolean { const head = text.slice(0, 4096); return /): boolean { - const meta = diagram.meta as Record | undefined; - if (meta && meta.repository) return true; - const nodes = diagram[EVIDENCE_COLLECTIONS[type]]; - return Array.isArray(nodes) && nodes.some((node) => Array.isArray(node?.sources) && node.sources.length > 0); -} - -/** Returns a copy of the diagram with repository evidence removed. */ -export function stripSourceEvidence(type: DiagramType, diagram: Record): Record { - const copy = structuredClone(diagram); - const meta = copy.meta as Record | undefined; - if (meta) delete meta.repository; - const nodes = copy[EVIDENCE_COLLECTIONS[type]]; - if (Array.isArray(nodes)) { - for (const node of nodes) { - if (node && typeof node === 'object') delete node.sources; - } - } - return copy; -} diff --git a/src/sources/evidence.ts b/src/sources/evidence.ts new file mode 100644 index 0000000..69126b0 --- /dev/null +++ b/src/sources/evidence.ts @@ -0,0 +1,31 @@ +import { DiagramType } from './detect'; + +/** Node collection that may carry `sources` evidence, per diagram type. */ +export const EVIDENCE_COLLECTIONS: Record = { + architecture: 'components', + workflow: 'nodes', + sequence: 'participants', + dataflow: 'nodes', + lifecycle: 'states', +}; + +export function hasSourceEvidence(type: DiagramType, diagram: Record): boolean { + const meta = diagram.meta as Record | undefined; + if (meta && meta.repository) return true; + const nodes = diagram[EVIDENCE_COLLECTIONS[type]]; + return Array.isArray(nodes) && nodes.some((node) => Array.isArray(node?.sources) && node.sources.length > 0); +} + +/** Returns a copy of the diagram with repository evidence removed. */ +export function stripSourceEvidence(type: DiagramType, diagram: Record): Record { + const copy = structuredClone(diagram); + const meta = copy.meta as Record | undefined; + if (meta) delete meta.repository; + const nodes = copy[EVIDENCE_COLLECTIONS[type]]; + if (Array.isArray(nodes)) { + for (const node of nodes) { + if (node && typeof node === 'object') delete node.sources; + } + } + return copy; +} diff --git a/src/util/files.ts b/src/util/files.ts new file mode 100644 index 0000000..b5cc00c --- /dev/null +++ b/src/util/files.ts @@ -0,0 +1,8 @@ +import * as vscode from 'vscode'; + +/** Current text of a document: the open editor buffer, else the file on disk. */ +export async function readText(uri: vscode.Uri): Promise { + const open = vscode.workspace.textDocuments.find((doc) => doc.uri.toString() === uri.toString()); + if (open) return open.getText(); + return new TextDecoder('utf-8').decode(await vscode.workspace.fs.readFile(uri)); +} diff --git a/src/util/log.ts b/src/util/log.ts new file mode 100644 index 0000000..356def6 --- /dev/null +++ b/src/util/log.ts @@ -0,0 +1,8 @@ +import * as vscode from 'vscode'; + +let output: vscode.LogOutputChannel | undefined; + +export function log(): vscode.LogOutputChannel { + output ??= vscode.window.createOutputChannel('Archify', { log: true }); + return output; +} diff --git a/src/util/settings.ts b/src/util/settings.ts new file mode 100644 index 0000000..c178dff --- /dev/null +++ b/src/util/settings.ts @@ -0,0 +1,11 @@ +import * as vscode from 'vscode'; +import { ViewerTheme } from '../webview/page'; + +/** Diagram theme from archify.theme: forced, VS Code's, or the diagram's own. */ +export function viewerTheme(): ViewerTheme { + const setting = vscode.workspace.getConfiguration('archify').get('theme', 'vscode'); + if (setting === 'dark' || setting === 'light') return setting; + if (setting === 'diagram') return undefined; + const kind = vscode.window.activeColorTheme.kind; + return kind === vscode.ColorThemeKind.Light || kind === vscode.ColorThemeKind.HighContrastLight ? 'light' : 'dark'; +} diff --git a/src/viewer3d/index.ts b/src/viewer3d/index.ts new file mode 100644 index 0000000..19638a5 --- /dev/null +++ b/src/viewer3d/index.ts @@ -0,0 +1,558 @@ +import { layerMarkup, rasterize } from './raster'; +import { CameraState, Layers, Stage } from './stage'; +import { DiagramModel, readModel } from './svgModel'; +import { STYLE } from './style'; + +/** + * 3D mode for the Archify viewer. It adds a 3D menu to Archify's toolbar + * and, when on, replaces the 2D canvas with a three.js stage built from the + * live SVG. Archify keeps owning the diagram: clicks in 3D go to the SVG + * nodes, so focus, Semantic Passport, reach and lens work as in 2D; the + * stage redraws whenever Archify changes the SVG, theme or preset, and + * follows Archify's animations as they play. + */ + +interface Host { + defaultView: '2d' | '3d'; + post(message: unknown): void; + getState(): Record; + setState(patch: Record): void; +} + +interface SavedView { + active?: boolean; + layered?: boolean; + camera?: CameraState; +} + +declare global { + interface Window { + __archifyHost?: Host; + } +} + +const REDRAW_MS = 220; +const MARGIN = 80; +/** Texture pixels per SVG unit; more costs memory and upload time for little gain. */ +const TEXTURE_SCALE = 2; + +type MenuValue = '2d' | '3d' | 'flat' | 'stacked' | 'reset' | 'top'; + +interface MenuEntry { + value: MenuValue; + title: string; + hint: string; + /** Radio group; actions have none. */ + group?: 'view' | 'levels'; + shortcut?: string; +} + +const MENU: { heading: string; entries: MenuEntry[] }[] = [ + { + heading: 'View', + entries: [ + { value: '2d', group: 'view', title: '2D', hint: 'The diagram as Archify draws it' }, + { value: '3d', group: 'view', title: '3D', hint: 'Blocks and pipes you can turn' }, + ], + }, + { + heading: 'Levels', + entries: [ + { value: 'flat', group: 'levels', title: 'Flat', hint: 'Every node on the floor' }, + { value: 'stacked', group: 'levels', title: 'Stacked', hint: 'Clients on top, data at the bottom' }, + ], + }, + { + heading: 'Camera', + entries: [ + { value: 'reset', title: 'Reset view', hint: 'Show the whole diagram', shortcut: '0' }, + { value: 'top', title: 'From above', hint: 'Look straight down, like 2D' }, + ], + }, +]; + + +class Viewer3D { + private stage: Stage | undefined; + private model: DiagramModel | undefined; + private active = false; + private redrawTimer: number | undefined; + private drawing: Promise | undefined; + private pendingRedraw = false; + private readonly observer = new MutationObserver((records) => this.onMutation(records)); + /** Keeps Archify's zoom buttons usable in 3D (see holdZoomButtons). */ + private readonly zoomObserver = new MutationObserver(() => this.enableZoomButtons()); + private zoomDisabled = new Map(); + private readonly button: HTMLButtonElement; + private readonly menu: HTMLDivElement; + private ambientReplayed = false; + private exportItem: HTMLButtonElement | undefined; + private saved: SavedView; + + constructor( + private readonly host: Host, + private readonly container: HTMLElement, + private readonly svg: SVGSVGElement, + ) { + this.saved = (host.getState().view3d as SavedView | undefined) ?? {}; + ({ button: this.button, menu: this.menu } = this.buildMenu()); + this.addExportItem(); + this.interceptViewerControls(); + if (this.saved.active ?? host.defaultView === '3d') this.setActive(true); + } + + // -------------------------------------------------------------- chrome + + /** + * The 3D menu: a toolbar button with a dropdown, built from Archify's own + * style-menu classes so it looks and behaves like the menus beside it. + */ + private buildMenu(): { button: HTMLButtonElement; menu: HTMLDivElement } { + const wrap = document.createElement('div'); + wrap.className = 'preset-wrap archify-3d-wrap'; + const button = document.createElement('button'); + button.id = 'btn-3d'; + button.type = 'button'; + button.title = '3D view: switch 2D / 3D, levels and camera'; + button.setAttribute('aria-label', '3D view options'); + button.setAttribute('aria-haspopup', 'menu'); + button.setAttribute('aria-expanded', 'false'); + button.setAttribute('aria-controls', 'archify-3d-menu'); + // Same make-up as Archify's Export button: a label and a chevron. + button.innerHTML = '3D '; + + const menu = document.createElement('div'); + menu.id = 'archify-3d-menu'; + menu.className = 'preset-menu archify-3d-menu'; + menu.setAttribute('role', 'menu'); + menu.setAttribute('aria-label', 'View options'); + for (const section of MENU) { + const heading = document.createElement('div'); + heading.className = 'preset-menu-heading'; + heading.setAttribute('role', 'presentation'); + heading.innerHTML = `${section.heading}`; + menu.appendChild(heading); + for (const entry of section.entries) { + const option = document.createElement('button'); + option.type = 'button'; + option.className = 'preset-option archify-3d-option'; + option.tabIndex = -1; + option.dataset.archify3d = entry.value; + option.setAttribute('role', entry.group ? 'menuitemradio' : 'menuitem'); + if (entry.group) option.setAttribute('aria-checked', 'false'); + option.innerHTML = + `` + + `${entry.title}${entry.hint}` + + (entry.group + ? '' + : ``); + option.addEventListener('click', (event) => { + event.stopPropagation(); + if (option.getAttribute('aria-disabled') === 'true') return; + this.choose(entry.value); + if (!entry.group) this.closeMenu(); + else this.syncButtons(); + }); + menu.appendChild(option); + } + } + + button.addEventListener('click', (event) => { + event.stopPropagation(); + if (menu.classList.contains('open')) this.closeMenu(); + else this.openMenu(); + }); + menu.addEventListener('keydown', (event) => this.menuKey(event)); + document.addEventListener('click', (event) => { + if (menu.classList.contains('open') && !wrap.contains(event.target as Node)) this.closeMenu(); + }); + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && menu.classList.contains('open')) { + this.closeMenu(); + button.focus(); + } + }); + + wrap.append(button, menu); + // Beside Export, outside the tools capsule, as its twin. + const exportWrap = document.querySelector('.toolbar .export-wrap'); + if (exportWrap?.parentElement) exportWrap.parentElement.insertBefore(wrap, exportWrap); + else document.querySelector('.toolbar')?.appendChild(wrap); + return { button, menu }; + } + + private options(): HTMLButtonElement[] { + return [...this.menu.querySelectorAll('.archify-3d-option')].filter((o) => o.getAttribute('aria-disabled') !== 'true'); + } + + private openMenu(): void { + // Only one toolbar menu at a time: close Archify's own. + document.querySelectorAll('.toolbar .preset-menu.open, .toolbar .export-menu.open').forEach((m) => { + if (m !== this.menu) (m.previousElementSibling as HTMLElement | null)?.click(); + }); + this.syncButtons(); + this.menu.classList.add('open'); + this.button.setAttribute('aria-expanded', 'true'); + const checked = this.options().find((o) => o.getAttribute('aria-checked') === 'true'); + (checked ?? this.options()[0])?.focus(); + } + + private closeMenu(): void { + this.menu.classList.remove('open'); + this.button.setAttribute('aria-expanded', 'false'); + } + + private menuKey(event: KeyboardEvent): void { + const options = this.options(); + const index = options.indexOf(document.activeElement as HTMLButtonElement); + const move = (to: number) => { + event.preventDefault(); + options[(to + options.length) % options.length]?.focus(); + }; + if (event.key === 'ArrowDown') move(index + 1); + else if (event.key === 'ArrowUp') move(index - 1); + else if (event.key === 'Home') move(0); + else if (event.key === 'End') move(options.length - 1); + else if (event.key === 'Tab') this.closeMenu(); + } + + private choose(value: MenuValue): void { + switch (value) { + case '2d': + case '3d': + if ((value === '3d') !== this.active) this.setActive(value === '3d'); + return; + case 'flat': + case 'stacked': + this.stage?.setLayered(value === 'stacked'); + this.save(); + return; + case 'reset': + this.stage?.fit(); + return; + case 'top': + this.stage?.top(); + return; + } + } + + private addExportItem(): void { + const png = document.querySelector('.export-menu button[data-format="png"]'); + if (!png?.parentElement) return; + const item = document.createElement('button'); + item.type = 'button'; + item.setAttribute('role', 'menuitem'); + item.tabIndex = -1; + item.hidden = true; + item.dataset.archify3dExport = ''; + item.innerHTML = '3D viewPNG of the current 3D view'; + item.addEventListener('click', (event) => { + event.stopPropagation(); + document.getElementById('btn-export')?.click(); + this.exportPng(); + }); + png.parentElement.insertBefore(item, png); + this.exportItem = item; + } + + /** Archify's zoom buttons and keys drive the 3D camera while 3D is on. */ + private interceptViewerControls(): void { + this.container.addEventListener( + 'click', + (event) => { + if (!this.active || !this.stage) return; + const button = (event.target as Element | null)?.closest?.('[data-view]'); + const view = button?.getAttribute('data-view'); + if (!view) return; + event.stopPropagation(); + event.preventDefault(); + if (view === 'in') this.stage.zoom(1.25); + else if (view === 'out') this.stage.zoom(0.8); + else if (view === 'reset') this.stage.fit(); + }, + true, + ); + window.addEventListener( + 'keydown', + (event) => { + if (!this.active || !this.stage || event.metaKey || event.ctrlKey || event.altKey) return; + const target = event.target as HTMLElement | null; + if (target && (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName))) return; + const actions: Record void> = { + '0': () => this.stage!.fit(), + '+': () => this.stage!.zoom(1.25), + '=': () => this.stage!.zoom(1.25), + '-': () => this.stage!.zoom(0.8), + }; + const action = actions[event.key]; + if (!action) return; + event.stopPropagation(); + event.preventDefault(); + action(); + }, + true, + ); + } + + private syncButtons(): void { + const layered = !!this.stage?.layered; + this.button.toggleAttribute('data-active', this.active); + for (const option of this.menu.querySelectorAll('.archify-3d-option')) { + const value = option.dataset.archify3d as MenuValue; + if (value === '2d' || value === '3d') option.setAttribute('aria-checked', String((value === '3d') === this.active)); + if (value === 'flat' || value === 'stacked') option.setAttribute('aria-checked', String((value === 'stacked') === layered)); + // Levels and camera only apply to the 3D view. + if (value !== '2d' && value !== '3d') option.setAttribute('aria-disabled', String(!this.active)); + } + if (this.exportItem) this.exportItem.hidden = !this.active; + } + + // ---------------------------------------------------------------- mode + + setActive(on: boolean): void { + this.active = on; + document.documentElement.classList.toggle('archify-3d-on', on); + if (on) { + if (!this.stage) this.createStage(); + this.container.appendChild(this.stage!.element); + this.stage!.start(); + this.observer.observe(this.svg, { subtree: true, attributes: true, childList: true, characterData: true }); + this.observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme', 'data-preset'] }); + this.holdZoomButtons(true); + void this.redraw().then(() => this.replayAmbient()); + } else { + this.holdZoomButtons(false); + this.observer.disconnect(); + this.stage?.stop(); + this.stage?.element.remove(); + } + this.syncButtons(); + this.save(); + } + + private zoomButtons(): HTMLButtonElement[] { + return [...this.container.querySelectorAll('button[data-view="in"], button[data-view="out"]')]; + } + + /** + * Archify disables its zoom buttons at the 2D limits (100% and 300%). The + * 3D camera has its own range, so the buttons stay enabled while 3D is on, + * and get Archify's state back when 3D is turned off. + */ + private holdZoomButtons(on: boolean): void { + if (on) { + this.zoomDisabled = new Map(this.zoomButtons().map((b) => [b, b.disabled])); + for (const button of this.zoomButtons()) this.zoomObserver.observe(button, { attributes: true, attributeFilter: ['disabled'] }); + this.enableZoomButtons(); + } else { + this.zoomObserver.disconnect(); + for (const [button, disabled] of this.zoomDisabled) button.disabled = disabled; + } + } + + private enableZoomButtons(): void { + for (const button of this.zoomButtons()) if (button.disabled) button.disabled = false; + } + + private createStage(): void { + const stage = new Stage(); + stage.onPick = (id) => this.pick(id); + stage.onHover = (id) => this.hover(id); + stage.onCameraChange = () => this.save(); + if (this.saved.layered) stage.setLayered(true, false); + if (this.saved.camera) stage.restoreCamera(this.saved.camera); + this.stage = stage; + } + + /** + * Archify plays its ambient trace once, when the page opens. Opening 3D + * later replays it once, so the 3D view starts the way 2D did. Readers who + * paused motion (Live off) or prefer reduced motion get no replay. + */ + private replayAmbient(): void { + const html = document.documentElement; + if (this.ambientReplayed || !this.active) return; + // The load-time pass may still be playing (usually under the not-yet-built + // 3D view): wait for it to settle, then replay it in 3D. + if (html.getAttribute('data-ambient-motion') === 'running') { + const wait = new MutationObserver(() => { + if (html.getAttribute('data-ambient-motion') === 'running') return; + wait.disconnect(); + this.replayAmbient(); + }); + wait.observe(html, { attributes: true, attributeFilter: ['data-ambient-motion'] }); + return; + } + this.ambientReplayed = true; + if (html.getAttribute('data-ambient-motion') !== 'settled' || html.getAttribute('data-motion') === 'still') return; + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + if (!this.svg.querySelector('[data-animate]')) return; + // Restart Archify's CSS trace: the "settled" state must be applied + // (style flushed) before "running" again, or the animations do not restart. + html.setAttribute('data-ambient-motion', 'settled'); + void getComputedStyle(this.svg.querySelector('[data-animate]')!).animationName; + html.setAttribute('data-ambient-motion', 'running'); + const settle = () => html.setAttribute('data-ambient-motion', 'settled'); + const done = () => { + if (this.svg.getAnimations({ subtree: true }).some((anim) => anim.playState === 'running')) return; + this.svg.removeEventListener('animationend', done, true); + this.svg.removeEventListener('animationcancel', done, true); + settle(); + }; + this.svg.addEventListener('animationend', done, true); + this.svg.addEventListener('animationcancel', done, true); + // Safety net if an animation never reports its end. + window.setTimeout(settle, 12000); + } + + private save(): void { + this.saved = { active: this.active, layered: this.stage?.layered ?? this.saved.layered, camera: this.stage?.cameraState() ?? this.saved.camera }; + this.host.setState({ view3d: this.saved }); + } + + // ------------------------------------------------------------- picking + + /** Hands a 3D click to Archify, as if the reader had clicked the 2D node. */ + private pick(id: string | undefined): void { + const target = id ? this.svg.querySelector(`[data-node-id="${CSS.escape(id)}"]`) : this.svg; + target?.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); + } + + private hovered: Element | undefined; + + /** + * Hands 3D hover to Archify as pointer events on the SVG node, so its + * intent trace (one-hop flows, fading the rest) plays as in 2D. + */ + private hover(id: string | undefined): void { + const next = id ? this.svg.querySelector(`[data-node-id="${CSS.escape(id)}"]`) ?? undefined : undefined; + if (next === this.hovered) return; + const init = { bubbles: true, cancelable: true, pointerType: 'mouse', isPrimary: true, view: window }; + this.hovered?.dispatchEvent(new PointerEvent('pointerout', init)); + next?.dispatchEvent(new PointerEvent('pointerover', init)); + this.hovered = next; + } + + // -------------------------------------------------------------- redraw + + private onMutation(records: MutationRecord[]): void { + if (!records.some((r) => this.changesDrawing(r))) return; + window.clearTimeout(this.redrawTimer); + // Let Archify's 140–200ms state transitions finish before sampling styles. + this.redrawTimer = window.setTimeout(() => void this.redraw(), REDRAW_MS); + } + + /** + * Whether a mutation changes what the textures show. Archify's camera + * (the SVG root's style), its temporary overlays and the ambient-motion + * flag only change motion, which the stage follows live. + */ + private changesDrawing(record: MutationRecord): boolean { + const target = record.target as Element; + if (target === this.svg && record.attributeName === 'style') return false; + if (target === document.documentElement) return record.attributeName === 'data-theme' || record.attributeName === 'data-preset'; + // Intent trace only fades and draws flows; the stage mirrors both live. + if (record.attributeName?.startsWith('data-intent-trace')) return false; + const inOverlay = (node: Node) => node instanceof Element && !!node.closest('[data-relationship-pulse-overlay], [data-intent-trace-overlay], [data-route-probe-overlay], [data-route-journey-overlay], [data-semantic-lens-overlay], [data-story-overlay], [data-story-carrier-overlay], [data-chapter-handoff-overlay], [data-relationship-hit-overlay]'); + if (record.type === 'childList') { + const nodes = [...record.addedNodes, ...record.removedNodes].filter((n) => n.nodeType === Node.ELEMENT_NODE); + if (nodes.length && nodes.every((n) => inOverlay(n) || [...(n as Element).getAttributeNames()].some((a) => a.endsWith('-overlay')))) return false; + return nodes.length > 0 || !inOverlay(target); + } + return !inOverlay(target); + } + + private async redraw(): Promise { + if (this.drawing) { + this.pendingRedraw = true; + return this.drawing; + } + this.drawing = this.draw().finally(() => { + this.drawing = undefined; + if (this.pendingRedraw) { + this.pendingRedraw = false; + void this.redraw(); + } + }); + return this.drawing; + } + + private async draw(): Promise { + if (!this.active || !this.stage) return; + await document.fonts?.ready; + const model = readModel(this.svg); + this.model = model; + const { viewBox } = model; + const area = { x: viewBox.x - MARGIN, y: viewBox.y - MARGIN, w: viewBox.w + MARGIN * 2, h: viewBox.h + MARGIN * 2 }; + const scale = Math.min(TEXTURE_SCALE, 8192 / Math.max(area.w, area.h)); + const root = getComputedStyle(document.documentElement); + const containerStyle = getComputedStyle(this.container); + const background = opaque(containerStyle.backgroundColor) ?? (root.getPropertyValue('--bg').trim() || '#020617'); + const dot = root.getPropertyValue('--canvas-dot').trim(); + + const paintFloor = (ctx: CanvasRenderingContext2D) => { + ctx.fillStyle = background; + ctx.fillRect(area.x, area.y, area.w, area.h); + if (dot) { + ctx.fillStyle = dot; + for (let x = Math.ceil(area.x / 20) * 20; x < area.x + area.w; x += 20) { + for (let y = Math.ceil(area.y / 20) * 20; y < area.y + area.h; y += 20) { + ctx.beginPath(); + ctx.arc(x, y, 0.9, 0, Math.PI * 2); + ctx.fill(); + } + } + } + }; + + const [floor, nodes, labels] = await Promise.all([ + rasterize(layerMarkup(this.svg, viewBox, { keep: ['floor'] }, scale), viewBox, area, scale, paintFloor), + rasterize(layerMarkup(this.svg, viewBox, { keep: ['node'], opaqueNodes: true }, scale), viewBox, area, scale), + rasterize(layerMarkup(this.svg, viewBox, { keep: ['label'] }, scale), viewBox, area, scale), + ]); + if (!this.active || this.model !== model) return; + const layers: Layers = { floor, nodes, labels }; + const mask = root.getPropertyValue('--mask').trim() || '#0f172a'; + this.stage.build(model, layers, { mask, dark: document.documentElement.getAttribute('data-theme') !== 'light' }); + } + + // -------------------------------------------------------------- export + + private exportPng(): void { + if (!this.stage) return; + const shot = new Image(); + shot.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = shot.width; + canvas.height = shot.height; + const ctx = canvas.getContext('2d')!; + ctx.fillStyle = opaque(getComputedStyle(this.container).backgroundColor) ?? getComputedStyle(document.documentElement).getPropertyValue('--bg'); + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(shot, 0, 0); + const data = canvas.toDataURL('image/png'); + const title = (document.querySelector('.header h1')?.textContent ?? 'archify').trim().replace(/[^\w.-]+/g, '-').toLowerCase(); + this.host.post({ type: 'save', filename: `${title || 'archify'}-3d.png`, base64: data.slice(data.indexOf(',') + 1) }); + }; + shot.src = this.stage.snapshot(); + } +} + +/** A CSS colour if it is not fully transparent. */ +function opaque(value: string): string | undefined { + if (!value || value === 'transparent' || /rgba\([^)]*,\s*0\)$/.test(value)) return undefined; + return value; +} + +function start(): void { + const host = window.__archifyHost; + const container = document.querySelector('.diagram-container'); + const svg = container?.querySelector(':scope > svg'); + if (!host || !container || !svg) return; + const style = document.createElement('style'); + style.textContent = STYLE; + document.head.appendChild(style); + new Viewer3D(host, container, svg); +} + +if (document.readyState === 'complete') start(); +else window.addEventListener('load', start, { once: true }); diff --git a/src/viewer3d/raster.ts b/src/viewer3d/raster.ts new file mode 100644 index 0000000..7cd44a8 --- /dev/null +++ b/src/viewer3d/raster.ts @@ -0,0 +1,126 @@ +import { Box, Role, roleOf } from './svgModel'; + +/** + * Turns parts of the live Archify SVG into images. Each element's computed + * style is written inline into a standalone copy, so the image carries the + * current theme, preset, focus and dimming exactly as the 2D view shows them. + */ + +/** Presentation properties copied from computed style. */ +const PROPS = [ + 'fill', 'fill-opacity', 'fill-rule', + 'stroke', 'stroke-width', 'stroke-opacity', 'stroke-dasharray', 'stroke-dashoffset', + 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', + // visibility is left out: the 3D view hides the 2D SVG with it. + 'opacity', 'display', 'filter', 'paint-order', + 'font-family', 'font-size', 'font-weight', 'font-style', 'letter-spacing', 'text-anchor', + 'dominant-baseline', 'text-transform', +]; + +let fontCss: string | undefined; + +/** The page's @font-face rules (Archify embeds JetBrains Mono as data URIs). */ +function fontFaces(): string { + if (fontCss !== undefined) return fontCss; + const rules: string[] = []; + for (const sheet of [...document.styleSheets]) { + let list: CSSRuleList; + try { + list = sheet.cssRules; + } catch { + continue; + } + for (const rule of [...list]) if (rule instanceof CSSFontFaceRule) rules.push(rule.cssText); + } + fontCss = rules.join('\n'); + return fontCss; +} + +export interface LayerOptions { + /** Roles whose elements are drawn; everything else is hidden. */ + keep: Role[]; + /** Draw kept nodes fully opaque; the 3D view applies their dimming itself. */ + opaqueNodes?: boolean; +} + +/** Copies computed styles from the live tree onto a clone with the same shape. */ +function inlineStyles(live: Element, copy: Element, options: LayerOptions, inherited: Role | undefined, root: boolean): void { + const role = inherited ?? roleOf(live); + const isDefs = live.tagName === 'defs' || !!live.closest('defs'); + const style = getComputedStyle(live); + const parts: string[] = []; + for (const prop of PROPS) { + const value = style.getPropertyValue(prop); + if (value) parts.push(`${prop}:${value}`); + } + let hidden = false; + if (root) { + // The root carries viewer state (camera transform, our fade); keep only + // what its children inherit. + copy.setAttribute('style', parts.filter((p) => /^(font|letter|text)/.test(p)).join(';')); + } else { + if (!isDefs) { + if (role) hidden = !options.keep.includes(role); + else if (live.children.length === 0) hidden = !options.keep.includes('floor'); + } + if (options.opaqueNodes && role === 'node' && !inherited) parts.push('opacity:1'); + if (hidden) parts.push('display:none'); + copy.setAttribute('style', parts.join(';')); + } + copy.removeAttribute('class'); + if (hidden) return; + const liveKids = live.children; + const copyKids = copy.children; + for (let i = 0; i < liveKids.length && i < copyKids.length; i++) { + inlineStyles(liveKids[i], copyKids[i], options, role, false); + } +} + +/** + * Standalone SVG markup for one layer of the diagram, sized at `scale` + * pixels per user unit so the browser rasterizes it sharply. + */ +export function layerMarkup(svg: SVGSVGElement, viewBox: Box, options: LayerOptions, scale: number): string { + const copy = svg.cloneNode(true) as SVGSVGElement; + inlineStyles(svg, copy, options, undefined, true); + // Viewer-only children (focus overlays, hit areas) carry no data roles but + // are not part of the authored drawing. + copy.querySelectorAll('[data-viewer-only], .no-export').forEach((el) => el.remove()); + copy.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + copy.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.w} ${viewBox.h}`); + copy.setAttribute('width', String(Math.round(viewBox.w * scale))); + copy.setAttribute('height', String(Math.round(viewBox.h * scale))); + const style = document.createElementNS('http://www.w3.org/2000/svg', 'style'); + style.textContent = fontFaces(); + copy.insertBefore(style, copy.firstChild); + return new XMLSerializer().serializeToString(copy); +} + +export interface Raster { + canvas: HTMLCanvasElement; + /** Pixels per SVG user unit. */ + scale: number; + /** SVG area the canvas covers (the view box plus a margin). */ + area: Box; +} + +/** Rasterizes SVG markup over `area` (in user units) at `scale` pixels per unit. */ +export async function rasterize(markup: string, viewBox: Box, area: Box, scale: number, paint?: (ctx: CanvasRenderingContext2D) => void): Promise { + const canvas = document.createElement('canvas'); + canvas.width = Math.max(1, Math.round(area.w * scale)); + canvas.height = Math.max(1, Math.round(area.h * scale)); + const ctx = canvas.getContext('2d')!; + ctx.scale(scale, scale); + ctx.translate(-area.x, -area.y); + paint?.(ctx); + const url = URL.createObjectURL(new Blob([markup], { type: 'image/svg+xml' })); + try { + const image = new Image(); + image.src = url; + await image.decode(); + ctx.drawImage(image, viewBox.x, viewBox.y, viewBox.w, viewBox.h); + } finally { + URL.revokeObjectURL(url); + } + return { canvas, scale, area }; +} diff --git a/src/viewer3d/stage.ts b/src/viewer3d/stage.ts new file mode 100644 index 0000000..0c014d2 --- /dev/null +++ b/src/viewer3d/stage.ts @@ -0,0 +1,1019 @@ +import * as THREE from 'three'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; +import type { Raster } from './raster'; +import { DiagramModel, EdgeShape, FrameShape, LabelShape, NodeShape, effectiveOpacity, FLOW_SELECTOR } from './svgModel'; + +/** + * The 3D scene. World units are Archify's SVG user units: x is the SVG x, + * z the SVG y, and y points up. The floor is the diagram itself; nodes rise + * from it as slabs whose top face is the node as Archify draws it. + */ + +const NODE_HEIGHT = 16; +/** Pipes run at mid-height of the blocks, so they plug into their sides. */ +const PIPE_Y = NODE_HEIGHT / 2; +/** Height offsets that let crossing pipes pass over each other. */ +const PIPE_LANES = [0, 3.5, -3.5]; +const FRAME_WALL = 12; +const FLY_MS = 650; +/** Glowing flows (focus pulse, traces) run in a slightly wider pipe over the relationship. */ +const FLOW_SCALE = 1.4; + +/** Height tier per semantic kind, for the stacked view. */ +const KIND_TIER: Record = { external: 3, frontend: 3, security: 2, backend: 1, cloud: 1, messagebus: 1, database: 0 }; + +export interface Layers { + floor: Raster; + nodes: Raster; + labels: Raster; +} + +export interface Palette { + mask: string; + dark: boolean; +} + +interface NodeView { + shape: NodeShape; + group: THREE.Group; + body: THREE.Mesh; + materials: THREE.Material[]; + lift: number; + /** Extra height while Archify's node pulse plays. */ + pulse: number; + /** Opacity at build time; live dimming (intent trace) multiplies it. */ + baseOpacity: number; + dim: number; +} + +interface EdgeView { + shape: EdgeShape; + tube: THREE.Mesh; + material: THREE.MeshStandardMaterial; + cap: THREE.Mesh; + arrow?: THREE.Mesh; + arrowLength: number; + radius: number; + bias: number; + /** Opacity of the path's ancestors; the path's own opacity is read live. */ + baseOpacity: number; + /** Archify's dashes are sized for a thin stroke; pipes stretch them by this. */ + dashScale: number; + /** The route as built, for flows that run along it. */ + path?: THREE.CurvePath; + pipeLength: number; + /** Last mirrored stroke-dasharray, stroke-dashoffset and opacity. */ + mirrored: string; +} + +/** A glowing flow Archify draws over a relationship (focus pulse, trace, lens…). */ +interface FlowView { + el: SVGPathElement; + edge: EdgeView; + mesh: THREE.Mesh; + material: THREE.MeshBasicMaterial; +} + +interface LabelView { + shape: LabelShape; + mesh: THREE.Mesh; + edge?: EdgeView; +} + +interface FrameView { + shape: FrameShape; + wall: THREE.Mesh; + rim: THREE.Line; + members: NodeShape[]; +} + +export interface CameraState { + position: [number, number, number]; + target: [number, number, number]; +} + +/** + * True for a trackpad two-finger scroll, false for a mouse wheel. Trackpads + * send pixel deltas, often sideways or fractional; wheels send coarse + * vertical steps. + */ +function isTrackpadScroll(event: WheelEvent): boolean { + if (event.deltaMode !== WheelEvent.DOM_DELTA_PIXEL) return false; + if (event.deltaX !== 0) return true; + return !Number.isInteger(event.deltaY) || Math.abs(event.deltaY) < 40; +} + +const reducedMotion = () => window.matchMedia('(prefers-reduced-motion: reduce)').matches; + +function cssColor(value: string, fallback = '#888'): THREE.Color { + try { + return new THREE.Color().setStyle(value && value !== 'none' ? value : fallback); + } catch { + return new THREE.Color(fallback); + } +} + +function canvasTexture(canvas: HTMLCanvasElement): THREE.CanvasTexture { + const texture = new THREE.CanvasTexture(canvas); + texture.colorSpace = THREE.SRGBColorSpace; + texture.anisotropy = 8; + texture.generateMipmaps = true; + texture.minFilter = THREE.LinearMipmapLinearFilter; + return texture; +} + +/** A soft blurred rectangle, stretched under each node as a contact shadow. */ +const shadowTexture = (() => { + let texture: THREE.CanvasTexture | undefined; + return () => { + if (texture) return texture; + const canvas = document.createElement('canvas'); + canvas.width = canvas.height = 128; + const ctx = canvas.getContext('2d')!; + ctx.filter = 'blur(12px)'; + ctx.fillStyle = '#000'; + ctx.fillRect(28, 28, 72, 72); + texture = new THREE.CanvasTexture(canvas); + return texture; + }; +})(); + +/** Parses a computed stroke-dasharray ("10px, 8px", "none") into numbers. */ +function parseDashes(value: string): number[] { + if (!value || value === 'none') return []; + return value.split(/[\s,]+/).map((v) => parseFloat(v)).filter((v) => v >= 0 && Number.isFinite(v)); +} + +const dashCache = new Map(); + +/** Alpha stripes for a dash pattern; textures are shared per pattern. */ +function dashFor(dasharray: number[]): { texture: THREE.Texture; period: number } | undefined { + if (!dasharray.length || dasharray.every((d) => d === 0)) return undefined; + const key = dasharray.map((d) => d.toFixed(4)).join(','); + let entry = dashCache.get(key); + if (!entry) { + entry = dashTexture(dasharray); + dashCache.set(key, entry); + } + // A clone shares the image but has its own repeat/offset. + const texture = entry.texture.clone(); + texture.needsUpdate = true; + return { texture, period: entry.period }; +} + +/** Alpha stripes for a dashed relationship, from its stroke-dasharray. */ +function dashTexture(dasharray: number[]): { texture: THREE.CanvasTexture; period: number } { + const pattern = dasharray.length % 2 ? [...dasharray, ...dasharray] : dasharray; + const period = pattern.reduce((a, b) => a + b, 0); + const canvas = document.createElement('canvas'); + // Enough pixels per period for both 10px dashes and pathLength=1 patterns. + const px = Math.min(4096, Math.max(8, 512 / Math.max(period, 1e-6))) ; + canvas.width = Math.max(2, Math.min(4096, Math.round(period * px))); + canvas.height = 1; + const ctx = canvas.getContext('2d')!; + let x = 0; + pattern.forEach((len, i) => { + ctx.fillStyle = i % 2 ? '#000' : '#fff'; + ctx.fillRect(Math.round(x * px), 0, Math.ceil(len * px), 1); + x += len; + }); + const texture = new THREE.CanvasTexture(canvas); + texture.wrapS = THREE.RepeatWrapping; + texture.magFilter = THREE.NearestFilter; + return { texture, period }; +} + +function disposeObject(root: THREE.Object3D): void { + root.traverse((obj) => { + const mesh = obj as THREE.Mesh; + mesh.geometry?.dispose(); + const material = mesh.material as THREE.Material | THREE.Material[] | undefined; + for (const m of Array.isArray(material) ? material : material ? [material] : []) { + for (const value of Object.values(m)) if (value instanceof THREE.Texture && value !== shadowTexture() && ![...dashCache.values()].some((d) => d.texture === value)) value.dispose(); + m.dispose(); + } + }); +} + +export class Stage { + readonly element: HTMLDivElement; + private readonly renderer: THREE.WebGLRenderer; + private readonly scene = new THREE.Scene(); + private readonly camera = new THREE.PerspectiveCamera(32, 1, 1, 40000); + private readonly controls: OrbitControls; + private world = new THREE.Group(); + private model: DiagramModel | undefined; + private nodes = new Map(); + private edges: EdgeView[] = []; + private flows = new Map(); + /** Edge views by their SVG path data, to place Archify's flow overlays. */ + private edgesByPath = new Map(); + private glowColor = new THREE.Color('#34d399'); + private pulsing = new Set(); + /** Something changed that the next frame must draw. */ + private dirty = true; + private motionActive = false; + private labels: LabelView[] = []; + private frames: FrameView[] = []; + private textures: THREE.Texture[] = []; + private levels = new Map(); + private levelGap = 60; + private levelMix = 0; + private levelTarget = 0; + private hovered: string | undefined; + private frameId = 0; + private lastTime = 0; + private flight: { from: THREE.Vector3; to: THREE.Vector3; fromTarget: THREE.Vector3; toTarget: THREE.Vector3; start: number } | undefined; + private fitted = false; + private readonly raycaster = new THREE.Raycaster(); + private downAt: { x: number; y: number } | undefined; + private panTimer: number | undefined; + + /** Called with a node id, or undefined for empty space. */ + onPick: (id: string | undefined) => void = () => {}; + /** Called when the pointer moves onto another node, or off all nodes. */ + onHover: (id: string | undefined) => void = () => {}; + /** Called when the camera settles after the reader moves it. */ + onCameraChange: () => void = () => {}; + + constructor() { + this.element = document.createElement('div'); + this.element.className = 'archify-3d-stage'; + this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); + // Full retina density costs 4x the pixels for little gain on a moving scene. + this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); + this.renderer.setClearColor(0x000000, 0); + this.element.appendChild(this.renderer.domElement); + + this.controls = new OrbitControls(this.camera, this.renderer.domElement); + this.controls.enableDamping = true; + this.controls.dampingFactor = 0.09; + this.controls.screenSpacePanning = false; + this.controls.maxPolarAngle = Math.PI * 0.47; + this.controls.zoomToCursor = true; + this.controls.addEventListener('end', () => this.onCameraChange()); + this.controls.addEventListener('change', () => (this.dirty = true)); + + this.scene.add(new THREE.HemisphereLight(0xffffff, 0x8090a0, 2.2)); + const sun = new THREE.DirectionalLight(0xffffff, 1.4); + sun.position.set(-0.4, 1, 0.6); + this.scene.add(sun); + this.scene.add(this.world); + + const canvas = this.renderer.domElement; + // Keep Archify's 2D pan and zoom (which start on the container) out of + // 3D input. pointermove/pointerup must still reach the document, where + // OrbitControls tracks a drag. + for (const type of ['pointerdown', 'wheel', 'dblclick']) { + canvas.addEventListener(type, (event) => event.stopPropagation()); + } + canvas.addEventListener('pointerdown', (event) => (this.downAt = { x: event.clientX, y: event.clientY })); + // Archify dismisses focus on document-level clicks outside the SVG. Catch + // the canvas click at window capture, before those listeners, and hand + // Archify a click on the node instead. + window.addEventListener( + 'click', + (event) => { + if (event.target !== canvas) return; + event.stopPropagation(); + const still = this.downAt && Math.hypot(event.clientX - this.downAt.x, event.clientY - this.downAt.y) <= 4; + this.downAt = undefined; + if (still) this.onPick(this.pick(event)); + }, + true, + ); + canvas.addEventListener('pointermove', (event) => { + const id = this.pick(event); + if (id !== this.hovered) { + this.dirty = true; + this.onHover(id); + } + this.hovered = id; + canvas.style.cursor = id ? 'pointer' : 'grab'; + }); + canvas.addEventListener('pointerleave', () => { + if (this.hovered) this.onHover(undefined); + this.hovered = undefined; + this.dirty = true; + }); + canvas.addEventListener('dblclick', (event) => { + const id = this.pick(event); + if (id) this.focusNode(id); + else this.fit(); + }); + + // Trackpad scrolling pans across the floor, in both directions, like + // scrolling over the 2D diagram. Pinch (ctrl+wheel) and mouse wheels + // fall through to OrbitControls, which zooms. + this.element.addEventListener( + 'wheel', + (event) => { + if (event.ctrlKey || !isTrackpadScroll(event)) return; + event.preventDefault(); + event.stopPropagation(); + this.flight = undefined; + this.panBy(event.deltaX, event.deltaY); + }, + { capture: true, passive: false }, + ); + + new ResizeObserver(() => this.resize()).observe(this.element); + } + + /** Moves camera and target along the floor by a scroll delta in pixels. */ + private panBy(dx: number, dy: number): void { + const distance = this.camera.position.distanceTo(this.controls.target); + const perPixel = (2 * distance * Math.tan(THREE.MathUtils.degToRad(this.camera.fov / 2))) / Math.max(1, this.element.clientHeight); + const forward = this.controls.target.clone().sub(this.camera.position).setY(0); + if (forward.lengthSq() < 1e-6) forward.set(0, 0, -1).applyQuaternion(this.camera.quaternion).setY(0); + if (forward.lengthSq() < 1e-6) forward.set(0, 0, -1); + forward.normalize(); + const right = new THREE.Vector3().crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize(); + // Scrolling down reveals what is below: move towards the reader. + const move = right.multiplyScalar(dx * perPixel).addScaledVector(forward, -dy * perPixel); + this.camera.position.add(move); + this.controls.target.add(move); + this.controls.update(); + window.clearTimeout(this.panTimer); + this.panTimer = window.setTimeout(() => this.onCameraChange(), 250); + } + + // ---------------------------------------------------------------- build + + /** Rebuilds the scene from the current SVG state. The camera is kept. */ + build(model: DiagramModel, layers: Layers, palette: Palette): void { + disposeObject(this.world); + this.scene.remove(this.world); + for (const t of this.textures) t.dispose(); + this.world = new THREE.Group(); + this.scene.add(this.world); + this.model = model; + this.nodes.clear(); + this.edges = []; + this.edgesByPath.clear(); + this.flows.clear(); + this.pulsing.clear(); + this.mirrored = false; + const root = getComputedStyle(document.documentElement); + this.glowColor = cssColor(root.getPropertyValue('--arrow-emphasis').trim(), '#34d399'); + this.labels = []; + this.frames = []; + + const floorTexture = canvasTexture(layers.floor.canvas); + const nodeTexture = canvasTexture(layers.nodes.canvas); + const labelTexture = canvasTexture(layers.labels.canvas); + this.textures = [floorTexture, nodeTexture, labelTexture]; + + this.computeLevels(model); + this.buildFloor(layers.floor, floorTexture); + for (const shape of model.nodes) this.buildNode(shape, layers.nodes, nodeTexture, palette); + model.edges.forEach((shape, index) => this.buildEdge(shape, index)); + for (const shape of model.labels) this.buildLabel(shape, layers.labels, labelTexture); + for (const shape of model.frames) this.buildFrame(shape, model); + this.layout(); + + if (!this.fitted) { + this.fit(false); + this.fitted = true; + } + this.dirty = true; + } + + private mirrored = false; + + private computeLevels(model: DiagramModel): void { + const tiers = new Map(model.nodes.map((n) => [n.id, KIND_TIER[n.kind] ?? 1])); + const used = [...new Set(tiers.values())].sort((a, b) => a - b); + this.levels = new Map([...tiers].map(([id, tier]) => [id, used.indexOf(tier)])); + const { w, h } = model.viewBox; + this.levelGap = Math.max(48, Math.min(140, Math.max(w, h) * 0.08)); + } + + private levelY(id: string): number { + return (this.levels.get(id) ?? 0) * this.levelGap * this.levelMix; + } + + private buildFloor(raster: Raster, texture: THREE.Texture): void { + const { area } = raster; + const floor = new THREE.Mesh( + new THREE.PlaneGeometry(area.w, area.h).rotateX(-Math.PI / 2), + new THREE.MeshBasicMaterial({ map: texture }), + ); + floor.position.set(area.x + area.w / 2, 0, area.y + area.h / 2); + floor.renderOrder = -1; + this.world.add(floor); + } + + private buildNode(shape: NodeShape, raster: Raster, texture: THREE.Texture, palette: Palette): void { + const { x, y, w, h } = shape; + const r = Math.min(shape.rx, w / 2, h / 2); + const outline = new THREE.Shape(); + outline.moveTo(x + r, y); + outline.lineTo(x + w - r, y); + outline.quadraticCurveTo(x + w, y, x + w, y + r); + outline.lineTo(x + w, y + h - r); + outline.quadraticCurveTo(x + w, y + h, x + w - r, y + h); + outline.lineTo(x + r, y + h); + outline.quadraticCurveTo(x, y + h, x, y + h - r); + outline.lineTo(x, y + r); + outline.quadraticCurveTo(x, y, x + r, y); + + const geometry = new THREE.ExtrudeGeometry(outline, { depth: NODE_HEIGHT, bevelEnabled: false, curveSegments: 6 }); + // Map the caps onto the node layer: same SVG coordinates, same pixels. + const pos = geometry.getAttribute('position'); + const uv = geometry.getAttribute('uv'); + const { area } = raster; + for (let i = 0; i < pos.count; i++) { + uv.setXY(i, (pos.getX(i) - area.x) / area.w, 1 - (pos.getY(i) - area.y) / area.h); + } + // Shape y (SVG y, downwards) becomes world z; the extrusion becomes height. + geometry.rotateX(Math.PI / 2); + geometry.translate(0, NODE_HEIGHT, 0); + // Group 0 holds both caps; keep only the top one. The bottom cap carries + // the same picture and shows through when a faded block turns see-through. + const caps = geometry.groups[0]; + const half = caps.count / 2; + const bottomFirst = geometry.getAttribute('position').getY(caps.start) < NODE_HEIGHT / 2; + caps.start += bottomFirst ? half : 0; + caps.count = half; + + const body = this.model ? getComputedStyle(shape.el.querySelector(`rect.c-${CSS.escape(shape.kind)}`) ?? shape.el) : undefined; + const stroke = cssColor(body?.stroke ?? '', '#64748b'); + const side = stroke.clone().lerp(cssColor(palette.mask), palette.dark ? 0.45 : 0.25); + const capMaterial = new THREE.MeshBasicMaterial({ map: texture, transparent: true, side: THREE.DoubleSide }); + const sideMaterial = new THREE.MeshLambertMaterial({ color: side, transparent: true, side: THREE.DoubleSide }); + const mesh = new THREE.Mesh(geometry, [capMaterial, sideMaterial]); + mesh.userData.id = shape.id; + + // Contact shadow on the floor. + const shadowMaterial = new THREE.MeshBasicMaterial({ + map: shadowTexture(), + transparent: true, + opacity: palette.dark ? 0.55 : 0.22, + depthWrite: false, + }); + const shadow = new THREE.Mesh(new THREE.PlaneGeometry(w * 1.55, h * 1.9).rotateX(-Math.PI / 2), shadowMaterial); + shadow.position.set(x + w / 2, 0.15, y + h / 2 + 6); + shadow.renderOrder = 0; + this.world.add(shadow); + + const group = new THREE.Group(); + group.add(mesh); + this.world.add(group); + + const opacity = effectiveOpacity(shape.el, this.model!.svg); + for (const m of [capMaterial, sideMaterial]) m.opacity = opacity; + shadowMaterial.opacity *= opacity; + this.nodes.set(shape.id, { shape, group, body: mesh, materials: [capMaterial, sideMaterial, shadowMaterial], lift: 0, pulse: 0, baseOpacity: opacity, dim: 1 }); + } + + private buildEdge(shape: EdgeShape, index: number): void { + const style = getComputedStyle(shape.path); + const width = parseFloat(style.strokeWidth) || 1.5; + const color = cssColor(style.stroke, '#94a3b8'); + const parent: Element | null = shape.path.parentElement; + const baseOpacity = (parent && parent !== this.model!.svg ? effectiveOpacity(parent, this.model!.svg) : 1) * (Number(style.strokeOpacity) || 1); + const radius = Math.min(4.5, Math.max(2.4, width * 2)); + const material = new THREE.MeshStandardMaterial({ + color, + roughness: 0.32, + metalness: 0.18, + emissive: color, + emissiveIntensity: 0.14, + transparent: true, + opacity: baseOpacity * Number(style.opacity || 1), + }); + const tube = new THREE.Mesh(new THREE.BufferGeometry(), material); + const cap = new THREE.Mesh(new THREE.SphereGeometry(radius, 12, 8), material); + this.world.add(tube, cap); + + // Arrowhead: a cone in the colour of Archify's marker, tip at the route end. + let arrow: THREE.Mesh | undefined; + const arrowLength = radius * 4.2; + const marker = /url\(["']?#([^"')]+)["']?\)/.exec(shape.path.getAttribute('marker-end') ?? '')?.[1]; + const polygon = marker ? this.model!.svg.querySelector(`marker#${CSS.escape(marker)} polygon, marker#${CSS.escape(marker)} path`) : null; + if (polygon) { + const fill = cssColor(getComputedStyle(polygon).fill, style.stroke); + const geometry = new THREE.ConeGeometry(radius * 2.1, arrowLength, 16); + geometry.rotateZ(-Math.PI / 2); + geometry.translate(-arrowLength / 2, 0, 0); + arrow = new THREE.Mesh( + geometry, + new THREE.MeshStandardMaterial({ color: fill, roughness: 0.32, metalness: 0.18, emissive: fill, emissiveIntensity: 0.18, transparent: true, opacity: material.opacity }), + ); + this.world.add(arrow); + } + const view: EdgeView = { + shape, + tube, + material, + cap, + arrow, + arrowLength: arrow ? arrowLength : 0, + radius, + bias: PIPE_LANES[index % PIPE_LANES.length], + baseOpacity, + // Archify's dash lengths suit a thin stroke; stretch them to the pipe's + // thickness so a dashed pipe reads as separate pipe sections. + dashScale: Math.max(1, (radius * 2) / (width * 2.2)), + pipeLength: shape.length, + mirrored: '', + }; + this.edges.push(view); + const d = shape.path.getAttribute('d'); + if (d) this.edgesByPath.set(d.trim(), view); + this.mirrorEdge(view); + } + + /** + * Copies the live dash, offset and opacity of the 2D path onto its pipe. + * Archify animates exactly these (trace draw-in, focus, Live); mirroring + * them keeps the 3D motion in step with 2D, in every preset. + */ + private mirrorEdge(view: EdgeView): boolean { + const style = getComputedStyle(view.shape.path); + const key = `${style.strokeDasharray}|${style.strokeDashoffset}|${style.opacity}`; + if (key === view.mirrored) return false; + const dashKey = view.mirrored.split('|')[0]; + view.mirrored = key; + const material = view.material; + if (style.strokeDasharray !== dashKey) { + const dash = dashFor(parseDashes(style.strokeDasharray).map((d) => d * view.dashScale)); + material.alphaMap?.dispose(); + material.alphaMap = dash?.texture ?? null; + material.alphaTest = dash ? 0.5 : 0; + material.userData.period = dash?.period ?? 1; + material.needsUpdate = true; + } + const alpha = material.alphaMap; + if (alpha) { + const period = material.userData.period as number; + alpha.repeat.set(view.pipeLength / period, 1); + alpha.offset.x = ((parseFloat(style.strokeDashoffset) || 0) * view.dashScale) / period; + } + const opacity = view.baseOpacity * Number(style.opacity || 1); + material.opacity = opacity; + // A relationship that is animating glows a little, as Signal Flow does in 2D. + const animating = view.shape.path.getAnimations().some((anim) => anim.playState === 'running'); + material.emissiveIntensity = animating ? 0.45 : 0.14; + const arrowMaterial = view.arrow?.material as THREE.MeshStandardMaterial | undefined; + if (arrowMaterial) arrowMaterial.opacity = opacity; + return true; + } + + private buildLabel(shape: LabelShape, raster: Raster, texture: THREE.Texture): void { + const { area } = raster; + const map = texture.clone(); + map.repeat.set(shape.w / area.w, shape.h / area.h); + map.offset.set((shape.x - area.x) / area.w, 1 - (shape.y - area.y + shape.h) / area.h); + map.needsUpdate = true; + this.textures.push(map); + const mesh = new THREE.Mesh( + new THREE.PlaneGeometry(shape.w, shape.h), + new THREE.MeshBasicMaterial({ map, transparent: true, depthWrite: false, side: THREE.DoubleSide }), + ); + mesh.renderOrder = 2; + this.world.add(mesh); + this.labels.push({ shape, mesh, edge: this.edges.find((e) => e.shape.key === shape.key) }); + } + + private buildFrame(shape: FrameShape, model: DiagramModel): void { + if (shape.kind === 'lane') return; + const style = getComputedStyle(shape.el); + const color = cssColor(style.stroke, '#64748b'); + const r = Math.min(shape.rx, shape.w / 2, shape.h / 2); + const outline = new THREE.Shape(); + const { x, y, w, h } = shape; + outline.moveTo(x + r, y); + outline.lineTo(x + w - r, y); + outline.quadraticCurveTo(x + w, y, x + w, y + r); + outline.lineTo(x + w, y + h - r); + outline.quadraticCurveTo(x + w, y + h, x + w - r, y + h); + outline.lineTo(x + r, y + h); + outline.quadraticCurveTo(x, y + h, x, y + h - r); + outline.lineTo(x, y + r); + outline.quadraticCurveTo(x, y, x + r, y); + const points = outline.getPoints(8); + + // A vertical strip along the outline; its height is set in layout(). + const positions: number[] = []; + for (let i = 0; i < points.length - 1; i++) { + const a = points[i]; + const b = points[i + 1]; + positions.push(a.x, 0, a.y, b.x, 0, b.y, b.x, 1, b.y, a.x, 0, a.y, b.x, 1, b.y, a.x, 1, a.y); + } + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); + const wall = new THREE.Mesh( + geometry, + new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.07, side: THREE.DoubleSide, depthWrite: false }), + ); + const rim = new THREE.Line( + new THREE.BufferGeometry().setFromPoints(points.map((p) => new THREE.Vector3(p.x, 1, p.y))), + new THREE.LineBasicMaterial({ color, transparent: true, opacity: 0.55 }), + ); + this.world.add(wall, rim); + const members = model.nodes.filter((n) => n.x + n.w / 2 > x && n.x + n.w / 2 < x + w && n.y + n.h / 2 > y && n.y + n.h / 2 < y + h); + this.frames.push({ shape, wall, rim, members }); + } + + /** Positions that depend on the level mix and hover. */ + private layout(): void { + for (const view of this.nodes.values()) view.group.position.y = this.levelY(view.shape.id) + view.lift + view.pulse; + + for (const view of this.edges) { + const { points, from, to } = view.shape; + const y0 = this.levelY(from); + const y1 = this.levelY(to); + const n = points.length; + const pts: THREE.Vector3[] = []; + for (let i = 0; i < n; i++) { + const s = i / (n - 1); + const k = s * s * (3 - 2 * s); + pts.push(new THREE.Vector3(points[i][0], PIPE_Y + view.bias + y0 + (y1 - y0) * k, points[i][1])); + } + const end = pts[n - 1]; + // The pipe stops where the arrowhead's base begins. + let cut = n - 1; + while (cut > 1 && pts[cut].distanceTo(end) < view.arrowLength * 0.85) cut--; + const path = new THREE.CurvePath(); + for (let i = 1; i <= cut; i++) path.add(new THREE.LineCurve3(pts[i - 1], pts[i])); + view.tube.geometry.dispose(); + view.tube.geometry = new THREE.TubeGeometry(path, Math.max(2, cut), view.radius, 10, false); + view.cap.position.copy(pts[0]); + view.path = path; + view.pipeLength = path.getLength(); + view.mirrored = ''; + this.mirrorEdge(view); + if (view.arrow) { + const t = end.clone().sub(pts[Math.max(0, n - 4)]).normalize(); + const side = new THREE.Vector3().crossVectors(t, new THREE.Vector3(0, 1, 0)); + if (side.lengthSq() < 1e-6) side.set(1, 0, 0); + side.normalize(); + const normal = new THREE.Vector3().crossVectors(side, t); + view.arrow.quaternion.setFromRotationMatrix(new THREE.Matrix4().makeBasis(t, normal, side)); + view.arrow.position.copy(end); + } + } + + for (const view of this.labels) { + const { shape } = view; + const cx = shape.x + shape.w / 2; + const cz = shape.y + shape.h / 2; + // Labels lie on top of their pipe. + let y = PIPE_Y + (view.edge ? view.edge.bias + view.edge.radius : 4) + 1.2; + if (view.edge && this.levelMix > 0) { + const { points, from, to } = view.edge.shape; + let best = 0; + let bestD = Infinity; + points.forEach(([px, pz], i) => { + const d = (px - cx) ** 2 + (pz - cz) ** 2; + if (d < bestD) (bestD = d), (best = i); + }); + const s = best / Math.max(1, points.length - 1); + const k = s * s * (3 - 2 * s); + y += this.levelY(from) + (this.levelY(to) - this.levelY(from)) * k; + } + view.mesh.position.set(cx, y + (this.levelMix > 0.5 ? shape.h / 2 : 0), cz); + view.mesh.userData.billboard = this.levelMix > 0.5; + if (!view.mesh.userData.billboard) view.mesh.rotation.set(-Math.PI / 2, 0, 0); + } + + for (const view of this.frames) { + const top = view.members.length + ? Math.max(...view.members.map((m) => this.levelY(m.id))) + NODE_HEIGHT + FRAME_WALL + : FRAME_WALL; + view.wall.scale.y = top; + view.rim.position.y = top - 1; + } + for (const flow of this.flows.values()) this.removeFlow(flow); + this.dirty = true; + } + + // --------------------------------------------------------------- motion + + /** + * Follows Archify's own animations, which keep running on the hidden 2D + * SVG: the ambient trace (relationships drawing in, nodes pulsing) and the + * flows drawn over relationships (focus pulse, intent trace, route probe, + * lens, story). Returns true while anything moves. + */ + private syncMotion(): boolean { + const svg = this.model?.svg; + if (!svg) return false; + const running = svg.getAnimations({ subtree: true }).filter((a) => a.playState === 'running'); + const overlays = svg.querySelectorAll(FLOW_SELECTOR); + if (!running.length && !overlays.length && !this.flows.size && !this.motionActive) return false; + + let changed = false; + for (const view of this.edges) changed = this.mirrorEdge(view) || changed; + for (const view of this.nodes.values()) changed = this.mirrorNode(view) || changed; + + // Node pulses: Archify animates glow and stroke on [data-animate="node"]. + const pulsing = new Set(); + for (const animation of running) { + const name = (animation as CSSAnimation).animationName ?? ''; + if (!/node-pulse/.test(name)) continue; + const target = (animation.effect as KeyframeEffect | null)?.target as Element | null; + const id = target?.closest('[data-node-id]')?.getAttribute('data-node-id'); + const view = id ? this.nodes.get(id) : undefined; + if (!view) continue; + const p = animation.effect?.getComputedTiming().progress ?? 0; + // Archify's envelope: rise to 18%, hold to 36%, fall back by 72%. + const f = p < 0.18 ? p / 0.18 : p < 0.36 ? 1 : p < 0.72 ? 1 - (p - 0.36) / 0.36 : 0; + this.setPulse(view, f); + pulsing.add(view); + } + for (const view of this.pulsing) if (!pulsing.has(view)) this.setPulse(view, 0); + this.pulsing = pulsing; + + // Flows over relationships. + const seen = new Set(); + overlays.forEach((el) => { + const edge = this.edgesByPath.get((el.getAttribute('d') ?? '').trim()); + if (!edge?.path) return; + seen.add(el); + let flow = this.flows.get(el); + if (!flow) { + flow = this.addFlow(el, edge); + if (!flow) return; + } + this.mirrorFlow(flow); + }); + for (const flow of [...this.flows.values()]) if (!seen.has(flow.el)) this.removeFlow(flow); + + const active = running.length > 0 || this.flows.size > 0; + const result = changed || active || pulsing.size > 0 || this.motionActive !== active; + this.motionActive = active; + return result; + } + + /** + * Live node dimming: Archify's intent trace fades unrelated nodes with + * fill/stroke opacity, set from attributes that do not rebuild textures. + */ + private mirrorNode(view: NodeView): boolean { + const dim = Math.max(0.18, Number(getComputedStyle(view.shape.el).fillOpacity || 1)); + if (Math.abs(dim - view.dim) < 0.01) return false; + view.dim = dim; + const [cap, side, shadow] = view.materials; + cap.opacity = side.opacity = view.baseOpacity * dim; + shadow.opacity = (shadow.userData.base ??= shadow.opacity) * dim; + return true; + } + + private setPulse(view: NodeView, f: number): void { + const side = view.materials[1] as THREE.MeshLambertMaterial; + side.emissive.copy(this.glowColor); + side.emissiveIntensity = f * 0.9; + view.pulse = f * 3; + view.group.position.y = this.levelY(view.shape.id) + view.lift + view.pulse; + } + + private addFlow(el: SVGPathElement, edge: EdgeView): FlowView | undefined { + if (!edge.path) return undefined; + const material = new THREE.MeshBasicMaterial({ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending }); + const mesh = new THREE.Mesh(new THREE.TubeGeometry(edge.path, Math.max(2, Math.round(edge.pipeLength / 3)), edge.radius * FLOW_SCALE, 10, false), material); + mesh.renderOrder = 3; + this.world.add(mesh); + const flow: FlowView = { el, edge, mesh, material }; + this.flows.set(el, flow); + return flow; + } + + /** Flows use pathLength="1": their dashes are fractions of the route. */ + private mirrorFlow(flow: FlowView): void { + const style = getComputedStyle(flow.el); + const material = flow.material; + material.color = cssColor(style.stroke, '#34d399'); + material.opacity = effectiveOpacity(flow.el, this.model!.svg) * Number(style.strokeOpacity || 1); + const pattern = parseDashes(style.strokeDasharray); + const key = pattern.join(','); + if (material.userData.dashKey !== key) { + material.alphaMap?.dispose(); + const dash = dashFor(pattern); + material.alphaMap = dash?.texture ?? null; + material.alphaTest = dash ? 0.5 : 0; + material.userData = { dashKey: key, period: dash?.period ?? 1 }; + material.needsUpdate = true; + } + if (material.alphaMap) { + const period = material.userData.period as number; + material.alphaMap.repeat.set(1 / period, 1); + material.alphaMap.offset.x = (parseFloat(style.strokeDashoffset) || 0) / period; + } + } + + private removeFlow(flow: FlowView): void { + this.world.remove(flow.mesh); + flow.mesh.geometry.dispose(); + flow.material.alphaMap?.dispose(); + flow.material.dispose(); + this.flows.delete(flow.el); + } + + // --------------------------------------------------------------- camera + + private bounds(): THREE.Box3 { + const box = new THREE.Box3(); + if (!this.model) return box; + const { x, y, w, h } = this.model.viewBox; + box.expandByPoint(new THREE.Vector3(x, 0, y)); + box.expandByPoint(new THREE.Vector3(x + w, NODE_HEIGHT + this.levelGap * this.levelMix * 3, y + h)); + return box; + } + + private flyTo(position: THREE.Vector3, target: THREE.Vector3, animate = true): void { + if (!animate || reducedMotion()) { + this.flight = undefined; + this.camera.position.copy(position); + this.controls.target.copy(target); + this.controls.update(); + this.dirty = true; + return; + } + this.flight = { from: this.camera.position.clone(), to: position, fromTarget: this.controls.target.clone(), toTarget: target, start: performance.now() }; + } + + /** + * Camera position looking at the box centre from `direction`, close enough + * that every corner of the box projects inside the view (with a margin). + */ + private framing(box: THREE.Box3, direction: THREE.Vector3, margin = 0.9): { position: THREE.Vector3; target: THREE.Vector3 } { + const target = box.getCenter(new THREE.Vector3()); + const corners: THREE.Vector3[] = []; + for (const x of [box.min.x, box.max.x]) for (const y of [box.min.y, box.max.y]) for (const z of [box.min.z, box.max.z]) corners.push(new THREE.Vector3(x, y, z)); + const probe = this.camera.clone(); + const fits = (distance: number) => { + probe.position.copy(target).addScaledVector(direction, distance); + probe.up.set(0, 1, 0); + probe.lookAt(target); + probe.updateMatrixWorld(); + probe.updateProjectionMatrix(); + return corners.every((c) => { + const p = c.clone().project(probe); + return Math.abs(p.x) <= margin && Math.abs(p.y) <= margin && p.z < 1; + }); + }; + let lo = 1; + let hi = box.getSize(new THREE.Vector3()).length() * 4 + 100; + while (!fits(hi)) hi *= 2; + for (let i = 0; i < 32; i++) { + const mid = (lo + hi) / 2; + if (fits(mid)) hi = mid; + else lo = mid; + } + return { position: target.clone().addScaledVector(direction, hi), target }; + } + + /** Frames the whole diagram from the front, raised like a reading desk. */ + fit(animate = true): void { + const { position, target } = this.framing(this.bounds(), new THREE.Vector3(-0.16, 1.25, 1).normalize()); + this.flyTo(position, target, animate); + } + + /** Straight down, like the 2D view. */ + top(animate = true): void { + const { position, target } = this.framing(this.bounds(), new THREE.Vector3(0, 1, 0.0001).normalize(), 0.95); + this.flyTo(position, target, animate); + } + + focusNode(id: string): void { + const view = this.nodes.get(id); + if (!view) return; + const { x, y, w, h } = view.shape; + const target = new THREE.Vector3(x + w / 2, this.levelY(id) + NODE_HEIGHT, y + h / 2); + const offset = this.camera.position.clone().sub(this.controls.target).setLength(Math.max(w, h) * 4.5 + 120); + this.flyTo(target.clone().add(offset), target); + } + + zoom(factor: number): void { + const offset = this.camera.position.clone().sub(this.controls.target).multiplyScalar(1 / factor); + this.flyTo(this.controls.target.clone().add(offset), this.controls.target.clone()); + } + + /** The camera, once the scene has been framed. */ + cameraState(): CameraState | undefined { + if (!this.fitted) return undefined; + return { position: this.camera.position.toArray() as CameraState['position'], target: this.controls.target.toArray() as CameraState['target'] }; + } + + restoreCamera(state: CameraState): void { + this.flyTo(new THREE.Vector3(...state.position), new THREE.Vector3(...state.target), false); + this.fitted = true; + } + + get layered(): boolean { + return this.levelTarget === 1; + } + + setLayered(on: boolean, animate = true): void { + this.levelTarget = on ? 1 : 0; + this.dirty = true; + if (!animate || reducedMotion()) { + this.levelMix = this.levelTarget; + this.layout(); + } + } + + // -------------------------------------------------------------- picking + + private pick(event: MouseEvent): string | undefined { + const rect = this.renderer.domElement.getBoundingClientRect(); + const pointer = new THREE.Vector2(((event.clientX - rect.left) / rect.width) * 2 - 1, -((event.clientY - rect.top) / rect.height) * 2 + 1); + this.raycaster.setFromCamera(pointer, this.camera); + const hit = this.raycaster.intersectObjects([...this.nodes.values()].map((v) => v.body), false)[0]; + return hit?.object.userData.id; + } + + // ---------------------------------------------------------------- loop + + private resize(): void { + const { clientWidth: w, clientHeight: h } = this.element; + if (!w || !h) return; + this.renderer.setSize(w, h); + this.camera.aspect = w / h; + this.camera.updateProjectionMatrix(); + this.dirty = true; + } + + start(): void { + if (this.frameId) return; + this.resize(); + this.lastTime = performance.now(); + const tick = (now: number) => { + this.frameId = requestAnimationFrame(tick); + this.frame(now); + }; + this.frameId = requestAnimationFrame(tick); + } + + stop(): void { + cancelAnimationFrame(this.frameId); + this.frameId = 0; + } + + /** + * One animation frame. The scene is drawn only when something changed: + * the camera, a transition, hover, or one of Archify's animations. An idle + * diagram costs no GPU time. + */ + private frame(now: number): void { + const dt = Math.min(0.1, (now - this.lastTime) / 1000); + this.lastTime = now; + let draw = this.dirty; + this.dirty = false; + + if (this.levelMix !== this.levelTarget) { + const step = dt / 0.7; + this.levelMix = this.levelTarget > this.levelMix ? Math.min(1, this.levelMix + step) : Math.max(0, this.levelMix - step); + this.layout(); + draw = true; + } + for (const view of this.nodes.values()) { + const goal = view.shape.id === this.hovered ? 4 : 0; + if (Math.abs(view.lift - goal) > 0.01) { + view.lift += (goal - view.lift) * Math.min(1, dt * 14); + view.group.position.y = this.levelY(view.shape.id) + view.lift + view.pulse; + draw = true; + } + } + + if (this.flight) { + const t = Math.min(1, (now - this.flight.start) / FLY_MS); + const k = 1 - Math.pow(1 - t, 3); + this.camera.position.lerpVectors(this.flight.from, this.flight.to, k); + this.controls.target.lerpVectors(this.flight.fromTarget, this.flight.toTarget, k); + draw = true; + if (t === 1) { + this.flight = undefined; + this.onCameraChange(); + } + } + + if (this.syncMotion()) draw = true; + // update() returns true while the camera moves (including damping). + if (this.controls.update()) draw = true; + if (!draw) return; + + for (const view of this.labels) { + if (view.mesh.userData.billboard) view.mesh.quaternion.copy(this.camera.quaternion); + } + this.renderer.render(this.scene, this.camera); + } + + /** PNG of the current view. */ + snapshot(): string { + this.renderer.render(this.scene, this.camera); + return this.renderer.domElement.toDataURL('image/png'); + } + + dispose(): void { + this.stop(); + disposeObject(this.world); + for (const t of this.textures) t.dispose(); + this.controls.dispose(); + this.renderer.dispose(); + this.element.remove(); + } +} diff --git a/src/viewer3d/style.ts b/src/viewer3d/style.ts new file mode 100644 index 0000000..aebf40b --- /dev/null +++ b/src/viewer3d/style.ts @@ -0,0 +1,45 @@ +/** Styles for the 3D mode; they reuse Archify's tokens so every preset fits. */ +export const STYLE = ` +.diagram-container { position: relative; } +/* The 2D SVG stays in the page (its styles and animations drive the 3D + view) but is not painted while 3D is on. */ +html.archify-3d-on .diagram-container > svg { visibility: hidden !important; pointer-events: none !important; } +html.archify-3d-on .diagram-container { min-height: max(420px, 62vh); } +.archify-3d-stage { position: absolute; inset: 0; z-index: 1; border-radius: inherit; overflow: hidden; } +.archify-3d-stage canvas { display: block; touch-action: none; cursor: grab; } +.archify-3d-stage canvas:active { cursor: grabbing; } +html.archify-3d-on .diagram-container > :not(svg):not(.archify-3d-stage) { z-index: 2; } + +/* The 3D button is Export's twin: same pill, same type, same menu. */ +.toolbar #btn-3d { padding-right: 0.95rem; padding-left: 1rem; color: var(--bg); font-weight: 550; } +.toolbar #btn-3d::before { inset: 0.25rem 0; border-radius: 0.6875rem; background: var(--text); + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12), 0 6px 16px -8px rgba(15, 23, 42, 0.4); } +.toolbar #btn-3d:hover, +.toolbar #btn-3d[aria-expanded="true"] { color: var(--bg); } +.toolbar #btn-3d:hover::before, +.toolbar #btn-3d[aria-expanded="true"]::before { background: color-mix(in srgb, var(--text) 86%, var(--bg)); } +/* While the 3D view is on, the pill wears a cyan ring. */ +.toolbar #btn-3d[data-active]::before { box-shadow: 0 0 0 2px var(--frontend-stroke), 0 6px 16px -8px rgba(15, 23, 42, 0.4); } +.toolbar .archify-3d-wrap .archify-3d-menu { right: 0; } +.toolbar .export-menu button[data-archify3d-export]::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7z'/%3E%3Cpath d='M3 6.5l7 4 7-4M10 10.5v7'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7z'/%3E%3Cpath d='M3 6.5l7 4 7-4M10 10.5v7'/%3E%3C/g%3E%3C/svg%3E"); } + +/* Menu rows: Archify's .preset-option layout, with an icon swatch. */ +.toolbar .archify-3d-menu { width: 18.5rem; } +.toolbar .archify-3d-menu .preset-menu-heading { padding-top: 0.6rem; } +.toolbar .archify-3d-menu .preset-menu-heading:first-child { padding-top: 0.45rem; } +.archify-3d-swatch { display: grid; place-items: center; width: 2.375rem; height: 1.75rem; border-radius: 0.4375rem; color: var(--text-muted); + background: color-mix(in srgb, var(--text) 5%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 14%, transparent); + transition: box-shadow 0.14s ease, color 0.14s ease; } +.toolbar .archify-3d-option[aria-checked="true"] .archify-3d-swatch { color: var(--frontend-stroke); box-shadow: 0 0 0 2px var(--toolbar-menu-bg), 0 0 0 3.5px var(--frontend-stroke); } +.archify-3d-swatch-icon { width: 1.05rem; height: 1.05rem; background: currentColor; -webkit-mask: center / contain no-repeat; mask: center / contain no-repeat; } +.archify-3d-i-2d { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='14' height='12' rx='1.5'/%3E%3Cpath d='M6 8h3M6 11h5M12.5 8h1.5'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='14' height='12' rx='1.5'/%3E%3Cpath d='M6 8h3M6 11h5M12.5 8h1.5'/%3E%3C/g%3E%3C/svg%3E"); } +.archify-3d-i-3d { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7z'/%3E%3Cpath d='M3 6.5l7 4 7-4M10 10.5v7'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7z'/%3E%3Cpath d='M3 6.5l7 4 7-4M10 10.5v7'/%3E%3C/g%3E%3C/svg%3E"); } +.archify-3d-i-flat { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 6 17.5 9.8 10 13.6 2.5 9.8z'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 6 17.5 9.8 10 13.6 2.5 9.8z'/%3E%3C/g%3E%3C/svg%3E"); } +.archify-3d-i-stacked { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5 17.5 6.3 10 10 2.5 6.3z'/%3E%3Cpath d='m2.5 10 7.5 3.8 7.5-3.8M2.5 13.7 10 17.5l7.5-3.8'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5 17.5 6.3 10 10 2.5 6.3z'/%3E%3Cpath d='m2.5 10 7.5 3.8 7.5-3.8M2.5 13.7 10 17.5l7.5-3.8'/%3E%3C/g%3E%3C/svg%3E"); } +.archify-3d-i-reset { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 10a6.5 6.5 0 1 0 2-4.7'/%3E%3Cpath d='M3.5 3.5v3.5H7'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 10a6.5 6.5 0 1 0 2-4.7'/%3E%3Cpath d='M3.5 3.5v3.5H7'/%3E%3C/g%3E%3C/svg%3E"); } +.archify-3d-i-top { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5v9M6.5 8l3.5 3.5L13.5 8'/%3E%3Cpath d='M3 14.5h14M5 17.5h10'/%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 2.5v9M6.5 8l3.5 3.5L13.5 8'/%3E%3Cpath d='M3 14.5h14M5 17.5h10'/%3E%3C/g%3E%3C/svg%3E"); } +.toolbar .archify-3d-option[aria-disabled="true"] { opacity: 0.42; cursor: default; } +.toolbar .archify-3d-option[aria-disabled="true"]:hover { background: transparent; } +.archify-3d-kbd { justify-self: end; min-width: 1rem; color: var(--text-faint); font: inherit; font-size: 0.6875rem; text-align: center; } +.archify-3d-kbd:empty { visibility: hidden; } +`; diff --git a/src/viewer3d/svgModel.ts b/src/viewer3d/svgModel.ts new file mode 100644 index 0000000..a61b42b --- /dev/null +++ b/src/viewer3d/svgModel.ts @@ -0,0 +1,181 @@ +/** + * Reads the semantic parts of a rendered Archify SVG: nodes, routed + * relationships, their labels and structural frames. Archify marks them + * with the same data attributes in all five diagram types, so the 3D view + * needs no per-type code. + */ + +export interface Box { + x: number; + y: number; + w: number; + h: number; +} + +export interface NodeShape extends Box { + id: string; + kind: string; + rx: number; + el: SVGGElement; +} + +export interface EdgeShape { + key: string; + from: string; + to: string; + path: SVGPathElement; + /** Points along the route, every SAMPLE_STEP user units. */ + points: [number, number][]; + length: number; + animated: boolean; +} + +export interface LabelShape extends Box { + key: string; + el: SVGGElement; +} + +export interface FrameShape extends Box { + kind: string; + rx: number; + el: SVGRectElement; +} + +export interface DiagramModel { + svg: SVGSVGElement; + viewBox: Box; + nodes: NodeShape[]; + edges: EdgeShape[]; + labels: LabelShape[]; + frames: FrameShape[]; +} + +const SAMPLE_STEP = 3; + +/** Role of an element for the 3D view; descendants share their ancestor's role. */ +export type Role = 'node' | 'edge' | 'label' | 'floor' | 'overlay'; + +/** + * Archify's temporary viewer layers (focus pulse, intent trace, route probe, + * journey, lens, story) are marked data-…-overlay. They are animated in 3D + * from the live SVG and never baked into the textures. + */ +function isOverlay(el: Element): boolean { + for (const name of el.getAttributeNames()) if (name.startsWith('data-') && name.endsWith('-overlay')) return true; + return false; +} + +/** Flow paths inside those overlays: clones of a relationship path with pathLength="1". */ +export const FLOW_SELECTOR = [ + 'relationship-pulse', + 'intent-trace', + 'route-probe', + 'route-journey', + 'semantic-lens', + 'story', + 'story-carrier', + 'chapter-handoff', +].map((name) => `[data-${name}-overlay] path[pathLength]`).join(', '); + +export function roleOf(el: Element): Role | undefined { + if (isOverlay(el)) return 'overlay'; + if (el.hasAttribute('data-node-id')) return 'node'; + if (el.getAttribute('data-graph-role') === 'automatic-crossover') return 'edge'; + if (el.tagName === 'path' && el.hasAttribute('data-edge-from')) return 'edge'; + if (el.getAttribute('data-graph-role') === 'automatic-crossover-underlay') return 'edge'; + if (el.tagName === 'g' && el.hasAttribute('data-edge-key') && el.querySelector('text')) return 'label'; + return undefined; +} + +function num(el: Element, name: string): number { + return Number(el.getAttribute(name) ?? 0) || 0; +} + +function rectBox(rect: SVGRectElement): Box & { rx: number } { + return { x: num(rect, 'x'), y: num(rect, 'y'), w: num(rect, 'width'), h: num(rect, 'height'), rx: num(rect, 'rx') }; +} + +function nodeShape(el: SVGGElement): NodeShape | undefined { + const id = el.getAttribute('data-node-id'); + if (!id) return undefined; + const kind = el.getAttribute('data-node-kind') ?? 'backend'; + // The node body is the semantic rect (c-); c-mask sits under it. + const rect = + el.querySelector(`:scope > rect.c-${CSS.escape(kind)}`) ?? + el.querySelector(':scope > rect:not(.c-mask)') ?? + el.querySelector(':scope > rect'); + if (rect) return { id, kind, el, ...rectBox(rect) }; + const bbox = el.getBBox(); + return { id, kind, el, x: bbox.x, y: bbox.y, w: bbox.width, h: bbox.height, rx: 0 }; +} + +function samplePath(path: SVGPathElement): { points: [number, number][]; length: number } { + const length = path.getTotalLength(); + const steps = Math.max(2, Math.ceil(length / SAMPLE_STEP)); + const points: [number, number][] = []; + for (let i = 0; i <= steps; i++) { + const p = path.getPointAtLength((length * i) / steps); + points.push([p.x, p.y]); + } + return { points, length }; +} + +export function readModel(svg: SVGSVGElement): DiagramModel { + const vb = svg.viewBox.baseVal; + const viewBox = vb && vb.width > 0 ? { x: vb.x, y: vb.y, w: vb.width, h: vb.height } : { x: 0, y: 0, w: svg.clientWidth, h: svg.clientHeight }; + + const nodes = [...svg.querySelectorAll('g[data-node-id]')] + .map(nodeShape) + .filter((n): n is NodeShape => !!n && n.w > 0 && n.h > 0); + + const edges: EdgeShape[] = []; + svg.querySelectorAll('path[data-edge-from]:not([data-graph-role])').forEach((path, index) => { + const from = path.getAttribute('data-edge-from') ?? ''; + const to = path.getAttribute('data-edge-to') ?? ''; + const group = path.parentElement; + const { points, length } = samplePath(path); + if (length <= 0) return; + edges.push({ + key: path.getAttribute('data-edge-key') ?? `e${index}`, + from, + to, + path, + points, + length, + animated: path.hasAttribute('data-animate') || !!group?.hasAttribute('data-animate'), + }); + }); + + const labels: LabelShape[] = []; + svg.querySelectorAll('g[data-edge-key]').forEach((el) => { + if (roleOf(el) !== 'label') return; + const rect = el.querySelector(':scope > rect'); + const box = rect ? rectBox(rect) : (() => { + const b = el.getBBox(); + return { x: b.x, y: b.y, w: b.width, h: b.height, rx: 0 }; + })(); + if (box.w > 0 && box.h > 0) labels.push({ key: el.getAttribute('data-edge-key') ?? '', el, ...box }); + }); + + const frames = [...svg.querySelectorAll('rect[data-graph-role="structural-frame"]')].map((el) => ({ + kind: el.getAttribute('data-composition-frame-kind') ?? 'region', + el, + ...rectBox(el), + })); + + return { svg, viewBox, nodes, edges, labels, frames }; +} + +/** + * Opacity of an element including its ancestors below the SVG root. The + * root is left out: the 3D view hides the 2D SVG by fading the root. + */ +export function effectiveOpacity(el: Element, root: Element): number { + let value = 1; + for (let cur: Element | null = el; cur && cur !== root; cur = cur.parentElement) { + const style = getComputedStyle(cur); + if (style.display === 'none') return 0; + value *= Number(style.opacity || 1); + } + return value; +} diff --git a/src/preview.ts b/src/views/diagramView.ts similarity index 92% rename from src/preview.ts rename to src/views/diagramView.ts index 45f68c6..68a9e33 100644 --- a/src/preview.ts +++ b/src/views/diagramView.ts @@ -1,10 +1,14 @@ import path from 'node:path'; import * as vscode from 'vscode'; -import { isArchifyFile, readDiagramSource } from './detect'; -import { renderDiagram } from './renderer'; -import { evidenceMode, log, readText, runtimeFor, viewerTheme } from './runtime'; -import { handleWebviewMessage, WebviewMessage } from './webviewMessages'; -import { makeNonce, messagePage, prepareDiagramHtml } from './webviewHtml'; +import { renderDiagram } from '../archify/cli'; +import { evidenceMode, runtimeFor } from '../archify/runtime'; +import { isArchifyFile, readDiagramSource } from '../sources/detect'; +import { readText } from '../util/files'; +import { log } from '../util/log'; +import { viewerTheme } from '../util/settings'; +import { handleWebviewMessage, WebviewMessage } from '../webview/messages'; +import { makeNonce, messagePage, prepareDiagramHtml } from '../webview/page'; +import { viewer3dOption, viewerRoots } from '../webview/viewer3d'; export const PREVIEW_VIEW_TYPE = 'archify.preview'; /** Default editor for .archify files: the rendered diagram. */ @@ -34,7 +38,7 @@ class DiagramPreview implements vscode.Disposable { readonly isEditor = false, ) { this.hash = state?.hash; - panel.webview.options = { enableScripts: true, localResourceRoots: [] }; + panel.webview.options = { enableScripts: true, localResourceRoots: viewerRoots(this.context.extensionUri) }; if (!isEditor) { panel.iconPath = vscode.Uri.joinPath(context.extensionUri, 'media', 'preview.svg'); panel.title = `Preview ${path.basename(uri.path)}`; @@ -134,6 +138,7 @@ class DiagramPreview implements vscode.Disposable { hash: this.hash, state: this.state(), notice: result.evidenceNotice ? { kind: 'warning', text: result.evidenceNotice } : undefined, + viewer3d: viewer3dOption(this.panel.webview, this.context.extensionUri), }); // Nonces differ per render, so compare the renderer output instead. const fingerprint = `${viewerTheme()}\u0000${result.evidenceNotice ?? ''}\u0000${result.html}`; @@ -158,7 +163,7 @@ function firstLines(text: string | undefined, count = 6): string { return (text ?? '').split('\n').slice(0, count).join('\n'); } -export class PreviewManager +export class DiagramViewManager implements vscode.Disposable, vscode.WebviewPanelSerializer, vscode.CustomTextEditorProvider { /** Side previews, one per source file. */ @@ -204,7 +209,7 @@ export class PreviewManager /** Opening a .archify file lands here: the diagram is the editor. */ async resolveCustomTextEditor(document: vscode.TextDocument, panel: vscode.WebviewPanel): Promise { - panel.webview.options = { enableScripts: true, localResourceRoots: [] }; + panel.webview.options = { enableScripts: true, localResourceRoots: viewerRoots(this.context.extensionUri) }; this.track(new DiagramPreview(this.context, panel, document.uri, undefined, true)); } @@ -218,7 +223,7 @@ export class PreviewManager PREVIEW_VIEW_TYPE, `Preview ${path.basename(uri.path)}`, { viewColumn, preserveFocus }, - { enableScripts: true, retainContextWhenHidden: true, enableFindWidget: true, localResourceRoots: [] }, + { enableScripts: true, retainContextWhenHidden: true, enableFindWidget: true, localResourceRoots: viewerRoots(this.context.extensionUri) }, ); this.track(new DiagramPreview(this.context, panel, uri)); } diff --git a/src/htmlViewer.ts b/src/views/htmlViewer.ts similarity index 88% rename from src/htmlViewer.ts rename to src/views/htmlViewer.ts index 117f87c..739665b 100644 --- a/src/htmlViewer.ts +++ b/src/views/htmlViewer.ts @@ -1,9 +1,10 @@ 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'; +import { isArchifyHtml } from '../sources/detect'; +import { viewerTheme } from '../util/settings'; +import { handleWebviewMessage, WebviewMessage } from '../webview/messages'; +import { makeNonce, messagePage, prepareDiagramHtml } from '../webview/page'; +import { viewer3dOption, viewerRoots } from '../webview/viewer3d'; export const HTML_VIEW_TYPE = 'archify.htmlViewer'; @@ -18,7 +19,7 @@ export class HtmlViewerProvider implements vscode.CustomReadonlyEditorProvider { const uri = document.uri; let hash: string | undefined; - panel.webview.options = { enableScripts: true, localResourceRoots: [] }; + panel.webview.options = { enableScripts: true, localResourceRoots: viewerRoots(this.extensionUri) }; const reload = async () => { let text: string; @@ -70,6 +71,7 @@ export class HtmlViewerProvider implements vscode.CustomReadonlyEditorProvider]*\bnonce=)/gi, `\n`; + page = /<\/head>/i.test(page) ? page.replace(/<\/head>/i, `${tag}`) : `${tag}${page}`; + } + const headMatch = /]*>/i.exec(page); if (headMatch) { const at = headMatch.index + headMatch[0].length; diff --git a/src/webview/viewer3d.ts b/src/webview/viewer3d.ts new file mode 100644 index 0000000..201de7b --- /dev/null +++ b/src/webview/viewer3d.ts @@ -0,0 +1,15 @@ +import * as vscode from 'vscode'; + +/** Folder of the bundled webview scripts; webviews may load files from it. */ +export function viewerRoots(extensionUri: vscode.Uri): vscode.Uri[] { + return [vscode.Uri.joinPath(extensionUri, 'dist')]; +} + +/** Page option that adds the 3D viewer to an Archify page. */ +export function viewer3dOption(webview: vscode.Webview, extensionUri: vscode.Uri): { scriptUri: string; defaultView: '2d' | '3d' } { + const defaultView = vscode.workspace.getConfiguration('archify').get('defaultView', '2d') === '3d' ? '3d' : '2d'; + return { + scriptUri: webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'dist', 'viewer3d.js')).toString(), + defaultView, + }; +} diff --git a/test/brands.test.ts b/test/brands.test.ts index ac19170..ecb5896 100644 --- a/test/brands.test.ts +++ b/test/brands.test.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; import path from 'node:path'; import { test } from 'node:test'; -import { brandValueAt, loadBrandCatalog } from '../src/brands'; -import { diagnosticPointer } from '../src/jsonPointer'; +import { brandValueAt, loadBrandCatalog } from '../src/language/brands'; +import { diagnosticPointer } from '../src/language/jsonPointer'; const root = path.resolve(__dirname, '..', '..'); const catalogFile = path.join(root, 'vendor', 'archify', 'brand-marks', 'catalog.json'); diff --git a/test/detect.test.ts b/test/detect.test.ts index fd66f45..a0923ff 100644 --- a/test/detect.test.ts +++ b/test/detect.test.ts @@ -1,7 +1,8 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; -import { hasSourceEvidence, isArchifyHtml, readDiagramSource, stripSourceEvidence, typeFromFilename } from '../src/detect'; -import { pointerSegments, rangeForPointer, rangeForPointerYaml, yamlSyntaxProblems } from '../src/jsonPointer'; +import { pointerSegments, rangeForPointer, rangeForPointerYaml, yamlSyntaxProblems } from '../src/language/jsonPointer'; +import { isArchifyHtml, readDiagramSource, typeFromFilename } from '../src/sources/detect'; +import { hasSourceEvidence, stripSourceEvidence } from '../src/sources/evidence'; test('reads diagram_type from content', () => { const source = readDiagramSource('{"schema_version":1,"diagram_type":"sequence","meta":{}}', 'plain.json'); @@ -49,7 +50,7 @@ test('json pointer ranges point at keys for containers and values for leaves', ( }); test('.archify files are diagram sources typed by their content', async () => { - const { diagramBaseName, isArchifyFile, isDiagramFileName } = await import('../src/detect'); + const { diagramBaseName, isArchifyFile, isDiagramFileName } = await import('../src/sources/detect'); assert.ok(isArchifyFile('/w/My Platform.archify')); assert.ok(isDiagramFileName('/w/x.ARCHIFY')); assert.ok(isDiagramFileName('/w/x.workflow.json')); diff --git a/test/webviewHtml.test.ts b/test/page.test.ts similarity index 51% rename from test/webviewHtml.test.ts rename to test/page.test.ts index afcbec7..693bf03 100644 --- a/test/webviewHtml.test.ts +++ b/test/page.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; -import { prepareDiagramHtml } from '../src/webviewHtml'; +import { prepareDiagramHtml } from '../src/webview/page'; const page = ` @@ -12,7 +12,7 @@ test('adds one CSP and nonces every inline script', () => { const html = prepareDiagramHtml(page, { cspSource: 'vscode-resource:', nonce: 'N0NCE', theme: 'light' }); const csps = html.match(/Content-Security-Policy/g) ?? []; assert.equal(csps.length, 1); - assert.match(html, /script-src ('|')nonce-N0NCE/); + assert.match(html, /script-src vscode-resource: ('|')nonce-N0NCE/); const scripts = html.match(/]*>/g) ?? []; assert.equal(scripts.length, 4, 'bridge plus the three page scripts'); for (const tag of scripts) assert.match(tag, /nonce="N0NCE"/); @@ -35,3 +35,29 @@ test('embedded config cannot close the script element', () => { }); assert.ok(!html.includes('