excalidraw-gen 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +320 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +107 -0
- package/dist/cli.js.map +1 -0
- package/dist/exporter/index.d.ts +4 -0
- package/dist/exporter/index.js +33 -0
- package/dist/exporter/index.js.map +1 -0
- package/dist/layout/arrow-router.d.ts +14 -0
- package/dist/layout/arrow-router.js +171 -0
- package/dist/layout/arrow-router.js.map +1 -0
- package/dist/layout/dag.d.ts +13 -0
- package/dist/layout/dag.js +158 -0
- package/dist/layout/dag.js.map +1 -0
- package/dist/layout/grid.d.ts +3 -0
- package/dist/layout/grid.js +52 -0
- package/dist/layout/grid.js.map +1 -0
- package/dist/layout/index.d.ts +5 -0
- package/dist/layout/index.js +21 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/normalizer/index.d.ts +5 -0
- package/dist/normalizer/index.js +37 -0
- package/dist/normalizer/index.js.map +1 -0
- package/dist/parser/index.d.ts +5 -0
- package/dist/parser/index.js +154 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/renderer/elements.d.ts +4 -0
- package/dist/renderer/elements.js +210 -0
- package/dist/renderer/elements.js.map +1 -0
- package/dist/renderer/index.d.ts +2 -0
- package/dist/renderer/index.js +76 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/renderer/seed.d.ts +5 -0
- package/dist/renderer/seed.js +18 -0
- package/dist/renderer/seed.js.map +1 -0
- package/dist/templates/index.d.ts +4 -0
- package/dist/templates/index.js +93 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/themes.d.ts +3 -0
- package/dist/templates/themes.js +90 -0
- package/dist/templates/themes.js.map +1 -0
- package/dist/types/index.d.ts +222 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/validator/index.d.ts +4 -0
- package/dist/validator/index.js +132 -0
- package/dist/validator/index.js.map +1 -0
- package/dist/validator/preflight.d.ts +10 -0
- package/dist/validator/preflight.js +138 -0
- package/dist/validator/preflight.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VERTICAL_THRESHOLD = void 0;
|
|
4
|
+
exports.routeArrow = routeArrow;
|
|
5
|
+
// ── Edge anchor calculation ──────────────────────────────────────────────────
|
|
6
|
+
const FIXED_POINTS = {
|
|
7
|
+
top: [0.5, 0],
|
|
8
|
+
bottom: [0.5, 1],
|
|
9
|
+
left: [0, 0.5],
|
|
10
|
+
right: [1, 0.5],
|
|
11
|
+
};
|
|
12
|
+
function getEdgeAnchor(node, side) {
|
|
13
|
+
let x, y;
|
|
14
|
+
switch (side) {
|
|
15
|
+
case "top":
|
|
16
|
+
x = node.x + node.width / 2;
|
|
17
|
+
y = node.y;
|
|
18
|
+
break;
|
|
19
|
+
case "bottom":
|
|
20
|
+
x = node.x + node.width / 2;
|
|
21
|
+
y = node.y + node.height;
|
|
22
|
+
break;
|
|
23
|
+
case "left":
|
|
24
|
+
x = node.x;
|
|
25
|
+
y = node.y + node.height / 2;
|
|
26
|
+
break;
|
|
27
|
+
case "right":
|
|
28
|
+
x = node.x + node.width;
|
|
29
|
+
y = node.y + node.height / 2;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
return { x, y, side, fixedPoint: FIXED_POINTS[side] };
|
|
33
|
+
}
|
|
34
|
+
/** Stagger start anchor for N arrows leaving the same edge.
|
|
35
|
+
* Only applied to left/right exits — for top/bottom, the step routing
|
|
36
|
+
* pattern naturally keeps arrows distinguishable without staggering the start.
|
|
37
|
+
*/
|
|
38
|
+
function staggerAnchor(node, side, index, total) {
|
|
39
|
+
// Top/bottom exits: always use center — step routing handles fan-out visually
|
|
40
|
+
if (side === "top" || side === "bottom" || total <= 1) {
|
|
41
|
+
return getEdgeAnchor(node, side);
|
|
42
|
+
}
|
|
43
|
+
// Left/right exits: stagger vertically along the edge
|
|
44
|
+
const pct = 0.2 + (0.6 * index) / (total - 1);
|
|
45
|
+
const x = side === "right" ? node.x + node.width : node.x;
|
|
46
|
+
const y = node.y + node.height * pct;
|
|
47
|
+
return {
|
|
48
|
+
x,
|
|
49
|
+
y,
|
|
50
|
+
side,
|
|
51
|
+
fixedPoint: [FIXED_POINTS[side][0], pct],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// ── Best edge selection ──────────────────────────────────────────────────────
|
|
55
|
+
/**
|
|
56
|
+
* Minimum y-difference to consider nodes as being on different rows.
|
|
57
|
+
* Matches SPACING_Y * 0.4 from the layout engine (150 * 0.4 = 60).
|
|
58
|
+
* Exported so the renderer can use the same threshold for stagger grouping.
|
|
59
|
+
*/
|
|
60
|
+
exports.VERTICAL_THRESHOLD = 60;
|
|
61
|
+
/**
|
|
62
|
+
* Choose optimal source → target edge sides based on relative position.
|
|
63
|
+
* Strongly prefers vertical (bottom→top / top→bottom) routing when the
|
|
64
|
+
* target is in a different layout row — prevents backward/crossing arrows.
|
|
65
|
+
* Only falls back to horizontal routing for same-row connections.
|
|
66
|
+
*/
|
|
67
|
+
function chooseSides(source, target) {
|
|
68
|
+
const sc = { x: source.x + source.width / 2, y: source.y + source.height / 2 };
|
|
69
|
+
const tc = { x: target.x + target.width / 2, y: target.y + target.height / 2 };
|
|
70
|
+
const dx = tc.x - sc.x;
|
|
71
|
+
const dy = tc.y - sc.y;
|
|
72
|
+
// Different rows: always route vertically regardless of horizontal offset.
|
|
73
|
+
// This avoids backward/crossing arrows when nodes are side-by-side across rows.
|
|
74
|
+
if (Math.abs(dy) > exports.VERTICAL_THRESHOLD) {
|
|
75
|
+
if (dy > 0)
|
|
76
|
+
return { sourceSide: "bottom", targetSide: "top" };
|
|
77
|
+
else
|
|
78
|
+
return { sourceSide: "top", targetSide: "bottom" };
|
|
79
|
+
}
|
|
80
|
+
// Same row: route horizontally.
|
|
81
|
+
if (dx > 0)
|
|
82
|
+
return { sourceSide: "right", targetSide: "left" };
|
|
83
|
+
if (dx < 0)
|
|
84
|
+
return { sourceSide: "left", targetSide: "right" };
|
|
85
|
+
// Fallback (same position — self-loop guard should have filtered this)
|
|
86
|
+
return { sourceSide: "bottom", targetSide: "top" };
|
|
87
|
+
}
|
|
88
|
+
// ── Bounding box of points ───────────────────────────────────────────────────
|
|
89
|
+
function boundingBox(points) {
|
|
90
|
+
let maxAbsX = 0;
|
|
91
|
+
let maxAbsY = 0;
|
|
92
|
+
for (const [px, py] of points) {
|
|
93
|
+
if (Math.abs(px) > maxAbsX)
|
|
94
|
+
maxAbsX = Math.abs(px);
|
|
95
|
+
if (Math.abs(py) > maxAbsY)
|
|
96
|
+
maxAbsY = Math.abs(py);
|
|
97
|
+
}
|
|
98
|
+
return { width: Math.max(maxAbsX, 1), height: Math.max(maxAbsY, 1) };
|
|
99
|
+
}
|
|
100
|
+
// ── Elbow point generation ───────────────────────────────────────────────────
|
|
101
|
+
function buildElbowPoints(sourceAnchor, targetAnchor) {
|
|
102
|
+
const dx = targetAnchor.x - sourceAnchor.x;
|
|
103
|
+
const dy = targetAnchor.y - sourceAnchor.y;
|
|
104
|
+
const sourceSide = sourceAnchor.side;
|
|
105
|
+
const CLEARANCE = 50;
|
|
106
|
+
// Row gap between levels is 70px — use ~half of it so the horizontal
|
|
107
|
+
// segment of a step-routed arrow stays in the lane between rows,
|
|
108
|
+
// not through intermediate nodes on skip-level connections.
|
|
109
|
+
const ROW_CLEARANCE = 35;
|
|
110
|
+
// Nearly straight lines
|
|
111
|
+
if (Math.abs(dx) < 2 && (sourceSide === "top" || sourceSide === "bottom")) {
|
|
112
|
+
return [[0, 0], [0, dy]];
|
|
113
|
+
}
|
|
114
|
+
if (Math.abs(dy) < 2 && (sourceSide === "left" || sourceSide === "right")) {
|
|
115
|
+
return [[0, 0], [dx, 0]];
|
|
116
|
+
}
|
|
117
|
+
switch (sourceSide) {
|
|
118
|
+
case "bottom":
|
|
119
|
+
case "top": {
|
|
120
|
+
if (targetAnchor.side === "top" || targetAnchor.side === "bottom") {
|
|
121
|
+
// Step routing: exit downward/upward ROW_CLEARANCE px (into the row gap),
|
|
122
|
+
// turn horizontal, then continue to target.
|
|
123
|
+
// Using ROW_CLEARANCE (not halfDy) ensures the horizontal segment stays
|
|
124
|
+
// just below/above the source node — skip-level arrows never cut through
|
|
125
|
+
// intermediate nodes that sit at the dy/2 position.
|
|
126
|
+
const exitDir = sourceSide === "bottom" ? 1 : -1;
|
|
127
|
+
const exitOffset = exitDir * ROW_CLEARANCE;
|
|
128
|
+
return [[0, 0], [0, exitOffset], [dx, exitOffset], [dx, dy]];
|
|
129
|
+
}
|
|
130
|
+
// bottom/top → left/right side: exit vertically first, then go across
|
|
131
|
+
return [[0, 0], [0, dy], [dx, dy]];
|
|
132
|
+
}
|
|
133
|
+
case "right":
|
|
134
|
+
case "left": {
|
|
135
|
+
if (targetAnchor.side === "left" || targetAnchor.side === "right") {
|
|
136
|
+
if (sourceSide === "right" &&
|
|
137
|
+
targetAnchor.side === "left" &&
|
|
138
|
+
targetAnchor.x > sourceAnchor.x) {
|
|
139
|
+
// Straight across with Y correction
|
|
140
|
+
return Math.abs(dy) < 2 ? [[0, 0], [dx, 0]] : [[0, 0], [dx, 0], [dx, dy]];
|
|
141
|
+
}
|
|
142
|
+
// U-turn: same side or back direction
|
|
143
|
+
return [
|
|
144
|
+
[0, 0],
|
|
145
|
+
[CLEARANCE, 0],
|
|
146
|
+
[CLEARANCE, dy],
|
|
147
|
+
[dx, dy],
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
return [[0, 0], [dx, 0], [dx, dy]];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Fallback — straight line
|
|
154
|
+
return [[0, 0], [dx, dy]];
|
|
155
|
+
}
|
|
156
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
157
|
+
/**
|
|
158
|
+
* Route an elbow arrow from source → target nodes.
|
|
159
|
+
*
|
|
160
|
+
* @param sourceEdgeIndex 0-based index of this arrow among all arrows sharing the same source edge
|
|
161
|
+
* @param totalFromSource total count of arrows leaving from the same edge of source
|
|
162
|
+
*/
|
|
163
|
+
function routeArrow(source, target, sourceEdgeIndex = 0, totalFromSource = 1) {
|
|
164
|
+
const { sourceSide, targetSide } = chooseSides(source, target);
|
|
165
|
+
const sourceAnchor = staggerAnchor(source, sourceSide, sourceEdgeIndex, totalFromSource);
|
|
166
|
+
const targetAnchor = getEdgeAnchor(target, targetSide);
|
|
167
|
+
const points = buildElbowPoints(sourceAnchor, targetAnchor);
|
|
168
|
+
const { width, height } = boundingBox(points);
|
|
169
|
+
return { sourceAnchor, targetAnchor, points, width, height };
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=arrow-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arrow-router.js","sourceRoot":"","sources":["../../src/layout/arrow-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Diagram, LayoutNode } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* BFS-based layered DAG layout using Kahn's algorithm.
|
|
4
|
+
* Each node is enqueued exactly once (when all its predecessors are processed),
|
|
5
|
+
* so cycles can never cause an infinite loop — cycle nodes are placed after the
|
|
6
|
+
* DAG portion at maxLevel + 1.
|
|
7
|
+
*/
|
|
8
|
+
export declare function layoutDAG(diagram: Diagram): LayoutNode[];
|
|
9
|
+
/**
|
|
10
|
+
* BFS-based layered DAG layout.
|
|
11
|
+
* 1. Assign each node a level via BFS from roots (nodes with no incoming edges).
|
|
12
|
+
* 2. Within each level, position nodes in a row and center each row against the widest row.
|
|
13
|
+
*/
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.layoutDAG = layoutDAG;
|
|
4
|
+
const DEFAULT_NODE_WIDTH = 180;
|
|
5
|
+
const DEFAULT_NODE_HEIGHT = 80;
|
|
6
|
+
const MIN_NODE_HEIGHT = 60;
|
|
7
|
+
const SPACING_X = 220; // horizontal gap between node left edges
|
|
8
|
+
const SPACING_GAP_Y = 70; // vertical gap between bottom of one row and top of next
|
|
9
|
+
const MARGIN_X = 80;
|
|
10
|
+
const MARGIN_Y = 80;
|
|
11
|
+
// Font constants mirrored from renderer — used to estimate node height from label
|
|
12
|
+
const FONT_SIZE = 16;
|
|
13
|
+
const LINE_HEIGHT = 1.25;
|
|
14
|
+
const CHAR_WIDTH_EST = 9; // approximate px per character at fontSize 16
|
|
15
|
+
const TEXT_PADDING_X = 16; // total horizontal text padding
|
|
16
|
+
const TEXT_PADDING_Y = 24; // total vertical text padding
|
|
17
|
+
/** Estimate how many lines a label needs inside a box of the given width. */
|
|
18
|
+
function estimateLines(label, nodeWidth) {
|
|
19
|
+
const charsPerLine = Math.max(1, Math.floor((nodeWidth - TEXT_PADDING_X) / CHAR_WIDTH_EST));
|
|
20
|
+
let lines = 0;
|
|
21
|
+
for (const para of label.split("\n")) {
|
|
22
|
+
lines += Math.max(1, Math.ceil(para.length / charsPerLine));
|
|
23
|
+
}
|
|
24
|
+
return lines;
|
|
25
|
+
}
|
|
26
|
+
/** Compute the effective node height accounting for label wrapping. */
|
|
27
|
+
function effectiveHeight(label, nodeWidth, styleHeight) {
|
|
28
|
+
if (styleHeight !== undefined)
|
|
29
|
+
return styleHeight;
|
|
30
|
+
const lines = estimateLines(label, nodeWidth);
|
|
31
|
+
const textH = Math.ceil(lines * FONT_SIZE * LINE_HEIGHT);
|
|
32
|
+
return Math.max(MIN_NODE_HEIGHT, textH + TEXT_PADDING_Y);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* BFS-based layered DAG layout using Kahn's algorithm.
|
|
36
|
+
* Each node is enqueued exactly once (when all its predecessors are processed),
|
|
37
|
+
* so cycles can never cause an infinite loop — cycle nodes are placed after the
|
|
38
|
+
* DAG portion at maxLevel + 1.
|
|
39
|
+
*/
|
|
40
|
+
function layoutDAG(diagram) {
|
|
41
|
+
const { nodes, edges } = diagram;
|
|
42
|
+
if (nodes.length === 0)
|
|
43
|
+
return [];
|
|
44
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
45
|
+
const inDegree = new Map();
|
|
46
|
+
const adjacency = new Map();
|
|
47
|
+
for (const id of nodeIds) {
|
|
48
|
+
inDegree.set(id, 0);
|
|
49
|
+
adjacency.set(id, []);
|
|
50
|
+
}
|
|
51
|
+
for (const edge of edges) {
|
|
52
|
+
if (edge.from !== edge.to) {
|
|
53
|
+
adjacency.get(edge.from)?.push(edge.to);
|
|
54
|
+
inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// ── Kahn's level assignment (no infinite loop) ────────────────────────────
|
|
58
|
+
// A node is enqueued only when its in-degree reaches 0 (all predecessors done).
|
|
59
|
+
// Cycle nodes never reach in-degree 0, so they never enter the queue.
|
|
60
|
+
const inDegreeMut = new Map(inDegree);
|
|
61
|
+
const level = new Map();
|
|
62
|
+
const queue = [];
|
|
63
|
+
for (const [id, deg] of inDegreeMut) {
|
|
64
|
+
if (deg === 0) {
|
|
65
|
+
level.set(id, 0);
|
|
66
|
+
queue.push(id);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
while (queue.length) {
|
|
70
|
+
const curr = queue.shift();
|
|
71
|
+
const currLevel = level.get(curr) ?? 0;
|
|
72
|
+
for (const neighbor of adjacency.get(curr) ?? []) {
|
|
73
|
+
// Longest-path level update
|
|
74
|
+
const proposed = currLevel + 1;
|
|
75
|
+
if (!level.has(neighbor) || proposed > (level.get(neighbor) ?? 0)) {
|
|
76
|
+
level.set(neighbor, proposed);
|
|
77
|
+
}
|
|
78
|
+
const newDeg = (inDegreeMut.get(neighbor) ?? 0) - 1;
|
|
79
|
+
inDegreeMut.set(neighbor, newDeg);
|
|
80
|
+
if (newDeg === 0) {
|
|
81
|
+
queue.push(neighbor);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Place cycle nodes (never reached in-degree 0) after the DAG portion
|
|
86
|
+
if (level.size === 0) {
|
|
87
|
+
// Fully cyclic graph — all at level 0
|
|
88
|
+
for (const id of nodeIds)
|
|
89
|
+
level.set(id, 0);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const maxLevel = Math.max(...level.values());
|
|
93
|
+
for (const id of nodeIds) {
|
|
94
|
+
if (!level.has(id))
|
|
95
|
+
level.set(id, maxLevel + 1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ── Group nodes by level ─────────────────────────────────────────────────
|
|
99
|
+
const levelGroups = new Map();
|
|
100
|
+
for (const [id, lvl] of level) {
|
|
101
|
+
if (!levelGroups.has(lvl))
|
|
102
|
+
levelGroups.set(lvl, []);
|
|
103
|
+
levelGroups.get(lvl).push(id);
|
|
104
|
+
}
|
|
105
|
+
const sortedLevels = [...levelGroups.keys()].sort((a, b) => a - b);
|
|
106
|
+
// Widest level (node count) drives the total canvas width
|
|
107
|
+
const maxLevelCount = Math.max(...sortedLevels.map((l) => levelGroups.get(l).length));
|
|
108
|
+
const totalCanvasWidth = maxLevelCount * SPACING_X;
|
|
109
|
+
// ── Compute effective dimensions per node ─────────────────────────────────
|
|
110
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
111
|
+
const nodeDims = new Map();
|
|
112
|
+
for (const id of nodeIds) {
|
|
113
|
+
const n = nodeMap.get(id);
|
|
114
|
+
const w = n.style?.width ?? DEFAULT_NODE_WIDTH;
|
|
115
|
+
const h = effectiveHeight(n.label, w, n.style?.height);
|
|
116
|
+
nodeDims.set(id, { width: w, height: h });
|
|
117
|
+
}
|
|
118
|
+
// ── Compute cumulative Y per level (dynamic row heights) ──────────────────
|
|
119
|
+
let cumulativeY = MARGIN_Y;
|
|
120
|
+
const levelY = new Map();
|
|
121
|
+
for (const lvl of sortedLevels) {
|
|
122
|
+
levelY.set(lvl, cumulativeY);
|
|
123
|
+
const maxH = Math.max(...levelGroups.get(lvl).map((id) => nodeDims.get(id).height));
|
|
124
|
+
cumulativeY += maxH + SPACING_GAP_Y;
|
|
125
|
+
}
|
|
126
|
+
// ── Build LayoutNode array ───────────────────────────────────────────────
|
|
127
|
+
const layoutNodes = [];
|
|
128
|
+
for (const lvl of sortedLevels) {
|
|
129
|
+
const group = levelGroups.get(lvl);
|
|
130
|
+
group.sort((a, b) => nodeIds.indexOf(a) - nodeIds.indexOf(b));
|
|
131
|
+
const levelWidth = group.length * SPACING_X;
|
|
132
|
+
const offsetX = MARGIN_X + Math.floor((totalCanvasWidth - levelWidth) / 2);
|
|
133
|
+
group.forEach((id, colIndex) => {
|
|
134
|
+
const inputNode = nodeMap.get(id);
|
|
135
|
+
const { width, height } = nodeDims.get(id);
|
|
136
|
+
layoutNodes.push({
|
|
137
|
+
id,
|
|
138
|
+
label: inputNode.label,
|
|
139
|
+
type: inputNode.type ?? "process",
|
|
140
|
+
metadata: inputNode.metadata,
|
|
141
|
+
style: inputNode.style,
|
|
142
|
+
x: offsetX + colIndex * SPACING_X,
|
|
143
|
+
y: levelY.get(lvl),
|
|
144
|
+
width,
|
|
145
|
+
height,
|
|
146
|
+
level: lvl,
|
|
147
|
+
colIndex,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return layoutNodes;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* BFS-based layered DAG layout.
|
|
155
|
+
* 1. Assign each node a level via BFS from roots (nodes with no incoming edges).
|
|
156
|
+
* 2. Within each level, position nodes in a row and center each row against the widest row.
|
|
157
|
+
*/
|
|
158
|
+
//# sourceMappingURL=dag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dag.js","sourceRoot":"","sources":["../../src/layout/dag.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.layoutGrid = layoutGrid;
|
|
4
|
+
const DEFAULT_NODE_WIDTH = 180;
|
|
5
|
+
const DEFAULT_NODE_HEIGHT = 80;
|
|
6
|
+
const MIN_NODE_HEIGHT = 60;
|
|
7
|
+
const SPACING_X = 220;
|
|
8
|
+
const SPACING_Y = 150;
|
|
9
|
+
const MARGIN_X = 80;
|
|
10
|
+
const MARGIN_Y = 80;
|
|
11
|
+
const FONT_SIZE = 16;
|
|
12
|
+
const LINE_HEIGHT = 1.25;
|
|
13
|
+
const CHAR_WIDTH_EST = 9;
|
|
14
|
+
const TEXT_PADDING_X = 16;
|
|
15
|
+
const TEXT_PADDING_Y = 24;
|
|
16
|
+
function effectiveHeight(label, nodeWidth, styleHeight) {
|
|
17
|
+
if (styleHeight !== undefined)
|
|
18
|
+
return styleHeight;
|
|
19
|
+
const charsPerLine = Math.max(1, Math.floor((nodeWidth - TEXT_PADDING_X) / CHAR_WIDTH_EST));
|
|
20
|
+
let lines = 0;
|
|
21
|
+
for (const para of label.split("\n")) {
|
|
22
|
+
lines += Math.max(1, Math.ceil(para.length / charsPerLine));
|
|
23
|
+
}
|
|
24
|
+
return Math.max(MIN_NODE_HEIGHT, Math.ceil(lines * FONT_SIZE * LINE_HEIGHT) + TEXT_PADDING_Y);
|
|
25
|
+
}
|
|
26
|
+
/** Simple grid layout: arranges nodes in rows of sqrt(n) columns */
|
|
27
|
+
function layoutGrid(diagram) {
|
|
28
|
+
const { nodes } = diagram;
|
|
29
|
+
if (nodes.length === 0)
|
|
30
|
+
return [];
|
|
31
|
+
const cols = Math.max(1, Math.ceil(Math.sqrt(nodes.length)));
|
|
32
|
+
return nodes.map((node, i) => {
|
|
33
|
+
const col = i % cols;
|
|
34
|
+
const row = Math.floor(i / cols);
|
|
35
|
+
const w = node.style?.width ?? DEFAULT_NODE_WIDTH;
|
|
36
|
+
const h = effectiveHeight(node.label, w, node.style?.height);
|
|
37
|
+
return {
|
|
38
|
+
id: node.id,
|
|
39
|
+
label: node.label,
|
|
40
|
+
type: node.type ?? "process",
|
|
41
|
+
metadata: node.metadata,
|
|
42
|
+
style: node.style,
|
|
43
|
+
x: MARGIN_X + col * SPACING_X,
|
|
44
|
+
y: MARGIN_Y + row * SPACING_Y,
|
|
45
|
+
width: w,
|
|
46
|
+
height: h,
|
|
47
|
+
level: row,
|
|
48
|
+
colIndex: col,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=grid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grid.js","sourceRoot":"","sources":["../../src/layout/grid.ts"],"names":[],"mappings":";;AA0BA,gCAyBC;AAjDD,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAC/B,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAC/B,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,QAAQ,GAAG,EAAE,CAAC;AACpB,MAAM,QAAQ,GAAG,EAAE,CAAC;AACpB,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,WAAW,GAAG,IAAI,CAAC;AACzB,MAAM,cAAc,GAAG,CAAC,CAAC;AACzB,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B,SAAS,eAAe,CAAC,KAAa,EAAE,SAAiB,EAAE,WAAoB;IAC7E,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAClD,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,cAAc,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC;IAC5F,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,GAAG,WAAW,CAAC,GAAG,cAAc,CAAC,CAAC;AAChG,CAAC;AAED,oEAAoE;AACpE,SAAgB,UAAU,CAAC,OAAgB;IACzC,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAC1B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAElC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAE7D,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QAC3B,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,KAAK,IAAI,kBAAkB,CAAC;QAClD,MAAM,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC7D,OAAO;YACL,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,SAAS;YAC5B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,CAAC,EAAE,QAAQ,GAAG,GAAG,GAAG,SAAS;YAC7B,CAAC,EAAE,QAAQ,GAAG,GAAG,GAAG,SAAS;YAC7B,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,CAAC;YACT,KAAK,EAAE,GAAG;YACV,QAAQ,EAAE,GAAG;SACd,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Diagram, LayoutNode, LayoutType } from "../types/index.js";
|
|
2
|
+
type LayoutFn = (diagram: Diagram) => LayoutNode[];
|
|
3
|
+
export declare function registerLayoutEngine(name: string, fn: LayoutFn): void;
|
|
4
|
+
export declare function runLayout(diagram: Diagram, type: LayoutType): LayoutNode[];
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerLayoutEngine = registerLayoutEngine;
|
|
4
|
+
exports.runLayout = runLayout;
|
|
5
|
+
const dag_js_1 = require("./dag.js");
|
|
6
|
+
const grid_js_1 = require("./grid.js");
|
|
7
|
+
const registry = new Map([
|
|
8
|
+
["dag", dag_js_1.layoutDAG],
|
|
9
|
+
["grid", grid_js_1.layoutGrid],
|
|
10
|
+
]);
|
|
11
|
+
function registerLayoutEngine(name, fn) {
|
|
12
|
+
registry.set(name, fn);
|
|
13
|
+
}
|
|
14
|
+
function runLayout(diagram, type) {
|
|
15
|
+
const fn = registry.get(type);
|
|
16
|
+
if (!fn) {
|
|
17
|
+
throw new Error(`Unknown layout type: "${type}". Available: ${[...registry.keys()].join(", ")}`);
|
|
18
|
+
}
|
|
19
|
+
return fn(diagram);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/layout/index.ts"],"names":[],"mappings":";;AAWA,oDAEC;AAED,8BAQC;AAtBD,qCAAqC;AACrC,uCAAuC;AAIvC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAmB;IACzC,CAAC,KAAK,EAAE,kBAAS,CAAC;IAClB,CAAC,MAAM,EAAE,oBAAU,CAAC;CACrB,CAAC,CAAC;AAEH,SAAgB,oBAAoB,CAAC,IAAY,EAAE,EAAY;IAC7D,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AACzB,CAAC;AAED,SAAgB,SAAS,CAAC,OAAgB,EAAE,IAAgB;IAC1D,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CACb,yBAAyB,IAAI,iBAAiB,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalize = normalize;
|
|
4
|
+
const DEFAULT_NODE_TYPE = "process";
|
|
5
|
+
function normalize(diagram) {
|
|
6
|
+
const warnings = [];
|
|
7
|
+
const nodes = diagram.nodes.map((node) => ({
|
|
8
|
+
...node,
|
|
9
|
+
id: node.id.trim(),
|
|
10
|
+
label: node.label.trim(),
|
|
11
|
+
type: (node.type ?? DEFAULT_NODE_TYPE).trim().toLowerCase(),
|
|
12
|
+
metadata: node.metadata ?? {},
|
|
13
|
+
}));
|
|
14
|
+
// Deduplicate edges: same directed from/to pair — keep first, warn on extras
|
|
15
|
+
const seenEdges = new Map(); // key → label of first seen
|
|
16
|
+
const edges = diagram.edges
|
|
17
|
+
.filter((edge) => {
|
|
18
|
+
const key = `${edge.from.trim()}→${edge.to.trim()}`;
|
|
19
|
+
if (seenEdges.has(key)) {
|
|
20
|
+
warnings.push(`Duplicate edge "${edge.from}" → "${edge.to}" dropped (only the first is kept)`);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
seenEdges.set(key, edge.label);
|
|
24
|
+
return true;
|
|
25
|
+
})
|
|
26
|
+
.map((edge) => ({
|
|
27
|
+
...edge,
|
|
28
|
+
from: edge.from.trim(),
|
|
29
|
+
to: edge.to.trim(),
|
|
30
|
+
label: edge.label?.trim(),
|
|
31
|
+
}));
|
|
32
|
+
return {
|
|
33
|
+
diagram: { ...diagram, nodes, edges, title: diagram.title?.trim() },
|
|
34
|
+
warnings,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/normalizer/index.ts"],"names":[],"mappings":";;AAIA,8BAoCC;AAtCD,MAAM,iBAAiB,GAAG,SAAS,CAAC;AAEpC,SAAgB,SAAS,CAAC,OAAgB;IACxC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,MAAM,KAAK,GAAgB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACtD,GAAG,IAAI;QACP,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QACxB,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,iBAAiB,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;QAC3D,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,EAAE;KAC9B,CAAC,CAAC,CAAC;IAEJ,6EAA6E;IAC7E,MAAM,SAAS,GAAG,IAAI,GAAG,EAA8B,CAAC,CAAC,4BAA4B;IACrF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK;SACxB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QACf,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QACpD,IAAI,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CACX,mBAAmB,IAAI,CAAC,IAAI,QAAQ,IAAI,CAAC,EAAE,oCAAoC,CAChF,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACd,GAAG,IAAI;QACP,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;QACtB,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;KAC1B,CAAC,CAAC,CAAC;IAEN,OAAO;QACL,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACnE,QAAQ;KACT,CAAC;AACJ,CAAC"}
|