kymostudio 0.1.0 → 0.2.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/README.md +52 -29
- package/dist/bpmn-shapes.d.ts +15 -0
- package/dist/bpmn-shapes.d.ts.map +1 -0
- package/dist/bpmn-shapes.js +344 -0
- package/dist/bpmn-shapes.js.map +1 -0
- package/dist/from-bpmn.d.ts +15 -0
- package/dist/from-bpmn.d.ts.map +1 -0
- package/dist/from-bpmn.js +228 -0
- package/dist/from-bpmn.js.map +1 -0
- package/dist/icons-builtin.d.ts +14 -0
- package/dist/icons-builtin.d.ts.map +1 -0
- package/{src/js → dist}/icons-builtin.js +70 -115
- package/dist/icons-builtin.js.map +1 -0
- package/dist/icons-loader.d.ts +29 -0
- package/dist/icons-loader.d.ts.map +1 -0
- package/dist/icons-loader.js +88 -0
- package/dist/icons-loader.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +166 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +165 -0
- package/dist/model.js.map +1 -0
- package/dist/render.d.ts +27 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +161 -0
- package/dist/render.js.map +1 -0
- package/dist/xml.d.ts +22 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +116 -0
- package/dist/xml.js.map +1 -0
- package/package.json +29 -11
- package/src/js/icons-loader.js +0 -91
- package/src/js/index.js +0 -13
- package/src/js/model.js +0 -176
package/dist/xml.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, dependency-free XML reader — just enough to walk a BPMN 2.0
|
|
3
|
+
* document. Produces a tree of {@link XmlEl} nodes with namespace prefixes
|
|
4
|
+
* stripped (so `bpmn:task` and `bpmn2:task` both read as `task`). Handles
|
|
5
|
+
* comments, CDATA, processing instructions / the XML declaration, doctype,
|
|
6
|
+
* self-closing tags, single/double-quoted attributes, and the standard +
|
|
7
|
+
* numeric entity references. It is a reader, not a validator.
|
|
8
|
+
*/
|
|
9
|
+
const local = (name) => {
|
|
10
|
+
const c = name.indexOf(":");
|
|
11
|
+
return c >= 0 ? name.slice(c + 1) : name;
|
|
12
|
+
};
|
|
13
|
+
function decode(s) {
|
|
14
|
+
if (s.indexOf("&") < 0)
|
|
15
|
+
return s;
|
|
16
|
+
return s.replace(/&(#x?[0-9a-fA-F]+|\w+);/g, (m, body) => {
|
|
17
|
+
if (body[0] === "#") {
|
|
18
|
+
const code = body[1] === "x" || body[1] === "X"
|
|
19
|
+
? parseInt(body.slice(2), 16)
|
|
20
|
+
: parseInt(body.slice(1), 10);
|
|
21
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : m;
|
|
22
|
+
}
|
|
23
|
+
return { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" }[body] ?? m;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const ATTR_RE = /([\w:.-]+)\s*=\s*("([^"]*)"|'([^']*)')/g;
|
|
27
|
+
function parseTag(body) {
|
|
28
|
+
const nameMatch = /^\s*([\w:.-]+)/.exec(body);
|
|
29
|
+
const name = nameMatch ? nameMatch[1] : "";
|
|
30
|
+
const attrs = {};
|
|
31
|
+
let m;
|
|
32
|
+
ATTR_RE.lastIndex = nameMatch ? nameMatch[0].length : 0;
|
|
33
|
+
while ((m = ATTR_RE.exec(body)) !== null) {
|
|
34
|
+
attrs[local(m[1])] = decode(m[3] !== undefined ? m[3] : m[4]);
|
|
35
|
+
}
|
|
36
|
+
return { name, attrs };
|
|
37
|
+
}
|
|
38
|
+
/** Index of the closing `>` of a tag starting at `start`, respecting quotes. */
|
|
39
|
+
function tagEnd(src, start) {
|
|
40
|
+
let q = "";
|
|
41
|
+
for (let i = start; i < src.length; i++) {
|
|
42
|
+
const ch = src[i];
|
|
43
|
+
if (q) {
|
|
44
|
+
if (ch === q)
|
|
45
|
+
q = "";
|
|
46
|
+
}
|
|
47
|
+
else if (ch === '"' || ch === "'")
|
|
48
|
+
q = ch;
|
|
49
|
+
else if (ch === ">")
|
|
50
|
+
return i;
|
|
51
|
+
}
|
|
52
|
+
return src.length;
|
|
53
|
+
}
|
|
54
|
+
/** Parse `src` and return its root element. */
|
|
55
|
+
export function parseXml(src) {
|
|
56
|
+
const root = { tag: "#root", attrs: {}, children: [], text: "" };
|
|
57
|
+
const stack = [root];
|
|
58
|
+
const top = () => stack[stack.length - 1];
|
|
59
|
+
const n = src.length;
|
|
60
|
+
let i = 0;
|
|
61
|
+
while (i < n) {
|
|
62
|
+
const lt = src.indexOf("<", i);
|
|
63
|
+
if (lt < 0)
|
|
64
|
+
break;
|
|
65
|
+
if (lt > i) {
|
|
66
|
+
const t = decode(src.slice(i, lt));
|
|
67
|
+
if (t.trim())
|
|
68
|
+
top().text += t;
|
|
69
|
+
}
|
|
70
|
+
if (src.startsWith("<!--", lt)) {
|
|
71
|
+
const e = src.indexOf("-->", lt + 4);
|
|
72
|
+
i = e < 0 ? n : e + 3;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (src.startsWith("<![CDATA[", lt)) {
|
|
76
|
+
const e = src.indexOf("]]>", lt + 9);
|
|
77
|
+
top().text += src.slice(lt + 9, e < 0 ? n : e);
|
|
78
|
+
i = e < 0 ? n : e + 3;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (src.startsWith("<?", lt)) {
|
|
82
|
+
const e = src.indexOf("?>", lt + 2);
|
|
83
|
+
i = e < 0 ? n : e + 2;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (src.startsWith("<!", lt)) {
|
|
87
|
+
const e = tagEnd(src, lt + 2);
|
|
88
|
+
i = e + 1;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (src.startsWith("</", lt)) {
|
|
92
|
+
const e = src.indexOf(">", lt + 2);
|
|
93
|
+
if (stack.length > 1)
|
|
94
|
+
stack.pop();
|
|
95
|
+
i = e < 0 ? n : e + 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const e = tagEnd(src, lt + 1);
|
|
99
|
+
const inner = src.slice(lt + 1, e);
|
|
100
|
+
const selfClose = inner.endsWith("/");
|
|
101
|
+
const { name, attrs } = parseTag(selfClose ? inner.slice(0, -1) : inner);
|
|
102
|
+
const el = { tag: local(name), attrs, children: [], text: "" };
|
|
103
|
+
top().children.push(el);
|
|
104
|
+
if (!selfClose)
|
|
105
|
+
stack.push(el);
|
|
106
|
+
i = e + 1;
|
|
107
|
+
}
|
|
108
|
+
return root.children[0] ?? root;
|
|
109
|
+
}
|
|
110
|
+
/** Depth-first (document order) iterator over `el` and all descendants. */
|
|
111
|
+
export function* iterAll(el) {
|
|
112
|
+
yield el;
|
|
113
|
+
for (const c of el.children)
|
|
114
|
+
yield* iterAll(c);
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=xml.js.map
|
package/dist/xml.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml.js","sourceRoot":"","sources":["../src/xml.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,30 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kymostudio",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Diagram-as-code
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Diagram-as-code for browser/Node — data model, icon library, SVG renderer, and BPMN 2.0 importer. Independent, dependency-free TypeScript implementation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "rain1024",
|
|
8
|
-
"keywords": [
|
|
9
|
-
|
|
8
|
+
"keywords": [
|
|
9
|
+
"diagram",
|
|
10
|
+
"diagram-as-code",
|
|
11
|
+
"svg",
|
|
12
|
+
"architecture",
|
|
13
|
+
"icons"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/kymostudio/kymostudio#readme",
|
|
10
16
|
"repository": {
|
|
11
17
|
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/
|
|
18
|
+
"url": "git+https://github.com/kymostudio/kymostudio.git",
|
|
19
|
+
"directory": "packages/js"
|
|
13
20
|
},
|
|
14
21
|
"bugs": {
|
|
15
|
-
"url": "https://github.com/
|
|
22
|
+
"url": "https://github.com/kymostudio/kymostudio/issues"
|
|
16
23
|
},
|
|
17
|
-
"main": "
|
|
24
|
+
"main": "dist/index.js",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
18
26
|
"exports": {
|
|
19
|
-
".":
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
20
31
|
"./icons-manifest.json": "./icons-manifest.json"
|
|
21
32
|
},
|
|
22
33
|
"scripts": {
|
|
34
|
+
"build": "tsc",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
23
36
|
"build-manifest": "node scripts/build-manifest.mjs",
|
|
24
|
-
"
|
|
37
|
+
"prepack": "npm run build",
|
|
38
|
+
"test": "npm run build && node --test"
|
|
25
39
|
},
|
|
26
40
|
"files": [
|
|
27
|
-
"
|
|
41
|
+
"dist/",
|
|
28
42
|
"icons-manifest.json"
|
|
29
|
-
]
|
|
43
|
+
],
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^25.9.1",
|
|
46
|
+
"typescript": "^6.0.3"
|
|
47
|
+
}
|
|
30
48
|
}
|
package/src/js/icons-loader.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Icon loader — JS mirror of `src/icons.py:get_icon`.
|
|
3
|
-
*
|
|
4
|
-
* Built-in glyphs (from `icons-builtin.js`) are returned synchronously
|
|
5
|
-
* wrapped in `Promise.resolve()`. File-backed PNG icons are resolved
|
|
6
|
-
* lazily: on first request the manifest is fetched, then the PNG bytes
|
|
7
|
-
* are fetched and embedded as a base64 `<image>` tag inside an SVG
|
|
8
|
-
* fragment. Subsequent lookups are cache hits.
|
|
9
|
-
*
|
|
10
|
-
* Configure the base URL with `setIconBaseURL(url)` before the first
|
|
11
|
-
* call — defaults to `""` (paths are relative to the page).
|
|
12
|
-
*/
|
|
13
|
-
import { ICONS } from "./icons-builtin.js";
|
|
14
|
-
|
|
15
|
-
const IMAGE_SIZE = 64;
|
|
16
|
-
const _cache = new Map(Object.entries(ICONS)); // builtin keys start cached
|
|
17
|
-
let _manifest = null;
|
|
18
|
-
let _manifestPromise = null;
|
|
19
|
-
let _baseURL = "";
|
|
20
|
-
|
|
21
|
-
/** Set the URL prefix used to fetch the manifest + icon files. */
|
|
22
|
-
export function setIconBaseURL(url) {
|
|
23
|
-
_baseURL = url.endsWith("/") ? url.slice(0, -1) : url;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Optional override for tests: inject a manifest map directly. */
|
|
27
|
-
export function setManifest(map) {
|
|
28
|
-
_manifest = { ...map };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Optional override for tests: register a single key → SVG fragment. */
|
|
32
|
-
export function registerIcon(key, svg) {
|
|
33
|
-
_cache.set(key, svg);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function loadManifest() {
|
|
37
|
-
if (_manifest) return _manifest;
|
|
38
|
-
if (_manifestPromise) return _manifestPromise;
|
|
39
|
-
const url = `${_baseURL}/icons-manifest.json`.replace(/^\//, "");
|
|
40
|
-
_manifestPromise = fetch(url).then(r => {
|
|
41
|
-
if (!r.ok) throw new Error(`manifest fetch failed: ${r.status}`);
|
|
42
|
-
return r.json();
|
|
43
|
-
}).then(m => (_manifest = m));
|
|
44
|
-
return _manifestPromise;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function pngBytesToImageTag(bytes, size = IMAGE_SIZE) {
|
|
48
|
-
// Base64-encode the PNG bytes for embedding in an SVG `<image>` tag.
|
|
49
|
-
let bin = "";
|
|
50
|
-
for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
|
|
51
|
-
const b64 = (typeof btoa !== "undefined") ? btoa(bin) : Buffer.from(bytes).toString("base64");
|
|
52
|
-
const half = (size / 2) | 0;
|
|
53
|
-
return `<image href="data:image/png;base64,${b64}" ` +
|
|
54
|
-
`x="-${half}" y="-${half}" width="${size}" height="${size}"/>`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function svgTextToInline(text, size = IMAGE_SIZE) {
|
|
58
|
-
const half = (size / 2) | 0;
|
|
59
|
-
return `<svg x="-${half}" y="-${half}" width="${size}" height="${size}" ` +
|
|
60
|
-
`overflow="visible">${text}</svg>`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Resolve `key` to an SVG fragment. Returns a Promise; built-in icons
|
|
65
|
-
* resolve immediately, file-backed PNGs/SVGs after a network fetch.
|
|
66
|
-
* Throws when the key is not in the built-in registry and not in the
|
|
67
|
-
* manifest.
|
|
68
|
-
*/
|
|
69
|
-
export async function getIcon(key) {
|
|
70
|
-
if (_cache.has(key)) return _cache.get(key);
|
|
71
|
-
|
|
72
|
-
const manifest = await loadManifest();
|
|
73
|
-
const path = manifest[key];
|
|
74
|
-
if (!path) throw new Error(`unknown icon: ${JSON.stringify(key)}`);
|
|
75
|
-
|
|
76
|
-
const url = _baseURL ? `${_baseURL}/${path}` : path;
|
|
77
|
-
const res = await fetch(url);
|
|
78
|
-
if (!res.ok) throw new Error(`icon fetch failed: ${path} (${res.status})`);
|
|
79
|
-
|
|
80
|
-
let svg;
|
|
81
|
-
if (path.endsWith(".svg")) {
|
|
82
|
-
svg = svgTextToInline(await res.text());
|
|
83
|
-
} else {
|
|
84
|
-
const buf = new Uint8Array(await res.arrayBuffer());
|
|
85
|
-
svg = pngBytesToImageTag(buf);
|
|
86
|
-
}
|
|
87
|
-
_cache.set(key, svg);
|
|
88
|
-
return svg;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export { ICONS };
|
package/src/js/index.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* kymo — browser/Node port of the diagram-as-code model + icon library.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors the Python source-of-truth at `src/python/kymo/`. This entry
|
|
5
|
-
* point re-exports the data model (`makeComponent`, `makeEdge`, `anchor`,
|
|
6
|
-
* `resolveAnchors`, …) and the icon registry/loader (`ICONS`, `getIcon`,
|
|
7
|
-
* `setIconBaseURL`, …).
|
|
8
|
-
*
|
|
9
|
-
* Note: the DSL parser, layout engine and SVG renderer are currently
|
|
10
|
-
* Python-only; this package ships the shared model + icons.
|
|
11
|
-
*/
|
|
12
|
-
export * from "./model.js";
|
|
13
|
-
export * from "./icons-loader.js"; // re-exports ICONS, getIcon, setIconBaseURL, setManifest, registerIcon
|
package/src/js/model.js
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Data model for the container diagram — JS mirror of `src/model.py`.
|
|
3
|
-
*
|
|
4
|
-
* Components, regions and edges are plain object literals (no classes).
|
|
5
|
-
* `anchor(node, side)` and `resolveAnchors(edge, src, dst)` mimic the
|
|
6
|
-
* Python implementations 1:1.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** @type {Record<string, [number, number]>} */
|
|
10
|
-
export const SHAPE_HALF = {
|
|
11
|
-
"circle": [38, 38],
|
|
12
|
-
"cube": [40, 40],
|
|
13
|
-
"cube-big": [50, 50],
|
|
14
|
-
"box": [35, 35],
|
|
15
|
-
"cylinder": [35, 35],
|
|
16
|
-
"hex": [35, 32], // flat-top hexagon — wider than tall
|
|
17
|
-
"annotation": [0, 0],
|
|
18
|
-
"aws-tile": [32, 32],
|
|
19
|
-
"aws-tile-hero": [40, 40],
|
|
20
|
-
"badge": [14, 14],
|
|
21
|
-
"image": [32, 32],
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/** @type {Record<string, number>} */
|
|
25
|
-
export const LABEL_HEIGHT = {
|
|
26
|
-
"circle": 38,
|
|
27
|
-
"cube": 42,
|
|
28
|
-
"cube-big": 48,
|
|
29
|
-
"box": 38,
|
|
30
|
-
"cylinder": 38,
|
|
31
|
-
"hex": 40,
|
|
32
|
-
"annotation": 0,
|
|
33
|
-
"aws-tile": 48,
|
|
34
|
-
"aws-tile-hero": 48,
|
|
35
|
-
"badge": 0,
|
|
36
|
-
"image": 26,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// ── Factories (mirror Python @dataclass with defaults) ────────────────
|
|
40
|
-
|
|
41
|
-
export function makeComponent({
|
|
42
|
-
id, name = "", subtitle = "", icon = "", shape = "box", accent = "green",
|
|
43
|
-
pos = [0, 0],
|
|
44
|
-
parent = null, align = null, alignGap = 24, alignOffset = [0, 0],
|
|
45
|
-
}) {
|
|
46
|
-
return {
|
|
47
|
-
id, name, subtitle, icon, shape, accent, pos,
|
|
48
|
-
parent, align, alignGap, alignOffset,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function makeRegion({
|
|
53
|
-
id, label = "", bounds = [0, 0, 0, 0], contains = [],
|
|
54
|
-
padding = [24, 24], paddingBottom = null, style = "outer",
|
|
55
|
-
icon = null, layout = null, pos = null, gap = 24, align = "center",
|
|
56
|
-
visible = true, borderDash = null, borderStroke = null,
|
|
57
|
-
labelAnchor = "middle", labelPosition = null,
|
|
58
|
-
}) {
|
|
59
|
-
return {
|
|
60
|
-
id, label, bounds, contains, padding, paddingBottom, style, icon,
|
|
61
|
-
layout, pos, gap, align, visible, borderDash, borderStroke,
|
|
62
|
-
labelAnchor, labelPosition,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function makeEdge({
|
|
67
|
-
src, dst, label = "", style = "gray",
|
|
68
|
-
srcAnchor = null, dstAnchor = null,
|
|
69
|
-
route = "auto", via = [],
|
|
70
|
-
srcOffset = [0, 0], dstOffset = [0, 0],
|
|
71
|
-
labelOffset = [0, 0], labelAnchor = "mid",
|
|
72
|
-
labelSmall = false, labelPos = null,
|
|
73
|
-
dashed = false, noArrow = false,
|
|
74
|
-
trunkOffset = 0, sharedPort = false,
|
|
75
|
-
}) {
|
|
76
|
-
return {
|
|
77
|
-
src, dst, label, style, srcAnchor, dstAnchor, route, via,
|
|
78
|
-
srcOffset, dstOffset, labelOffset, labelAnchor, labelSmall, labelPos,
|
|
79
|
-
dashed, noArrow, trunkOffset, sharedPort,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function makeDiagram({
|
|
84
|
-
width = 0, height = 0, title = "", subtitle = "",
|
|
85
|
-
components = [], regions = [], edges = [], layoutTrees = [],
|
|
86
|
-
} = {}) {
|
|
87
|
-
return { width, height, title, subtitle, components, regions, edges, layoutTrees };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ── Lookups & geometry helpers ────────────────────────────────────────
|
|
91
|
-
|
|
92
|
-
export function componentHalf(c) {
|
|
93
|
-
return SHAPE_HALF[c.shape];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function regionHalf(r) {
|
|
97
|
-
const [, , w, h] = r.bounds;
|
|
98
|
-
return [(w / 2) | 0, (h / 2) | 0];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Edge attach point on `node` for a given side.
|
|
103
|
-
* For a Component, `bottom` pushes past `LABEL_HEIGHT` when the
|
|
104
|
-
* component actually has a name or subtitle.
|
|
105
|
-
*/
|
|
106
|
-
export function anchor(node, side) {
|
|
107
|
-
// Region path — has `bounds`, no `pos`.
|
|
108
|
-
if (node.bounds !== undefined && node.pos === undefined) {
|
|
109
|
-
return regionAnchor(node, side);
|
|
110
|
-
}
|
|
111
|
-
// Component path — has `pos`.
|
|
112
|
-
return componentAnchor(node, side);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function componentAnchor(c, side) {
|
|
116
|
-
const [cx, cy] = c.pos;
|
|
117
|
-
const [hw, hh] = SHAPE_HALF[c.shape];
|
|
118
|
-
const labelled = (c.name && c.name.length > 0) || (c.subtitle && c.subtitle.length > 0);
|
|
119
|
-
const lh = labelled ? (LABEL_HEIGHT[c.shape] || 0) : 0;
|
|
120
|
-
switch (side) {
|
|
121
|
-
case "top": return [cx, cy - hh];
|
|
122
|
-
case "right": return [cx + hw, cy];
|
|
123
|
-
case "bottom": return [cx, cy + hh + lh];
|
|
124
|
-
case "left": return [cx - hw, cy];
|
|
125
|
-
case "center": return [cx, cy];
|
|
126
|
-
}
|
|
127
|
-
throw new Error(`anchor: bad side ${side}`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function regionAnchor(r, side) {
|
|
131
|
-
const [x, y, w, h] = r.bounds;
|
|
132
|
-
switch (side) {
|
|
133
|
-
case "top": return [x + ((w / 2) | 0), y];
|
|
134
|
-
case "right": return [x + w, y + ((h / 2) | 0)];
|
|
135
|
-
case "bottom": return [x + ((w / 2) | 0), y + h];
|
|
136
|
-
case "left": return [x, y + ((h / 2) | 0)];
|
|
137
|
-
case "center": return [x + ((w / 2) | 0), y + ((h / 2) | 0)];
|
|
138
|
-
}
|
|
139
|
-
throw new Error(`anchor: bad side ${side}`);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Pick effective (srcAnchor, dstAnchor) for an edge. `null` slots are
|
|
144
|
-
* filled from geometry: horizontal-biased — vertical wins only when
|
|
145
|
-
* `|dy| > 2·|dx|`.
|
|
146
|
-
*/
|
|
147
|
-
export function resolveAnchors(e, src, dst) {
|
|
148
|
-
let { srcAnchor: sa, dstAnchor: da } = e;
|
|
149
|
-
if (sa !== null && da !== null) return [sa, da];
|
|
150
|
-
const [scx, scy] = anchor(src, "center");
|
|
151
|
-
const [dcx, dcy] = anchor(dst, "center");
|
|
152
|
-
const dx = dcx - scx, dy = dcy - scy;
|
|
153
|
-
let autoSa, autoDa;
|
|
154
|
-
if (Math.abs(dy) > 2 * Math.abs(dx)) {
|
|
155
|
-
[autoSa, autoDa] = dy >= 0 ? ["bottom", "top"] : ["top", "bottom"];
|
|
156
|
-
} else {
|
|
157
|
-
[autoSa, autoDa] = dx >= 0 ? ["right", "left"] : ["left", "right"];
|
|
158
|
-
}
|
|
159
|
-
return [sa || autoSa, da || autoDa];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ── Diagram node lookups ──────────────────────────────────────────────
|
|
163
|
-
|
|
164
|
-
export function getComponent(d, id) {
|
|
165
|
-
const c = d.components.find(c => c.id === id);
|
|
166
|
-
if (!c) throw new Error(`component ${JSON.stringify(id)} not in diagram`);
|
|
167
|
-
return c;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export function getNode(d, id) {
|
|
171
|
-
const c = d.components.find(c => c.id === id);
|
|
172
|
-
if (c) return c;
|
|
173
|
-
const r = d.regions.find(r => r.id === id);
|
|
174
|
-
if (r) return r;
|
|
175
|
-
throw new Error(`node ${JSON.stringify(id)} not in diagram (checked components + regions)`);
|
|
176
|
-
}
|