flowfig 0.7.0 → 0.8.1
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 +55 -23
- package/dist/check.js +68 -6
- package/dist/cli.js +24 -13
- package/dist/code.d.ts +26 -0
- package/dist/code.js +568 -0
- package/dist/diff.js +12 -5
- package/dist/draw.js +1 -1
- package/dist/edges.d.ts +7 -0
- package/dist/edges.js +557 -0
- package/dist/geometry.d.ts +11 -2
- package/dist/geometry.js +255 -67
- package/dist/guide.d.ts +22 -2
- package/dist/guide.js +282 -219
- package/dist/imports.d.ts +11 -0
- package/dist/imports.js +235 -0
- package/dist/index.js +36 -28
- package/dist/load.d.ts +2 -0
- package/dist/load.js +24 -1
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +22 -9
- package/dist/model.d.ts +13 -2
- package/dist/model.js +54 -11
- package/dist/scene.d.ts +2 -0
- package/dist/source.d.ts +1 -1
- package/dist/source.js +3 -3
- package/dist/svg.js +49 -32
- package/dist/type-index.d.ts +55 -0
- package/dist/type-index.js +383 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +733 -0
- package/dist/verify.d.ts +25 -3
- package/dist/verify.js +67 -22
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/model.js
CHANGED
|
@@ -8,6 +8,14 @@ export const toBeat = (b) => {
|
|
|
8
8
|
hops: hops.map((h) => (typeof h === 'string' ? { edge: h, back: false } : { back: false, ...h })),
|
|
9
9
|
};
|
|
10
10
|
};
|
|
11
|
+
export const edgeTip = (id, source, beats) => {
|
|
12
|
+
const all = new Set(source ? [source] : []);
|
|
13
|
+
for (const b of beats)
|
|
14
|
+
for (const h of b.hops)
|
|
15
|
+
if (h.edge === id && h.source)
|
|
16
|
+
all.add(h.source);
|
|
17
|
+
return all.size ? [...all].join('\n') : undefined;
|
|
18
|
+
};
|
|
11
19
|
// A React node can be an object, so test for the `text` key.
|
|
12
20
|
export const isRows = (c) => Array.isArray(c) && c.every((r) => r != null && typeof r === 'object' && 'text' in r);
|
|
13
21
|
export const edgeId = (e) => e.id ?? `${e.from}->${e.to}`;
|
|
@@ -86,18 +94,47 @@ export function laneColumns(fig) {
|
|
|
86
94
|
cols.set(n.id, n.at);
|
|
87
95
|
return cols;
|
|
88
96
|
}
|
|
97
|
+
export const LABEL_LINE = 18;
|
|
89
98
|
export const FRAME_SIDE = 18, NODE_MIN_W = 100, NODE_MAX_W = 190;
|
|
90
99
|
export const LANE_BLOCK_GAP = 40;
|
|
91
100
|
export const STUB = 12, STUB_CLEAR = 8, STUB_ROOM = 30;
|
|
92
101
|
/** The width of a box as the SVG draws it, before a diamond adds its 70 px. */
|
|
93
|
-
export function nodeWidth(item, carded) {
|
|
102
|
+
export function nodeWidth(item, carded, cap = NODE_MAX_W) {
|
|
94
103
|
if (item.width != null)
|
|
95
104
|
return item.width;
|
|
96
105
|
if (carded)
|
|
97
106
|
return CARD_WIDTH;
|
|
98
107
|
const label = textWidth(str(item.label), 14) + 32;
|
|
99
108
|
const sub = textWidth(str(item.sub), 12) + 32;
|
|
100
|
-
return Math.min(NODE_MAX_W, Math.max(NODE_MIN_W, label, sub));
|
|
109
|
+
return Math.min(item.shape === 'decision' ? NODE_MAX_W : cap, Math.max(NODE_MIN_W, label, sub));
|
|
110
|
+
}
|
|
111
|
+
export function labelLines(item, w) {
|
|
112
|
+
const label = str(item.label);
|
|
113
|
+
if (item.shape === 'decision' || textWidth(label, 14) <= w - 32 + 0.5)
|
|
114
|
+
return [label];
|
|
115
|
+
const lines = wrap(label, w - 32, 14);
|
|
116
|
+
return lines.length === 2 && lines.every((l) => textWidth(l, 14) <= w - 32) ? lines : [label];
|
|
117
|
+
}
|
|
118
|
+
export function itemWidth(item, carded, edges, cap = NODE_MAX_W) {
|
|
119
|
+
if (!isGroup(item))
|
|
120
|
+
return nodeWidth(item, carded.has(item.id), cap) + (item.shape === 'decision' ? 70 : 0);
|
|
121
|
+
const ws = item.children.map((c) => itemWidth(c, carded, edges, cap));
|
|
122
|
+
const inner = item.direction === 'column' ? Math.max(...ws) : ws.reduce((a, b) => a + b, 0) + groupGap(item, edges) * (ws.length - 1);
|
|
123
|
+
return inner + (item.label != null ? FRAME_SIDE * 2 : 0);
|
|
124
|
+
}
|
|
125
|
+
export function fitCap(fig, width, padding = 24) {
|
|
126
|
+
if ((fig.lanes || fig.timeline) && isLanesLayout(fig.layout))
|
|
127
|
+
return NODE_MAX_W;
|
|
128
|
+
const carded = new Set((fig.steps ?? []).flatMap((s) => s.flow.flatMap((b) => Object.keys(toBeat(b).show ?? {}))));
|
|
129
|
+
const fits = (n, cap) => {
|
|
130
|
+
const w = nodeWidth(n, carded.has(n.id), cap);
|
|
131
|
+
return Math.max(textWidth(str(n.sub), 12), ...labelLines(n, w).map((l) => textWidth(l, 14))) + 32 <= w + 0.5;
|
|
132
|
+
};
|
|
133
|
+
const keep = nodes(fig.layout).filter((n) => fits(n, NODE_MAX_W));
|
|
134
|
+
let cap = NODE_MAX_W;
|
|
135
|
+
while (cap > 110 && itemWidth(fig.layout, carded, fig.edges, cap) + padding * 2 > width && keep.every((n) => fits(n, cap - 20)))
|
|
136
|
+
cap -= 20;
|
|
137
|
+
return cap;
|
|
101
138
|
}
|
|
102
139
|
export const diamondRoom = (w, h, far) => w * Math.max(0, 1 - far / (h / 2)) - 8;
|
|
103
140
|
/** The lines of a diamond with no card. The diamond grows 15 px for each sub line that wraps. */
|
|
@@ -415,16 +452,22 @@ export const counts = (p) => {
|
|
|
415
452
|
};
|
|
416
453
|
};
|
|
417
454
|
const idsIn = (c) => (isGroup(c) ? [...(c.id ? [c.id] : []), ...c.children.flatMap(idsIn)] : [c.id]);
|
|
418
|
-
/** A row's default gap grows to hold its widest edge label, plus 8 px each side. */
|
|
419
455
|
export function groupGap(g, edges) {
|
|
420
|
-
if (g.gap != null)
|
|
421
|
-
return g.gap;
|
|
422
|
-
if (g.direction === 'column')
|
|
423
|
-
return 28;
|
|
424
456
|
const side = new Map();
|
|
425
457
|
g.children.forEach((c, i) => idsIn(c).forEach((id) => side.set(id, i)));
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
458
|
+
const spans = edges.filter((e) => e.label != null && side.has(e.from) && side.has(e.to) && side.get(e.from) !== side.get(e.to) && !(g.gap != null && e.around));
|
|
459
|
+
if (g.gap != null && !spans.length)
|
|
460
|
+
return g.gap;
|
|
461
|
+
const need = Math.max(0, ...spans.map((e) => labelPillW(str(e.label)) + 16));
|
|
462
|
+
if (g.gap != null && g.direction !== 'column')
|
|
463
|
+
return Math.max(g.gap, need - 16);
|
|
464
|
+
let auto;
|
|
465
|
+
if (g.direction === 'column') {
|
|
466
|
+
const ends = spans.map((e) => [side.get(e.from), side.get(e.to)].sort((p, q) => p - q));
|
|
467
|
+
const most = Math.max(0, ...g.children.map((_, k) => ends.filter(([lo, hi]) => lo <= k && k < hi).length));
|
|
468
|
+
auto = most > 1 ? Math.min(most, 3) * 22 + 16 : 28;
|
|
469
|
+
}
|
|
470
|
+
else
|
|
471
|
+
auto = Math.max(56, need);
|
|
472
|
+
return Math.max(g.gap ?? 0, auto);
|
|
430
473
|
}
|
package/dist/scene.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export type Scene = {
|
|
|
33
33
|
edges: SceneEdge[];
|
|
34
34
|
/** The smallest reading text in px, before any scale. */
|
|
35
35
|
minFont: number;
|
|
36
|
+
/** The drawn map area. An edge label stays inside it. */
|
|
37
|
+
area?: Rect;
|
|
36
38
|
/** The lane bands of a swimlanes figure. A stub pill stays inside one. */
|
|
37
39
|
lanes?: {
|
|
38
40
|
id: string;
|
package/dist/source.d.ts
CHANGED
|
@@ -12,6 +12,6 @@ export type Link = {
|
|
|
12
12
|
symbol?: string;
|
|
13
13
|
};
|
|
14
14
|
/** Every box, edge and hop with its raw `source`, well-formed or not. The one owner list that `links` and `checkSpec` share. */
|
|
15
|
-
export declare function owners(fig: FlowProps): [string, string | undefined][];
|
|
15
|
+
export declare function owners(fig: FlowProps): [string, string | undefined, string | undefined][];
|
|
16
16
|
/** Every well-formed link in the figure, with the box, edge or hop that carries it. `bad-source` covers the rest. */
|
|
17
17
|
export declare function links(fig: FlowProps): Link[];
|
package/dist/source.js
CHANGED
|
@@ -15,9 +15,9 @@ export const headingSlug = (text) => text
|
|
|
15
15
|
/** Every box, edge and hop with its raw `source`, well-formed or not. The one owner list that `links` and `checkSpec` share. */
|
|
16
16
|
export function owners(fig) {
|
|
17
17
|
return [
|
|
18
|
-
...nodes(fig.layout).map((n) => [`box "${n.id}"`, n.source]),
|
|
19
|
-
...fig.edges.map((e) => [`edge "${edgeId(e)}"`, e.source]),
|
|
20
|
-
...(fig.steps ?? []).flatMap((s) => s.flow.flatMap((b) => toBeat(b).hops.map((h) => [`hop on "${h.edge}"`, h.source]))),
|
|
18
|
+
...nodes(fig.layout).map((n) => [`box "${n.id}"`, n.source, undefined]),
|
|
19
|
+
...fig.edges.map((e) => [`edge "${edgeId(e)}"`, e.source, e.via]),
|
|
20
|
+
...(fig.steps ?? []).flatMap((s) => s.flow.flatMap((b) => toBeat(b).hops.map((h) => [`hop on "${h.edge}"`, h.source, h.via]))),
|
|
21
21
|
];
|
|
22
22
|
}
|
|
23
23
|
/** Every well-formed link in the figure, with the box, edge or hop that carries it. `bad-source` covers the rest. */
|
package/dist/svg.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Wrapping is approximate: text is measured by character class, not by a browser.
|
|
2
|
-
import { route } from './geometry.js';
|
|
2
|
+
import { arcRoom, avoidOf, route } from './geometry.js';
|
|
3
3
|
import { foldedLabel, groupBox, layoutRail, railState, RAIL } from './rail.js';
|
|
4
4
|
import { textWidth, wrap } from './text.js';
|
|
5
5
|
import { checkRendered, planFor } from './check.js';
|
|
6
6
|
export { parseSource, links } from './source.js';
|
|
7
7
|
export { diff, formatDiff } from './diff.js';
|
|
8
|
-
import { ASYNC_TAG_W, BASE_RATE, CARD_LINE, CARD_PAD, CARD_WIDTH, DARK, EDGE_OFF, EDGE_ON, LIGHT, TONES, toneFill, toneTint, labelPillW, ON_ACCENT, str, edgeId, groupGap, isGroup, isRows, LANE_PAD, LANE_ROW_GAP, LANE_BLOCK_GAP, FRAME_SIDE, laneGutter as gutterOf, STUB_ROOM, laneBlock, laneEnd, tightCopies,
|
|
8
|
+
import { ASYNC_TAG_W, BASE_RATE, CARD_LINE, CARD_PAD, CARD_WIDTH, DARK, EDGE_OFF, EDGE_ON, LIGHT, TONES, toneFill, toneTint, labelPillW, ON_ACCENT, str, edgeId, groupGap, isGroup, isRows, LANE_PAD, LANE_ROW_GAP, LANE_BLOCK_GAP, FRAME_SIDE, laneGutter as gutterOf, STUB_ROOM, laneBlock, laneEnd, tightCopies, LABEL_LINE, itemWidth, labelLines, fitCap, diamondLines, diamondRoom, isLanesLayout, timelineBeats, timelineLayout, TL_AXIS_H, TL_AXIS_W, TL_BAR_H, TL_ROW_GAP, toBeat, beatMs, STEP_HOLD_MS, playheadItem, loopStartItem, dateLabelRaised, labelSpan, outsideLabelRect, edgeTip, } from './model.js';
|
|
9
9
|
export { LIGHT, DARK } from './model.js';
|
|
10
10
|
const LINE = CARD_LINE, CARD_SIDE = 8, ROW_GAP = 4;
|
|
11
|
-
const
|
|
11
|
+
const SUB_LINE = 15;
|
|
12
12
|
const FRAME_TOP = 37, FRAME_BOTTOM = 18;
|
|
13
13
|
// The card-on fills approximate the player's 8 % accent tint.
|
|
14
14
|
const vars = (t, cardOn) => `--accent:${t.accent}; --fg:${t.fg}; --muted:${t.muted}; --bg:${t.bg}; --surface:${t.surface}; --border:${t.border}; --card-on:${cardOn}; --tint:color-mix(in srgb, var(--accent) 10%, var(--surface));`;
|
|
@@ -42,20 +42,18 @@ function layoutCard(c, width) {
|
|
|
42
42
|
function size(item, s) {
|
|
43
43
|
if (!isGroup(item)) {
|
|
44
44
|
const contents = s.cards.get(item.id);
|
|
45
|
-
const w =
|
|
45
|
+
const w = itemWidth(item, s.cards, s.fig.edges, s.cap);
|
|
46
46
|
const top = item.shape === 'store' ? 24 : 10;
|
|
47
47
|
const card = contents ? 8 + s.cardH.get(item.id) : 0;
|
|
48
|
-
const
|
|
48
|
+
const label = labelLines(item, w).length * LABEL_LINE;
|
|
49
|
+
const h = Math.max(top + label + (item.sub ? SUB_LINE : 0) + card + 10, s.minH(item.id));
|
|
49
50
|
if (item.shape !== 'decision')
|
|
50
51
|
return { w, h };
|
|
51
52
|
return { w, h: contents ? h + 24 : Math.max(diamondLines(item, w).h, s.minH(item.id) + 24) };
|
|
52
53
|
}
|
|
53
54
|
const kids = item.children.map((c) => size(c, s));
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const across = Math.max(...kids.map((k) => (item.direction === 'column' ? k.w : k.h)));
|
|
57
|
-
const inner = item.direction === 'column' ? { w: across, h: along } : { w: along, h: across };
|
|
58
|
-
return item.label != null ? { w: inner.w + FRAME_SIDE * 2, h: inner.h + FRAME_TOP + FRAME_BOTTOM } : inner;
|
|
55
|
+
const h = item.direction === 'column' ? kids.reduce((n, k) => n + k.h, 0) + s.gap(item) * (kids.length - 1) : Math.max(...kids.map((k) => k.h));
|
|
56
|
+
return { w: itemWidth(item, s.cards, s.fig.edges, s.cap), h: h + (item.label != null ? FRAME_TOP + FRAME_BOTTOM : 0) };
|
|
59
57
|
}
|
|
60
58
|
function placeLanes(fig, x, y, s, out) {
|
|
61
59
|
const lanes = fig.layout.children;
|
|
@@ -186,7 +184,16 @@ export function render(fig, opts = {}) {
|
|
|
186
184
|
const cardH = new Map();
|
|
187
185
|
const lanes = !tl && fig.lanes && isLanesLayout(fig.layout);
|
|
188
186
|
const plan = lanes ? planFor(fig, opts) : null;
|
|
189
|
-
const sizes = {
|
|
187
|
+
const sizes = {
|
|
188
|
+
cards,
|
|
189
|
+
cardH,
|
|
190
|
+
minH,
|
|
191
|
+
gap: (g) => groupGap(g, fig.edges),
|
|
192
|
+
fig,
|
|
193
|
+
plan,
|
|
194
|
+
tall: new Set(plan?.tall),
|
|
195
|
+
cap: fitCap(fig, opts.width ?? 830, pad),
|
|
196
|
+
};
|
|
190
197
|
for (const [id, contents] of cards) {
|
|
191
198
|
const width = CARD_WIDTH;
|
|
192
199
|
const widths = [width];
|
|
@@ -227,13 +234,10 @@ export function render(fig, opts = {}) {
|
|
|
227
234
|
around: e.around ?? (plan?.around.has(ids[i]) ? 'below' : undefined),
|
|
228
235
|
...(tl && { sides: ['r', 'l'], elbow: true }),
|
|
229
236
|
...(stubs.has(ids[i]) && { stub: stubs.get(ids[i]).map(labelPillW), bands: [bandOf(ids[i], e.from, 0), bandOf(ids[i], e.to, 1)] }),
|
|
230
|
-
...(
|
|
231
|
-
})), rects, tips, stubs.size
|
|
232
|
-
? placed.
|
|
233
|
-
: [
|
|
234
|
-
...placed.filter((p) => p.tl && !p.tl.labelInside).map((p) => outsideLabelRect(p, str(p.item.label))),
|
|
235
|
-
...placed.filter((p) => p.tl).map((p) => ({ x: p.x, y: p.y, w: p.w, h: p.h, box: true })),
|
|
236
|
-
], stubs.size ? { x: placed[0].x + gutter, y: placed[0].y, w: placed[0].w - gutter, h: placed[0].h } : undefined, lanes ? { bands: placed.filter((p) => p.lane), boxes: placed.filter((p) => !isGroup(p.item)) } : undefined);
|
|
237
|
+
...(!tl && e.label != null && { labelW: labelPillW(str(e.label)) }),
|
|
238
|
+
})), rects, tips, avoidOf(placed.filter((p) => !isGroup(p.item)), stubs.size
|
|
239
|
+
? placed.filter((p) => p.lane).map((p) => ({ x: p.x, y: p.y, w: gutter, h: p.h }))
|
|
240
|
+
: placed.filter((p) => p.tl && !p.tl.labelInside).map((p) => outsideLabelRect(p, str(p.item.label))), stubs.size > 0), stubs.size ? { x: placed[0].x + gutter, y: placed[0].y, w: placed[0].w - gutter, h: placed[0].h } : undefined, lanes ? { bands: placed.filter((p) => p.lane), boxes: placed.filter((p) => !isGroup(p.item)) } : undefined, pad);
|
|
237
241
|
};
|
|
238
242
|
let routed = go();
|
|
239
243
|
for (let pass = 0; plan && pass < 3; pass++) {
|
|
@@ -260,7 +264,11 @@ export function render(fig, opts = {}) {
|
|
|
260
264
|
const dl = dia ? diamondLines(p.item, p.w) : null;
|
|
261
265
|
const room = (far) => diamondRoom(p.w, p.h, far);
|
|
262
266
|
const flat = (p.item.shape === 'decision' ? p.w - 70 : p.w) - 16;
|
|
263
|
-
const texts =
|
|
267
|
+
const texts = labelLines(p.item, p.w).map((text) => ({
|
|
268
|
+
text,
|
|
269
|
+
fontSize: 14,
|
|
270
|
+
room: dl ? room(dl.label.far) : flat,
|
|
271
|
+
}));
|
|
264
272
|
if (dl)
|
|
265
273
|
for (const l of dl.subs)
|
|
266
274
|
texts.push({ text: l.text, fontSize: 12, room: room(l.far) });
|
|
@@ -443,6 +451,7 @@ export function render(fig, opts = {}) {
|
|
|
443
451
|
cls(p.lane && 'lane', anim(on, 'stroke: var(--accent)', 'stroke: var(--border)', 'n')) +
|
|
444
452
|
`/><text x="${n2(p.x + FRAME_SIDE)}" y="${n2(p.lane ? p.y + p.h / 2 + 4 : p.y + 20)}" class="frame">${esc(str(item.label).toUpperCase())}</text>`);
|
|
445
453
|
}
|
|
454
|
+
const tip = (inner) => (item.source ? `<g><title>${esc(item.source)}</title>${inner}</g>` : inner);
|
|
446
455
|
const bt = item.tone && TONES[item.tone];
|
|
447
456
|
const stroke = cls(boxAnim(item.id, true, bt));
|
|
448
457
|
if (p.tl) {
|
|
@@ -455,7 +464,7 @@ export function render(fig, opts = {}) {
|
|
|
455
464
|
? `<polygon points="${n2(cx)},${n2(p.y)} ${n2(p.x + p.w)},${n2(cy)} ${n2(cx)},${n2(p.y + p.h)} ${n2(p.x)},${n2(cy)}" fill="${fill0}" stroke="${stroke0}"${stroke}/>`
|
|
456
465
|
: `<rect x="${n2(p.x)}" y="${n2(p.y)}" width="${n2(p.w)}" height="${n2(p.h)}" rx="6" fill="${fill0}" stroke="${stroke0}"${stroke}/>`;
|
|
457
466
|
const tx = inside ? p.x + 8 : p.x + p.w + 6;
|
|
458
|
-
return shape + `<text x="${n2(tx)}" y="${n2(cy + 4.5)}" class="bar">${esc(label)}</text
|
|
467
|
+
return tip(shape + `<text x="${n2(tx)}" y="${n2(cy + 4.5)}" class="bar">${esc(label)}</text>`);
|
|
459
468
|
}
|
|
460
469
|
const rim = item.shape === 'store' ? cls(boxAnim(item.id, false, bt)) : '';
|
|
461
470
|
const fill0 = bt ? toneTint(bt, 'var(--bg)') : 'var(--bg)';
|
|
@@ -471,11 +480,14 @@ export function render(fig, opts = {}) {
|
|
|
471
480
|
? `<path d="M${n2(p.x)} ${n2(p.y + 12)} a ${n2(p.w / 2)} 12 0 0 1 ${n2(p.w)} 0 v ${n2(p.h - 24)} a ${n2(p.w / 2)} 12 0 0 1 ${n2(-p.w)} 0 z" fill="${fill0}" stroke="${stroke0}"${stroke}/>` +
|
|
472
481
|
`<path d="M${n2(p.x)} ${n2(p.y + 12)} a ${n2(p.w / 2)} 12 0 0 0 ${n2(p.w)} 0" fill="none" stroke="${stroke0}"${rim}/>`
|
|
473
482
|
: `<rect x="${n2(p.x)}" y="${n2(p.y)}" width="${n2(p.w)}" height="${n2(p.h)}" rx="10" fill="${fill0}" stroke="${stroke0}"${stroke}/>`;
|
|
474
|
-
const
|
|
483
|
+
const lines = labelLines(item, p.w);
|
|
484
|
+
const extra = (lines.length - 1) * LABEL_LINE;
|
|
485
|
+
const firstY = item.shape === 'store' || contents ? labelY : labelY - extra / 2;
|
|
486
|
+
const label = lines.map((l, i) => `<text x="${n2(cx)}" y="${n2(firstY + i * LABEL_LINE)}" class="label">${esc(l)}</text>`).join('');
|
|
475
487
|
const sub = dl
|
|
476
488
|
? dl.subs.map((l) => `<text x="${n2(cx)}" y="${n2(p.y + p.h / 2 + l.dy)}" class="sub">${esc(l.text)}</text>`).join('')
|
|
477
489
|
: item.sub
|
|
478
|
-
? `<text x="${n2(cx)}" y="${n2(
|
|
490
|
+
? `<text x="${n2(cx)}" y="${n2(firstY + extra + SUB_LINE)}" class="sub">${esc(str(item.sub))}</text>`
|
|
479
491
|
: '';
|
|
480
492
|
const dot = bt ?? 'var(--accent)';
|
|
481
493
|
const my = n2(p.y + p.h / 2);
|
|
@@ -484,7 +496,7 @@ export function render(fig, opts = {}) {
|
|
|
484
496
|
: item.mark === 'end'
|
|
485
497
|
? `<circle cx="${n2(p.x + p.w + 12)}" cy="${my}" r="6.25" fill="none" stroke="${dot}" stroke-width="1.5"/><circle cx="${n2(p.x + p.w + 12)}" cy="${my}" r="4" fill="${dot}"/>`
|
|
486
498
|
: '';
|
|
487
|
-
return shape + label + sub + mark + (contents ? card(p, cardTop, contents) : '');
|
|
499
|
+
return tip(shape + label + sub + mark + (contents ? card(p, cardTop, contents) : ''));
|
|
488
500
|
});
|
|
489
501
|
function card(p, top, contents) {
|
|
490
502
|
const id = p.item.id;
|
|
@@ -550,11 +562,13 @@ export function render(fig, opts = {}) {
|
|
|
550
562
|
const lit = cls(toned
|
|
551
563
|
? frames(on.map((x, i) => (x ? `stroke: ${col[i]}; stroke-width: ${EDGE_ON}` : off)), 'e', off)
|
|
552
564
|
: anim(on, `stroke: var(--accent); stroke-width: ${EDGE_ON}`, off, 'e'));
|
|
553
|
-
const
|
|
565
|
+
const tip = edgeTip(r.id, e.source, beats.flat());
|
|
566
|
+
const path0 = r.stub
|
|
554
567
|
? r.stub.parts
|
|
555
568
|
.map((d) => `<path d="${d}" fill="none" stroke="var(--muted)" stroke-width="${EDGE_OFF}" marker-end="url(#arrow)"${lit}/>`)
|
|
556
569
|
.join('') + `<path id="p-${esc(r.id)}" d="${r.d}" fill="none" stroke="none"/>`
|
|
557
570
|
: `<path id="p-${esc(r.id)}" d="${r.d}" fill="none" stroke="var(--muted)" stroke-width="${EDGE_OFF}" marker-end="url(#arrow)"${lit}/>`;
|
|
571
|
+
const path = tip ? `<g><title>${esc(tip)}</title>${path0}</g>` : path0;
|
|
558
572
|
const pill = (x, y, lw, text) => `<rect x="${n2(x - lw / 2)}" y="${n2(y - 9)}" width="${n2(lw)}" height="18" rx="9" fill="var(--bg)" stroke="var(--border)"` +
|
|
559
573
|
cls(toned
|
|
560
574
|
? frames(on.map((x, i) => x ? `fill: ${tone[i] ? toneFill(col[i]) : col[i]}; stroke: ${col[i]}` : 'fill: var(--bg); stroke: var(--border)'), 'l', 'fill: var(--bg); stroke: var(--border)')
|
|
@@ -668,18 +682,18 @@ export function render(fig, opts = {}) {
|
|
|
668
682
|
}
|
|
669
683
|
}
|
|
670
684
|
const bounds = placed[0];
|
|
671
|
-
const
|
|
685
|
+
const [arcT, arcB, arcL, arcR] = ['above', 'below', 'left', 'right'].map((w) => arcRoom(routed, w));
|
|
672
686
|
const capLines = [...new Set(captions)].flatMap((c) => wrap(c, Math.max(560, bounds.w), 13.5).length);
|
|
673
|
-
const mapW = Math.max(bounds.w + pad * 2, 560);
|
|
687
|
+
const mapW = Math.max(bounds.w + pad * 2 + arcL + arcR, 560);
|
|
674
688
|
const rail = fig.rail ? layoutRail(fig, fig.rail === 'only' ? 560 : mapW) : null;
|
|
675
689
|
const only = fig.rail === 'only' && rail != null;
|
|
676
690
|
const capTop = rail ? 20 : 26;
|
|
677
691
|
const capH = steps.length ? capTop + 4 + Math.max(0, ...capLines) * 20 : 0;
|
|
678
692
|
const W = only ? Math.max(560, rail.width) : Math.max(mapW, rail?.width ?? 0);
|
|
679
|
-
const mapH = only ? 0 : bounds.h + pad * 2 +
|
|
693
|
+
const mapH = only ? 0 : bounds.h + pad * 2 + arcT + arcB;
|
|
680
694
|
const top = only ? pad : mapH + RAIL.gap;
|
|
681
695
|
const H = only ? pad + rail.height + capH : mapH + (rail ? RAIL.gap + rail.height : 0) + capH;
|
|
682
|
-
const shift = (W - (bounds.w + pad * 2)) / 2;
|
|
696
|
+
const shift = (W - (bounds.w + pad * 2 + arcL + arcR)) / 2 + arcL;
|
|
683
697
|
const railX = rail ? (W - rail.width) / 2 : 0;
|
|
684
698
|
const labels = rail || synthetic
|
|
685
699
|
? []
|
|
@@ -758,7 +772,8 @@ export function render(fig, opts = {}) {
|
|
|
758
772
|
? `<rect x="${n2(row.pill.x)}" y="${n2(ly - 9)}" width="${n2(row.pill.w)}" height="18" rx="9" fill="var(--bg)" stroke="var(--border)"${cls(anim(now, `fill: ${row.tone ? toneFill(col) : col}; stroke: ${col}`, 'fill: var(--bg); stroke: var(--border)', row.tone ? 'l' + col : 'l'))}/>` +
|
|
759
773
|
`<text x="${n2(row.pill.x + row.pill.w / 2)}" y="${n2(ly + 4)}"${cls('edgelabel', anim(now, `fill: ${ON_ACCENT}`, 'fill: var(--muted)', 'x'))}>${esc(row.text)}</text>`
|
|
760
774
|
: '';
|
|
761
|
-
|
|
775
|
+
const tip = edgeTip(row.edge, fig.edges[ids.indexOf(row.edge)].source, beats.flat());
|
|
776
|
+
parts.push(`<g${cls('railrow', moveRow(i))}>${tip ? `<title>${esc(tip)}</title>` : ''}<g${cls(anim(next, 'opacity: .45', 'opacity: 1', 'r'))}>` +
|
|
762
777
|
`<path d="M ${n2(x1)} ${n2(ly)} H ${n2(x2)}" fill="none" stroke="var(--muted)" stroke-width="${EDGE_OFF}"${row.async ? ' stroke-dasharray="4 3"' : ''} marker-end="url(#arrow)"${cls(lit)}/>` +
|
|
763
778
|
tag +
|
|
764
779
|
pill +
|
|
@@ -796,7 +811,7 @@ ${css.join('\n')}
|
|
|
796
811
|
<rect width="100%" height="100%" fill="var(--bg)"/>
|
|
797
812
|
${only
|
|
798
813
|
? ''
|
|
799
|
-
: `<g transform="translate(${n2(shift)} ${
|
|
814
|
+
: `<g transform="translate(${n2(shift)} ${arcT})">
|
|
800
815
|
${axisSvg}
|
|
801
816
|
${tl ? boxes.filter((b, i) => b && isGroup(placed[i].item)).join('\n') : ''}${lineSvg && `\n${lineSvg}`}
|
|
802
817
|
${tl ? edgeSvg.join('\n') : ''}
|
|
@@ -814,6 +829,7 @@ ${said.join('\n')}
|
|
|
814
829
|
fonts.push(12, 13);
|
|
815
830
|
const scene = {
|
|
816
831
|
width: n2(W),
|
|
832
|
+
...(!only && { area: { x: -shift, y: -arcT, w: W, h: mapH } }),
|
|
817
833
|
boxes: only ? [] : sceneBoxes,
|
|
818
834
|
edges: [
|
|
819
835
|
...(only ? [] : routed).flatMap((r) => {
|
|
@@ -827,14 +843,15 @@ ${said.join('\n')}
|
|
|
827
843
|
to: e.to,
|
|
828
844
|
curve: r.curve,
|
|
829
845
|
label: labelRects[r.id],
|
|
830
|
-
...(tl && { behind: true
|
|
846
|
+
...(tl && { behind: true }),
|
|
847
|
+
...(r.elbow && { elbow: r.elbow }),
|
|
831
848
|
},
|
|
832
849
|
];
|
|
833
850
|
}),
|
|
834
851
|
...(rail?.rows ?? []).flatMap((row, i) => {
|
|
835
852
|
if (row.kind !== 'message')
|
|
836
853
|
return [];
|
|
837
|
-
const dx = only ? railX : railX - shift, dy = only ? top : top -
|
|
854
|
+
const dx = only ? railX : railX - shift, dy = only ? top : top - arcT;
|
|
838
855
|
const y = dy + railState(rail, row.step)[i].y + RAIL.row / 2;
|
|
839
856
|
const [x1, x2] = [dx + rail.columns[row.from].x, dx + rail.columns[row.to].x];
|
|
840
857
|
const p = { x: x1, y }, q = { x: x2, y };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type CodeFile, type Lang, type Read } from './code.ts';
|
|
2
|
+
export type Decl = {
|
|
3
|
+
info: CodeFile;
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
name: string;
|
|
7
|
+
kind: 'class' | 'interface';
|
|
8
|
+
dir?: string;
|
|
9
|
+
};
|
|
10
|
+
export type Bind = {
|
|
11
|
+
mod: string | null;
|
|
12
|
+
imported: string;
|
|
13
|
+
dir: boolean;
|
|
14
|
+
};
|
|
15
|
+
type Globals = {
|
|
16
|
+
types: Map<string, string[]>;
|
|
17
|
+
impls: Map<string, Set<string>>;
|
|
18
|
+
aliases: Map<string, {
|
|
19
|
+
to: string;
|
|
20
|
+
path: string;
|
|
21
|
+
}>;
|
|
22
|
+
usings: Set<string>;
|
|
23
|
+
projects: string[];
|
|
24
|
+
};
|
|
25
|
+
type Index = {
|
|
26
|
+
types: Map<string, Decl | null>;
|
|
27
|
+
binds: Map<string, Map<string, Bind>>;
|
|
28
|
+
repo: string[] | null;
|
|
29
|
+
globals: Map<Lang, Globals>;
|
|
30
|
+
};
|
|
31
|
+
type Cache = Map<string, CodeFile | null>;
|
|
32
|
+
export type Ctx = {
|
|
33
|
+
root: string;
|
|
34
|
+
read: Read;
|
|
35
|
+
cache: Cache;
|
|
36
|
+
ix: Index;
|
|
37
|
+
};
|
|
38
|
+
export declare function makeCtx(root: string, read?: Read, cache?: Cache): Ctx;
|
|
39
|
+
export declare const lastSeg: (s: string) => string;
|
|
40
|
+
export declare const file: (cx: Ctx, path: string) => CodeFile | null;
|
|
41
|
+
export declare function bindings(cx: Ctx, f: CodeFile): Map<string, Bind>;
|
|
42
|
+
export declare function goDirFiles(cx: Ctx, dir: string): CodeFile[];
|
|
43
|
+
export declare function global(cx: Ctx, lang: 'java' | 'cs' | 'rs'): Globals;
|
|
44
|
+
export declare function declIn(info: CodeFile | null, name: string): Decl | null;
|
|
45
|
+
export declare function findType(cx: Ctx, ctx: CodeFile, name: string | undefined, qual?: string | null): Decl | null;
|
|
46
|
+
export declare function ancestors(cx: Ctx, d: Decl): {
|
|
47
|
+
name: string;
|
|
48
|
+
d: Decl | null;
|
|
49
|
+
}[];
|
|
50
|
+
export declare function goMethods(cx: Ctx, d: Decl): Set<string>;
|
|
51
|
+
export declare const goIfaceMethods: (d: Decl) => string[];
|
|
52
|
+
export declare function defines(cx: Ctx, d: Decl, n: string): boolean;
|
|
53
|
+
export declare function rustUses(fi: CodeFile, n: string, tp: string): 'yes' | 'no' | 'outside';
|
|
54
|
+
export declare const rustMod: (tp: string) => string;
|
|
55
|
+
export {};
|