kymostudio 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 +201 -0
- package/README.md +53 -0
- package/icons-manifest.json +1 -0
- package/package.json +30 -0
- package/src/js/icons-builtin.js +399 -0
- package/src/js/icons-loader.js +91 -0
- package/src/js/index.js +13 -0
- package/src/js/model.js +176 -0
package/src/js/model.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
}
|