@lensmap/starlight 0.3.0 → 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.js +281 -84
- package/dist/layout.js +24 -12
- package/package.json +3 -3
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,6 +10,34 @@ 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";
|
|
15
43
|
function silhouette(contour, distance, padding) {
|
|
@@ -89,16 +117,17 @@ function layoutGraph(nodes, edges) {
|
|
|
89
117
|
}
|
|
90
118
|
const plans = roots.map((root) => plan(root, true));
|
|
91
119
|
function seed(node, subtree, point, heading, isRoot) {
|
|
92
|
-
const
|
|
93
|
-
const
|
|
120
|
+
const pinX = positionAxis(node, "x");
|
|
121
|
+
const pinY = positionAxis(node, "y");
|
|
122
|
+
const actual = { x: pinX ?? point.x, y: pinY ?? point.y };
|
|
94
123
|
placed.set(node.key, {
|
|
95
124
|
key: node.key,
|
|
96
125
|
x: actual.x,
|
|
97
126
|
y: actual.y,
|
|
98
127
|
targetX: actual.x,
|
|
99
128
|
targetY: actual.y,
|
|
100
|
-
fx:
|
|
101
|
-
fy:
|
|
129
|
+
fx: pinX !== void 0 || isRoot ? actual.x : void 0,
|
|
130
|
+
fy: pinY !== void 0 || isRoot ? actual.y : void 0
|
|
102
131
|
});
|
|
103
132
|
subtree.children.forEach((placement) => {
|
|
104
133
|
const child = byKey2.get(placement.subtree.key);
|
|
@@ -111,12 +140,18 @@ function layoutGraph(nodes, edges) {
|
|
|
111
140
|
x: actual.x + Math.cos(angle) * placement.distance,
|
|
112
141
|
y: actual.y + Math.sin(angle) * placement.distance
|
|
113
142
|
};
|
|
114
|
-
const childPoint =
|
|
143
|
+
const childPoint = {
|
|
144
|
+
x: positionAxis(child, "x") ?? target.x,
|
|
145
|
+
y: positionAxis(child, "y") ?? target.y
|
|
146
|
+
};
|
|
115
147
|
const direction = childPoint.x === actual.x && childPoint.y === actual.y ? angle : Math.atan2(childPoint.y - actual.y, childPoint.x - actual.x);
|
|
116
148
|
seed(child, placement.subtree, target, direction, false);
|
|
117
149
|
});
|
|
118
150
|
}
|
|
119
|
-
const anchor =
|
|
151
|
+
const anchor = {
|
|
152
|
+
x: positionAxis(nodes[0], "x") ?? 0,
|
|
153
|
+
y: positionAxis(nodes[0], "y") ?? 0
|
|
154
|
+
};
|
|
120
155
|
const rootRadius = roots.length > 1 ? step * 2 / Math.sin(Math.PI / roots.length) : 0;
|
|
121
156
|
roots.forEach((root, index) => {
|
|
122
157
|
const angle = -Math.PI / 2 + Math.PI * 2 * index / roots.length;
|
|
@@ -152,30 +187,6 @@ function layoutGraph(nodes, edges) {
|
|
|
152
187
|
};
|
|
153
188
|
}
|
|
154
189
|
|
|
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
|
-
}
|
|
178
|
-
|
|
179
190
|
// ../../apps/web/src/Viewer.tsx
|
|
180
191
|
import {
|
|
181
192
|
connectionWording as connectionWording4,
|
|
@@ -187,10 +198,10 @@ import {
|
|
|
187
198
|
useEffect as useEffect4,
|
|
188
199
|
useEffectEvent as useEffectEvent2,
|
|
189
200
|
useId as useId2,
|
|
190
|
-
useLayoutEffect,
|
|
201
|
+
useLayoutEffect as useLayoutEffect2,
|
|
191
202
|
useMemo,
|
|
192
|
-
useRef as
|
|
193
|
-
useState as
|
|
203
|
+
useRef as useRef5,
|
|
204
|
+
useState as useState3
|
|
194
205
|
} from "react";
|
|
195
206
|
import Markdown2 from "react-markdown";
|
|
196
207
|
|
|
@@ -886,6 +897,133 @@ function neighbourhood(graph, key, nodeRadius2 = 43) {
|
|
|
886
897
|
return { nodes, edges, continuations };
|
|
887
898
|
}
|
|
888
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
|
+
|
|
889
1027
|
// ../../apps/web/src/SidebarResizeHandle.tsx
|
|
890
1028
|
import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
|
|
891
1029
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
@@ -1086,6 +1224,48 @@ function useMapKeyboard(ref, controls) {
|
|
|
1086
1224
|
return { stop };
|
|
1087
1225
|
}
|
|
1088
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
|
+
|
|
1089
1269
|
// ../../apps/web/src/ViewerInspector.tsx
|
|
1090
1270
|
import { thoughtAnchors } from "@lensmap/document";
|
|
1091
1271
|
import Markdown from "react-markdown";
|
|
@@ -1248,41 +1428,46 @@ function Viewer({
|
|
|
1248
1428
|
renderSidebar,
|
|
1249
1429
|
renderExternalInspector
|
|
1250
1430
|
}) {
|
|
1251
|
-
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(
|
|
1252
1439
|
/* @__PURE__ */ new Map()
|
|
1253
1440
|
);
|
|
1441
|
+
const previousPresentation = useRef5(null);
|
|
1254
1442
|
const graph = useMemo(() => {
|
|
1255
|
-
const
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}, [inputGraph, measured]);
|
|
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]);
|
|
1265
1452
|
const scope = useId2();
|
|
1266
1453
|
const anchors = useMemo(() => thoughtAnchors2(graph.nodes), [graph.nodes]);
|
|
1267
1454
|
const entryNode = graph.nodes.find((n) => n.key === initialTarget);
|
|
1268
1455
|
const restoredNode = graph.nodes.find(
|
|
1269
1456
|
(n) => n.key === initialState?.selected
|
|
1270
1457
|
);
|
|
1271
|
-
const
|
|
1272
|
-
const
|
|
1273
|
-
const
|
|
1274
|
-
const doubleCandidate = useRef4(null);
|
|
1458
|
+
const scene = useRef5(null);
|
|
1459
|
+
const pendingActivation = useRef5(null);
|
|
1460
|
+
const doubleCandidate = useRef5(null);
|
|
1275
1461
|
const cancelActivation = useCallback2(() => {
|
|
1276
1462
|
if (pendingActivation.current !== null)
|
|
1277
1463
|
clearTimeout(pendingActivation.current);
|
|
1278
1464
|
pendingActivation.current = null;
|
|
1279
1465
|
doubleCandidate.current = null;
|
|
1280
1466
|
}, []);
|
|
1281
|
-
const drag =
|
|
1282
|
-
const suppressPointerClick =
|
|
1283
|
-
const [
|
|
1284
|
-
|
|
1285
|
-
useLayoutEffect(() => {
|
|
1467
|
+
const drag = useRef5(null);
|
|
1468
|
+
const suppressPointerClick = useRef5(false);
|
|
1469
|
+
const [widths, setWidths] = useState3(/* @__PURE__ */ new Map());
|
|
1470
|
+
useLayoutEffect2(() => {
|
|
1286
1471
|
const root = canvas.current;
|
|
1287
1472
|
if (!root) return;
|
|
1288
1473
|
setWidths(
|
|
@@ -1299,38 +1484,38 @@ function Viewer({
|
|
|
1299
1484
|
)
|
|
1300
1485
|
);
|
|
1301
1486
|
}, [graph, size]);
|
|
1302
|
-
const [camera, setCamera] =
|
|
1487
|
+
const [camera, setCamera] = useState3(
|
|
1303
1488
|
initialState?.camera ?? (entryNode ? { ...initialCamera, x: entryNode.x, y: entryNode.y } : initialCamera)
|
|
1304
1489
|
);
|
|
1305
1490
|
const { nodes, edges } = graph;
|
|
1306
1491
|
const lanes = useMemo(() => edgeLanes(edges), [edges]);
|
|
1307
|
-
const [selected, setSelected] =
|
|
1492
|
+
const [selected, setSelected] = useState3(
|
|
1308
1493
|
restoredNode?.key ?? entryNode?.key ?? nodes[0]?.key ?? ""
|
|
1309
1494
|
);
|
|
1310
|
-
const [sidebarWidth, setSidebarWidth] =
|
|
1311
|
-
const [strength, setStrength] =
|
|
1312
|
-
const [neighbourhoodStrength, setNeighbourhoodStrength] =
|
|
1495
|
+
const [sidebarWidth, setSidebarWidth] = useState3(340);
|
|
1496
|
+
const [strength, setStrength] = useState3(initialState?.strength ?? 1.1);
|
|
1497
|
+
const [neighbourhoodStrength, setNeighbourhoodStrength] = useState3(
|
|
1313
1498
|
initialState?.neighbourhoodStrength ?? 1.1
|
|
1314
1499
|
);
|
|
1315
|
-
const [overview, setOverview] =
|
|
1500
|
+
const [overview, setOverview] = useState3(
|
|
1316
1501
|
initialState?.overview ?? initialOverview ?? false
|
|
1317
1502
|
);
|
|
1318
|
-
const [inNeighbourhood, setInNeighbourhood] =
|
|
1503
|
+
const [inNeighbourhood, setInNeighbourhood] = useState3(
|
|
1319
1504
|
!!restoredNode && (initialState?.inNeighbourhood ?? false)
|
|
1320
1505
|
);
|
|
1321
|
-
const [neighbourhoodCamera, setNeighbourhoodCamera] =
|
|
1506
|
+
const [neighbourhoodCamera, setNeighbourhoodCamera] = useState3(
|
|
1322
1507
|
initialState?.neighbourhoodCamera ?? {
|
|
1323
1508
|
x: 0,
|
|
1324
1509
|
y: 0,
|
|
1325
1510
|
zoom: 1
|
|
1326
1511
|
}
|
|
1327
1512
|
);
|
|
1328
|
-
const [dragging, setDragging] =
|
|
1329
|
-
const [message, setMessage] =
|
|
1330
|
-
const [keyboardTarget, setKeyboardTarget] =
|
|
1331
|
-
const [keyboardActive, setKeyboardActive] =
|
|
1332
|
-
const [maximumPanSpeed, setMaximumPanSpeed] =
|
|
1333
|
-
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();
|
|
1334
1519
|
const dismissConnectionChoice = useCallback2(() => {
|
|
1335
1520
|
const focused = document.activeElement;
|
|
1336
1521
|
if (focused instanceof Element && canvas.current?.contains(focused) && focused.closest("[data-connection-chooser]")) {
|
|
@@ -1338,7 +1523,7 @@ function Viewer({
|
|
|
1338
1523
|
}
|
|
1339
1524
|
setConnectionChoice(void 0);
|
|
1340
1525
|
}, []);
|
|
1341
|
-
const returnOverview =
|
|
1526
|
+
const returnOverview = useRef5(initialState?.returnOverview ?? false);
|
|
1342
1527
|
const reportState = useEffectEvent2(
|
|
1343
1528
|
() => onStateChange?.({
|
|
1344
1529
|
selected,
|
|
@@ -1389,15 +1574,14 @@ function Viewer({
|
|
|
1389
1574
|
[graph, inNeighbourhood, selected]
|
|
1390
1575
|
);
|
|
1391
1576
|
const measurementView = inNeighbourhood ? selected : null;
|
|
1392
|
-
|
|
1577
|
+
useLayoutEffect2(() => {
|
|
1393
1578
|
let alive = true;
|
|
1394
1579
|
const measure = () => {
|
|
1395
1580
|
if (!alive || !canvas.current) return;
|
|
1396
1581
|
const visible = /* @__PURE__ */ new Map();
|
|
1397
1582
|
canvas.current.querySelectorAll(".node[data-auto-size='true']").forEach((element) => {
|
|
1398
1583
|
const key = element.dataset.node;
|
|
1399
|
-
if (!key || !
|
|
1400
|
-
return;
|
|
1584
|
+
if (!key || !graph.nodes.some((node) => node.key === key)) return;
|
|
1401
1585
|
const clone = element.cloneNode(true);
|
|
1402
1586
|
clone.removeAttribute("id");
|
|
1403
1587
|
clone.classList.remove("selected", "connected");
|
|
@@ -1423,7 +1607,7 @@ function Viewer({
|
|
|
1423
1607
|
});
|
|
1424
1608
|
setMeasured((previous) => {
|
|
1425
1609
|
const next = /* @__PURE__ */ new Map();
|
|
1426
|
-
for (const node of
|
|
1610
|
+
for (const node of graph.nodes) {
|
|
1427
1611
|
if (node.style?.scale !== "auto") continue;
|
|
1428
1612
|
const radius2 = visible.get(node.key) ?? previous.get(node.key);
|
|
1429
1613
|
if (radius2 !== void 0) next.set(node.key, radius2);
|
|
@@ -1434,11 +1618,13 @@ function Viewer({
|
|
|
1434
1618
|
measure();
|
|
1435
1619
|
void document.fonts.ready.then(measure);
|
|
1436
1620
|
document.fonts.addEventListener("loadingdone", measure);
|
|
1621
|
+
window.addEventListener("resize", measure);
|
|
1437
1622
|
return () => {
|
|
1438
1623
|
alive = false;
|
|
1439
1624
|
document.fonts.removeEventListener("loadingdone", measure);
|
|
1625
|
+
window.removeEventListener("resize", measure);
|
|
1440
1626
|
};
|
|
1441
|
-
}, [
|
|
1627
|
+
}, [graph, measurementView, size.width, size.height]);
|
|
1442
1628
|
useEffect4(() => cancelActivation, [cancelActivation]);
|
|
1443
1629
|
useEffect4(() => {
|
|
1444
1630
|
const element = canvas.current;
|
|
@@ -1685,8 +1871,9 @@ function Viewer({
|
|
|
1685
1871
|
if (node) focusNode(node.key);
|
|
1686
1872
|
else setMessage(`No thought has anchor "${id}".`);
|
|
1687
1873
|
}
|
|
1688
|
-
const previousGraph =
|
|
1689
|
-
const
|
|
1874
|
+
const previousGraph = useRef5(graph);
|
|
1875
|
+
const previousInput = useRef5(inputGraph);
|
|
1876
|
+
const lastRequest = useRef5(
|
|
1690
1877
|
void 0
|
|
1691
1878
|
);
|
|
1692
1879
|
const focusedBeforeUpdate = typeof document === "undefined" ? null : document.activeElement;
|
|
@@ -1698,9 +1885,10 @@ function Viewer({
|
|
|
1698
1885
|
graph,
|
|
1699
1886
|
selected,
|
|
1700
1887
|
camera,
|
|
1701
|
-
updateMode
|
|
1888
|
+
previousInput.current === inputGraph ? "live" : updateMode
|
|
1702
1889
|
);
|
|
1703
1890
|
previousGraph.current = graph;
|
|
1891
|
+
previousInput.current = inputGraph;
|
|
1704
1892
|
cancelActivation();
|
|
1705
1893
|
keyboard.stop();
|
|
1706
1894
|
stopDrag();
|
|
@@ -1712,11 +1900,20 @@ function Viewer({
|
|
|
1712
1900
|
setInNeighbourhood(false);
|
|
1713
1901
|
setMessage("");
|
|
1714
1902
|
});
|
|
1715
|
-
|
|
1903
|
+
useLayoutEffect2(() => {
|
|
1716
1904
|
reconcileGraph();
|
|
1717
1905
|
if (hadViewerFocus && focusedBeforeUpdate && !focusedBeforeUpdate.isConnected)
|
|
1718
1906
|
canvas.current?.focus({ preventScroll: true });
|
|
1719
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]);
|
|
1720
1917
|
const receiveFocus = useEffectEvent2(() => {
|
|
1721
1918
|
if (!focusRequest || lastRequest.current?.key === focusRequest.key && lastRequest.current?.sequence === focusRequest.sequence)
|
|
1722
1919
|
return;
|
|
@@ -1736,7 +1933,7 @@ function Viewer({
|
|
|
1736
1933
|
setCamera((current) => ({ ...current, x: node.x, y: node.y }));
|
|
1737
1934
|
setMessage("");
|
|
1738
1935
|
});
|
|
1739
|
-
|
|
1936
|
+
useLayoutEffect2(() => {
|
|
1740
1937
|
receiveFocus();
|
|
1741
1938
|
});
|
|
1742
1939
|
function stopDrag() {
|
|
@@ -2315,7 +2512,7 @@ function Viewer({
|
|
|
2315
2512
|
}
|
|
2316
2513
|
|
|
2317
2514
|
// src/SelectedThought.tsx
|
|
2318
|
-
import { useId as useId3, useMemo as useMemo2, useState as
|
|
2515
|
+
import { useId as useId3, useMemo as useMemo2, useState as useState4 } from "react";
|
|
2319
2516
|
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2320
2517
|
function readingExcerpt(html) {
|
|
2321
2518
|
const template = document.createElement("template");
|
|
@@ -2332,7 +2529,7 @@ function readingExcerpt(html) {
|
|
|
2332
2529
|
}
|
|
2333
2530
|
function SelectedThought({ html }) {
|
|
2334
2531
|
const excerpt = useMemo2(() => readingExcerpt(html), [html]);
|
|
2335
|
-
const [expanded, setExpanded] =
|
|
2532
|
+
const [expanded, setExpanded] = useState4(false);
|
|
2336
2533
|
const fullId = useId3();
|
|
2337
2534
|
if (!excerpt) {
|
|
2338
2535
|
return /* @__PURE__ */ jsx8(
|
|
@@ -2430,8 +2627,8 @@ function ViewerHost({
|
|
|
2430
2627
|
getLabel,
|
|
2431
2628
|
mapId
|
|
2432
2629
|
}) {
|
|
2433
|
-
const [liveGraph, setLiveGraph] =
|
|
2434
|
-
const [stale, setStale] =
|
|
2630
|
+
const [liveGraph, setLiveGraph] = useState5(graph);
|
|
2631
|
+
const [stale, setStale] = useState5(false);
|
|
2435
2632
|
useEffect5(() => setLiveGraph(graph), [graph]);
|
|
2436
2633
|
useEffect5(() => {
|
|
2437
2634
|
const hot = import.meta.hot;
|
|
@@ -2453,7 +2650,7 @@ function ViewerHost({
|
|
|
2453
2650
|
hot.on("lensmap:vocabulary", update);
|
|
2454
2651
|
return () => hot.off("lensmap:vocabulary", update);
|
|
2455
2652
|
}, [mapId]);
|
|
2456
|
-
const [targets, setTargets] =
|
|
2653
|
+
const [targets, setTargets] = useState5([]);
|
|
2457
2654
|
useEffect5(() => {
|
|
2458
2655
|
setTargets(
|
|
2459
2656
|
[
|
package/dist/layout.js
CHANGED
|
@@ -6,6 +6,15 @@ import {
|
|
|
6
6
|
forceY
|
|
7
7
|
} from "d3-force";
|
|
8
8
|
|
|
9
|
+
// ../../apps/web/src/node-style.ts
|
|
10
|
+
function positionAxis(node, axis) {
|
|
11
|
+
const value = node.style?.[axis];
|
|
12
|
+
return value === "auto" ? void 0 : value ?? node.position?.[axis];
|
|
13
|
+
}
|
|
14
|
+
function baseRadius(style) {
|
|
15
|
+
return 43 * (typeof style?.scale === "number" ? style.scale : 1);
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
// ../../apps/web/src/document/subtree.ts
|
|
10
19
|
import { polygonHull } from "d3-polygon";
|
|
11
20
|
function silhouette(contour, distance, padding) {
|
|
@@ -85,16 +94,17 @@ function layoutGraph(nodes, edges) {
|
|
|
85
94
|
}
|
|
86
95
|
const plans = roots.map((root) => plan(root, true));
|
|
87
96
|
function seed(node, subtree, point, heading, isRoot) {
|
|
88
|
-
const
|
|
89
|
-
const
|
|
97
|
+
const pinX = positionAxis(node, "x");
|
|
98
|
+
const pinY = positionAxis(node, "y");
|
|
99
|
+
const actual = { x: pinX ?? point.x, y: pinY ?? point.y };
|
|
90
100
|
placed.set(node.key, {
|
|
91
101
|
key: node.key,
|
|
92
102
|
x: actual.x,
|
|
93
103
|
y: actual.y,
|
|
94
104
|
targetX: actual.x,
|
|
95
105
|
targetY: actual.y,
|
|
96
|
-
fx:
|
|
97
|
-
fy:
|
|
106
|
+
fx: pinX !== void 0 || isRoot ? actual.x : void 0,
|
|
107
|
+
fy: pinY !== void 0 || isRoot ? actual.y : void 0
|
|
98
108
|
});
|
|
99
109
|
subtree.children.forEach((placement) => {
|
|
100
110
|
const child = byKey.get(placement.subtree.key);
|
|
@@ -107,12 +117,18 @@ function layoutGraph(nodes, edges) {
|
|
|
107
117
|
x: actual.x + Math.cos(angle) * placement.distance,
|
|
108
118
|
y: actual.y + Math.sin(angle) * placement.distance
|
|
109
119
|
};
|
|
110
|
-
const childPoint =
|
|
120
|
+
const childPoint = {
|
|
121
|
+
x: positionAxis(child, "x") ?? target.x,
|
|
122
|
+
y: positionAxis(child, "y") ?? target.y
|
|
123
|
+
};
|
|
111
124
|
const direction = childPoint.x === actual.x && childPoint.y === actual.y ? angle : Math.atan2(childPoint.y - actual.y, childPoint.x - actual.x);
|
|
112
125
|
seed(child, placement.subtree, target, direction, false);
|
|
113
126
|
});
|
|
114
127
|
}
|
|
115
|
-
const anchor =
|
|
128
|
+
const anchor = {
|
|
129
|
+
x: positionAxis(nodes[0], "x") ?? 0,
|
|
130
|
+
y: positionAxis(nodes[0], "y") ?? 0
|
|
131
|
+
};
|
|
116
132
|
const rootRadius = roots.length > 1 ? step * 2 / Math.sin(Math.PI / roots.length) : 0;
|
|
117
133
|
roots.forEach((root, index) => {
|
|
118
134
|
const angle = -Math.PI / 2 + Math.PI * 2 * index / roots.length;
|
|
@@ -148,17 +164,13 @@ function layoutGraph(nodes, edges) {
|
|
|
148
164
|
};
|
|
149
165
|
}
|
|
150
166
|
|
|
151
|
-
// ../../apps/web/src/node-style.ts
|
|
152
|
-
function baseRadius(style) {
|
|
153
|
-
return 43 * (typeof style?.scale === "number" ? style.scale : 1);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
167
|
// ../../apps/web/src/semantic-layout.ts
|
|
157
168
|
var branches = ["blue", "amber", "mint", "rose"];
|
|
158
169
|
var layoutKey = (graph) => JSON.stringify({
|
|
159
170
|
nodes: graph.nodes.map((node) => [
|
|
160
171
|
node.key,
|
|
161
|
-
node
|
|
172
|
+
positionAxis(node, "x"),
|
|
173
|
+
positionAxis(node, "y"),
|
|
162
174
|
baseRadius(node.style)
|
|
163
175
|
]),
|
|
164
176
|
edges: graph.edges.map((edge) => [edge.key, edge.from, edge.to, edge.kind])
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.4.0",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"./viewer.css": "./dist/ViewerHost.css"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@lensmap/astro-loader": "0.
|
|
37
|
-
"@lensmap/document": "0.
|
|
36
|
+
"@lensmap/astro-loader": "0.4.0",
|
|
37
|
+
"@lensmap/document": "0.4.0",
|
|
38
38
|
"d3-force": "3.0.1",
|
|
39
39
|
"d3-polygon": "3.0.2",
|
|
40
40
|
"react-markdown": "10.1.0"
|