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.
86 lines
3.4 KiB
TypeScript
86 lines
3.4 KiB
TypeScript
import { parse, ParseError } from 'jsonc-parser';
|
|
import { parse as parseYaml } from 'yaml';
|
|
|
|
export const DIAGRAM_TYPES = ['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle'] as const;
|
|
export type DiagramType = (typeof DIAGRAM_TYPES)[number];
|
|
|
|
const FILENAME_RE = /\.(architecture|workflow|sequence|dataflow|lifecycle)\.json$/i;
|
|
const ARCHIFY_EXT_RE = /\.archify$/i;
|
|
const MAX_SNIFF_BYTES = 8 * 1024 * 1024;
|
|
|
|
export function isDiagramType(value: unknown): value is DiagramType {
|
|
return typeof value === 'string' && (DIAGRAM_TYPES as readonly string[]).includes(value);
|
|
}
|
|
|
|
export function typeFromFilename(fileName: string): DiagramType | undefined {
|
|
const match = FILENAME_RE.exec(fileName);
|
|
return match ? (match[1].toLowerCase() as DiagramType) : undefined;
|
|
}
|
|
|
|
/** `name.archify`: an Archify diagram whose type comes from `diagram_type`. */
|
|
export function isArchifyFile(fileName: string): boolean {
|
|
return ARCHIFY_EXT_RE.test(fileName);
|
|
}
|
|
|
|
/** File names that are diagram sources regardless of their content. */
|
|
export function isDiagramFileName(fileName: string): boolean {
|
|
return isArchifyFile(fileName) || FILENAME_RE.test(fileName);
|
|
}
|
|
|
|
/** Base name without the diagram source extension (`web.archify` → `web`). */
|
|
export function diagramBaseName(fileName: string): string {
|
|
const base = fileName.split(/[\\/]/).pop() ?? fileName;
|
|
return base.replace(ARCHIFY_EXT_RE, '').replace(/\.json$/i, '');
|
|
}
|
|
|
|
export interface DiagramSource {
|
|
type: DiagramType;
|
|
diagram: Record<string, unknown>;
|
|
/** Source syntax: `.archify` files are YAML, Archify's own sources JSON. */
|
|
format: 'yaml' | 'json';
|
|
/** JSON text for the Archify renderer, which only reads JSON. */
|
|
json: string;
|
|
}
|
|
|
|
/** Parses `.archify` (YAML) or JSON text; undefined when it does not parse. */
|
|
export function parseDiagramText(text: string, fileName = ''): unknown {
|
|
if (isArchifyFile(fileName)) {
|
|
try {
|
|
// YAML 1.2 also accepts the JSON form of older .archify files.
|
|
return parseYaml(text, { prettyErrors: false, uniqueKeys: false });
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
const errors: ParseError[] = [];
|
|
return parse(text, errors, { allowTrailingComma: true });
|
|
}
|
|
|
|
/**
|
|
* Returns the diagram when the text declares an Archify `diagram_type`.
|
|
* Falls back to the file name for documents that do not parse yet, so a
|
|
* half-typed source still counts as a diagram.
|
|
*/
|
|
export function readDiagramSource(text: string, fileName = ''): DiagramSource | undefined {
|
|
if (text.length > MAX_SNIFF_BYTES) return undefined;
|
|
const format = isArchifyFile(fileName) ? 'yaml' : 'json';
|
|
const value = parseDiagramText(text, fileName) as Record<string, unknown> | undefined;
|
|
const isObject = value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
const json = (diagram: unknown) => (format === 'json' ? text : JSON.stringify(diagram ?? {}, null, 2));
|
|
if (isObject && isDiagramType(value.diagram_type)) {
|
|
return { type: value.diagram_type, diagram: value, format, json: json(value) };
|
|
}
|
|
const byName = typeFromFilename(fileName);
|
|
if (byName) {
|
|
const diagram = isObject ? value : {};
|
|
return { type: byName, diagram, format, json: json(diagram) };
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/** True when the HTML was produced by the Archify renderer. */
|
|
export function isArchifyHtml(text: string): boolean {
|
|
const head = text.slice(0, 4096);
|
|
return /<meta\s+name=["']generator["']\s+content=["']archify\b/i.test(head);
|
|
}
|