@lensmap/starlight 0.2.1 → 0.4.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 +867 -278
- package/dist/components/MapContent.astro +2 -1
- package/dist/index.js +16 -1
- package/dist/layout.js +56 -19
- package/package.json +14 -14
package/dist/ViewerHost.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/ViewerHost.tsx
|
|
2
|
-
import { useEffect as useEffect5, useState as
|
|
2
|
+
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
|
|
5
5
|
// ../../apps/web/src/document/layout.ts
|
|
@@ -10,8 +10,182 @@ import {
|
|
|
10
10
|
forceY
|
|
11
11
|
} from "d3-force";
|
|
12
12
|
|
|
13
|
+
// ../../apps/web/src/node-style.ts
|
|
14
|
+
function positionAxis(node, axis) {
|
|
15
|
+
const value = node.style?.[axis];
|
|
16
|
+
return value === "auto" ? void 0 : value ?? node.position?.[axis];
|
|
17
|
+
}
|
|
18
|
+
function baseRadius(style) {
|
|
19
|
+
return 43 * (typeof style?.scale === "number" ? style.scale : 1);
|
|
20
|
+
}
|
|
21
|
+
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))?)$/;
|
|
22
|
+
var applied = /* @__PURE__ */ new WeakMap();
|
|
23
|
+
function applyNodeStyle(element, style, borderColour) {
|
|
24
|
+
for (const property of applied.get(element) ?? [])
|
|
25
|
+
element.style.removeProperty(property);
|
|
26
|
+
if (borderColour) element.style.borderColor = borderColour;
|
|
27
|
+
applied.set(
|
|
28
|
+
element,
|
|
29
|
+
(style?.declarations ?? []).map((d) => d.property).filter((p) => properties.test(p))
|
|
30
|
+
);
|
|
31
|
+
for (const declaration of style?.declarations ?? []) {
|
|
32
|
+
if (properties.test(declaration.property))
|
|
33
|
+
element.style.setProperty(
|
|
34
|
+
declaration.property,
|
|
35
|
+
declaration.value,
|
|
36
|
+
declaration.important ? "important" : ""
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
13
41
|
// ../../apps/web/src/document/subtree.ts
|
|
14
42
|
import { polygonHull } from "d3-polygon";
|
|
43
|
+
function silhouette(contour, distance, padding) {
|
|
44
|
+
let low = Infinity;
|
|
45
|
+
let high = -Infinity;
|
|
46
|
+
for (const [x, y] of contour) {
|
|
47
|
+
const theta = Math.atan2(y, distance + x);
|
|
48
|
+
const half = Math.asin(padding / Math.hypot(distance + x, y));
|
|
49
|
+
low = Math.min(low, theta - half);
|
|
50
|
+
high = Math.max(high, theta + half);
|
|
51
|
+
}
|
|
52
|
+
return { low, high };
|
|
53
|
+
}
|
|
54
|
+
function planSubtree(key, children, fullCircle, step, padding) {
|
|
55
|
+
if (children.length === 0) return { key, contour: [[0, 0]], children: [] };
|
|
56
|
+
const atDistance = (distance2) => children.map((child) => silhouette(child.contour, distance2, padding));
|
|
57
|
+
const width = (bounds2) => bounds2.reduce((sum, bound) => sum + bound.high - bound.low, 0);
|
|
58
|
+
let distance = step;
|
|
59
|
+
let bounds = atDistance(distance);
|
|
60
|
+
const span = fullCircle ? Math.PI * 2 : Math.min(Math.PI, Math.max(Math.PI * 2 / 3, width(bounds)));
|
|
61
|
+
if (width(bounds) > span) {
|
|
62
|
+
let lower = distance;
|
|
63
|
+
while (width(atDistance(distance)) > span) distance *= 2;
|
|
64
|
+
while (distance - lower > 0.5) {
|
|
65
|
+
const middle = (lower + distance) / 2;
|
|
66
|
+
if (width(atDistance(middle)) > span) lower = middle;
|
|
67
|
+
else distance = middle;
|
|
68
|
+
}
|
|
69
|
+
bounds = atDistance(distance);
|
|
70
|
+
}
|
|
71
|
+
const required = width(bounds);
|
|
72
|
+
const gap = fullCircle ? (span - required) / children.length : children.length > 1 ? (span - required) / (children.length - 1) : 0;
|
|
73
|
+
let cursor = fullCircle ? -Math.PI / 2 + bounds[0].low : children.length === 1 ? -required / 2 : -span / 2;
|
|
74
|
+
const contour = [[0, 0]];
|
|
75
|
+
const placements = children.map((subtree, index) => {
|
|
76
|
+
const bound = bounds[index];
|
|
77
|
+
const angle = cursor - bound.low;
|
|
78
|
+
cursor += bound.high - bound.low + gap;
|
|
79
|
+
const cos = Math.cos(angle), sin = Math.sin(angle);
|
|
80
|
+
for (const [x, y] of subtree.contour) {
|
|
81
|
+
contour.push([
|
|
82
|
+
(distance + x) * cos - y * sin,
|
|
83
|
+
(distance + x) * sin + y * cos
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
return { subtree, angle, distance };
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
key,
|
|
90
|
+
contour: polygonHull(contour) ?? contour,
|
|
91
|
+
children: placements
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ../../apps/web/src/document/layout.ts
|
|
96
|
+
function layoutGraph(nodes, edges) {
|
|
97
|
+
if (nodes.length === 0) return { nodes: [], edges };
|
|
98
|
+
const byKey2 = new Map(nodes.map((node) => [node.key, node]));
|
|
99
|
+
const children = new Map(nodes.map((node) => [node.key, []]));
|
|
100
|
+
const childKeys = /* @__PURE__ */ new Set();
|
|
101
|
+
for (const edge of edges) {
|
|
102
|
+
if (edge.kind !== "branch") continue;
|
|
103
|
+
const child = byKey2.get(edge.to);
|
|
104
|
+
if (child) children.get(edge.from)?.push(child);
|
|
105
|
+
childKeys.add(edge.to);
|
|
106
|
+
}
|
|
107
|
+
const roots = nodes.filter((node) => !childKeys.has(node.key));
|
|
108
|
+
const placed = /* @__PURE__ */ new Map();
|
|
109
|
+
const maximum = Math.max(43, ...nodes.map((n) => n.baseRadius ?? 43));
|
|
110
|
+
const step = Math.max(180, maximum * 2 + 24);
|
|
111
|
+
const padding = Math.max(56, maximum + 13);
|
|
112
|
+
function plan(node, isRoot) {
|
|
113
|
+
const descendants = (children.get(node.key) ?? []).map(
|
|
114
|
+
(child) => plan(child, false)
|
|
115
|
+
);
|
|
116
|
+
return planSubtree(node.key, descendants, isRoot, step, padding);
|
|
117
|
+
}
|
|
118
|
+
const plans = roots.map((root) => plan(root, true));
|
|
119
|
+
function seed(node, subtree, point, heading, isRoot) {
|
|
120
|
+
const pinX = positionAxis(node, "x");
|
|
121
|
+
const pinY = positionAxis(node, "y");
|
|
122
|
+
const actual = { x: pinX ?? point.x, y: pinY ?? point.y };
|
|
123
|
+
placed.set(node.key, {
|
|
124
|
+
key: node.key,
|
|
125
|
+
x: actual.x,
|
|
126
|
+
y: actual.y,
|
|
127
|
+
targetX: actual.x,
|
|
128
|
+
targetY: actual.y,
|
|
129
|
+
fx: pinX !== void 0 || isRoot ? actual.x : void 0,
|
|
130
|
+
fy: pinY !== void 0 || isRoot ? actual.y : void 0
|
|
131
|
+
});
|
|
132
|
+
subtree.children.forEach((placement) => {
|
|
133
|
+
const child = byKey2.get(placement.subtree.key);
|
|
134
|
+
if (!child)
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Automatic layout could not find child "${placement.subtree.key}".`
|
|
137
|
+
);
|
|
138
|
+
const angle = heading + placement.angle;
|
|
139
|
+
const target = {
|
|
140
|
+
x: actual.x + Math.cos(angle) * placement.distance,
|
|
141
|
+
y: actual.y + Math.sin(angle) * placement.distance
|
|
142
|
+
};
|
|
143
|
+
const childPoint = {
|
|
144
|
+
x: positionAxis(child, "x") ?? target.x,
|
|
145
|
+
y: positionAxis(child, "y") ?? target.y
|
|
146
|
+
};
|
|
147
|
+
const direction = childPoint.x === actual.x && childPoint.y === actual.y ? angle : Math.atan2(childPoint.y - actual.y, childPoint.x - actual.x);
|
|
148
|
+
seed(child, placement.subtree, target, direction, false);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const anchor = {
|
|
152
|
+
x: positionAxis(nodes[0], "x") ?? 0,
|
|
153
|
+
y: positionAxis(nodes[0], "y") ?? 0
|
|
154
|
+
};
|
|
155
|
+
const rootRadius = roots.length > 1 ? step * 2 / Math.sin(Math.PI / roots.length) : 0;
|
|
156
|
+
roots.forEach((root, index) => {
|
|
157
|
+
const angle = -Math.PI / 2 + Math.PI * 2 * index / roots.length;
|
|
158
|
+
seed(
|
|
159
|
+
root,
|
|
160
|
+
plans[index],
|
|
161
|
+
{
|
|
162
|
+
x: anchor.x + Math.cos(angle) * rootRadius,
|
|
163
|
+
y: anchor.y + rootRadius + Math.sin(angle) * rootRadius
|
|
164
|
+
},
|
|
165
|
+
0,
|
|
166
|
+
true
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
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));
|
|
170
|
+
const ticks = Math.ceil(
|
|
171
|
+
Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())
|
|
172
|
+
);
|
|
173
|
+
simulation.tick(ticks);
|
|
174
|
+
return {
|
|
175
|
+
nodes: nodes.map(({ position, ...node }) => {
|
|
176
|
+
const point = placed.get(node.key);
|
|
177
|
+
if (!point)
|
|
178
|
+
throw new Error(`Automatic layout could not place node "${node.key}".`);
|
|
179
|
+
return {
|
|
180
|
+
...node,
|
|
181
|
+
...position ? { position } : {},
|
|
182
|
+
x: point.x,
|
|
183
|
+
y: point.y
|
|
184
|
+
};
|
|
185
|
+
}),
|
|
186
|
+
edges
|
|
187
|
+
};
|
|
188
|
+
}
|
|
15
189
|
|
|
16
190
|
// ../../apps/web/src/Viewer.tsx
|
|
17
191
|
import {
|
|
@@ -23,11 +197,11 @@ import {
|
|
|
23
197
|
useCallback as useCallback2,
|
|
24
198
|
useEffect as useEffect4,
|
|
25
199
|
useEffectEvent as useEffectEvent2,
|
|
26
|
-
useId as
|
|
27
|
-
useLayoutEffect,
|
|
200
|
+
useId as useId2,
|
|
201
|
+
useLayoutEffect as useLayoutEffect2,
|
|
28
202
|
useMemo,
|
|
29
|
-
useRef as
|
|
30
|
-
useState as
|
|
203
|
+
useRef as useRef5,
|
|
204
|
+
useState as useState3
|
|
31
205
|
} from "react";
|
|
32
206
|
import Markdown2 from "react-markdown";
|
|
33
207
|
|
|
@@ -93,7 +267,11 @@ function ConnectionChooser({
|
|
|
93
267
|
/* @__PURE__ */ jsxs("span", { id: `connection-details-${candidate.node.key}`, children: [
|
|
94
268
|
candidate.routes.map((route, routeIndex) => /* @__PURE__ */ jsxs("span", { children: [
|
|
95
269
|
routeIndex > 0 ? " \xB7 " : "",
|
|
96
|
-
connectionWording(
|
|
270
|
+
connectionWording(
|
|
271
|
+
route.edge.type,
|
|
272
|
+
route.direction,
|
|
273
|
+
route.edge.presentation
|
|
274
|
+
),
|
|
97
275
|
` (${route.direction} ${route.edge.kind === "branch" ? "branch" : "cross-link"})`
|
|
98
276
|
] }, `${route.edge.key}:${route.direction}`)),
|
|
99
277
|
!visibleKeys.has(candidate.node.key) && /* @__PURE__ */ jsx("span", { className: "outside-view", children: "outside this view" })
|
|
@@ -134,69 +312,197 @@ function ConnectionChooser({
|
|
|
134
312
|
// ../../apps/web/src/ConnectionGap.tsx
|
|
135
313
|
import { useId } from "react";
|
|
136
314
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
137
|
-
function
|
|
138
|
-
|
|
315
|
+
function Shape({
|
|
316
|
+
kind,
|
|
139
317
|
colour,
|
|
140
|
-
|
|
318
|
+
cut = false
|
|
141
319
|
}) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
320
|
+
if (kind === "circle")
|
|
321
|
+
return /* @__PURE__ */ jsx2(
|
|
322
|
+
"circle",
|
|
323
|
+
{
|
|
324
|
+
cx: "-3",
|
|
325
|
+
cy: "0",
|
|
326
|
+
r: cut ? 2.5 : 3,
|
|
327
|
+
fill: cut ? "black" : "none",
|
|
328
|
+
stroke: cut ? "black" : colour,
|
|
329
|
+
strokeWidth: cut ? 0 : 1
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
if (kind === "diamond")
|
|
333
|
+
return /* @__PURE__ */ jsx2(
|
|
334
|
+
"path",
|
|
335
|
+
{
|
|
336
|
+
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",
|
|
337
|
+
fill: cut ? "black" : "none",
|
|
338
|
+
stroke: cut ? "black" : colour,
|
|
339
|
+
strokeWidth: cut ? 0 : 1
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
if (kind === "bar")
|
|
343
|
+
return cut ? /* @__PURE__ */ jsx2("rect", { x: "-4.5", y: "-5", width: "3", height: "10", fill: "black" }) : /* @__PURE__ */ jsxs2("g", { stroke: colour, strokeWidth: "1", children: [
|
|
344
|
+
/* @__PURE__ */ jsx2("line", { x1: "-5", x2: "-5", y1: "-3", y2: "3" }),
|
|
345
|
+
/* @__PURE__ */ jsx2("line", { x1: "-1", x2: "-1", y1: "-3", y2: "3" })
|
|
346
|
+
] });
|
|
347
|
+
if (kind === "arrow" && !cut)
|
|
348
|
+
return /* @__PURE__ */ jsx2(
|
|
349
|
+
"path",
|
|
350
|
+
{
|
|
351
|
+
d: "M -5 -3 L 0 0 L -5 3",
|
|
352
|
+
fill: "none",
|
|
353
|
+
stroke: colour,
|
|
354
|
+
strokeWidth: "1"
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
return null;
|
|
159
358
|
}
|
|
160
|
-
function
|
|
359
|
+
function StyledConnection({
|
|
161
360
|
path,
|
|
162
361
|
points,
|
|
362
|
+
presentation,
|
|
363
|
+
width,
|
|
364
|
+
colour,
|
|
163
365
|
className,
|
|
164
|
-
|
|
165
|
-
|
|
366
|
+
attributes,
|
|
367
|
+
continuation
|
|
166
368
|
}) {
|
|
167
369
|
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
|
-
|
|
370
|
+
const start = continuation === "incoming" ? "none" : presentation.startMarker;
|
|
371
|
+
const end = continuation === "outgoing" ? "none" : presentation.endMarker;
|
|
372
|
+
const paint = presentation.lineColor === "auto" ? colour : presentation.lineColor;
|
|
373
|
+
const marker = (kind, side, cut = false) => /* @__PURE__ */ jsx2(
|
|
374
|
+
"marker",
|
|
375
|
+
{
|
|
376
|
+
id: `${id}-${kind}-${side}`,
|
|
377
|
+
markerUnits: "strokeWidth",
|
|
378
|
+
markerWidth: 14,
|
|
379
|
+
markerHeight: 14,
|
|
380
|
+
viewBox: "-7 -7 14 14",
|
|
381
|
+
refX: "0",
|
|
382
|
+
refY: "0",
|
|
383
|
+
orient: "auto-start-reverse",
|
|
384
|
+
overflow: "visible",
|
|
385
|
+
children: /* @__PURE__ */ jsx2(Shape, { kind, colour: paint, cut })
|
|
386
|
+
},
|
|
387
|
+
side
|
|
388
|
+
);
|
|
389
|
+
const pad = Math.max(64, width * 16);
|
|
390
|
+
const x = Math.min(...points.map((p) => p.x)) - pad;
|
|
391
|
+
const y = Math.min(...points.map((p) => p.y)) - pad;
|
|
392
|
+
const w = Math.max(...points.map((p) => p.x)) - x + pad;
|
|
393
|
+
const h = Math.max(...points.map((p) => p.y)) - y + pad;
|
|
394
|
+
const style = { strokeWidth: width, color: paint };
|
|
395
|
+
return /* @__PURE__ */ jsxs2(
|
|
396
|
+
"g",
|
|
397
|
+
{
|
|
398
|
+
"data-presentation-width": width,
|
|
399
|
+
mask: continuation ? `url(#${id}-fade)` : void 0,
|
|
400
|
+
children: [
|
|
401
|
+
/* @__PURE__ */ jsxs2("defs", { children: [
|
|
402
|
+
continuation && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
403
|
+
/* @__PURE__ */ jsxs2(
|
|
404
|
+
"linearGradient",
|
|
405
|
+
{
|
|
406
|
+
id: `${id}-gradient`,
|
|
407
|
+
gradientUnits: "userSpaceOnUse",
|
|
408
|
+
x1: points[0].x,
|
|
409
|
+
y1: points[0].y,
|
|
410
|
+
x2: points[1].x,
|
|
411
|
+
y2: points[1].y,
|
|
412
|
+
children: [
|
|
413
|
+
/* @__PURE__ */ jsx2("stop", { offset: "0%", stopColor: "white", stopOpacity: 0.65 }),
|
|
414
|
+
/* @__PURE__ */ jsx2("stop", { offset: "100%", stopColor: "white", stopOpacity: 0 })
|
|
415
|
+
]
|
|
416
|
+
}
|
|
417
|
+
),
|
|
418
|
+
/* @__PURE__ */ jsx2(
|
|
419
|
+
"mask",
|
|
420
|
+
{
|
|
421
|
+
id: `${id}-fade`,
|
|
422
|
+
maskUnits: "userSpaceOnUse",
|
|
423
|
+
x,
|
|
424
|
+
y,
|
|
425
|
+
width: w,
|
|
426
|
+
height: h,
|
|
427
|
+
style: { maskType: "luminance" },
|
|
428
|
+
children: /* @__PURE__ */ jsx2(
|
|
429
|
+
"rect",
|
|
430
|
+
{
|
|
431
|
+
x,
|
|
432
|
+
y,
|
|
433
|
+
width: w,
|
|
434
|
+
height: h,
|
|
435
|
+
fill: `url(#${id}-gradient)`
|
|
436
|
+
}
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
] }),
|
|
441
|
+
marker(start, "start"),
|
|
442
|
+
marker(end, "end"),
|
|
443
|
+
marker(start, "start-cut", true),
|
|
444
|
+
marker(end, "end-cut", true),
|
|
445
|
+
/* @__PURE__ */ jsxs2(
|
|
446
|
+
"mask",
|
|
188
447
|
{
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
448
|
+
id: `${id}-mask`,
|
|
449
|
+
maskUnits: "userSpaceOnUse",
|
|
450
|
+
x,
|
|
451
|
+
y,
|
|
452
|
+
width: w,
|
|
453
|
+
height: h,
|
|
454
|
+
style: { maskType: "luminance" },
|
|
455
|
+
children: [
|
|
456
|
+
/* @__PURE__ */ jsx2("rect", { x, y, width: w, height: h, fill: "white" }),
|
|
457
|
+
/* @__PURE__ */ jsx2(
|
|
458
|
+
"path",
|
|
459
|
+
{
|
|
460
|
+
d: path,
|
|
461
|
+
fill: "none",
|
|
462
|
+
stroke: "none",
|
|
463
|
+
strokeWidth: width,
|
|
464
|
+
markerStart: `url(#${id}-${start}-start-cut)`,
|
|
465
|
+
markerEnd: `url(#${id}-${end}-end-cut)`
|
|
466
|
+
}
|
|
467
|
+
)
|
|
468
|
+
]
|
|
193
469
|
}
|
|
194
470
|
)
|
|
195
|
-
]
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
471
|
+
] }),
|
|
472
|
+
/* @__PURE__ */ jsxs2(
|
|
473
|
+
"g",
|
|
474
|
+
{
|
|
475
|
+
visibility: width === 0 ? "hidden" : void 0,
|
|
476
|
+
mask: `url(#${id}-mask)`,
|
|
477
|
+
children: [
|
|
478
|
+
/* @__PURE__ */ jsx2(
|
|
479
|
+
"path",
|
|
480
|
+
{
|
|
481
|
+
d: path,
|
|
482
|
+
className: "edge-halo",
|
|
483
|
+
style: { strokeWidth: width * 3.2 }
|
|
484
|
+
}
|
|
485
|
+
),
|
|
486
|
+
/* @__PURE__ */ jsx2(
|
|
487
|
+
"path",
|
|
488
|
+
{
|
|
489
|
+
...attributes,
|
|
490
|
+
d: path,
|
|
491
|
+
className,
|
|
492
|
+
stroke: paint,
|
|
493
|
+
style,
|
|
494
|
+
markerStart: start === "none" ? void 0 : `url(#${id}-${start}-start)`,
|
|
495
|
+
markerEnd: end === "none" ? void 0 : `url(#${id}-${end}-end)`,
|
|
496
|
+
strokeLinecap: presentation.lineStyle === "dotted" ? "round" : void 0,
|
|
497
|
+
strokeDasharray: presentation.lineStyle === "dotted" ? `0 ${width * 2.5}` : presentation.lineStyle === "dashed" ? `${width * 2.8} ${width * 2.4}` : void 0
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
]
|
|
501
|
+
}
|
|
502
|
+
)
|
|
503
|
+
]
|
|
504
|
+
}
|
|
505
|
+
);
|
|
200
506
|
}
|
|
201
507
|
|
|
202
508
|
// ../../apps/web/src/clearance.ts
|
|
@@ -204,7 +510,9 @@ function limitMagnification(points, nodeRadius2, strength) {
|
|
|
204
510
|
const amount = Math.max(0, Math.min(1, strength));
|
|
205
511
|
if (amount === 0 || points.length < 2)
|
|
206
512
|
return points.map((point) => ({ ...point }));
|
|
207
|
-
const radii = points.map(
|
|
513
|
+
const radii = points.map(
|
|
514
|
+
(point) => point.scale * (point.baseRadius ?? nodeRadius2)
|
|
515
|
+
);
|
|
208
516
|
const minimum = radii.map((radius2) => Math.min(radius2, 2));
|
|
209
517
|
const limited = [...radii];
|
|
210
518
|
for (let i = 0; i < points.length; i++) {
|
|
@@ -230,7 +538,7 @@ function limitMagnification(points, nodeRadius2, strength) {
|
|
|
230
538
|
}
|
|
231
539
|
return points.map((point, index) => ({
|
|
232
540
|
...point,
|
|
233
|
-
scale: point.scale + (limited[index] / nodeRadius2 - point.scale) * amount
|
|
541
|
+
scale: point.scale + (limited[index] / (point.baseRadius ?? nodeRadius2) - point.scale) * amount
|
|
234
542
|
}));
|
|
235
543
|
}
|
|
236
544
|
|
|
@@ -242,6 +550,39 @@ function connectionAppearance(type, continuation) {
|
|
|
242
550
|
...continuation !== "outgoing" && (type === "precedes" || type === "prevents") ? { end: type === "precedes" ? "arrow" : "bar" } : {}
|
|
243
551
|
};
|
|
244
552
|
}
|
|
553
|
+
function fallbackPresentation(type) {
|
|
554
|
+
const appearance = connectionAppearance(type);
|
|
555
|
+
return {
|
|
556
|
+
strokeWidth: 2.5,
|
|
557
|
+
lineStyle: appearance.dashed ? "dashed" : "solid",
|
|
558
|
+
lineColor: "auto",
|
|
559
|
+
startMarker: appearance.start ?? "none",
|
|
560
|
+
endMarker: appearance.end ?? "none",
|
|
561
|
+
text: "connected to",
|
|
562
|
+
reverseText: "connected to"
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// ../../apps/web/src/connection-style.ts
|
|
567
|
+
function markerClearance(marker, width) {
|
|
568
|
+
if (marker === "arrow") return width * Math.hypot(5, 3) / 6;
|
|
569
|
+
if (marker === "diamond") return width / Math.SQRT2;
|
|
570
|
+
return marker === "circle" ? width / 2 : 0;
|
|
571
|
+
}
|
|
572
|
+
function resolveConnectionWidth(value, root, width, height) {
|
|
573
|
+
if (typeof value === "number") return value;
|
|
574
|
+
const container = document.createElement("div");
|
|
575
|
+
container.style.cssText = "position:absolute;visibility:hidden;pointer-events:none;height:0;overflow:hidden";
|
|
576
|
+
container.style.width = `${Math.hypot(width, height) / Math.SQRT2}px`;
|
|
577
|
+
const probe = document.createElement("div");
|
|
578
|
+
probe.style.width = CSS.supports("width", value) ? value : `calc((${value}) * 1px)`;
|
|
579
|
+
probe.style.height = "0";
|
|
580
|
+
container.append(probe);
|
|
581
|
+
root.append(container);
|
|
582
|
+
const result = Number.parseFloat(getComputedStyle(probe).width);
|
|
583
|
+
container.remove();
|
|
584
|
+
return Number.isFinite(result) ? Math.max(0, result) : 0;
|
|
585
|
+
}
|
|
245
586
|
|
|
246
587
|
// ../../apps/web/src/edge-routing.ts
|
|
247
588
|
function edgeLanes(edges) {
|
|
@@ -296,10 +637,10 @@ function zoomCamera(camera, factor) {
|
|
|
296
637
|
}
|
|
297
638
|
function fitCamera(points, size, radius2) {
|
|
298
639
|
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
|
|
640
|
+
const minX = Math.min(...points.map((p) => p.x - (p.baseRadius ?? radius2)));
|
|
641
|
+
const maxX = Math.max(...points.map((p) => p.x + (p.baseRadius ?? radius2)));
|
|
642
|
+
const minY = Math.min(...points.map((p) => p.y - (p.baseRadius ?? radius2)));
|
|
643
|
+
const maxY = Math.max(...points.map((p) => p.y + (p.baseRadius ?? radius2)));
|
|
303
644
|
return {
|
|
304
645
|
x: (minX + maxX) / 2,
|
|
305
646
|
y: (minY + maxY) / 2,
|
|
@@ -316,7 +657,6 @@ function fitCamera(points, size, radius2) {
|
|
|
316
657
|
|
|
317
658
|
// ../../apps/web/src/NeighbourhoodContinuations.tsx
|
|
318
659
|
import { connectionWording as connectionWording2 } from "@lensmap/document";
|
|
319
|
-
import { useId as useId2 } from "react";
|
|
320
660
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
321
661
|
function NeighbourhoodContinuations({
|
|
322
662
|
continuations,
|
|
@@ -325,25 +665,25 @@ function NeighbourhoodContinuations({
|
|
|
325
665
|
nodes,
|
|
326
666
|
nodeRadius: nodeRadius2,
|
|
327
667
|
previewEdges,
|
|
328
|
-
|
|
668
|
+
widths
|
|
329
669
|
}) {
|
|
330
|
-
const prefix = useId2();
|
|
331
670
|
const origin = positions.get(centreKey);
|
|
332
671
|
const byKey2 = new Map(nodes.map((node) => [node.key, node]));
|
|
333
|
-
const segments = continuations.flatMap((continuation
|
|
672
|
+
const segments = continuations.flatMap((continuation) => {
|
|
334
673
|
const point = positions.get(continuation.at);
|
|
335
674
|
const source = byKey2.get(continuation.edge.from);
|
|
336
675
|
const target = byKey2.get(continuation.edge.to);
|
|
337
676
|
if (!point || !origin || !source || !target) return [];
|
|
338
677
|
const incoming = continuation.edge.to === continuation.at;
|
|
339
|
-
const appearance = connectionAppearance(
|
|
340
|
-
continuation.edge.type,
|
|
341
|
-
incoming ? "incoming" : "outgoing"
|
|
342
|
-
);
|
|
343
678
|
const angle = Math.atan2(point.y - origin.y, point.x - origin.x) + continuation.offset;
|
|
344
679
|
const ux = Math.cos(angle), uy = Math.sin(angle);
|
|
345
|
-
const
|
|
346
|
-
const
|
|
680
|
+
const width = widths.get(continuation.edge.key) ?? 2.5;
|
|
681
|
+
const presentation = continuation.edge.presentation ?? fallbackPresentation(continuation.edge.type);
|
|
682
|
+
const clearance = markerClearance(
|
|
683
|
+
incoming ? presentation.endMarker : presentation.startMarker,
|
|
684
|
+
width
|
|
685
|
+
);
|
|
686
|
+
const radius2 = (byKey2.get(continuation.at)?.baseRadius ?? nodeRadius2) * point.scale + clearance;
|
|
347
687
|
const near = { x: point.x + ux * radius2, y: point.y + uy * radius2 };
|
|
348
688
|
const far = {
|
|
349
689
|
x: near.x + ux * 70 * point.scale,
|
|
@@ -353,91 +693,49 @@ function NeighbourhoodContinuations({
|
|
|
353
693
|
return [
|
|
354
694
|
{
|
|
355
695
|
...continuation,
|
|
356
|
-
|
|
696
|
+
presentation,
|
|
357
697
|
incoming,
|
|
358
698
|
near,
|
|
359
699
|
far,
|
|
360
700
|
target,
|
|
361
|
-
description: `${label(source)} ${connectionWording2(continuation.edge.type, "outgoing")} ${label(target)}`,
|
|
362
|
-
id: `${prefix}-${index}`,
|
|
701
|
+
description: `${label(source)} ${connectionWording2(continuation.edge.type, "outgoing", continuation.edge.presentation)} ${label(target)}`,
|
|
363
702
|
path: `M ${from.x} ${from.y} L ${to.x} ${to.y}`
|
|
364
703
|
}
|
|
365
704
|
];
|
|
366
705
|
});
|
|
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(
|
|
706
|
+
return /* @__PURE__ */ jsx3("g", { className: "continuations", children: segments.map((segment) => {
|
|
707
|
+
const edgeClass = `edge continuation ${segment.edge.kind} ${previewEdges.has(segment.edge.key) ? "connection-preview" : ""}`;
|
|
708
|
+
return /* @__PURE__ */ jsxs3("g", { children: [
|
|
709
|
+
/* @__PURE__ */ jsx3(
|
|
710
|
+
StyledConnection,
|
|
711
|
+
{
|
|
712
|
+
path: segment.path,
|
|
713
|
+
points: [segment.near, segment.far],
|
|
714
|
+
presentation: segment.presentation,
|
|
715
|
+
width: widths.get(segment.edge.key) ?? 2.5,
|
|
716
|
+
colour: colours[segment.target.branch],
|
|
717
|
+
className: edgeClass,
|
|
718
|
+
continuation: segment.incoming ? "incoming" : "outgoing",
|
|
719
|
+
attributes: {
|
|
720
|
+
"data-continuation": segment.edge.key,
|
|
721
|
+
"data-connection-preview": previewEdges.has(segment.edge.key) ? "true" : void 0,
|
|
722
|
+
"data-kind": segment.edge.kind,
|
|
723
|
+
"data-type": segment.edge.type ?? "connected",
|
|
724
|
+
"data-direction": segment.incoming ? "incoming" : "outgoing"
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
/* @__PURE__ */ jsx3(
|
|
402
729
|
"path",
|
|
403
730
|
{
|
|
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",
|
|
731
|
+
className: "edge-hit",
|
|
732
|
+
"data-continuation-hit": segment.edge.key,
|
|
410
733
|
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
|
|
734
|
+
children: /* @__PURE__ */ jsx3("title", { children: segment.description })
|
|
416
735
|
}
|
|
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
|
-
] });
|
|
736
|
+
)
|
|
737
|
+
] }, segment.edge.key);
|
|
738
|
+
}) });
|
|
441
739
|
}
|
|
442
740
|
|
|
443
741
|
// ../../apps/web/src/navigation.ts
|
|
@@ -557,9 +855,14 @@ function neighbourhood(graph, key, nodeRadius2 = 43) {
|
|
|
557
855
|
neighbours.reduce((sum, n, i) => sum + Math.sin(n.bearing - i * step), 0),
|
|
558
856
|
neighbours.reduce((sum, n, i) => sum + Math.cos(n.bearing - i * step), 0)
|
|
559
857
|
);
|
|
858
|
+
const maximum = Math.max(
|
|
859
|
+
nodeRadius2,
|
|
860
|
+
...graph.nodes.filter((n) => visible.has(n.key)).map((n) => n.baseRadius ?? nodeRadius2)
|
|
861
|
+
);
|
|
560
862
|
const radius2 = Math.max(
|
|
863
|
+
(centre.baseRadius ?? nodeRadius2) + maximum + 24,
|
|
561
864
|
260,
|
|
562
|
-
(2 *
|
|
865
|
+
(2 * maximum + 24) / (2 * Math.sin(Math.PI / Math.max(2, neighbours.length)))
|
|
563
866
|
);
|
|
564
867
|
const nodes = [
|
|
565
868
|
{ ...centre, x: 0, y: 0 },
|
|
@@ -594,6 +897,133 @@ function neighbourhood(graph, key, nodeRadius2 = 43) {
|
|
|
594
897
|
return { nodes, edges, continuations };
|
|
595
898
|
}
|
|
596
899
|
|
|
900
|
+
// ../../apps/web/src/responsive-style.ts
|
|
901
|
+
import {
|
|
902
|
+
resolveNodePresentation
|
|
903
|
+
} from "@lensmap/document";
|
|
904
|
+
var conditionKey = (condition) => JSON.stringify([condition.kind, condition.query]);
|
|
905
|
+
function styleConditions(graph) {
|
|
906
|
+
const conditions = /* @__PURE__ */ new Map();
|
|
907
|
+
for (const node of graph.nodes)
|
|
908
|
+
for (const rule of node.style?.rules ?? [])
|
|
909
|
+
for (const condition of rule.conditions ?? [])
|
|
910
|
+
conditions.set(conditionKey(condition), condition);
|
|
911
|
+
return [...conditions].sort(([a], [b]) => a.localeCompare(b)).map(([, c]) => c);
|
|
912
|
+
}
|
|
913
|
+
var geometryKey = (graph) => JSON.stringify({
|
|
914
|
+
nodes: graph.nodes.map((node) => [
|
|
915
|
+
node.key,
|
|
916
|
+
positionAxis(node, "x"),
|
|
917
|
+
positionAxis(node, "y"),
|
|
918
|
+
node.baseRadius ?? 43
|
|
919
|
+
]),
|
|
920
|
+
edges: graph.edges.map((edge) => [edge.key, edge.from, edge.to, edge.kind])
|
|
921
|
+
});
|
|
922
|
+
var placementKey = (graph) => JSON.stringify(graph.nodes.map((node) => [node.key, node.x, node.y]));
|
|
923
|
+
function resolveGraphPresentation(input, matches, measured = /* @__PURE__ */ new Map(), previous) {
|
|
924
|
+
const nodes = input.nodes.map((node) => {
|
|
925
|
+
const style = node.style?.rules ? resolveNodePresentation(node.style.rules, matches) : node.style;
|
|
926
|
+
const radius2 = (style?.scale === "auto" ? measured.get(node.key) : void 0) ?? baseRadius(style);
|
|
927
|
+
return style === node.style && radius2 === (node.baseRadius ?? 43) ? node : { ...node, style, baseRadius: radius2 };
|
|
928
|
+
});
|
|
929
|
+
const next = { nodes, edges: input.edges };
|
|
930
|
+
if (previous && placementKey(previous.input) === placementKey(input) && geometryKey(previous.graph) === geometryKey(next)) {
|
|
931
|
+
const points = new Map(
|
|
932
|
+
previous.graph.nodes.map((node) => [node.key, node])
|
|
933
|
+
);
|
|
934
|
+
return {
|
|
935
|
+
...next,
|
|
936
|
+
nodes: nodes.map((node) => ({
|
|
937
|
+
...node,
|
|
938
|
+
x: points.get(node.key)?.x ?? node.x,
|
|
939
|
+
y: points.get(node.key)?.y ?? node.y
|
|
940
|
+
}))
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
if (geometryKey(input) === geometryKey(next))
|
|
944
|
+
return nodes.every((node, i) => node === input.nodes[i]) ? input : next;
|
|
945
|
+
return layoutGraph(nodes, input.edges);
|
|
946
|
+
}
|
|
947
|
+
function createStyleEnvironment(conditions, changed) {
|
|
948
|
+
const media = /* @__PURE__ */ new Map();
|
|
949
|
+
const flags = /* @__PURE__ */ new Map();
|
|
950
|
+
const host = document.createElement("div");
|
|
951
|
+
host.setAttribute("aria-hidden", "true");
|
|
952
|
+
host.style.cssText = "position:fixed;left:-100000px;top:0;visibility:hidden;pointer-events:none;container-type:size;box-sizing:content-box;padding:0;border:0;min-width:0;max-width:none;min-height:0;max-height:none;width:0;height:0;";
|
|
953
|
+
const shadow = host.attachShadow({ mode: "open" });
|
|
954
|
+
const sheet = new CSSStyleSheet();
|
|
955
|
+
const sheetRules = [];
|
|
956
|
+
for (const [index, condition] of conditions.entries()) {
|
|
957
|
+
const key = conditionKey(condition);
|
|
958
|
+
if (condition.kind === "media") {
|
|
959
|
+
media.set(key, matchMedia(condition.query));
|
|
960
|
+
} else if (condition.kind === "container") {
|
|
961
|
+
const id = `query-${index}`;
|
|
962
|
+
const text = `@container ${condition.query}{#${id}{--lensmap-query-match:1}}`;
|
|
963
|
+
try {
|
|
964
|
+
const check = new CSSStyleSheet();
|
|
965
|
+
check.insertRule(text);
|
|
966
|
+
const outer = check.cssRules[0];
|
|
967
|
+
if (!(outer instanceof CSSContainerRule) || outer.containerName || outer.cssRules.length !== 1)
|
|
968
|
+
continue;
|
|
969
|
+
const rule = outer.cssRules[0];
|
|
970
|
+
if (!(rule instanceof CSSStyleRule) || rule.selectorText !== `#${id}` || rule.style.length !== 1 || rule.style.getPropertyValue("--lensmap-query-match") !== "1")
|
|
971
|
+
continue;
|
|
972
|
+
const flag = document.createElement("i");
|
|
973
|
+
flag.id = id;
|
|
974
|
+
shadow.append(flag);
|
|
975
|
+
flags.set(key, flag);
|
|
976
|
+
sheetRules.push(text);
|
|
977
|
+
} catch {
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
sheet.replaceSync(sheetRules.join("\n"));
|
|
982
|
+
shadow.adoptedStyleSheets = [sheet];
|
|
983
|
+
if (flags.size) document.body.append(host);
|
|
984
|
+
let previous = /* @__PURE__ */ new Map();
|
|
985
|
+
let context;
|
|
986
|
+
const refresh = () => {
|
|
987
|
+
if (context) {
|
|
988
|
+
const style = getComputedStyle(context);
|
|
989
|
+
host.style.font = style.font;
|
|
990
|
+
host.style.writingMode = style.writingMode;
|
|
991
|
+
}
|
|
992
|
+
const next = /* @__PURE__ */ new Map();
|
|
993
|
+
for (const condition of conditions) {
|
|
994
|
+
const key = conditionKey(condition);
|
|
995
|
+
const flag = flags.get(key);
|
|
996
|
+
next.set(
|
|
997
|
+
key,
|
|
998
|
+
media.get(key)?.matches ?? (flag ? getComputedStyle(flag).getPropertyValue("--lensmap-query-match").trim() === "1" : false)
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
if (JSON.stringify([...previous]) !== JSON.stringify([...next])) {
|
|
1002
|
+
previous = next;
|
|
1003
|
+
changed?.(next);
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
for (const query of media.values()) query.addEventListener("change", refresh);
|
|
1007
|
+
window.addEventListener("resize", refresh);
|
|
1008
|
+
document.fonts.addEventListener("loadingdone", refresh);
|
|
1009
|
+
return {
|
|
1010
|
+
matches: (condition) => previous.get(conditionKey(condition)) ?? false,
|
|
1011
|
+
resize(width, height, element) {
|
|
1012
|
+
context = element;
|
|
1013
|
+
host.style.width = `${width}px`;
|
|
1014
|
+
host.style.height = `${height}px`;
|
|
1015
|
+
refresh();
|
|
1016
|
+
},
|
|
1017
|
+
dispose() {
|
|
1018
|
+
for (const query of media.values())
|
|
1019
|
+
query.removeEventListener("change", refresh);
|
|
1020
|
+
window.removeEventListener("resize", refresh);
|
|
1021
|
+
document.fonts.removeEventListener("loadingdone", refresh);
|
|
1022
|
+
host.remove();
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
|
|
597
1027
|
// ../../apps/web/src/SidebarResizeHandle.tsx
|
|
598
1028
|
import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
|
|
599
1029
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
@@ -794,6 +1224,48 @@ function useMapKeyboard(ref, controls) {
|
|
|
794
1224
|
return { stop };
|
|
795
1225
|
}
|
|
796
1226
|
|
|
1227
|
+
// ../../apps/web/src/useNodeStyleMatches.ts
|
|
1228
|
+
import { useLayoutEffect, useRef as useRef4, useState as useState2 } from "react";
|
|
1229
|
+
function useNodeStyleMatches(graph, canvas, size) {
|
|
1230
|
+
const conditions = styleConditions(graph);
|
|
1231
|
+
const signature = JSON.stringify(conditions);
|
|
1232
|
+
const environment = useRef4(null);
|
|
1233
|
+
const [matches, setMatches] = useState2(
|
|
1234
|
+
/* @__PURE__ */ new Map()
|
|
1235
|
+
);
|
|
1236
|
+
const refreshSize = () => {
|
|
1237
|
+
const element = canvas.current;
|
|
1238
|
+
if (!element || !environment.current) return;
|
|
1239
|
+
const style = getComputedStyle(element);
|
|
1240
|
+
environment.current.resize(
|
|
1241
|
+
element.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight),
|
|
1242
|
+
element.clientHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom),
|
|
1243
|
+
element
|
|
1244
|
+
);
|
|
1245
|
+
};
|
|
1246
|
+
useLayoutEffect(() => {
|
|
1247
|
+
const conditions2 = JSON.parse(signature);
|
|
1248
|
+
if (!conditions2.length) {
|
|
1249
|
+
setMatches((previous) => previous.size ? /* @__PURE__ */ new Map() : previous);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
const current = createStyleEnvironment(conditions2, setMatches);
|
|
1253
|
+
environment.current = current;
|
|
1254
|
+
refreshSize();
|
|
1255
|
+
return () => {
|
|
1256
|
+
current.dispose();
|
|
1257
|
+
if (environment.current === current) environment.current = null;
|
|
1258
|
+
};
|
|
1259
|
+
}, [signature]);
|
|
1260
|
+
useLayoutEffect(refreshSize, [size.width, size.height]);
|
|
1261
|
+
return {
|
|
1262
|
+
matches,
|
|
1263
|
+
ready: conditions.every(
|
|
1264
|
+
(condition) => matches.has(conditionKey(condition))
|
|
1265
|
+
)
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
|
|
797
1269
|
// ../../apps/web/src/ViewerInspector.tsx
|
|
798
1270
|
import { thoughtAnchors } from "@lensmap/document";
|
|
799
1271
|
import Markdown from "react-markdown";
|
|
@@ -825,8 +1297,9 @@ function ViewerConnections({
|
|
|
825
1297
|
"data-focus-key": key,
|
|
826
1298
|
onClick: () => onFocus(key),
|
|
827
1299
|
children: [
|
|
828
|
-
connectionWording3(edge.type, direction),
|
|
829
|
-
"
|
|
1300
|
+
connectionWording3(edge.type, direction, edge.presentation),
|
|
1301
|
+
" ",
|
|
1302
|
+
"\xB7 ",
|
|
830
1303
|
getLabel(node)
|
|
831
1304
|
]
|
|
832
1305
|
},
|
|
@@ -935,11 +1408,11 @@ function reconcileViewer(previous, next, selected, camera, mode) {
|
|
|
935
1408
|
}
|
|
936
1409
|
|
|
937
1410
|
// ../../apps/web/src/Viewer.tsx
|
|
938
|
-
import {
|
|
1411
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
939
1412
|
var initialCamera = { x: 0, y: 0, zoom: 0.85 };
|
|
940
1413
|
var nodeRadius = 43;
|
|
941
1414
|
function Viewer({
|
|
942
|
-
graph,
|
|
1415
|
+
graph: inputGraph,
|
|
943
1416
|
embedded = false,
|
|
944
1417
|
navigationControls,
|
|
945
1418
|
thoughtLink,
|
|
@@ -955,57 +1428,94 @@ function Viewer({
|
|
|
955
1428
|
renderSidebar,
|
|
956
1429
|
renderExternalInspector
|
|
957
1430
|
}) {
|
|
958
|
-
const
|
|
1431
|
+
const canvas = useRef5(null);
|
|
1432
|
+
const [size, setSize] = useState3({ width: 900, height: 640 });
|
|
1433
|
+
const { matches, ready: presentationReady } = useNodeStyleMatches(
|
|
1434
|
+
inputGraph,
|
|
1435
|
+
canvas,
|
|
1436
|
+
size
|
|
1437
|
+
);
|
|
1438
|
+
const [measured, setMeasured] = useState3(
|
|
1439
|
+
/* @__PURE__ */ new Map()
|
|
1440
|
+
);
|
|
1441
|
+
const previousPresentation = useRef5(null);
|
|
1442
|
+
const graph = useMemo(() => {
|
|
1443
|
+
const resolved = resolveGraphPresentation(
|
|
1444
|
+
inputGraph,
|
|
1445
|
+
(condition) => matches.get(conditionKey(condition)) ?? false,
|
|
1446
|
+
measured,
|
|
1447
|
+
previousPresentation.current ?? void 0
|
|
1448
|
+
);
|
|
1449
|
+
previousPresentation.current = { input: inputGraph, graph: resolved };
|
|
1450
|
+
return resolved;
|
|
1451
|
+
}, [inputGraph, measured, matches]);
|
|
1452
|
+
const scope = useId2();
|
|
959
1453
|
const anchors = useMemo(() => thoughtAnchors2(graph.nodes), [graph.nodes]);
|
|
960
1454
|
const entryNode = graph.nodes.find((n) => n.key === initialTarget);
|
|
961
1455
|
const restoredNode = graph.nodes.find(
|
|
962
1456
|
(n) => n.key === initialState?.selected
|
|
963
1457
|
);
|
|
964
|
-
const
|
|
965
|
-
const
|
|
966
|
-
const
|
|
967
|
-
const doubleCandidate = useRef4(null);
|
|
1458
|
+
const scene = useRef5(null);
|
|
1459
|
+
const pendingActivation = useRef5(null);
|
|
1460
|
+
const doubleCandidate = useRef5(null);
|
|
968
1461
|
const cancelActivation = useCallback2(() => {
|
|
969
1462
|
if (pendingActivation.current !== null)
|
|
970
1463
|
clearTimeout(pendingActivation.current);
|
|
971
1464
|
pendingActivation.current = null;
|
|
972
1465
|
doubleCandidate.current = null;
|
|
973
1466
|
}, []);
|
|
974
|
-
const drag =
|
|
975
|
-
const suppressPointerClick =
|
|
976
|
-
const [
|
|
977
|
-
|
|
1467
|
+
const drag = useRef5(null);
|
|
1468
|
+
const suppressPointerClick = useRef5(false);
|
|
1469
|
+
const [widths, setWidths] = useState3(/* @__PURE__ */ new Map());
|
|
1470
|
+
useLayoutEffect2(() => {
|
|
1471
|
+
const root = canvas.current;
|
|
1472
|
+
if (!root) return;
|
|
1473
|
+
setWidths(
|
|
1474
|
+
new Map(
|
|
1475
|
+
graph.edges.map((edge) => [
|
|
1476
|
+
edge.key,
|
|
1477
|
+
resolveConnectionWidth(
|
|
1478
|
+
edge.presentation?.strokeWidth ?? 2.5,
|
|
1479
|
+
root,
|
|
1480
|
+
size.width,
|
|
1481
|
+
size.height
|
|
1482
|
+
)
|
|
1483
|
+
])
|
|
1484
|
+
)
|
|
1485
|
+
);
|
|
1486
|
+
}, [graph, size]);
|
|
1487
|
+
const [camera, setCamera] = useState3(
|
|
978
1488
|
initialState?.camera ?? (entryNode ? { ...initialCamera, x: entryNode.x, y: entryNode.y } : initialCamera)
|
|
979
1489
|
);
|
|
980
1490
|
const { nodes, edges } = graph;
|
|
981
1491
|
const lanes = useMemo(() => edgeLanes(edges), [edges]);
|
|
982
|
-
const [selected, setSelected] =
|
|
1492
|
+
const [selected, setSelected] = useState3(
|
|
983
1493
|
restoredNode?.key ?? entryNode?.key ?? nodes[0]?.key ?? ""
|
|
984
1494
|
);
|
|
985
|
-
const [sidebarWidth, setSidebarWidth] =
|
|
986
|
-
const [strength, setStrength] =
|
|
987
|
-
const [neighbourhoodStrength, setNeighbourhoodStrength] =
|
|
1495
|
+
const [sidebarWidth, setSidebarWidth] = useState3(340);
|
|
1496
|
+
const [strength, setStrength] = useState3(initialState?.strength ?? 1.1);
|
|
1497
|
+
const [neighbourhoodStrength, setNeighbourhoodStrength] = useState3(
|
|
988
1498
|
initialState?.neighbourhoodStrength ?? 1.1
|
|
989
1499
|
);
|
|
990
|
-
const [overview, setOverview] =
|
|
1500
|
+
const [overview, setOverview] = useState3(
|
|
991
1501
|
initialState?.overview ?? initialOverview ?? false
|
|
992
1502
|
);
|
|
993
|
-
const [inNeighbourhood, setInNeighbourhood] =
|
|
1503
|
+
const [inNeighbourhood, setInNeighbourhood] = useState3(
|
|
994
1504
|
!!restoredNode && (initialState?.inNeighbourhood ?? false)
|
|
995
1505
|
);
|
|
996
|
-
const [neighbourhoodCamera, setNeighbourhoodCamera] =
|
|
1506
|
+
const [neighbourhoodCamera, setNeighbourhoodCamera] = useState3(
|
|
997
1507
|
initialState?.neighbourhoodCamera ?? {
|
|
998
1508
|
x: 0,
|
|
999
1509
|
y: 0,
|
|
1000
1510
|
zoom: 1
|
|
1001
1511
|
}
|
|
1002
1512
|
);
|
|
1003
|
-
const [dragging, setDragging] =
|
|
1004
|
-
const [message, setMessage] =
|
|
1005
|
-
const [keyboardTarget, setKeyboardTarget] =
|
|
1006
|
-
const [keyboardActive, setKeyboardActive] =
|
|
1007
|
-
const [maximumPanSpeed, setMaximumPanSpeed] =
|
|
1008
|
-
const [connectionChoice, setConnectionChoice] =
|
|
1513
|
+
const [dragging, setDragging] = useState3(false);
|
|
1514
|
+
const [message, setMessage] = useState3("");
|
|
1515
|
+
const [keyboardTarget, setKeyboardTarget] = useState3();
|
|
1516
|
+
const [keyboardActive, setKeyboardActive] = useState3(false);
|
|
1517
|
+
const [maximumPanSpeed, setMaximumPanSpeed] = useState3(900);
|
|
1518
|
+
const [connectionChoice, setConnectionChoice] = useState3();
|
|
1009
1519
|
const dismissConnectionChoice = useCallback2(() => {
|
|
1010
1520
|
const focused = document.activeElement;
|
|
1011
1521
|
if (focused instanceof Element && canvas.current?.contains(focused) && focused.closest("[data-connection-chooser]")) {
|
|
@@ -1013,7 +1523,7 @@ function Viewer({
|
|
|
1013
1523
|
}
|
|
1014
1524
|
setConnectionChoice(void 0);
|
|
1015
1525
|
}, []);
|
|
1016
|
-
const returnOverview =
|
|
1526
|
+
const returnOverview = useRef5(initialState?.returnOverview ?? false);
|
|
1017
1527
|
const reportState = useEffectEvent2(
|
|
1018
1528
|
() => onStateChange?.({
|
|
1019
1529
|
selected,
|
|
@@ -1063,6 +1573,58 @@ function Viewer({
|
|
|
1063
1573
|
() => inNeighbourhood ? neighbourhood(graph, selected, nodeRadius) : null,
|
|
1064
1574
|
[graph, inNeighbourhood, selected]
|
|
1065
1575
|
);
|
|
1576
|
+
const measurementView = inNeighbourhood ? selected : null;
|
|
1577
|
+
useLayoutEffect2(() => {
|
|
1578
|
+
let alive = true;
|
|
1579
|
+
const measure = () => {
|
|
1580
|
+
if (!alive || !canvas.current) return;
|
|
1581
|
+
const visible = /* @__PURE__ */ new Map();
|
|
1582
|
+
canvas.current.querySelectorAll(".node[data-auto-size='true']").forEach((element) => {
|
|
1583
|
+
const key = element.dataset.node;
|
|
1584
|
+
if (!key || !graph.nodes.some((node) => node.key === key)) return;
|
|
1585
|
+
const clone = element.cloneNode(true);
|
|
1586
|
+
clone.removeAttribute("id");
|
|
1587
|
+
clone.classList.remove("selected", "connected");
|
|
1588
|
+
clone.style.cssText = element.style.cssText;
|
|
1589
|
+
Object.assign(clone.style, {
|
|
1590
|
+
transform: "none",
|
|
1591
|
+
visibility: "hidden",
|
|
1592
|
+
position: "absolute",
|
|
1593
|
+
width: "max-content",
|
|
1594
|
+
maxWidth: "260px",
|
|
1595
|
+
height: "auto",
|
|
1596
|
+
minHeight: "0",
|
|
1597
|
+
left: "0",
|
|
1598
|
+
top: "0"
|
|
1599
|
+
});
|
|
1600
|
+
canvas.current?.append(clone);
|
|
1601
|
+
const box = clone.getBoundingClientRect();
|
|
1602
|
+
visible.set(
|
|
1603
|
+
key,
|
|
1604
|
+
Math.max(43, Math.ceil(Math.hypot(box.width, box.height) / 2))
|
|
1605
|
+
);
|
|
1606
|
+
clone.remove();
|
|
1607
|
+
});
|
|
1608
|
+
setMeasured((previous) => {
|
|
1609
|
+
const next = /* @__PURE__ */ new Map();
|
|
1610
|
+
for (const node of graph.nodes) {
|
|
1611
|
+
if (node.style?.scale !== "auto") continue;
|
|
1612
|
+
const radius2 = visible.get(node.key) ?? previous.get(node.key);
|
|
1613
|
+
if (radius2 !== void 0) next.set(node.key, radius2);
|
|
1614
|
+
}
|
|
1615
|
+
return JSON.stringify([...previous]) === JSON.stringify([...next]) ? previous : next;
|
|
1616
|
+
});
|
|
1617
|
+
};
|
|
1618
|
+
measure();
|
|
1619
|
+
void document.fonts.ready.then(measure);
|
|
1620
|
+
document.fonts.addEventListener("loadingdone", measure);
|
|
1621
|
+
window.addEventListener("resize", measure);
|
|
1622
|
+
return () => {
|
|
1623
|
+
alive = false;
|
|
1624
|
+
document.fonts.removeEventListener("loadingdone", measure);
|
|
1625
|
+
window.removeEventListener("resize", measure);
|
|
1626
|
+
};
|
|
1627
|
+
}, [graph, measurementView, size.width, size.height]);
|
|
1066
1628
|
useEffect4(() => cancelActivation, [cancelActivation]);
|
|
1067
1629
|
useEffect4(() => {
|
|
1068
1630
|
const element = canvas.current;
|
|
@@ -1106,7 +1668,19 @@ function Viewer({
|
|
|
1106
1668
|
keyboard.stop,
|
|
1107
1669
|
dismissConnectionChoice
|
|
1108
1670
|
]);
|
|
1109
|
-
const
|
|
1671
|
+
const overviewPoints = nodes.flatMap((node) => {
|
|
1672
|
+
const loops = edges.filter(
|
|
1673
|
+
(edge) => edge.from === node.key && edge.to === node.key
|
|
1674
|
+
);
|
|
1675
|
+
if (!node.style || loops.length === 0) return [node];
|
|
1676
|
+
const reach = (node.baseRadius ?? nodeRadius) * (3 + Math.max(...loops.map((edge) => lanes.get(edge.key)?.index ?? 0)));
|
|
1677
|
+
return [
|
|
1678
|
+
node,
|
|
1679
|
+
{ x: node.x - reach, y: node.y - reach, baseRadius: 0 },
|
|
1680
|
+
{ x: node.x + reach, y: node.y - reach, baseRadius: 0 }
|
|
1681
|
+
];
|
|
1682
|
+
});
|
|
1683
|
+
const shownCamera = inNeighbourhood ? neighbourhoodCamera : overview ? fitCamera(overviewPoints, size, nodeRadius) : camera;
|
|
1110
1684
|
const projected = (local?.nodes ?? nodes).map((node) => ({
|
|
1111
1685
|
...projectPoint(
|
|
1112
1686
|
node,
|
|
@@ -1114,7 +1688,8 @@ function Viewer({
|
|
|
1114
1688
|
size,
|
|
1115
1689
|
overview ? 0 : shownStrength
|
|
1116
1690
|
),
|
|
1117
|
-
node
|
|
1691
|
+
node,
|
|
1692
|
+
baseRadius: node.baseRadius
|
|
1118
1693
|
}));
|
|
1119
1694
|
const circles = overview ? projected : limitMagnification(projected, nodeRadius, shownStrength);
|
|
1120
1695
|
const positions = new Map(circles.map((point) => [point.node.key, point]));
|
|
@@ -1296,8 +1871,9 @@ function Viewer({
|
|
|
1296
1871
|
if (node) focusNode(node.key);
|
|
1297
1872
|
else setMessage(`No thought has anchor "${id}".`);
|
|
1298
1873
|
}
|
|
1299
|
-
const previousGraph =
|
|
1300
|
-
const
|
|
1874
|
+
const previousGraph = useRef5(graph);
|
|
1875
|
+
const previousInput = useRef5(inputGraph);
|
|
1876
|
+
const lastRequest = useRef5(
|
|
1301
1877
|
void 0
|
|
1302
1878
|
);
|
|
1303
1879
|
const focusedBeforeUpdate = typeof document === "undefined" ? null : document.activeElement;
|
|
@@ -1309,9 +1885,10 @@ function Viewer({
|
|
|
1309
1885
|
graph,
|
|
1310
1886
|
selected,
|
|
1311
1887
|
camera,
|
|
1312
|
-
updateMode
|
|
1888
|
+
previousInput.current === inputGraph ? "live" : updateMode
|
|
1313
1889
|
);
|
|
1314
1890
|
previousGraph.current = graph;
|
|
1891
|
+
previousInput.current = inputGraph;
|
|
1315
1892
|
cancelActivation();
|
|
1316
1893
|
keyboard.stop();
|
|
1317
1894
|
stopDrag();
|
|
@@ -1323,11 +1900,20 @@ function Viewer({
|
|
|
1323
1900
|
setInNeighbourhood(false);
|
|
1324
1901
|
setMessage("");
|
|
1325
1902
|
});
|
|
1326
|
-
|
|
1903
|
+
useLayoutEffect2(() => {
|
|
1327
1904
|
reconcileGraph();
|
|
1328
1905
|
if (hadViewerFocus && focusedBeforeUpdate && !focusedBeforeUpdate.isConnected)
|
|
1329
1906
|
canvas.current?.focus({ preventScroll: true });
|
|
1330
1907
|
});
|
|
1908
|
+
const initializedPresentation = useRef5(false);
|
|
1909
|
+
useLayoutEffect2(() => {
|
|
1910
|
+
if (!presentationReady || initializedPresentation.current) return;
|
|
1911
|
+
initializedPresentation.current = true;
|
|
1912
|
+
if (entryNode && !initialState)
|
|
1913
|
+
setCamera(
|
|
1914
|
+
(current) => current.x === entryNode.x && current.y === entryNode.y ? current : { ...current, x: entryNode.x, y: entryNode.y }
|
|
1915
|
+
);
|
|
1916
|
+
}, [presentationReady, entryNode, initialState]);
|
|
1331
1917
|
const receiveFocus = useEffectEvent2(() => {
|
|
1332
1918
|
if (!focusRequest || lastRequest.current?.key === focusRequest.key && lastRequest.current?.sequence === focusRequest.sequence)
|
|
1333
1919
|
return;
|
|
@@ -1347,7 +1933,7 @@ function Viewer({
|
|
|
1347
1933
|
setCamera((current) => ({ ...current, x: node.x, y: node.y }));
|
|
1348
1934
|
setMessage("");
|
|
1349
1935
|
});
|
|
1350
|
-
|
|
1936
|
+
useLayoutEffect2(() => {
|
|
1351
1937
|
receiveFocus();
|
|
1352
1938
|
});
|
|
1353
1939
|
function stopDrag() {
|
|
@@ -1617,39 +2203,6 @@ function Viewer({
|
|
|
1617
2203
|
height: size.height,
|
|
1618
2204
|
children: [
|
|
1619
2205
|
/* @__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
2206
|
local && /* @__PURE__ */ jsx7(
|
|
1654
2207
|
NeighbourhoodContinuations,
|
|
1655
2208
|
{
|
|
@@ -1659,23 +2212,32 @@ function Viewer({
|
|
|
1659
2212
|
nodes,
|
|
1660
2213
|
nodeRadius,
|
|
1661
2214
|
previewEdges,
|
|
1662
|
-
|
|
2215
|
+
widths
|
|
1663
2216
|
}
|
|
1664
2217
|
),
|
|
1665
2218
|
drawnEdges.map((edge) => {
|
|
1666
|
-
const
|
|
2219
|
+
const presentation = edge.presentation ?? fallbackPresentation(edge.type);
|
|
2220
|
+
const width = widths.get(edge.key) ?? 2.5;
|
|
2221
|
+
const startClearance = markerClearance(
|
|
2222
|
+
presentation.startMarker,
|
|
2223
|
+
width
|
|
2224
|
+
);
|
|
2225
|
+
const endClearance = markerClearance(
|
|
2226
|
+
presentation.endMarker,
|
|
2227
|
+
width
|
|
2228
|
+
);
|
|
1667
2229
|
const from = positions.get(edge.from);
|
|
1668
2230
|
const to = positions.get(edge.to);
|
|
1669
2231
|
if (!from || !to) return null;
|
|
1670
|
-
const description = `${label(from.node)} ${connectionWording4(edge.type, "outgoing")} ${label(to.node)}`;
|
|
2232
|
+
const description = `${label(from.node)} ${connectionWording4(edge.type, "outgoing", edge.presentation)} ${label(to.node)}`;
|
|
1671
2233
|
const dx = to.x - from.x;
|
|
1672
2234
|
const dy = to.y - from.y;
|
|
1673
2235
|
const distance = Math.max(1, Math.hypot(dx, dy));
|
|
1674
2236
|
const start = {
|
|
1675
|
-
x: from.x + dx / distance * nodeRadius * from.scale,
|
|
1676
|
-
y: from.y + dy / distance * nodeRadius * from.scale
|
|
2237
|
+
x: from.x + dx / distance * ((from.node.baseRadius ?? nodeRadius) * from.scale + startClearance),
|
|
2238
|
+
y: from.y + dy / distance * ((from.node.baseRadius ?? nodeRadius) * from.scale + startClearance)
|
|
1677
2239
|
};
|
|
1678
|
-
const targetRadius = nodeRadius * to.scale +
|
|
2240
|
+
const targetRadius = (to.node.baseRadius ?? nodeRadius) * to.scale + endClearance;
|
|
1679
2241
|
const end = {
|
|
1680
2242
|
x: to.x - dx / distance * targetRadius,
|
|
1681
2243
|
y: to.y - dy / distance * targetRadius
|
|
@@ -1687,18 +2249,18 @@ function Viewer({
|
|
|
1687
2249
|
const side = (((start.x + end.x) / 2 - centre.x) * -dy + ((start.y + end.y) / 2 - centre.y) * dx) / distance;
|
|
1688
2250
|
bend = (side < 0 ? -1 : 1) * (190 + (lane?.index ?? 0) * 60);
|
|
1689
2251
|
}
|
|
1690
|
-
const radius2 = nodeRadius * from.scale;
|
|
2252
|
+
const radius2 = (from.node.baseRadius ?? nodeRadius) * from.scale;
|
|
1691
2253
|
const loop = radius2 * (3 + (lane?.index ?? 0));
|
|
1692
2254
|
const points = edge.from === edge.to ? [
|
|
1693
2255
|
{
|
|
1694
|
-
x: from.x + radius2 * 0.7,
|
|
1695
|
-
y: from.y - radius2 * 0.7
|
|
2256
|
+
x: from.x + (radius2 + startClearance) * 0.7,
|
|
2257
|
+
y: from.y - (radius2 + startClearance) * 0.7
|
|
1696
2258
|
},
|
|
1697
2259
|
{ x: from.x + loop, y: from.y - loop },
|
|
1698
2260
|
{ x: from.x - loop, y: from.y - loop },
|
|
1699
2261
|
{
|
|
1700
|
-
x: from.x - radius2 * 0.7,
|
|
1701
|
-
y: from.y - radius2 * 0.7
|
|
2262
|
+
x: from.x - (radius2 + endClearance) * 0.7,
|
|
2263
|
+
y: from.y - (radius2 + endClearance) * 0.7
|
|
1702
2264
|
}
|
|
1703
2265
|
] : [
|
|
1704
2266
|
start,
|
|
@@ -1710,37 +2272,25 @@ function Viewer({
|
|
|
1710
2272
|
];
|
|
1711
2273
|
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
2274
|
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
2275
|
return /* @__PURE__ */ jsxs6("g", { children: [
|
|
1734
|
-
|
|
1735
|
-
|
|
2276
|
+
/* @__PURE__ */ jsx7(
|
|
2277
|
+
StyledConnection,
|
|
1736
2278
|
{
|
|
1737
2279
|
path,
|
|
1738
2280
|
points,
|
|
2281
|
+
presentation,
|
|
2282
|
+
width,
|
|
2283
|
+
colour: colours[to.node.branch],
|
|
1739
2284
|
className: edgeClass,
|
|
1740
|
-
|
|
1741
|
-
|
|
2285
|
+
attributes: {
|
|
2286
|
+
"data-edge": `${edge.from}-${edge.to}`,
|
|
2287
|
+
"data-edge-key": edge.key,
|
|
2288
|
+
"data-kind": edge.kind,
|
|
2289
|
+
"data-type": edge.type ?? "connected",
|
|
2290
|
+
"data-connection-preview": previewEdges.has(edge.key) ? "true" : void 0
|
|
2291
|
+
}
|
|
1742
2292
|
}
|
|
1743
|
-
)
|
|
2293
|
+
),
|
|
1744
2294
|
/* @__PURE__ */ jsx7(
|
|
1745
2295
|
"path",
|
|
1746
2296
|
{
|
|
@@ -1762,6 +2312,15 @@ function Viewer({
|
|
|
1762
2312
|
"button",
|
|
1763
2313
|
{
|
|
1764
2314
|
type: "button",
|
|
2315
|
+
ref: (element) => {
|
|
2316
|
+
if (element)
|
|
2317
|
+
applyNodeStyle(
|
|
2318
|
+
element,
|
|
2319
|
+
node.style,
|
|
2320
|
+
colours[node.branch]
|
|
2321
|
+
);
|
|
2322
|
+
},
|
|
2323
|
+
"data-auto-size": node.style?.scale === "auto" ? "true" : void 0,
|
|
1765
2324
|
id: `${scope}-thought-${node.key}`,
|
|
1766
2325
|
"data-anchor": anchors.get(node.key),
|
|
1767
2326
|
className: `node ${selected === node.key ? "selected" : ""} ${connectedNodes.has(node.key) ? "connected" : ""} ${node.unresolved ? "unresolved" : ""}`,
|
|
@@ -1772,6 +2331,8 @@ function Viewer({
|
|
|
1772
2331
|
"aria-pressed": selected === node.key,
|
|
1773
2332
|
title: label(node),
|
|
1774
2333
|
style: {
|
|
2334
|
+
width: 2 * (node.baseRadius ?? nodeRadius),
|
|
2335
|
+
height: 2 * (node.baseRadius ?? nodeRadius),
|
|
1775
2336
|
left: point.x,
|
|
1776
2337
|
top: point.y,
|
|
1777
2338
|
transform: `translate(-50%, -50%) scale(${point.scale})`,
|
|
@@ -1787,6 +2348,7 @@ function Viewer({
|
|
|
1787
2348
|
else focusNode(node.key);
|
|
1788
2349
|
},
|
|
1789
2350
|
children: [
|
|
2351
|
+
node.style?.icon && /* @__PURE__ */ jsx7("span", { className: "node-icon", "aria-hidden": "true", children: node.style.icon }),
|
|
1790
2352
|
node.destination && /* @__PURE__ */ jsx7(
|
|
1791
2353
|
"span",
|
|
1792
2354
|
{
|
|
@@ -1822,7 +2384,7 @@ function Viewer({
|
|
|
1822
2384
|
"aria-hidden": "true"
|
|
1823
2385
|
}
|
|
1824
2386
|
),
|
|
1825
|
-
/* @__PURE__ */ jsx7("span", { children: node.unresolved ? "?" : /* @__PURE__ */ jsx7(
|
|
2387
|
+
/* @__PURE__ */ jsx7("span", { className: "node-summary", children: node.unresolved ? "?" : /* @__PURE__ */ jsx7(
|
|
1826
2388
|
Markdown2,
|
|
1827
2389
|
{
|
|
1828
2390
|
allowedElements: [
|
|
@@ -1950,7 +2512,7 @@ function Viewer({
|
|
|
1950
2512
|
}
|
|
1951
2513
|
|
|
1952
2514
|
// src/SelectedThought.tsx
|
|
1953
|
-
import { useId as
|
|
2515
|
+
import { useId as useId3, useMemo as useMemo2, useState as useState4 } from "react";
|
|
1954
2516
|
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1955
2517
|
function readingExcerpt(html) {
|
|
1956
2518
|
const template = document.createElement("template");
|
|
@@ -1967,8 +2529,8 @@ function readingExcerpt(html) {
|
|
|
1967
2529
|
}
|
|
1968
2530
|
function SelectedThought({ html }) {
|
|
1969
2531
|
const excerpt = useMemo2(() => readingExcerpt(html), [html]);
|
|
1970
|
-
const [expanded, setExpanded] =
|
|
1971
|
-
const fullId =
|
|
2532
|
+
const [expanded, setExpanded] = useState4(false);
|
|
2533
|
+
const fullId = useId3();
|
|
1972
2534
|
if (!excerpt) {
|
|
1973
2535
|
return /* @__PURE__ */ jsx8(
|
|
1974
2536
|
"div",
|
|
@@ -2062,9 +2624,33 @@ function ViewerHost({
|
|
|
2062
2624
|
graph,
|
|
2063
2625
|
rendered,
|
|
2064
2626
|
inspectorId = "default",
|
|
2065
|
-
getLabel
|
|
2627
|
+
getLabel,
|
|
2628
|
+
mapId
|
|
2066
2629
|
}) {
|
|
2067
|
-
const [
|
|
2630
|
+
const [liveGraph, setLiveGraph] = useState5(graph);
|
|
2631
|
+
const [stale, setStale] = useState5(false);
|
|
2632
|
+
useEffect5(() => setLiveGraph(graph), [graph]);
|
|
2633
|
+
useEffect5(() => {
|
|
2634
|
+
const hot = import.meta.hot;
|
|
2635
|
+
if (!hot || !mapId) return;
|
|
2636
|
+
const update = (data) => {
|
|
2637
|
+
if (data.id !== mapId) return;
|
|
2638
|
+
setStale(data.stale);
|
|
2639
|
+
const edges = data.edges;
|
|
2640
|
+
if (!data.stale && edges)
|
|
2641
|
+
setLiveGraph((previous) => ({
|
|
2642
|
+
...previous,
|
|
2643
|
+
edges,
|
|
2644
|
+
nodes: previous.nodes.map((node) => ({
|
|
2645
|
+
...node,
|
|
2646
|
+
destination: data.nodes?.find((update2) => update2.key === node.key)?.destination
|
|
2647
|
+
}))
|
|
2648
|
+
}));
|
|
2649
|
+
};
|
|
2650
|
+
hot.on("lensmap:vocabulary", update);
|
|
2651
|
+
return () => hot.off("lensmap:vocabulary", update);
|
|
2652
|
+
}, [mapId]);
|
|
2653
|
+
const [targets, setTargets] = useState5([]);
|
|
2068
2654
|
useEffect5(() => {
|
|
2069
2655
|
setTargets(
|
|
2070
2656
|
[
|
|
@@ -2076,28 +2662,31 @@ function ViewerHost({
|
|
|
2076
2662
|
)
|
|
2077
2663
|
);
|
|
2078
2664
|
}, [inspectorId]);
|
|
2079
|
-
return /* @__PURE__ */
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2665
|
+
return /* @__PURE__ */ jsxs8("div", { "data-lensmap-stale": String(stale), children: [
|
|
2666
|
+
stale && /* @__PURE__ */ jsx9("p", { role: "status", children: "Connection vocabulary is invalid. Showing the last valid map." }),
|
|
2667
|
+
/* @__PURE__ */ jsx9(
|
|
2668
|
+
Viewer,
|
|
2669
|
+
{
|
|
2670
|
+
graph: liveGraph,
|
|
2671
|
+
embedded: true,
|
|
2672
|
+
showInspector: false,
|
|
2673
|
+
renderExternalInspector: (context) => targets.map(
|
|
2674
|
+
(target, index) => createPortal(
|
|
2675
|
+
/* @__PURE__ */ jsx9(
|
|
2676
|
+
Inspector,
|
|
2677
|
+
{
|
|
2678
|
+
context,
|
|
2679
|
+
rendered,
|
|
2680
|
+
getLabel
|
|
2681
|
+
}
|
|
2682
|
+
),
|
|
2683
|
+
target,
|
|
2684
|
+
String(index)
|
|
2685
|
+
)
|
|
2097
2686
|
)
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
);
|
|
2687
|
+
}
|
|
2688
|
+
)
|
|
2689
|
+
] });
|
|
2101
2690
|
}
|
|
2102
2691
|
export {
|
|
2103
2692
|
ViewerHost as default
|