feat: define recursive presentation figures
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { defineFigureCatalog } from "./catalog.js";
|
||||||
|
import {
|
||||||
|
cyclicCatalog,
|
||||||
|
duplicateFigureCatalog,
|
||||||
|
duplicateNodeCatalog,
|
||||||
|
unknownChildCatalog,
|
||||||
|
unknownEdgeCatalog,
|
||||||
|
unknownRootCatalog,
|
||||||
|
validCatalog,
|
||||||
|
} from "./test-fixtures.js";
|
||||||
|
|
||||||
|
describe("defineFigureCatalog", () => {
|
||||||
|
it("accepts a valid recursive catalog", () => {
|
||||||
|
expect(() => defineFigureCatalog(validCatalog)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["duplicate figure", duplicateFigureCatalog, "duplicate_figure"],
|
||||||
|
["duplicate node", duplicateNodeCatalog, "duplicate_node"],
|
||||||
|
["unknown root figure", unknownRootCatalog, "unknown_root_figure"],
|
||||||
|
["unknown edge endpoint", unknownEdgeCatalog, "unknown_edge_endpoint"],
|
||||||
|
["unknown child figure", unknownChildCatalog, "unknown_child_figure"],
|
||||||
|
["recursive child cycle", cyclicCatalog, "child_cycle"],
|
||||||
|
])("rejects %s", (_label, catalog, code) => {
|
||||||
|
expect(() => defineFigureCatalog(catalog)).toThrow(code);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import type {
|
||||||
|
FigureCatalogDefinition,
|
||||||
|
FigureDefinition,
|
||||||
|
FigureNodeDefinition,
|
||||||
|
} from "./model.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates and returns a figure catalog. Static authored data is validated once
|
||||||
|
* at module load; user or server payloads are not accepted through this interface.
|
||||||
|
*
|
||||||
|
* Throws an aggregated Error with one issue code per invalid reference so
|
||||||
|
* catalog authors see every problem in a single run.
|
||||||
|
*/
|
||||||
|
export const defineFigureCatalog = (
|
||||||
|
catalog: FigureCatalogDefinition,
|
||||||
|
): FigureCatalogDefinition => {
|
||||||
|
const issues: string[] = [];
|
||||||
|
|
||||||
|
const figureById = new Map<string, FigureDefinition>();
|
||||||
|
for (const figure of catalog.figures) {
|
||||||
|
if (figureById.has(figure.id)) {
|
||||||
|
issues.push(`duplicate_figure:${figure.id}`);
|
||||||
|
}
|
||||||
|
figureById.set(figure.id, figure);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!figureById.has(catalog.rootFigureId)) {
|
||||||
|
issues.push(`unknown_root_figure:${catalog.rootFigureId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeIdsByFigure = new Map<string, Set<string>>();
|
||||||
|
for (const figure of catalog.figures) {
|
||||||
|
const nodeIds = new Set<string>();
|
||||||
|
for (const node of figure.nodes) {
|
||||||
|
if (nodeIds.has(node.id)) {
|
||||||
|
issues.push(`duplicate_node:${figure.id}:${node.id}`);
|
||||||
|
}
|
||||||
|
nodeIds.add(node.id);
|
||||||
|
}
|
||||||
|
nodeIdsByFigure.set(figure.id, nodeIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const figure of catalog.figures) {
|
||||||
|
const nodeIds = nodeIdsByFigure.get(figure.id)!;
|
||||||
|
for (const edge of figure.edges) {
|
||||||
|
if (!nodeIds.has(edge.from)) {
|
||||||
|
issues.push(`unknown_edge_endpoint:${figure.id}:${edge.from}`);
|
||||||
|
}
|
||||||
|
if (!nodeIds.has(edge.to)) {
|
||||||
|
issues.push(`unknown_edge_endpoint:${figure.id}:${edge.to}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const figure of catalog.figures) {
|
||||||
|
for (const node of figure.nodes) {
|
||||||
|
if (node.childFigureId !== undefined) {
|
||||||
|
if (!figureById.has(node.childFigureId)) {
|
||||||
|
issues.push(`unknown_child_figure:${figure.id}:${node.childFigureId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const visited = new Set<string>();
|
||||||
|
const inStack = new Set<string>();
|
||||||
|
const visitChildChains = (figureId: string, path: string[]) => {
|
||||||
|
const figure = figureById.get(figureId);
|
||||||
|
if (!figure) return;
|
||||||
|
for (const node of figure.nodes) {
|
||||||
|
if (node.childFigureId === undefined) continue;
|
||||||
|
const key = `${figureId}:${node.childFigureId}`;
|
||||||
|
if (inStack.has(key)) {
|
||||||
|
issues.push(`child_cycle:${figureId}:${node.childFigureId}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (visited.has(key)) continue;
|
||||||
|
visited.add(key);
|
||||||
|
inStack.add(key);
|
||||||
|
visitChildChains(node.childFigureId, [...path, node.childFigureId]);
|
||||||
|
inStack.delete(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
visitChildChains(catalog.rootFigureId, [catalog.rootFigureId]);
|
||||||
|
|
||||||
|
if (issues.length > 0) {
|
||||||
|
throw new Error(issues.join(";"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return catalog;
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { popFigureFocus, pushFigureFocus, resolveFigureFocus } from "./focus.js";
|
||||||
|
import { validCatalog } from "./test-fixtures.js";
|
||||||
|
|
||||||
|
describe("figure focus", () => {
|
||||||
|
it("resolves a two-level Focus Path with breadcrumbs", () => {
|
||||||
|
const focus = resolveFigureFocus(validCatalog, ["runtime", "providers"]);
|
||||||
|
expect(focus.figure.id).toBe("provider-detail");
|
||||||
|
expect(focus.path).toEqual(["runtime", "providers"]);
|
||||||
|
expect(focus.breadcrumbs.map((item) => item.label)).toEqual([
|
||||||
|
"Architecture",
|
||||||
|
"Runtime & providers",
|
||||||
|
"Configured providers",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fails closed to the root for an invalid Focus Path", () => {
|
||||||
|
expect(resolveFigureFocus(validCatalog, ["missing"]).path).toEqual([]);
|
||||||
|
expect(resolveFigureFocus(validCatalog, ["runtime", "missing"]).figure.id)
|
||||||
|
.toBe("architecture-overview");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("pushes only expandable nodes and pops one level", () => {
|
||||||
|
const root = resolveFigureFocus(validCatalog, []);
|
||||||
|
const runtime = pushFigureFocus(validCatalog, root, "runtime");
|
||||||
|
expect(runtime.path).toEqual(["runtime"]);
|
||||||
|
expect(pushFigureFocus(validCatalog, runtime, "leaf")).toEqual(runtime);
|
||||||
|
expect(popFigureFocus(validCatalog, runtime).path).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import type {
|
||||||
|
FigureCatalogDefinition,
|
||||||
|
FigureDefinition,
|
||||||
|
FigureNodeDefinition,
|
||||||
|
} from "./model.js";
|
||||||
|
|
||||||
|
export type FigureBreadcrumb = {
|
||||||
|
readonly label: string;
|
||||||
|
readonly path: readonly string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FigureFocus = {
|
||||||
|
readonly figure: FigureDefinition;
|
||||||
|
readonly path: readonly string[];
|
||||||
|
readonly breadcrumbs: readonly FigureBreadcrumb[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const findFigure = (
|
||||||
|
catalog: FigureCatalogDefinition,
|
||||||
|
id: string,
|
||||||
|
): FigureDefinition | undefined =>
|
||||||
|
catalog.figures.find((f) => f.id === id);
|
||||||
|
|
||||||
|
const findNode = (
|
||||||
|
figure: FigureDefinition,
|
||||||
|
nodeId: string,
|
||||||
|
): FigureNodeDefinition | undefined =>
|
||||||
|
figure.nodes.find((n) => n.id === nodeId);
|
||||||
|
|
||||||
|
const buildBreadcrumbs = (
|
||||||
|
catalog: FigureCatalogDefinition,
|
||||||
|
path: readonly string[],
|
||||||
|
): readonly FigureBreadcrumb[] => {
|
||||||
|
const crumbs: FigureBreadcrumb[] = [];
|
||||||
|
let currentFigure = findFigure(catalog, catalog.rootFigureId);
|
||||||
|
if (!currentFigure) return crumbs;
|
||||||
|
|
||||||
|
crumbs.push({ label: currentFigure.title, path: [] });
|
||||||
|
|
||||||
|
for (const segment of path) {
|
||||||
|
const node = findNode(currentFigure, segment);
|
||||||
|
if (!node?.childFigureId) break;
|
||||||
|
const childFigure = findFigure(catalog, node.childFigureId);
|
||||||
|
if (!childFigure) break;
|
||||||
|
crumbs.push({ label: node.label, path: [...crumbs[crumbs.length - 1]!.path, segment] });
|
||||||
|
currentFigure = childFigure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return crumbs;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a Focus Path from the root figure, walking childFigureId references.
|
||||||
|
* If any path segment is missing or non-expandable, returns the root focus with
|
||||||
|
* an empty path.
|
||||||
|
*/
|
||||||
|
export const resolveFigureFocus = (
|
||||||
|
catalog: FigureCatalogDefinition,
|
||||||
|
path: readonly string[],
|
||||||
|
): FigureFocus => {
|
||||||
|
const rootFigure = findFigure(catalog, catalog.rootFigureId);
|
||||||
|
if (!rootFigure) {
|
||||||
|
return {
|
||||||
|
figure: { id: "", title: "", layout: { kind: "layered" }, nodes: [], edges: [] },
|
||||||
|
path: [],
|
||||||
|
breadcrumbs: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentFigure = rootFigure;
|
||||||
|
const resolvedPath: string[] = [];
|
||||||
|
|
||||||
|
for (const segment of path) {
|
||||||
|
const node = findNode(currentFigure, segment);
|
||||||
|
if (!node?.childFigureId) {
|
||||||
|
return { figure: rootFigure, path: [], breadcrumbs: buildBreadcrumbs(catalog, []) };
|
||||||
|
}
|
||||||
|
const childFigure = findFigure(catalog, node.childFigureId);
|
||||||
|
if (!childFigure) {
|
||||||
|
return { figure: rootFigure, path: [], breadcrumbs: buildBreadcrumbs(catalog, []) };
|
||||||
|
}
|
||||||
|
resolvedPath.push(segment);
|
||||||
|
currentFigure = childFigure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
figure: currentFigure,
|
||||||
|
path: resolvedPath,
|
||||||
|
breadcrumbs: buildBreadcrumbs(catalog, resolvedPath),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a node focus if the node is expandable; otherwise returns the current
|
||||||
|
* focus unchanged.
|
||||||
|
*/
|
||||||
|
export const pushFigureFocus = (
|
||||||
|
catalog: FigureCatalogDefinition,
|
||||||
|
focus: FigureFocus,
|
||||||
|
nodeId: string,
|
||||||
|
): FigureFocus => {
|
||||||
|
const node = findNode(focus.figure, nodeId);
|
||||||
|
if (!node?.childFigureId) return focus;
|
||||||
|
return resolveFigureFocus(catalog, [...focus.path, nodeId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pops one focus level, returning to the parent figure.
|
||||||
|
*/
|
||||||
|
export const popFigureFocus = (
|
||||||
|
catalog: FigureCatalogDefinition,
|
||||||
|
focus: FigureFocus,
|
||||||
|
): FigureFocus => {
|
||||||
|
if (focus.path.length === 0) return focus;
|
||||||
|
return resolveFigureFocus(catalog, focus.path.slice(0, -1));
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
export type FigureNodeKind =
|
||||||
|
| "actor"
|
||||||
|
| "operation"
|
||||||
|
| "artifact"
|
||||||
|
| "runtime"
|
||||||
|
| "boundary"
|
||||||
|
| "evidence";
|
||||||
|
|
||||||
|
export type FigureLayout =
|
||||||
|
| { readonly kind: "layered" }
|
||||||
|
| { readonly kind: "flow" }
|
||||||
|
| {
|
||||||
|
readonly kind: "explicit";
|
||||||
|
readonly positions: Readonly<Record<string, { readonly x: number; readonly y: number }>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FigureNodeDefinition = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly label: string;
|
||||||
|
readonly summary: string;
|
||||||
|
readonly kind: FigureNodeKind;
|
||||||
|
readonly evidencePointer?: string;
|
||||||
|
readonly childFigureId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FigureEdgeDefinition = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly from: string;
|
||||||
|
readonly to: string;
|
||||||
|
readonly label?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FigureDefinition = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly title: string;
|
||||||
|
readonly layout: FigureLayout;
|
||||||
|
readonly nodes: readonly FigureNodeDefinition[];
|
||||||
|
readonly edges: readonly FigureEdgeDefinition[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FigureCatalogDefinition = {
|
||||||
|
readonly rootFigureId: string;
|
||||||
|
readonly figures: readonly FigureDefinition[];
|
||||||
|
};
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
import type {
|
||||||
|
FigureCatalogDefinition,
|
||||||
|
FigureDefinition,
|
||||||
|
FigureEdgeDefinition,
|
||||||
|
FigureNodeDefinition,
|
||||||
|
} from "./model.js";
|
||||||
|
|
||||||
|
const runtimeNode: FigureNodeDefinition = {
|
||||||
|
id: "runtime",
|
||||||
|
label: "Runtime & providers",
|
||||||
|
summary: "WorkflowServer and provider composition",
|
||||||
|
kind: "runtime",
|
||||||
|
childFigureId: "runtime-detail",
|
||||||
|
};
|
||||||
|
|
||||||
|
const leafNode: FigureNodeDefinition = {
|
||||||
|
id: "leaf",
|
||||||
|
label: "Leaf node",
|
||||||
|
summary: "Non-expandable leaf",
|
||||||
|
kind: "artifact",
|
||||||
|
};
|
||||||
|
|
||||||
|
const clientNode: FigureNodeDefinition = {
|
||||||
|
id: "client",
|
||||||
|
label: "Client operations",
|
||||||
|
summary: "CLI, JSON-RPC, and web console callers",
|
||||||
|
kind: "actor",
|
||||||
|
};
|
||||||
|
|
||||||
|
const apiNode: FigureNodeDefinition = {
|
||||||
|
id: "api",
|
||||||
|
label: "Application lifecycle",
|
||||||
|
summary: "Public lifecycle operations",
|
||||||
|
kind: "operation",
|
||||||
|
};
|
||||||
|
|
||||||
|
const overviewFigure: FigureDefinition = {
|
||||||
|
id: "architecture-overview",
|
||||||
|
title: "Architecture",
|
||||||
|
layout: { kind: "layered" },
|
||||||
|
nodes: [clientNode, apiNode, runtimeNode, leafNode],
|
||||||
|
edges: [
|
||||||
|
{ id: "e-client-api", from: "client", to: "api", label: "calls" },
|
||||||
|
{ id: "e-api-runtime", from: "api", to: "runtime", label: "uses" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const providersNode: FigureNodeDefinition = {
|
||||||
|
id: "providers",
|
||||||
|
label: "Configured providers",
|
||||||
|
summary: "Built-in and external providers",
|
||||||
|
kind: "runtime",
|
||||||
|
childFigureId: "provider-detail",
|
||||||
|
};
|
||||||
|
|
||||||
|
const runtimeDetailFigure: FigureDefinition = {
|
||||||
|
id: "runtime-detail",
|
||||||
|
title: "Runtime detail",
|
||||||
|
layout: { kind: "layered" },
|
||||||
|
nodes: [providersNode, leafNode],
|
||||||
|
edges: [{ id: "e-providers-leaf", from: "providers", to: "leaf" }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const pythonProviderNode: FigureNodeDefinition = {
|
||||||
|
id: "python-provider",
|
||||||
|
label: "Python provider",
|
||||||
|
summary: "Trusted in-process Python execution",
|
||||||
|
kind: "runtime",
|
||||||
|
};
|
||||||
|
|
||||||
|
const providerDetailFigure: FigureDefinition = {
|
||||||
|
id: "provider-detail",
|
||||||
|
title: "Provider detail",
|
||||||
|
layout: { kind: "layered" },
|
||||||
|
nodes: [pythonProviderNode],
|
||||||
|
edges: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validCatalog: FigureCatalogDefinition = {
|
||||||
|
rootFigureId: "architecture-overview",
|
||||||
|
figures: [overviewFigure, runtimeDetailFigure, providerDetailFigure],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const duplicateFigureCatalog: FigureCatalogDefinition = {
|
||||||
|
rootFigureId: "architecture-overview",
|
||||||
|
figures: [
|
||||||
|
overviewFigure,
|
||||||
|
{ ...overviewFigure, id: "architecture-overview" },
|
||||||
|
runtimeDetailFigure,
|
||||||
|
providerDetailFigure,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const duplicateNodeCatalog: FigureCatalogDefinition = {
|
||||||
|
rootFigureId: "architecture-overview",
|
||||||
|
figures: [
|
||||||
|
{
|
||||||
|
...overviewFigure,
|
||||||
|
nodes: [
|
||||||
|
...overviewFigure.nodes,
|
||||||
|
{ id: "client", label: "Dup", summary: "Dup", kind: "actor" as const },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
runtimeDetailFigure,
|
||||||
|
providerDetailFigure,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unknownRootCatalog: FigureCatalogDefinition = {
|
||||||
|
rootFigureId: "nonexistent",
|
||||||
|
figures: [overviewFigure, runtimeDetailFigure, providerDetailFigure],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unknownEdgeCatalog: FigureCatalogDefinition = {
|
||||||
|
rootFigureId: "architecture-overview",
|
||||||
|
figures: [
|
||||||
|
{
|
||||||
|
...overviewFigure,
|
||||||
|
edges: [
|
||||||
|
{ id: "e-bad", from: "client", to: "nonexistent", label: "bad" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
runtimeDetailFigure,
|
||||||
|
providerDetailFigure,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unknownChildCatalog: FigureCatalogDefinition = {
|
||||||
|
rootFigureId: "architecture-overview",
|
||||||
|
figures: [
|
||||||
|
{
|
||||||
|
...overviewFigure,
|
||||||
|
nodes: overviewFigure.nodes.map((n) =>
|
||||||
|
n.id === "runtime" ? { ...n, childFigureId: "nonexistent" } : n,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
runtimeDetailFigure,
|
||||||
|
providerDetailFigure,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cyclicCatalog: FigureCatalogDefinition = {
|
||||||
|
rootFigureId: "architecture-overview",
|
||||||
|
figures: [
|
||||||
|
{
|
||||||
|
...overviewFigure,
|
||||||
|
nodes: overviewFigure.nodes.map((n) =>
|
||||||
|
n.id === "runtime" ? { ...n, childFigureId: "runtime-detail" } : n,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...runtimeDetailFigure,
|
||||||
|
nodes: runtimeDetailFigure.nodes.map((n) =>
|
||||||
|
n.id === "providers" ? { ...n, childFigureId: "architecture-overview" } : n,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
providerDetailFigure,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const edgeA: FigureEdgeDefinition = { id: "e1", from: "a", to: "b" };
|
||||||
|
const edgeB: FigureEdgeDefinition = { id: "e2", from: "b", to: "c" };
|
||||||
|
|
||||||
|
export const layeredFigure: FigureDefinition = {
|
||||||
|
id: "layered",
|
||||||
|
title: "Layered",
|
||||||
|
layout: { kind: "layered" },
|
||||||
|
nodes: [
|
||||||
|
{ id: "client", label: "Client", summary: "caller", kind: "actor" },
|
||||||
|
{ id: "runtime", label: "Runtime", summary: "server", kind: "runtime" },
|
||||||
|
],
|
||||||
|
edges: [edgeA],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const flowFigure: FigureDefinition = {
|
||||||
|
id: "flow",
|
||||||
|
title: "Flow",
|
||||||
|
layout: { kind: "flow" },
|
||||||
|
nodes: [
|
||||||
|
{ id: "discover", label: "Discover", summary: "find", kind: "operation" },
|
||||||
|
{ id: "repair", label: "Repair", summary: "fix", kind: "operation" },
|
||||||
|
],
|
||||||
|
edges: [edgeB],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const explicitFigure: FigureDefinition = {
|
||||||
|
id: "explicit",
|
||||||
|
title: "Explicit",
|
||||||
|
layout: {
|
||||||
|
kind: "explicit",
|
||||||
|
positions: { runtime: { x: 420, y: 180 }, client: { x: 100, y: 50 } },
|
||||||
|
},
|
||||||
|
nodes: [
|
||||||
|
{ id: "client", label: "Client", summary: "caller", kind: "actor" },
|
||||||
|
{ id: "runtime", label: "Runtime", summary: "server", kind: "runtime" },
|
||||||
|
],
|
||||||
|
edges: [{ id: "e-explicit", from: "client", to: "runtime" }],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const explicitFigureMissingPosition: FigureDefinition = {
|
||||||
|
id: "explicit-missing",
|
||||||
|
title: "Explicit Missing",
|
||||||
|
layout: {
|
||||||
|
kind: "explicit",
|
||||||
|
positions: { client: { x: 100, y: 50 } },
|
||||||
|
},
|
||||||
|
nodes: [
|
||||||
|
{ id: "client", label: "Client", summary: "caller", kind: "actor" },
|
||||||
|
{ id: "runtime", label: "Runtime", summary: "server", kind: "runtime" },
|
||||||
|
],
|
||||||
|
edges: [{ id: "e-explicit-missing", from: "client", to: "runtime" }],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const navigationLayout: FigureDefinition = {
|
||||||
|
id: "navigation",
|
||||||
|
title: "Navigation",
|
||||||
|
layout: {
|
||||||
|
kind: "explicit",
|
||||||
|
positions: {
|
||||||
|
left: { x: 0, y: 100 },
|
||||||
|
right: { x: 400, y: 100 },
|
||||||
|
top: { x: 200, y: 0 },
|
||||||
|
bottom: { x: 200, y: 200 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nodes: [
|
||||||
|
{ id: "left", label: "Left", summary: "left node", kind: "actor" },
|
||||||
|
{ id: "right", label: "Right", summary: "right node", kind: "actor" },
|
||||||
|
{ id: "top", label: "Top", summary: "top node", kind: "actor" },
|
||||||
|
{ id: "bottom", label: "Bottom", summary: "bottom node", kind: "actor" },
|
||||||
|
],
|
||||||
|
edges: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tiedNavigationLayout: FigureDefinition = {
|
||||||
|
id: "tied-navigation",
|
||||||
|
title: "Tied Navigation",
|
||||||
|
layout: {
|
||||||
|
kind: "explicit",
|
||||||
|
positions: {
|
||||||
|
start: { x: 0, y: 100 },
|
||||||
|
alpha: { x: 200, y: 0 },
|
||||||
|
beta: { x: 200, y: 200 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nodes: [
|
||||||
|
{ id: "start", label: "Start", summary: "start node", kind: "actor" },
|
||||||
|
{ id: "alpha", label: "Alpha", summary: "alpha node", kind: "actor" },
|
||||||
|
{ id: "beta", label: "Beta", summary: "beta node", kind: "actor" },
|
||||||
|
],
|
||||||
|
edges: [],
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user