@lensmap/starlight 0.2.0 → 0.3.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/dist/ViewerHost.css +15 -0
- package/dist/ViewerHost.d.ts +5 -1
- package/dist/ViewerHost.js +637 -245
- package/dist/components/MapContent.astro +2 -1
- package/dist/index.js +16 -1
- package/dist/layout.js +38 -13
- package/package.json +20 -21
package/dist/ViewerHost.js
CHANGED
|
@@ -12,6 +12,169 @@ import {
|
|
|
12
12
|
|
|
13
13
|
// ../../apps/web/src/document/subtree.ts
|
|
14
14
|
import { polygonHull } from "d3-polygon";
|
|
15
|
+
function silhouette(contour, distance, padding) {
|
|
16
|
+
let low = Infinity;
|
|
17
|
+
let high = -Infinity;
|
|
18
|
+
for (const [x, y] of contour) {
|
|
19
|
+
const theta = Math.atan2(y, distance + x);
|
|
20
|
+
const half = Math.asin(padding / Math.hypot(distance + x, y));
|
|
21
|
+
low = Math.min(low, theta - half);
|
|
22
|
+
high = Math.max(high, theta + half);
|
|
23
|
+
}
|
|
24
|
+
return { low, high };
|
|
25
|
+
}
|
|
26
|
+
function planSubtree(key, children, fullCircle, step, padding) {
|
|
27
|
+
if (children.length === 0) return { key, contour: [[0, 0]], children: [] };
|
|
28
|
+
const atDistance = (distance2) => children.map((child) => silhouette(child.contour, distance2, padding));
|
|
29
|
+
const width = (bounds2) => bounds2.reduce((sum, bound) => sum + bound.high - bound.low, 0);
|
|
30
|
+
let distance = step;
|
|
31
|
+
let bounds = atDistance(distance);
|
|
32
|
+
const span = fullCircle ? Math.PI * 2 : Math.min(Math.PI, Math.max(Math.PI * 2 / 3, width(bounds)));
|
|
33
|
+
if (width(bounds) > span) {
|
|
34
|
+
let lower = distance;
|
|
35
|
+
while (width(atDistance(distance)) > span) distance *= 2;
|
|
36
|
+
while (distance - lower > 0.5) {
|
|
37
|
+
const middle = (lower + distance) / 2;
|
|
38
|
+
if (width(atDistance(middle)) > span) lower = middle;
|
|
39
|
+
else distance = middle;
|
|
40
|
+
}
|
|
41
|
+
bounds = atDistance(distance);
|
|
42
|
+
}
|
|
43
|
+
const required = width(bounds);
|
|
44
|
+
const gap = fullCircle ? (span - required) / children.length : children.length > 1 ? (span - required) / (children.length - 1) : 0;
|
|
45
|
+
let cursor = fullCircle ? -Math.PI / 2 + bounds[0].low : children.length === 1 ? -required / 2 : -span / 2;
|
|
46
|
+
const contour = [[0, 0]];
|
|
47
|
+
const placements = children.map((subtree, index) => {
|
|
48
|
+
const bound = bounds[index];
|
|
49
|
+
const angle = cursor - bound.low;
|
|
50
|
+
cursor += bound.high - bound.low + gap;
|
|
51
|
+
const cos = Math.cos(angle), sin = Math.sin(angle);
|
|
52
|
+
for (const [x, y] of subtree.contour) {
|
|
53
|
+
contour.push([
|
|
54
|
+
(distance + x) * cos - y * sin,
|
|
55
|
+
(distance + x) * sin + y * cos
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
return { subtree, angle, distance };
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
key,
|
|
62
|
+
contour: polygonHull(contour) ?? contour,
|
|
63
|
+
children: placements
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ../../apps/web/src/document/layout.ts
|
|
68
|
+
function layoutGraph(nodes, edges) {
|
|
69
|
+
if (nodes.length === 0) return { nodes: [], edges };
|
|
70
|
+
const byKey2 = new Map(nodes.map((node) => [node.key, node]));
|
|
71
|
+
const children = new Map(nodes.map((node) => [node.key, []]));
|
|
72
|
+
const childKeys = /* @__PURE__ */ new Set();
|
|
73
|
+
for (const edge of edges) {
|
|
74
|
+
if (edge.kind !== "branch") continue;
|
|
75
|
+
const child = byKey2.get(edge.to);
|
|
76
|
+
if (child) children.get(edge.from)?.push(child);
|
|
77
|
+
childKeys.add(edge.to);
|
|
78
|
+
}
|
|
79
|
+
const roots = nodes.filter((node) => !childKeys.has(node.key));
|
|
80
|
+
const placed = /* @__PURE__ */ new Map();
|
|
81
|
+
const maximum = Math.max(43, ...nodes.map((n) => n.baseRadius ?? 43));
|
|
82
|
+
const step = Math.max(180, maximum * 2 + 24);
|
|
83
|
+
const padding = Math.max(56, maximum + 13);
|
|
84
|
+
function plan(node, isRoot) {
|
|
85
|
+
const descendants = (children.get(node.key) ?? []).map(
|
|
86
|
+
(child) => plan(child, false)
|
|
87
|
+
);
|
|
88
|
+
return planSubtree(node.key, descendants, isRoot, step, padding);
|
|
89
|
+
}
|
|
90
|
+
const plans = roots.map((root) => plan(root, true));
|
|
91
|
+
function seed(node, subtree, point, heading, isRoot) {
|
|
92
|
+
const actual = node.position ?? point;
|
|
93
|
+
const fixed = Boolean(node.position) || isRoot;
|
|
94
|
+
placed.set(node.key, {
|
|
95
|
+
key: node.key,
|
|
96
|
+
x: actual.x,
|
|
97
|
+
y: actual.y,
|
|
98
|
+
targetX: actual.x,
|
|
99
|
+
targetY: actual.y,
|
|
100
|
+
fx: fixed ? actual.x : void 0,
|
|
101
|
+
fy: fixed ? actual.y : void 0
|
|
102
|
+
});
|
|
103
|
+
subtree.children.forEach((placement) => {
|
|
104
|
+
const child = byKey2.get(placement.subtree.key);
|
|
105
|
+
if (!child)
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Automatic layout could not find child "${placement.subtree.key}".`
|
|
108
|
+
);
|
|
109
|
+
const angle = heading + placement.angle;
|
|
110
|
+
const target = {
|
|
111
|
+
x: actual.x + Math.cos(angle) * placement.distance,
|
|
112
|
+
y: actual.y + Math.sin(angle) * placement.distance
|
|
113
|
+
};
|
|
114
|
+
const childPoint = child.position ?? target;
|
|
115
|
+
const direction = childPoint.x === actual.x && childPoint.y === actual.y ? angle : Math.atan2(childPoint.y - actual.y, childPoint.x - actual.x);
|
|
116
|
+
seed(child, placement.subtree, target, direction, false);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const anchor = nodes[0].position ?? { x: 0, y: 0 };
|
|
120
|
+
const rootRadius = roots.length > 1 ? step * 2 / Math.sin(Math.PI / roots.length) : 0;
|
|
121
|
+
roots.forEach((root, index) => {
|
|
122
|
+
const angle = -Math.PI / 2 + Math.PI * 2 * index / roots.length;
|
|
123
|
+
seed(
|
|
124
|
+
root,
|
|
125
|
+
plans[index],
|
|
126
|
+
{
|
|
127
|
+
x: anchor.x + Math.cos(angle) * rootRadius,
|
|
128
|
+
y: anchor.y + rootRadius + Math.sin(angle) * rootRadius
|
|
129
|
+
},
|
|
130
|
+
0,
|
|
131
|
+
true
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
const simulation = forceSimulation([...placed.values()]).stop().force("x", forceX((node) => node.targetX).strength(0.2)).force("y", forceY((node) => node.targetY).strength(0.2)).force("collide", forceCollide(padding).iterations(2));
|
|
135
|
+
const ticks = Math.ceil(
|
|
136
|
+
Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())
|
|
137
|
+
);
|
|
138
|
+
simulation.tick(ticks);
|
|
139
|
+
return {
|
|
140
|
+
nodes: nodes.map(({ position, ...node }) => {
|
|
141
|
+
const point = placed.get(node.key);
|
|
142
|
+
if (!point)
|
|
143
|
+
throw new Error(`Automatic layout could not place node "${node.key}".`);
|
|
144
|
+
return {
|
|
145
|
+
...node,
|
|
146
|
+
...position ? { position } : {},
|
|
147
|
+
x: point.x,
|
|
148
|
+
y: point.y
|
|
149
|
+
};
|
|
150
|
+
}),
|
|
151
|
+
edges
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ../../apps/web/src/node-style.ts
|
|
156
|
+
function baseRadius(style) {
|
|
157
|
+
return 43 * (typeof style?.scale === "number" ? style.scale : 1);
|
|
158
|
+
}
|
|
159
|
+
var properties = /^(color|background-color|font-family|font-size|font-weight|font-style|line-height|border(?:-(?:color|width|style|top|right|bottom|left)(?:-(?:color|width|style))?)?|padding(?:-(?:top|right|bottom|left))?)$/;
|
|
160
|
+
var applied = /* @__PURE__ */ new WeakMap();
|
|
161
|
+
function applyNodeStyle(element, style, borderColour) {
|
|
162
|
+
for (const property of applied.get(element) ?? [])
|
|
163
|
+
element.style.removeProperty(property);
|
|
164
|
+
if (borderColour) element.style.borderColor = borderColour;
|
|
165
|
+
applied.set(
|
|
166
|
+
element,
|
|
167
|
+
(style?.declarations ?? []).map((d) => d.property).filter((p) => properties.test(p))
|
|
168
|
+
);
|
|
169
|
+
for (const declaration of style?.declarations ?? []) {
|
|
170
|
+
if (properties.test(declaration.property))
|
|
171
|
+
element.style.setProperty(
|
|
172
|
+
declaration.property,
|
|
173
|
+
declaration.value,
|
|
174
|
+
declaration.important ? "important" : ""
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
15
178
|
|
|
16
179
|
// ../../apps/web/src/Viewer.tsx
|
|
17
180
|
import {
|
|
@@ -23,7 +186,7 @@ import {
|
|
|
23
186
|
useCallback as useCallback2,
|
|
24
187
|
useEffect as useEffect4,
|
|
25
188
|
useEffectEvent as useEffectEvent2,
|
|
26
|
-
useId as
|
|
189
|
+
useId as useId2,
|
|
27
190
|
useLayoutEffect,
|
|
28
191
|
useMemo,
|
|
29
192
|
useRef as useRef4,
|
|
@@ -93,7 +256,11 @@ function ConnectionChooser({
|
|
|
93
256
|
/* @__PURE__ */ jsxs("span", { id: `connection-details-${candidate.node.key}`, children: [
|
|
94
257
|
candidate.routes.map((route, routeIndex) => /* @__PURE__ */ jsxs("span", { children: [
|
|
95
258
|
routeIndex > 0 ? " \xB7 " : "",
|
|
96
|
-
connectionWording(
|
|
259
|
+
connectionWording(
|
|
260
|
+
route.edge.type,
|
|
261
|
+
route.direction,
|
|
262
|
+
route.edge.presentation
|
|
263
|
+
),
|
|
97
264
|
` (${route.direction} ${route.edge.kind === "branch" ? "branch" : "cross-link"})`
|
|
98
265
|
] }, `${route.edge.key}:${route.direction}`)),
|
|
99
266
|
!visibleKeys.has(candidate.node.key) && /* @__PURE__ */ jsx("span", { className: "outside-view", children: "outside this view" })
|
|
@@ -134,69 +301,197 @@ function ConnectionChooser({
|
|
|
134
301
|
// ../../apps/web/src/ConnectionGap.tsx
|
|
135
302
|
import { useId } from "react";
|
|
136
303
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
137
|
-
function
|
|
138
|
-
|
|
304
|
+
function Shape({
|
|
305
|
+
kind,
|
|
139
306
|
colour,
|
|
140
|
-
|
|
307
|
+
cut = false
|
|
141
308
|
}) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
309
|
+
if (kind === "circle")
|
|
310
|
+
return /* @__PURE__ */ jsx2(
|
|
311
|
+
"circle",
|
|
312
|
+
{
|
|
313
|
+
cx: "-3",
|
|
314
|
+
cy: "0",
|
|
315
|
+
r: cut ? 2.5 : 3,
|
|
316
|
+
fill: cut ? "black" : "none",
|
|
317
|
+
stroke: cut ? "black" : colour,
|
|
318
|
+
strokeWidth: cut ? 0 : 1
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
if (kind === "diamond")
|
|
322
|
+
return /* @__PURE__ */ jsx2(
|
|
323
|
+
"path",
|
|
324
|
+
{
|
|
325
|
+
d: cut ? "M -0.7 0 L -3 -2.3 L -5.3 0 L -3 2.3 Z" : "M 0 0 L -3 -3 L -6 0 L -3 3 Z",
|
|
326
|
+
fill: cut ? "black" : "none",
|
|
327
|
+
stroke: cut ? "black" : colour,
|
|
328
|
+
strokeWidth: cut ? 0 : 1
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
if (kind === "bar")
|
|
332
|
+
return cut ? /* @__PURE__ */ jsx2("rect", { x: "-4.5", y: "-5", width: "3", height: "10", fill: "black" }) : /* @__PURE__ */ jsxs2("g", { stroke: colour, strokeWidth: "1", children: [
|
|
333
|
+
/* @__PURE__ */ jsx2("line", { x1: "-5", x2: "-5", y1: "-3", y2: "3" }),
|
|
334
|
+
/* @__PURE__ */ jsx2("line", { x1: "-1", x2: "-1", y1: "-3", y2: "3" })
|
|
335
|
+
] });
|
|
336
|
+
if (kind === "arrow" && !cut)
|
|
337
|
+
return /* @__PURE__ */ jsx2(
|
|
338
|
+
"path",
|
|
339
|
+
{
|
|
340
|
+
d: "M -5 -3 L 0 0 L -5 3",
|
|
341
|
+
fill: "none",
|
|
342
|
+
stroke: colour,
|
|
343
|
+
strokeWidth: "1"
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
return null;
|
|
159
347
|
}
|
|
160
|
-
function
|
|
348
|
+
function StyledConnection({
|
|
161
349
|
path,
|
|
162
350
|
points,
|
|
351
|
+
presentation,
|
|
352
|
+
width,
|
|
353
|
+
colour,
|
|
163
354
|
className,
|
|
164
|
-
|
|
165
|
-
|
|
355
|
+
attributes,
|
|
356
|
+
continuation
|
|
166
357
|
}) {
|
|
167
358
|
const id = useId();
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
"
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
359
|
+
const start = continuation === "incoming" ? "none" : presentation.startMarker;
|
|
360
|
+
const end = continuation === "outgoing" ? "none" : presentation.endMarker;
|
|
361
|
+
const paint = presentation.lineColor === "auto" ? colour : presentation.lineColor;
|
|
362
|
+
const marker = (kind, side, cut = false) => /* @__PURE__ */ jsx2(
|
|
363
|
+
"marker",
|
|
364
|
+
{
|
|
365
|
+
id: `${id}-${kind}-${side}`,
|
|
366
|
+
markerUnits: "strokeWidth",
|
|
367
|
+
markerWidth: 14,
|
|
368
|
+
markerHeight: 14,
|
|
369
|
+
viewBox: "-7 -7 14 14",
|
|
370
|
+
refX: "0",
|
|
371
|
+
refY: "0",
|
|
372
|
+
orient: "auto-start-reverse",
|
|
373
|
+
overflow: "visible",
|
|
374
|
+
children: /* @__PURE__ */ jsx2(Shape, { kind, colour: paint, cut })
|
|
375
|
+
},
|
|
376
|
+
side
|
|
377
|
+
);
|
|
378
|
+
const pad = Math.max(64, width * 16);
|
|
379
|
+
const x = Math.min(...points.map((p) => p.x)) - pad;
|
|
380
|
+
const y = Math.min(...points.map((p) => p.y)) - pad;
|
|
381
|
+
const w = Math.max(...points.map((p) => p.x)) - x + pad;
|
|
382
|
+
const h = Math.max(...points.map((p) => p.y)) - y + pad;
|
|
383
|
+
const style = { strokeWidth: width, color: paint };
|
|
384
|
+
return /* @__PURE__ */ jsxs2(
|
|
385
|
+
"g",
|
|
386
|
+
{
|
|
387
|
+
"data-presentation-width": width,
|
|
388
|
+
mask: continuation ? `url(#${id}-fade)` : void 0,
|
|
389
|
+
children: [
|
|
390
|
+
/* @__PURE__ */ jsxs2("defs", { children: [
|
|
391
|
+
continuation && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
392
|
+
/* @__PURE__ */ jsxs2(
|
|
393
|
+
"linearGradient",
|
|
394
|
+
{
|
|
395
|
+
id: `${id}-gradient`,
|
|
396
|
+
gradientUnits: "userSpaceOnUse",
|
|
397
|
+
x1: points[0].x,
|
|
398
|
+
y1: points[0].y,
|
|
399
|
+
x2: points[1].x,
|
|
400
|
+
y2: points[1].y,
|
|
401
|
+
children: [
|
|
402
|
+
/* @__PURE__ */ jsx2("stop", { offset: "0%", stopColor: "white", stopOpacity: 0.65 }),
|
|
403
|
+
/* @__PURE__ */ jsx2("stop", { offset: "100%", stopColor: "white", stopOpacity: 0 })
|
|
404
|
+
]
|
|
405
|
+
}
|
|
406
|
+
),
|
|
407
|
+
/* @__PURE__ */ jsx2(
|
|
408
|
+
"mask",
|
|
409
|
+
{
|
|
410
|
+
id: `${id}-fade`,
|
|
411
|
+
maskUnits: "userSpaceOnUse",
|
|
412
|
+
x,
|
|
413
|
+
y,
|
|
414
|
+
width: w,
|
|
415
|
+
height: h,
|
|
416
|
+
style: { maskType: "luminance" },
|
|
417
|
+
children: /* @__PURE__ */ jsx2(
|
|
418
|
+
"rect",
|
|
419
|
+
{
|
|
420
|
+
x,
|
|
421
|
+
y,
|
|
422
|
+
width: w,
|
|
423
|
+
height: h,
|
|
424
|
+
fill: `url(#${id}-gradient)`
|
|
425
|
+
}
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
)
|
|
429
|
+
] }),
|
|
430
|
+
marker(start, "start"),
|
|
431
|
+
marker(end, "end"),
|
|
432
|
+
marker(start, "start-cut", true),
|
|
433
|
+
marker(end, "end-cut", true),
|
|
434
|
+
/* @__PURE__ */ jsxs2(
|
|
435
|
+
"mask",
|
|
188
436
|
{
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
437
|
+
id: `${id}-mask`,
|
|
438
|
+
maskUnits: "userSpaceOnUse",
|
|
439
|
+
x,
|
|
440
|
+
y,
|
|
441
|
+
width: w,
|
|
442
|
+
height: h,
|
|
443
|
+
style: { maskType: "luminance" },
|
|
444
|
+
children: [
|
|
445
|
+
/* @__PURE__ */ jsx2("rect", { x, y, width: w, height: h, fill: "white" }),
|
|
446
|
+
/* @__PURE__ */ jsx2(
|
|
447
|
+
"path",
|
|
448
|
+
{
|
|
449
|
+
d: path,
|
|
450
|
+
fill: "none",
|
|
451
|
+
stroke: "none",
|
|
452
|
+
strokeWidth: width,
|
|
453
|
+
markerStart: `url(#${id}-${start}-start-cut)`,
|
|
454
|
+
markerEnd: `url(#${id}-${end}-end-cut)`
|
|
455
|
+
}
|
|
456
|
+
)
|
|
457
|
+
]
|
|
193
458
|
}
|
|
194
459
|
)
|
|
195
|
-
]
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
460
|
+
] }),
|
|
461
|
+
/* @__PURE__ */ jsxs2(
|
|
462
|
+
"g",
|
|
463
|
+
{
|
|
464
|
+
visibility: width === 0 ? "hidden" : void 0,
|
|
465
|
+
mask: `url(#${id}-mask)`,
|
|
466
|
+
children: [
|
|
467
|
+
/* @__PURE__ */ jsx2(
|
|
468
|
+
"path",
|
|
469
|
+
{
|
|
470
|
+
d: path,
|
|
471
|
+
className: "edge-halo",
|
|
472
|
+
style: { strokeWidth: width * 3.2 }
|
|
473
|
+
}
|
|
474
|
+
),
|
|
475
|
+
/* @__PURE__ */ jsx2(
|
|
476
|
+
"path",
|
|
477
|
+
{
|
|
478
|
+
...attributes,
|
|
479
|
+
d: path,
|
|
480
|
+
className,
|
|
481
|
+
stroke: paint,
|
|
482
|
+
style,
|
|
483
|
+
markerStart: start === "none" ? void 0 : `url(#${id}-${start}-start)`,
|
|
484
|
+
markerEnd: end === "none" ? void 0 : `url(#${id}-${end}-end)`,
|
|
485
|
+
strokeLinecap: presentation.lineStyle === "dotted" ? "round" : void 0,
|
|
486
|
+
strokeDasharray: presentation.lineStyle === "dotted" ? `0 ${width * 2.5}` : presentation.lineStyle === "dashed" ? `${width * 2.8} ${width * 2.4}` : void 0
|
|
487
|
+
}
|
|
488
|
+
)
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
)
|
|
492
|
+
]
|
|
493
|
+
}
|
|
494
|
+
);
|
|
200
495
|
}
|
|
201
496
|
|
|
202
497
|
// ../../apps/web/src/clearance.ts
|
|
@@ -204,7 +499,9 @@ function limitMagnification(points, nodeRadius2, strength) {
|
|
|
204
499
|
const amount = Math.max(0, Math.min(1, strength));
|
|
205
500
|
if (amount === 0 || points.length < 2)
|
|
206
501
|
return points.map((point) => ({ ...point }));
|
|
207
|
-
const radii = points.map(
|
|
502
|
+
const radii = points.map(
|
|
503
|
+
(point) => point.scale * (point.baseRadius ?? nodeRadius2)
|
|
504
|
+
);
|
|
208
505
|
const minimum = radii.map((radius2) => Math.min(radius2, 2));
|
|
209
506
|
const limited = [...radii];
|
|
210
507
|
for (let i = 0; i < points.length; i++) {
|
|
@@ -230,7 +527,7 @@ function limitMagnification(points, nodeRadius2, strength) {
|
|
|
230
527
|
}
|
|
231
528
|
return points.map((point, index) => ({
|
|
232
529
|
...point,
|
|
233
|
-
scale: point.scale + (limited[index] / nodeRadius2 - point.scale) * amount
|
|
530
|
+
scale: point.scale + (limited[index] / (point.baseRadius ?? nodeRadius2) - point.scale) * amount
|
|
234
531
|
}));
|
|
235
532
|
}
|
|
236
533
|
|
|
@@ -242,6 +539,39 @@ function connectionAppearance(type, continuation) {
|
|
|
242
539
|
...continuation !== "outgoing" && (type === "precedes" || type === "prevents") ? { end: type === "precedes" ? "arrow" : "bar" } : {}
|
|
243
540
|
};
|
|
244
541
|
}
|
|
542
|
+
function fallbackPresentation(type) {
|
|
543
|
+
const appearance = connectionAppearance(type);
|
|
544
|
+
return {
|
|
545
|
+
strokeWidth: 2.5,
|
|
546
|
+
lineStyle: appearance.dashed ? "dashed" : "solid",
|
|
547
|
+
lineColor: "auto",
|
|
548
|
+
startMarker: appearance.start ?? "none",
|
|
549
|
+
endMarker: appearance.end ?? "none",
|
|
550
|
+
text: "connected to",
|
|
551
|
+
reverseText: "connected to"
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ../../apps/web/src/connection-style.ts
|
|
556
|
+
function markerClearance(marker, width) {
|
|
557
|
+
if (marker === "arrow") return width * Math.hypot(5, 3) / 6;
|
|
558
|
+
if (marker === "diamond") return width / Math.SQRT2;
|
|
559
|
+
return marker === "circle" ? width / 2 : 0;
|
|
560
|
+
}
|
|
561
|
+
function resolveConnectionWidth(value, root, width, height) {
|
|
562
|
+
if (typeof value === "number") return value;
|
|
563
|
+
const container = document.createElement("div");
|
|
564
|
+
container.style.cssText = "position:absolute;visibility:hidden;pointer-events:none;height:0;overflow:hidden";
|
|
565
|
+
container.style.width = `${Math.hypot(width, height) / Math.SQRT2}px`;
|
|
566
|
+
const probe = document.createElement("div");
|
|
567
|
+
probe.style.width = CSS.supports("width", value) ? value : `calc((${value}) * 1px)`;
|
|
568
|
+
probe.style.height = "0";
|
|
569
|
+
container.append(probe);
|
|
570
|
+
root.append(container);
|
|
571
|
+
const result = Number.parseFloat(getComputedStyle(probe).width);
|
|
572
|
+
container.remove();
|
|
573
|
+
return Number.isFinite(result) ? Math.max(0, result) : 0;
|
|
574
|
+
}
|
|
245
575
|
|
|
246
576
|
// ../../apps/web/src/edge-routing.ts
|
|
247
577
|
function edgeLanes(edges) {
|
|
@@ -296,10 +626,10 @@ function zoomCamera(camera, factor) {
|
|
|
296
626
|
}
|
|
297
627
|
function fitCamera(points, size, radius2) {
|
|
298
628
|
if (points.length === 0) return { x: 0, y: 0, zoom: 1 };
|
|
299
|
-
const minX = Math.min(...points.map((p) => p.x
|
|
300
|
-
const maxX = Math.max(...points.map((p) => p.x
|
|
301
|
-
const minY = Math.min(...points.map((p) => p.y
|
|
302
|
-
const maxY = Math.max(...points.map((p) => p.y
|
|
629
|
+
const minX = Math.min(...points.map((p) => p.x - (p.baseRadius ?? radius2)));
|
|
630
|
+
const maxX = Math.max(...points.map((p) => p.x + (p.baseRadius ?? radius2)));
|
|
631
|
+
const minY = Math.min(...points.map((p) => p.y - (p.baseRadius ?? radius2)));
|
|
632
|
+
const maxY = Math.max(...points.map((p) => p.y + (p.baseRadius ?? radius2)));
|
|
303
633
|
return {
|
|
304
634
|
x: (minX + maxX) / 2,
|
|
305
635
|
y: (minY + maxY) / 2,
|
|
@@ -316,7 +646,6 @@ function fitCamera(points, size, radius2) {
|
|
|
316
646
|
|
|
317
647
|
// ../../apps/web/src/NeighbourhoodContinuations.tsx
|
|
318
648
|
import { connectionWording as connectionWording2 } from "@lensmap/document";
|
|
319
|
-
import { useId as useId2 } from "react";
|
|
320
649
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
321
650
|
function NeighbourhoodContinuations({
|
|
322
651
|
continuations,
|
|
@@ -325,25 +654,25 @@ function NeighbourhoodContinuations({
|
|
|
325
654
|
nodes,
|
|
326
655
|
nodeRadius: nodeRadius2,
|
|
327
656
|
previewEdges,
|
|
328
|
-
|
|
657
|
+
widths
|
|
329
658
|
}) {
|
|
330
|
-
const prefix = useId2();
|
|
331
659
|
const origin = positions.get(centreKey);
|
|
332
660
|
const byKey2 = new Map(nodes.map((node) => [node.key, node]));
|
|
333
|
-
const segments = continuations.flatMap((continuation
|
|
661
|
+
const segments = continuations.flatMap((continuation) => {
|
|
334
662
|
const point = positions.get(continuation.at);
|
|
335
663
|
const source = byKey2.get(continuation.edge.from);
|
|
336
664
|
const target = byKey2.get(continuation.edge.to);
|
|
337
665
|
if (!point || !origin || !source || !target) return [];
|
|
338
666
|
const incoming = continuation.edge.to === continuation.at;
|
|
339
|
-
const appearance = connectionAppearance(
|
|
340
|
-
continuation.edge.type,
|
|
341
|
-
incoming ? "incoming" : "outgoing"
|
|
342
|
-
);
|
|
343
667
|
const angle = Math.atan2(point.y - origin.y, point.x - origin.x) + continuation.offset;
|
|
344
668
|
const ux = Math.cos(angle), uy = Math.sin(angle);
|
|
345
|
-
const
|
|
346
|
-
const
|
|
669
|
+
const width = widths.get(continuation.edge.key) ?? 2.5;
|
|
670
|
+
const presentation = continuation.edge.presentation ?? fallbackPresentation(continuation.edge.type);
|
|
671
|
+
const clearance = markerClearance(
|
|
672
|
+
incoming ? presentation.endMarker : presentation.startMarker,
|
|
673
|
+
width
|
|
674
|
+
);
|
|
675
|
+
const radius2 = (byKey2.get(continuation.at)?.baseRadius ?? nodeRadius2) * point.scale + clearance;
|
|
347
676
|
const near = { x: point.x + ux * radius2, y: point.y + uy * radius2 };
|
|
348
677
|
const far = {
|
|
349
678
|
x: near.x + ux * 70 * point.scale,
|
|
@@ -353,91 +682,49 @@ function NeighbourhoodContinuations({
|
|
|
353
682
|
return [
|
|
354
683
|
{
|
|
355
684
|
...continuation,
|
|
356
|
-
|
|
685
|
+
presentation,
|
|
357
686
|
incoming,
|
|
358
687
|
near,
|
|
359
688
|
far,
|
|
360
689
|
target,
|
|
361
|
-
description: `${label(source)} ${connectionWording2(continuation.edge.type, "outgoing")} ${label(target)}`,
|
|
362
|
-
id: `${prefix}-${index}`,
|
|
690
|
+
description: `${label(source)} ${connectionWording2(continuation.edge.type, "outgoing", continuation.edge.presentation)} ${label(target)}`,
|
|
363
691
|
path: `M ${from.x} ${from.y} L ${to.x} ${to.y}`
|
|
364
692
|
}
|
|
365
693
|
];
|
|
366
694
|
});
|
|
367
|
-
return /* @__PURE__ */
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
stopColor: colours[segment.target.branch],
|
|
391
|
-
stopOpacity: 0
|
|
392
|
-
}
|
|
393
|
-
)
|
|
394
|
-
]
|
|
395
|
-
},
|
|
396
|
-
segment.edge.key
|
|
397
|
-
)) }),
|
|
398
|
-
segments.map((segment) => {
|
|
399
|
-
const appearance = segment.appearance;
|
|
400
|
-
const edgeClass = `edge continuation ${segment.edge.kind} ${previewEdges.has(segment.edge.key) ? "connection-preview" : ""}`;
|
|
401
|
-
const painted = /* @__PURE__ */ jsx3(
|
|
695
|
+
return /* @__PURE__ */ jsx3("g", { className: "continuations", children: segments.map((segment) => {
|
|
696
|
+
const edgeClass = `edge continuation ${segment.edge.kind} ${previewEdges.has(segment.edge.key) ? "connection-preview" : ""}`;
|
|
697
|
+
return /* @__PURE__ */ jsxs3("g", { children: [
|
|
698
|
+
/* @__PURE__ */ jsx3(
|
|
699
|
+
StyledConnection,
|
|
700
|
+
{
|
|
701
|
+
path: segment.path,
|
|
702
|
+
points: [segment.near, segment.far],
|
|
703
|
+
presentation: segment.presentation,
|
|
704
|
+
width: widths.get(segment.edge.key) ?? 2.5,
|
|
705
|
+
colour: colours[segment.target.branch],
|
|
706
|
+
className: edgeClass,
|
|
707
|
+
continuation: segment.incoming ? "incoming" : "outgoing",
|
|
708
|
+
attributes: {
|
|
709
|
+
"data-continuation": segment.edge.key,
|
|
710
|
+
"data-connection-preview": previewEdges.has(segment.edge.key) ? "true" : void 0,
|
|
711
|
+
"data-kind": segment.edge.kind,
|
|
712
|
+
"data-type": segment.edge.type ?? "connected",
|
|
713
|
+
"data-direction": segment.incoming ? "incoming" : "outgoing"
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
),
|
|
717
|
+
/* @__PURE__ */ jsx3(
|
|
402
718
|
"path",
|
|
403
719
|
{
|
|
404
|
-
"
|
|
405
|
-
"data-
|
|
406
|
-
"data-kind": segment.edge.kind,
|
|
407
|
-
"data-type": segment.edge.type ?? "connected",
|
|
408
|
-
strokeDasharray: appearance.dashed ? "7 6" : void 0,
|
|
409
|
-
"data-direction": segment.incoming ? "incoming" : "outgoing",
|
|
720
|
+
className: "edge-hit",
|
|
721
|
+
"data-continuation-hit": segment.edge.key,
|
|
410
722
|
d: segment.path,
|
|
411
|
-
|
|
412
|
-
style: { color: colours[segment.target.branch] },
|
|
413
|
-
className: edgeClass,
|
|
414
|
-
markerStart: appearance.start ? `url(#${markerPrefix}-arrow-${segment.target.branch})` : void 0,
|
|
415
|
-
markerEnd: appearance.end ? `url(#${markerPrefix}-${appearance.end}-${segment.target.branch})` : void 0
|
|
723
|
+
children: /* @__PURE__ */ jsx3("title", { children: segment.description })
|
|
416
724
|
}
|
|
417
|
-
)
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
ConnectionGap,
|
|
421
|
-
{
|
|
422
|
-
path: segment.path,
|
|
423
|
-
points: [segment.near, segment.far],
|
|
424
|
-
className: edgeClass,
|
|
425
|
-
markerId: `${markerPrefix}-bar-gap`,
|
|
426
|
-
children: painted
|
|
427
|
-
}
|
|
428
|
-
) : painted,
|
|
429
|
-
/* @__PURE__ */ jsx3(
|
|
430
|
-
"path",
|
|
431
|
-
{
|
|
432
|
-
className: "edge-hit",
|
|
433
|
-
"data-continuation-hit": segment.edge.key,
|
|
434
|
-
d: segment.path,
|
|
435
|
-
children: /* @__PURE__ */ jsx3("title", { children: segment.description })
|
|
436
|
-
}
|
|
437
|
-
)
|
|
438
|
-
] }, segment.edge.key);
|
|
439
|
-
})
|
|
440
|
-
] });
|
|
725
|
+
)
|
|
726
|
+
] }, segment.edge.key);
|
|
727
|
+
}) });
|
|
441
728
|
}
|
|
442
729
|
|
|
443
730
|
// ../../apps/web/src/navigation.ts
|
|
@@ -557,9 +844,14 @@ function neighbourhood(graph, key, nodeRadius2 = 43) {
|
|
|
557
844
|
neighbours.reduce((sum, n, i) => sum + Math.sin(n.bearing - i * step), 0),
|
|
558
845
|
neighbours.reduce((sum, n, i) => sum + Math.cos(n.bearing - i * step), 0)
|
|
559
846
|
);
|
|
847
|
+
const maximum = Math.max(
|
|
848
|
+
nodeRadius2,
|
|
849
|
+
...graph.nodes.filter((n) => visible.has(n.key)).map((n) => n.baseRadius ?? nodeRadius2)
|
|
850
|
+
);
|
|
560
851
|
const radius2 = Math.max(
|
|
852
|
+
(centre.baseRadius ?? nodeRadius2) + maximum + 24,
|
|
561
853
|
260,
|
|
562
|
-
(2 *
|
|
854
|
+
(2 * maximum + 24) / (2 * Math.sin(Math.PI / Math.max(2, neighbours.length)))
|
|
563
855
|
);
|
|
564
856
|
const nodes = [
|
|
565
857
|
{ ...centre, x: 0, y: 0 },
|
|
@@ -825,8 +1117,9 @@ function ViewerConnections({
|
|
|
825
1117
|
"data-focus-key": key,
|
|
826
1118
|
onClick: () => onFocus(key),
|
|
827
1119
|
children: [
|
|
828
|
-
connectionWording3(edge.type, direction),
|
|
829
|
-
"
|
|
1120
|
+
connectionWording3(edge.type, direction, edge.presentation),
|
|
1121
|
+
" ",
|
|
1122
|
+
"\xB7 ",
|
|
830
1123
|
getLabel(node)
|
|
831
1124
|
]
|
|
832
1125
|
},
|
|
@@ -935,11 +1228,11 @@ function reconcileViewer(previous, next, selected, camera, mode) {
|
|
|
935
1228
|
}
|
|
936
1229
|
|
|
937
1230
|
// ../../apps/web/src/Viewer.tsx
|
|
938
|
-
import {
|
|
1231
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
939
1232
|
var initialCamera = { x: 0, y: 0, zoom: 0.85 };
|
|
940
1233
|
var nodeRadius = 43;
|
|
941
1234
|
function Viewer({
|
|
942
|
-
graph,
|
|
1235
|
+
graph: inputGraph,
|
|
943
1236
|
embedded = false,
|
|
944
1237
|
navigationControls,
|
|
945
1238
|
thoughtLink,
|
|
@@ -955,7 +1248,21 @@ function Viewer({
|
|
|
955
1248
|
renderSidebar,
|
|
956
1249
|
renderExternalInspector
|
|
957
1250
|
}) {
|
|
958
|
-
const
|
|
1251
|
+
const [measured, setMeasured] = useState2(
|
|
1252
|
+
/* @__PURE__ */ new Map()
|
|
1253
|
+
);
|
|
1254
|
+
const graph = useMemo(() => {
|
|
1255
|
+
const nodes2 = inputGraph.nodes.map((node) => ({
|
|
1256
|
+
...node,
|
|
1257
|
+
baseRadius: (node.style?.scale === "auto" ? measured.get(node.key) : void 0) ?? baseRadius(node.style)
|
|
1258
|
+
}));
|
|
1259
|
+
if (!nodes2.some(
|
|
1260
|
+
(node, i) => node.baseRadius !== (inputGraph.nodes[i].baseRadius ?? 43)
|
|
1261
|
+
))
|
|
1262
|
+
return inputGraph;
|
|
1263
|
+
return layoutGraph(nodes2, inputGraph.edges);
|
|
1264
|
+
}, [inputGraph, measured]);
|
|
1265
|
+
const scope = useId2();
|
|
959
1266
|
const anchors = useMemo(() => thoughtAnchors2(graph.nodes), [graph.nodes]);
|
|
960
1267
|
const entryNode = graph.nodes.find((n) => n.key === initialTarget);
|
|
961
1268
|
const restoredNode = graph.nodes.find(
|
|
@@ -974,6 +1281,24 @@ function Viewer({
|
|
|
974
1281
|
const drag = useRef4(null);
|
|
975
1282
|
const suppressPointerClick = useRef4(false);
|
|
976
1283
|
const [size, setSize] = useState2({ width: 900, height: 640 });
|
|
1284
|
+
const [widths, setWidths] = useState2(/* @__PURE__ */ new Map());
|
|
1285
|
+
useLayoutEffect(() => {
|
|
1286
|
+
const root = canvas.current;
|
|
1287
|
+
if (!root) return;
|
|
1288
|
+
setWidths(
|
|
1289
|
+
new Map(
|
|
1290
|
+
graph.edges.map((edge) => [
|
|
1291
|
+
edge.key,
|
|
1292
|
+
resolveConnectionWidth(
|
|
1293
|
+
edge.presentation?.strokeWidth ?? 2.5,
|
|
1294
|
+
root,
|
|
1295
|
+
size.width,
|
|
1296
|
+
size.height
|
|
1297
|
+
)
|
|
1298
|
+
])
|
|
1299
|
+
)
|
|
1300
|
+
);
|
|
1301
|
+
}, [graph, size]);
|
|
977
1302
|
const [camera, setCamera] = useState2(
|
|
978
1303
|
initialState?.camera ?? (entryNode ? { ...initialCamera, x: entryNode.x, y: entryNode.y } : initialCamera)
|
|
979
1304
|
);
|
|
@@ -1063,6 +1388,57 @@ function Viewer({
|
|
|
1063
1388
|
() => inNeighbourhood ? neighbourhood(graph, selected, nodeRadius) : null,
|
|
1064
1389
|
[graph, inNeighbourhood, selected]
|
|
1065
1390
|
);
|
|
1391
|
+
const measurementView = inNeighbourhood ? selected : null;
|
|
1392
|
+
useLayoutEffect(() => {
|
|
1393
|
+
let alive = true;
|
|
1394
|
+
const measure = () => {
|
|
1395
|
+
if (!alive || !canvas.current) return;
|
|
1396
|
+
const visible = /* @__PURE__ */ new Map();
|
|
1397
|
+
canvas.current.querySelectorAll(".node[data-auto-size='true']").forEach((element) => {
|
|
1398
|
+
const key = element.dataset.node;
|
|
1399
|
+
if (!key || !inputGraph.nodes.some((node) => node.key === key))
|
|
1400
|
+
return;
|
|
1401
|
+
const clone = element.cloneNode(true);
|
|
1402
|
+
clone.removeAttribute("id");
|
|
1403
|
+
clone.classList.remove("selected", "connected");
|
|
1404
|
+
clone.style.cssText = element.style.cssText;
|
|
1405
|
+
Object.assign(clone.style, {
|
|
1406
|
+
transform: "none",
|
|
1407
|
+
visibility: "hidden",
|
|
1408
|
+
position: "absolute",
|
|
1409
|
+
width: "max-content",
|
|
1410
|
+
maxWidth: "260px",
|
|
1411
|
+
height: "auto",
|
|
1412
|
+
minHeight: "0",
|
|
1413
|
+
left: "0",
|
|
1414
|
+
top: "0"
|
|
1415
|
+
});
|
|
1416
|
+
canvas.current?.append(clone);
|
|
1417
|
+
const box = clone.getBoundingClientRect();
|
|
1418
|
+
visible.set(
|
|
1419
|
+
key,
|
|
1420
|
+
Math.max(43, Math.ceil(Math.hypot(box.width, box.height) / 2))
|
|
1421
|
+
);
|
|
1422
|
+
clone.remove();
|
|
1423
|
+
});
|
|
1424
|
+
setMeasured((previous) => {
|
|
1425
|
+
const next = /* @__PURE__ */ new Map();
|
|
1426
|
+
for (const node of inputGraph.nodes) {
|
|
1427
|
+
if (node.style?.scale !== "auto") continue;
|
|
1428
|
+
const radius2 = visible.get(node.key) ?? previous.get(node.key);
|
|
1429
|
+
if (radius2 !== void 0) next.set(node.key, radius2);
|
|
1430
|
+
}
|
|
1431
|
+
return JSON.stringify([...previous]) === JSON.stringify([...next]) ? previous : next;
|
|
1432
|
+
});
|
|
1433
|
+
};
|
|
1434
|
+
measure();
|
|
1435
|
+
void document.fonts.ready.then(measure);
|
|
1436
|
+
document.fonts.addEventListener("loadingdone", measure);
|
|
1437
|
+
return () => {
|
|
1438
|
+
alive = false;
|
|
1439
|
+
document.fonts.removeEventListener("loadingdone", measure);
|
|
1440
|
+
};
|
|
1441
|
+
}, [inputGraph, measurementView]);
|
|
1066
1442
|
useEffect4(() => cancelActivation, [cancelActivation]);
|
|
1067
1443
|
useEffect4(() => {
|
|
1068
1444
|
const element = canvas.current;
|
|
@@ -1106,7 +1482,19 @@ function Viewer({
|
|
|
1106
1482
|
keyboard.stop,
|
|
1107
1483
|
dismissConnectionChoice
|
|
1108
1484
|
]);
|
|
1109
|
-
const
|
|
1485
|
+
const overviewPoints = nodes.flatMap((node) => {
|
|
1486
|
+
const loops = edges.filter(
|
|
1487
|
+
(edge) => edge.from === node.key && edge.to === node.key
|
|
1488
|
+
);
|
|
1489
|
+
if (!node.style || loops.length === 0) return [node];
|
|
1490
|
+
const reach = (node.baseRadius ?? nodeRadius) * (3 + Math.max(...loops.map((edge) => lanes.get(edge.key)?.index ?? 0)));
|
|
1491
|
+
return [
|
|
1492
|
+
node,
|
|
1493
|
+
{ x: node.x - reach, y: node.y - reach, baseRadius: 0 },
|
|
1494
|
+
{ x: node.x + reach, y: node.y - reach, baseRadius: 0 }
|
|
1495
|
+
];
|
|
1496
|
+
});
|
|
1497
|
+
const shownCamera = inNeighbourhood ? neighbourhoodCamera : overview ? fitCamera(overviewPoints, size, nodeRadius) : camera;
|
|
1110
1498
|
const projected = (local?.nodes ?? nodes).map((node) => ({
|
|
1111
1499
|
...projectPoint(
|
|
1112
1500
|
node,
|
|
@@ -1114,7 +1502,8 @@ function Viewer({
|
|
|
1114
1502
|
size,
|
|
1115
1503
|
overview ? 0 : shownStrength
|
|
1116
1504
|
),
|
|
1117
|
-
node
|
|
1505
|
+
node,
|
|
1506
|
+
baseRadius: node.baseRadius
|
|
1118
1507
|
}));
|
|
1119
1508
|
const circles = overview ? projected : limitMagnification(projected, nodeRadius, shownStrength);
|
|
1120
1509
|
const positions = new Map(circles.map((point) => [point.node.key, point]));
|
|
@@ -1617,39 +2006,6 @@ function Viewer({
|
|
|
1617
2006
|
height: size.height,
|
|
1618
2007
|
children: [
|
|
1619
2008
|
/* @__PURE__ */ jsx7("title", { children: "Explicit thought connections" }),
|
|
1620
|
-
/* @__PURE__ */ jsxs6("defs", { children: [
|
|
1621
|
-
Object.entries(colours).map(([branch, colour]) => /* @__PURE__ */ jsx7(
|
|
1622
|
-
"marker",
|
|
1623
|
-
{
|
|
1624
|
-
id: `${scope}-arrow-${branch}`,
|
|
1625
|
-
viewBox: "0 0 10 10",
|
|
1626
|
-
refX: "8",
|
|
1627
|
-
refY: "5",
|
|
1628
|
-
markerWidth: "7",
|
|
1629
|
-
markerHeight: "7",
|
|
1630
|
-
orient: "auto-start-reverse",
|
|
1631
|
-
children: /* @__PURE__ */ jsx7(
|
|
1632
|
-
"path",
|
|
1633
|
-
{
|
|
1634
|
-
d: "M 1 1 L 9 5 L 1 9",
|
|
1635
|
-
fill: "none",
|
|
1636
|
-
stroke: colour,
|
|
1637
|
-
strokeWidth: "1.5"
|
|
1638
|
-
}
|
|
1639
|
-
)
|
|
1640
|
-
},
|
|
1641
|
-
branch
|
|
1642
|
-
)),
|
|
1643
|
-
Object.entries(colours).map(([branch, colour]) => /* @__PURE__ */ jsx7(
|
|
1644
|
-
BlockingMarker,
|
|
1645
|
-
{
|
|
1646
|
-
id: `${scope}-bar-${branch}`,
|
|
1647
|
-
colour
|
|
1648
|
-
},
|
|
1649
|
-
`bar-${branch}`
|
|
1650
|
-
)),
|
|
1651
|
-
/* @__PURE__ */ jsx7(BlockingMarker, { id: `${scope}-bar-gap`, colour: "black", gap: true })
|
|
1652
|
-
] }),
|
|
1653
2009
|
local && /* @__PURE__ */ jsx7(
|
|
1654
2010
|
NeighbourhoodContinuations,
|
|
1655
2011
|
{
|
|
@@ -1659,23 +2015,32 @@ function Viewer({
|
|
|
1659
2015
|
nodes,
|
|
1660
2016
|
nodeRadius,
|
|
1661
2017
|
previewEdges,
|
|
1662
|
-
|
|
2018
|
+
widths
|
|
1663
2019
|
}
|
|
1664
2020
|
),
|
|
1665
2021
|
drawnEdges.map((edge) => {
|
|
1666
|
-
const
|
|
2022
|
+
const presentation = edge.presentation ?? fallbackPresentation(edge.type);
|
|
2023
|
+
const width = widths.get(edge.key) ?? 2.5;
|
|
2024
|
+
const startClearance = markerClearance(
|
|
2025
|
+
presentation.startMarker,
|
|
2026
|
+
width
|
|
2027
|
+
);
|
|
2028
|
+
const endClearance = markerClearance(
|
|
2029
|
+
presentation.endMarker,
|
|
2030
|
+
width
|
|
2031
|
+
);
|
|
1667
2032
|
const from = positions.get(edge.from);
|
|
1668
2033
|
const to = positions.get(edge.to);
|
|
1669
2034
|
if (!from || !to) return null;
|
|
1670
|
-
const description = `${label(from.node)} ${connectionWording4(edge.type, "outgoing")} ${label(to.node)}`;
|
|
2035
|
+
const description = `${label(from.node)} ${connectionWording4(edge.type, "outgoing", edge.presentation)} ${label(to.node)}`;
|
|
1671
2036
|
const dx = to.x - from.x;
|
|
1672
2037
|
const dy = to.y - from.y;
|
|
1673
2038
|
const distance = Math.max(1, Math.hypot(dx, dy));
|
|
1674
2039
|
const start = {
|
|
1675
|
-
x: from.x + dx / distance * nodeRadius * from.scale,
|
|
1676
|
-
y: from.y + dy / distance * nodeRadius * from.scale
|
|
2040
|
+
x: from.x + dx / distance * ((from.node.baseRadius ?? nodeRadius) * from.scale + startClearance),
|
|
2041
|
+
y: from.y + dy / distance * ((from.node.baseRadius ?? nodeRadius) * from.scale + startClearance)
|
|
1677
2042
|
};
|
|
1678
|
-
const targetRadius = nodeRadius * to.scale +
|
|
2043
|
+
const targetRadius = (to.node.baseRadius ?? nodeRadius) * to.scale + endClearance;
|
|
1679
2044
|
const end = {
|
|
1680
2045
|
x: to.x - dx / distance * targetRadius,
|
|
1681
2046
|
y: to.y - dy / distance * targetRadius
|
|
@@ -1687,18 +2052,18 @@ function Viewer({
|
|
|
1687
2052
|
const side = (((start.x + end.x) / 2 - centre.x) * -dy + ((start.y + end.y) / 2 - centre.y) * dx) / distance;
|
|
1688
2053
|
bend = (side < 0 ? -1 : 1) * (190 + (lane?.index ?? 0) * 60);
|
|
1689
2054
|
}
|
|
1690
|
-
const radius2 = nodeRadius * from.scale;
|
|
2055
|
+
const radius2 = (from.node.baseRadius ?? nodeRadius) * from.scale;
|
|
1691
2056
|
const loop = radius2 * (3 + (lane?.index ?? 0));
|
|
1692
2057
|
const points = edge.from === edge.to ? [
|
|
1693
2058
|
{
|
|
1694
|
-
x: from.x + radius2 * 0.7,
|
|
1695
|
-
y: from.y - radius2 * 0.7
|
|
2059
|
+
x: from.x + (radius2 + startClearance) * 0.7,
|
|
2060
|
+
y: from.y - (radius2 + startClearance) * 0.7
|
|
1696
2061
|
},
|
|
1697
2062
|
{ x: from.x + loop, y: from.y - loop },
|
|
1698
2063
|
{ x: from.x - loop, y: from.y - loop },
|
|
1699
2064
|
{
|
|
1700
|
-
x: from.x - radius2 * 0.7,
|
|
1701
|
-
y: from.y - radius2 * 0.7
|
|
2065
|
+
x: from.x - (radius2 + endClearance) * 0.7,
|
|
2066
|
+
y: from.y - (radius2 + endClearance) * 0.7
|
|
1702
2067
|
}
|
|
1703
2068
|
] : [
|
|
1704
2069
|
start,
|
|
@@ -1710,37 +2075,25 @@ function Viewer({
|
|
|
1710
2075
|
];
|
|
1711
2076
|
const path = points.length === 4 ? `M ${points[0].x} ${points[0].y} C ${points[1].x} ${points[1].y} ${points[2].x} ${points[2].y} ${points[3].x} ${points[3].y}` : `M ${points[0].x} ${points[0].y} Q ${points[1].x} ${points[1].y} ${points[2].x} ${points[2].y}`;
|
|
1712
2077
|
const edgeClass = `edge ${edge.kind} ${connectedEdges.has(edge.key) ? "connected" : local ? "context" : ""} ${previewEdges.has(edge.key) ? "connection-preview" : ""}`;
|
|
1713
|
-
const painted = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1714
|
-
/* @__PURE__ */ jsx7("path", { d: path, className: "edge-halo" }),
|
|
1715
|
-
/* @__PURE__ */ jsx7(
|
|
1716
|
-
"path",
|
|
1717
|
-
{
|
|
1718
|
-
"data-edge": `${edge.from}-${edge.to}`,
|
|
1719
|
-
"data-edge-key": edge.key,
|
|
1720
|
-
"data-connection-preview": previewEdges.has(edge.key) ? "true" : void 0,
|
|
1721
|
-
"data-kind": edge.kind,
|
|
1722
|
-
"data-type": edge.type ?? "connected",
|
|
1723
|
-
strokeDasharray: appearance.dashed ? "7 6" : void 0,
|
|
1724
|
-
d: path,
|
|
1725
|
-
stroke: colours[to.node.branch],
|
|
1726
|
-
style: { color: colours[to.node.branch] },
|
|
1727
|
-
className: edgeClass,
|
|
1728
|
-
markerStart: appearance.start ? `url(#${scope}-${appearance.start}-${to.node.branch})` : void 0,
|
|
1729
|
-
markerEnd: appearance.end ? `url(#${scope}-${appearance.end}-${to.node.branch})` : void 0
|
|
1730
|
-
}
|
|
1731
|
-
)
|
|
1732
|
-
] });
|
|
1733
2078
|
return /* @__PURE__ */ jsxs6("g", { children: [
|
|
1734
|
-
|
|
1735
|
-
|
|
2079
|
+
/* @__PURE__ */ jsx7(
|
|
2080
|
+
StyledConnection,
|
|
1736
2081
|
{
|
|
1737
2082
|
path,
|
|
1738
2083
|
points,
|
|
2084
|
+
presentation,
|
|
2085
|
+
width,
|
|
2086
|
+
colour: colours[to.node.branch],
|
|
1739
2087
|
className: edgeClass,
|
|
1740
|
-
|
|
1741
|
-
|
|
2088
|
+
attributes: {
|
|
2089
|
+
"data-edge": `${edge.from}-${edge.to}`,
|
|
2090
|
+
"data-edge-key": edge.key,
|
|
2091
|
+
"data-kind": edge.kind,
|
|
2092
|
+
"data-type": edge.type ?? "connected",
|
|
2093
|
+
"data-connection-preview": previewEdges.has(edge.key) ? "true" : void 0
|
|
2094
|
+
}
|
|
1742
2095
|
}
|
|
1743
|
-
)
|
|
2096
|
+
),
|
|
1744
2097
|
/* @__PURE__ */ jsx7(
|
|
1745
2098
|
"path",
|
|
1746
2099
|
{
|
|
@@ -1762,6 +2115,15 @@ function Viewer({
|
|
|
1762
2115
|
"button",
|
|
1763
2116
|
{
|
|
1764
2117
|
type: "button",
|
|
2118
|
+
ref: (element) => {
|
|
2119
|
+
if (element)
|
|
2120
|
+
applyNodeStyle(
|
|
2121
|
+
element,
|
|
2122
|
+
node.style,
|
|
2123
|
+
colours[node.branch]
|
|
2124
|
+
);
|
|
2125
|
+
},
|
|
2126
|
+
"data-auto-size": node.style?.scale === "auto" ? "true" : void 0,
|
|
1765
2127
|
id: `${scope}-thought-${node.key}`,
|
|
1766
2128
|
"data-anchor": anchors.get(node.key),
|
|
1767
2129
|
className: `node ${selected === node.key ? "selected" : ""} ${connectedNodes.has(node.key) ? "connected" : ""} ${node.unresolved ? "unresolved" : ""}`,
|
|
@@ -1772,6 +2134,8 @@ function Viewer({
|
|
|
1772
2134
|
"aria-pressed": selected === node.key,
|
|
1773
2135
|
title: label(node),
|
|
1774
2136
|
style: {
|
|
2137
|
+
width: 2 * (node.baseRadius ?? nodeRadius),
|
|
2138
|
+
height: 2 * (node.baseRadius ?? nodeRadius),
|
|
1775
2139
|
left: point.x,
|
|
1776
2140
|
top: point.y,
|
|
1777
2141
|
transform: `translate(-50%, -50%) scale(${point.scale})`,
|
|
@@ -1787,6 +2151,7 @@ function Viewer({
|
|
|
1787
2151
|
else focusNode(node.key);
|
|
1788
2152
|
},
|
|
1789
2153
|
children: [
|
|
2154
|
+
node.style?.icon && /* @__PURE__ */ jsx7("span", { className: "node-icon", "aria-hidden": "true", children: node.style.icon }),
|
|
1790
2155
|
node.destination && /* @__PURE__ */ jsx7(
|
|
1791
2156
|
"span",
|
|
1792
2157
|
{
|
|
@@ -1822,7 +2187,7 @@ function Viewer({
|
|
|
1822
2187
|
"aria-hidden": "true"
|
|
1823
2188
|
}
|
|
1824
2189
|
),
|
|
1825
|
-
/* @__PURE__ */ jsx7("span", { children: node.unresolved ? "?" : /* @__PURE__ */ jsx7(
|
|
2190
|
+
/* @__PURE__ */ jsx7("span", { className: "node-summary", children: node.unresolved ? "?" : /* @__PURE__ */ jsx7(
|
|
1826
2191
|
Markdown2,
|
|
1827
2192
|
{
|
|
1828
2193
|
allowedElements: [
|
|
@@ -1950,7 +2315,7 @@ function Viewer({
|
|
|
1950
2315
|
}
|
|
1951
2316
|
|
|
1952
2317
|
// src/SelectedThought.tsx
|
|
1953
|
-
import { useId as
|
|
2318
|
+
import { useId as useId3, useMemo as useMemo2, useState as useState3 } from "react";
|
|
1954
2319
|
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1955
2320
|
function readingExcerpt(html) {
|
|
1956
2321
|
const template = document.createElement("template");
|
|
@@ -1968,7 +2333,7 @@ function readingExcerpt(html) {
|
|
|
1968
2333
|
function SelectedThought({ html }) {
|
|
1969
2334
|
const excerpt = useMemo2(() => readingExcerpt(html), [html]);
|
|
1970
2335
|
const [expanded, setExpanded] = useState3(false);
|
|
1971
|
-
const fullId =
|
|
2336
|
+
const fullId = useId3();
|
|
1972
2337
|
if (!excerpt) {
|
|
1973
2338
|
return /* @__PURE__ */ jsx8(
|
|
1974
2339
|
"div",
|
|
@@ -2062,8 +2427,32 @@ function ViewerHost({
|
|
|
2062
2427
|
graph,
|
|
2063
2428
|
rendered,
|
|
2064
2429
|
inspectorId = "default",
|
|
2065
|
-
getLabel
|
|
2430
|
+
getLabel,
|
|
2431
|
+
mapId
|
|
2066
2432
|
}) {
|
|
2433
|
+
const [liveGraph, setLiveGraph] = useState4(graph);
|
|
2434
|
+
const [stale, setStale] = useState4(false);
|
|
2435
|
+
useEffect5(() => setLiveGraph(graph), [graph]);
|
|
2436
|
+
useEffect5(() => {
|
|
2437
|
+
const hot = import.meta.hot;
|
|
2438
|
+
if (!hot || !mapId) return;
|
|
2439
|
+
const update = (data) => {
|
|
2440
|
+
if (data.id !== mapId) return;
|
|
2441
|
+
setStale(data.stale);
|
|
2442
|
+
const edges = data.edges;
|
|
2443
|
+
if (!data.stale && edges)
|
|
2444
|
+
setLiveGraph((previous) => ({
|
|
2445
|
+
...previous,
|
|
2446
|
+
edges,
|
|
2447
|
+
nodes: previous.nodes.map((node) => ({
|
|
2448
|
+
...node,
|
|
2449
|
+
destination: data.nodes?.find((update2) => update2.key === node.key)?.destination
|
|
2450
|
+
}))
|
|
2451
|
+
}));
|
|
2452
|
+
};
|
|
2453
|
+
hot.on("lensmap:vocabulary", update);
|
|
2454
|
+
return () => hot.off("lensmap:vocabulary", update);
|
|
2455
|
+
}, [mapId]);
|
|
2067
2456
|
const [targets, setTargets] = useState4([]);
|
|
2068
2457
|
useEffect5(() => {
|
|
2069
2458
|
setTargets(
|
|
@@ -2076,28 +2465,31 @@ function ViewerHost({
|
|
|
2076
2465
|
)
|
|
2077
2466
|
);
|
|
2078
2467
|
}, [inspectorId]);
|
|
2079
|
-
return /* @__PURE__ */
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2468
|
+
return /* @__PURE__ */ jsxs8("div", { "data-lensmap-stale": String(stale), children: [
|
|
2469
|
+
stale && /* @__PURE__ */ jsx9("p", { role: "status", children: "Connection vocabulary is invalid. Showing the last valid map." }),
|
|
2470
|
+
/* @__PURE__ */ jsx9(
|
|
2471
|
+
Viewer,
|
|
2472
|
+
{
|
|
2473
|
+
graph: liveGraph,
|
|
2474
|
+
embedded: true,
|
|
2475
|
+
showInspector: false,
|
|
2476
|
+
renderExternalInspector: (context) => targets.map(
|
|
2477
|
+
(target, index) => createPortal(
|
|
2478
|
+
/* @__PURE__ */ jsx9(
|
|
2479
|
+
Inspector,
|
|
2480
|
+
{
|
|
2481
|
+
context,
|
|
2482
|
+
rendered,
|
|
2483
|
+
getLabel
|
|
2484
|
+
}
|
|
2485
|
+
),
|
|
2486
|
+
target,
|
|
2487
|
+
String(index)
|
|
2488
|
+
)
|
|
2097
2489
|
)
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
);
|
|
2490
|
+
}
|
|
2491
|
+
)
|
|
2492
|
+
] });
|
|
2101
2493
|
}
|
|
2102
2494
|
export {
|
|
2103
2495
|
ViewerHost as default
|