@almadar/ui 5.149.0 → 5.151.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/avl/index.cjs +2315 -157
- package/dist/avl/index.js +2315 -157
- package/dist/components/index.cjs +2327 -153
- package/dist/components/index.d.cts +811 -15
- package/dist/components/index.d.ts +811 -15
- package/dist/components/index.js +2329 -155
- package/dist/hooks/index.cjs +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/lib/drawable/three/index.cjs +1 -1
- package/dist/lib/drawable/three/index.js +1 -1
- package/dist/marketing/index.cjs +1 -1
- package/dist/marketing/index.js +1 -1
- package/dist/providers/index.cjs +2306 -149
- package/dist/providers/index.js +2306 -149
- package/dist/runtime/index.cjs +2315 -157
- package/dist/runtime/index.d.cts +2 -2
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2315 -157
- package/package.json +4 -4
package/dist/runtime/index.js
CHANGED
|
@@ -135,7 +135,7 @@ function useEventBus() {
|
|
|
135
135
|
return {
|
|
136
136
|
...baseBus,
|
|
137
137
|
emit: (type, payload, source) => {
|
|
138
|
-
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
|
|
138
|
+
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
|
|
139
139
|
scopeLog.warn("emit:bare-key-no-scope", { type });
|
|
140
140
|
}
|
|
141
141
|
baseBus.emit(type, payload, source);
|
|
@@ -539,26 +539,6 @@ var init_useDragReorder = __esm({
|
|
|
539
539
|
"use client";
|
|
540
540
|
}
|
|
541
541
|
});
|
|
542
|
-
function useMediaQuery(query) {
|
|
543
|
-
const subscribe = useCallback(
|
|
544
|
-
(onChange) => {
|
|
545
|
-
const mql = window.matchMedia(query);
|
|
546
|
-
mql.addEventListener("change", onChange);
|
|
547
|
-
return () => mql.removeEventListener("change", onChange);
|
|
548
|
-
},
|
|
549
|
-
[query]
|
|
550
|
-
);
|
|
551
|
-
return useSyncExternalStore(
|
|
552
|
-
subscribe,
|
|
553
|
-
() => window.matchMedia(query).matches,
|
|
554
|
-
() => false
|
|
555
|
-
);
|
|
556
|
-
}
|
|
557
|
-
var init_useMediaQuery = __esm({
|
|
558
|
-
"hooks/useMediaQuery.ts"() {
|
|
559
|
-
"use client";
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
542
|
function useInfiniteScroll(onLoadMore, options = {}) {
|
|
563
543
|
const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
|
|
564
544
|
const observerRef = useRef(null);
|
|
@@ -11659,6 +11639,14 @@ function shapeBounds(shape) {
|
|
|
11659
11639
|
w: shape.radius * 2 + 8,
|
|
11660
11640
|
h: shape.radius * 2 + 8
|
|
11661
11641
|
};
|
|
11642
|
+
case "ellipse":
|
|
11643
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11644
|
+
return {
|
|
11645
|
+
x: shape.x - shape.width / 2 - 4,
|
|
11646
|
+
y: shape.y - shape.height / 2 - 4,
|
|
11647
|
+
w: shape.width + 8,
|
|
11648
|
+
h: shape.height + 8
|
|
11649
|
+
};
|
|
11662
11650
|
case "rect":
|
|
11663
11651
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11664
11652
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -11692,13 +11680,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
11692
11680
|
ctx.closePath();
|
|
11693
11681
|
ctx.fill();
|
|
11694
11682
|
}
|
|
11695
|
-
function drawShape(ctx, shape, width, height) {
|
|
11683
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
11696
11684
|
ctx.save();
|
|
11697
11685
|
const opacity = shape.opacity ?? 1;
|
|
11698
11686
|
ctx.globalAlpha = opacity;
|
|
11699
11687
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
11700
11688
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
11701
11689
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
11690
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
11702
11691
|
switch (shape.type) {
|
|
11703
11692
|
case "grid": {
|
|
11704
11693
|
const step = shape.step ?? 40;
|
|
@@ -11764,6 +11753,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11764
11753
|
ctx.stroke();
|
|
11765
11754
|
break;
|
|
11766
11755
|
}
|
|
11756
|
+
case "ellipse": {
|
|
11757
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11758
|
+
const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
|
|
11759
|
+
const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
|
|
11760
|
+
ctx.beginPath();
|
|
11761
|
+
ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
|
|
11762
|
+
if (fill) {
|
|
11763
|
+
ctx.fillStyle = fill;
|
|
11764
|
+
ctx.fill();
|
|
11765
|
+
}
|
|
11766
|
+
ctx.strokeStyle = stroke;
|
|
11767
|
+
ctx.stroke();
|
|
11768
|
+
break;
|
|
11769
|
+
}
|
|
11767
11770
|
case "rect": {
|
|
11768
11771
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11769
11772
|
if (fill) {
|
|
@@ -11810,21 +11813,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11810
11813
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
11811
11814
|
break;
|
|
11812
11815
|
}
|
|
11816
|
+
case "venn-region": {
|
|
11817
|
+
const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
|
|
11818
|
+
const c = allShapes.find((s) => s.type === "circle" && s.id === id);
|
|
11819
|
+
return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
|
|
11820
|
+
});
|
|
11821
|
+
const inside = resolveCircles(shape.inside);
|
|
11822
|
+
if (inside.length === 0) break;
|
|
11823
|
+
const outside = resolveCircles(shape.outside);
|
|
11824
|
+
const off = document.createElement("canvas");
|
|
11825
|
+
off.width = ctx.canvas.width;
|
|
11826
|
+
off.height = ctx.canvas.height;
|
|
11827
|
+
const octx = off.getContext("2d");
|
|
11828
|
+
if (!octx) break;
|
|
11829
|
+
octx.setTransform(ctx.getTransform());
|
|
11830
|
+
for (const c of inside) {
|
|
11831
|
+
const p = new Path2D();
|
|
11832
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11833
|
+
octx.clip(p);
|
|
11834
|
+
}
|
|
11835
|
+
octx.fillStyle = fill ?? stroke;
|
|
11836
|
+
octx.fillRect(0, 0, width, height);
|
|
11837
|
+
octx.globalCompositeOperation = "destination-out";
|
|
11838
|
+
for (const c of outside) {
|
|
11839
|
+
const p = new Path2D();
|
|
11840
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11841
|
+
octx.fill(p);
|
|
11842
|
+
}
|
|
11843
|
+
ctx.save();
|
|
11844
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
11845
|
+
ctx.drawImage(off, 0, 0);
|
|
11846
|
+
ctx.restore();
|
|
11847
|
+
break;
|
|
11848
|
+
}
|
|
11813
11849
|
}
|
|
11814
11850
|
ctx.restore();
|
|
11815
11851
|
}
|
|
11816
|
-
|
|
11852
|
+
function readoutShapes(readouts, width) {
|
|
11853
|
+
const out = [];
|
|
11854
|
+
const chipH = 18;
|
|
11855
|
+
const gap = 6;
|
|
11856
|
+
let rightEdge = width - 6;
|
|
11857
|
+
let rowY = 6;
|
|
11858
|
+
for (const readout of readouts) {
|
|
11859
|
+
const text = `${readout.label}: ${String(readout.value)}`;
|
|
11860
|
+
const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
|
|
11861
|
+
let chipX = rightEdge - chipW;
|
|
11862
|
+
if (chipX < 4) {
|
|
11863
|
+
rowY += chipH + 4;
|
|
11864
|
+
rightEdge = width - 6;
|
|
11865
|
+
chipX = rightEdge - chipW;
|
|
11866
|
+
}
|
|
11867
|
+
const color = readout.color ?? "#334155";
|
|
11868
|
+
out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
|
|
11869
|
+
out.push({
|
|
11870
|
+
type: "text",
|
|
11871
|
+
x: chipX + chipW / 2,
|
|
11872
|
+
y: rowY + chipH / 2,
|
|
11873
|
+
text,
|
|
11874
|
+
color: "#ffffff",
|
|
11875
|
+
fontSize: 10,
|
|
11876
|
+
align: "center"
|
|
11877
|
+
});
|
|
11878
|
+
rightEdge = chipX - gap;
|
|
11879
|
+
}
|
|
11880
|
+
return out;
|
|
11881
|
+
}
|
|
11882
|
+
function traceShapes(panel, k, width, height) {
|
|
11883
|
+
const w = panel.width ?? Math.round(width * 0.32);
|
|
11884
|
+
const h = panel.height ?? Math.round(height * 0.28);
|
|
11885
|
+
const x = panel.x ?? width - w - 8;
|
|
11886
|
+
const y = panel.y ?? height - h - 8 - k * (h + 8);
|
|
11887
|
+
const allSamples = panel.series.flatMap((series) => series.samples);
|
|
11888
|
+
let xLo = Math.min(...allSamples.map((p) => p.x));
|
|
11889
|
+
let xHi = Math.max(...allSamples.map((p) => p.x));
|
|
11890
|
+
let yLo = Math.min(...allSamples.map((p) => p.y));
|
|
11891
|
+
let yHi = Math.max(...allSamples.map((p) => p.y));
|
|
11892
|
+
if (xLo === xHi) {
|
|
11893
|
+
xLo -= 1;
|
|
11894
|
+
xHi += 1;
|
|
11895
|
+
}
|
|
11896
|
+
if (yLo === yHi) {
|
|
11897
|
+
yLo -= 1;
|
|
11898
|
+
yHi += 1;
|
|
11899
|
+
}
|
|
11900
|
+
const backgroundColor = panel.backgroundColor ?? "#ffffff";
|
|
11901
|
+
const frameColor = panel.frameColor ?? "#94a3b8";
|
|
11902
|
+
const out = [];
|
|
11903
|
+
out.push({
|
|
11904
|
+
type: "rect",
|
|
11905
|
+
x,
|
|
11906
|
+
y,
|
|
11907
|
+
width: w,
|
|
11908
|
+
height: h,
|
|
11909
|
+
color: backgroundColor,
|
|
11910
|
+
fill: backgroundColor,
|
|
11911
|
+
opacity: panel.backgroundOpacity ?? 0.85
|
|
11912
|
+
});
|
|
11913
|
+
out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
|
|
11914
|
+
panel.series.forEach((series, j) => {
|
|
11915
|
+
const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
|
|
11916
|
+
const mapped = series.samples.map((p) => ({
|
|
11917
|
+
x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
|
|
11918
|
+
y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
|
|
11919
|
+
}));
|
|
11920
|
+
for (let i = 1; i < mapped.length; i++) {
|
|
11921
|
+
out.push({
|
|
11922
|
+
type: "line",
|
|
11923
|
+
x1: mapped[i - 1].x,
|
|
11924
|
+
y1: mapped[i - 1].y,
|
|
11925
|
+
x2: mapped[i].x,
|
|
11926
|
+
y2: mapped[i].y,
|
|
11927
|
+
color,
|
|
11928
|
+
lineWidth: 1.5
|
|
11929
|
+
});
|
|
11930
|
+
}
|
|
11931
|
+
if (mapped.length > 0) {
|
|
11932
|
+
const last = mapped[mapped.length - 1];
|
|
11933
|
+
out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
|
|
11934
|
+
}
|
|
11935
|
+
if (series.label) {
|
|
11936
|
+
out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
|
|
11937
|
+
}
|
|
11938
|
+
});
|
|
11939
|
+
if (panel.yLabel) {
|
|
11940
|
+
out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
11941
|
+
}
|
|
11942
|
+
if (panel.xLabel) {
|
|
11943
|
+
out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
11944
|
+
}
|
|
11945
|
+
return out;
|
|
11946
|
+
}
|
|
11947
|
+
var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
|
|
11817
11948
|
var init_LearningCanvas = __esm({
|
|
11818
11949
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
11819
11950
|
"use client";
|
|
11820
11951
|
init_cn();
|
|
11821
11952
|
init_useEventBus();
|
|
11953
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
11954
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
11822
11955
|
LearningCanvas = ({
|
|
11823
11956
|
className,
|
|
11824
11957
|
width = 600,
|
|
11825
11958
|
height = 400,
|
|
11826
11959
|
backgroundColor,
|
|
11827
11960
|
shapes = [],
|
|
11961
|
+
readouts,
|
|
11962
|
+
traces,
|
|
11828
11963
|
interactive = false,
|
|
11829
11964
|
animate = false,
|
|
11830
11965
|
onShapeClick,
|
|
@@ -11850,6 +11985,12 @@ var init_LearningCanvas = __esm({
|
|
|
11850
11985
|
}
|
|
11851
11986
|
return -1;
|
|
11852
11987
|
}, [shapes]);
|
|
11988
|
+
const derivedShapes = useMemo(() => {
|
|
11989
|
+
if (!traces?.length && !readouts?.length) return shapes;
|
|
11990
|
+
const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
|
|
11991
|
+
const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
|
|
11992
|
+
return [...shapes, ...traceOut, ...readoutOut];
|
|
11993
|
+
}, [shapes, traces, readouts, width, height]);
|
|
11853
11994
|
const draw = useCallback(() => {
|
|
11854
11995
|
const canvas = canvasRef.current;
|
|
11855
11996
|
if (!canvas) return;
|
|
@@ -11866,13 +12007,13 @@ var init_LearningCanvas = __esm({
|
|
|
11866
12007
|
ctx.fillStyle = backgroundColor;
|
|
11867
12008
|
ctx.fillRect(0, 0, width, height);
|
|
11868
12009
|
}
|
|
11869
|
-
for (const shape of
|
|
11870
|
-
if (shape.type !== "text") drawShape(ctx, shape, width, height);
|
|
12010
|
+
for (const shape of derivedShapes) {
|
|
12011
|
+
if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11871
12012
|
}
|
|
11872
|
-
for (const shape of
|
|
11873
|
-
if (shape.type === "text") drawShape(ctx, shape, width, height);
|
|
12013
|
+
for (const shape of derivedShapes) {
|
|
12014
|
+
if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11874
12015
|
}
|
|
11875
|
-
}, [width, height, backgroundColor,
|
|
12016
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
11876
12017
|
useEffect(() => {
|
|
11877
12018
|
draw();
|
|
11878
12019
|
}, [draw]);
|
|
@@ -13422,7 +13563,363 @@ var init_ComponentPatterns = __esm({
|
|
|
13422
13563
|
AlertPattern.displayName = "AlertPattern";
|
|
13423
13564
|
}
|
|
13424
13565
|
});
|
|
13425
|
-
|
|
13566
|
+
function layoutCircle(nodes, width, height) {
|
|
13567
|
+
const cx = width / 2;
|
|
13568
|
+
const cy = height / 2;
|
|
13569
|
+
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
|
13570
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13571
|
+
const n = nodes.length;
|
|
13572
|
+
nodes.forEach((node, i) => {
|
|
13573
|
+
const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
|
|
13574
|
+
positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
|
|
13575
|
+
});
|
|
13576
|
+
return positions;
|
|
13577
|
+
}
|
|
13578
|
+
function layoutTree(nodes, edges, root, width, height) {
|
|
13579
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
13580
|
+
const idSet = new Set(nodeIds);
|
|
13581
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
13582
|
+
const hasIncoming = /* @__PURE__ */ new Set();
|
|
13583
|
+
for (const e of edges) {
|
|
13584
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
13585
|
+
const list = childrenOf.get(e.from) ?? [];
|
|
13586
|
+
list.push(e.to);
|
|
13587
|
+
childrenOf.set(e.from, list);
|
|
13588
|
+
hasIncoming.add(e.to);
|
|
13589
|
+
}
|
|
13590
|
+
const depth = /* @__PURE__ */ new Map();
|
|
13591
|
+
const treeChildren = /* @__PURE__ */ new Map();
|
|
13592
|
+
const visited = /* @__PURE__ */ new Set();
|
|
13593
|
+
const bfsFrom = (start) => {
|
|
13594
|
+
if (visited.has(start)) return;
|
|
13595
|
+
visited.add(start);
|
|
13596
|
+
depth.set(start, 0);
|
|
13597
|
+
const queue = [start];
|
|
13598
|
+
while (queue.length > 0) {
|
|
13599
|
+
const u = queue.shift();
|
|
13600
|
+
for (const v of childrenOf.get(u) ?? []) {
|
|
13601
|
+
if (visited.has(v)) continue;
|
|
13602
|
+
visited.add(v);
|
|
13603
|
+
depth.set(v, (depth.get(u) ?? 0) + 1);
|
|
13604
|
+
const list = treeChildren.get(u) ?? [];
|
|
13605
|
+
list.push(v);
|
|
13606
|
+
treeChildren.set(u, list);
|
|
13607
|
+
queue.push(v);
|
|
13608
|
+
}
|
|
13609
|
+
}
|
|
13610
|
+
};
|
|
13611
|
+
const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
|
|
13612
|
+
const rootsOrder = [];
|
|
13613
|
+
if (primaryRoot !== void 0) {
|
|
13614
|
+
bfsFrom(primaryRoot);
|
|
13615
|
+
rootsOrder.push(primaryRoot);
|
|
13616
|
+
}
|
|
13617
|
+
for (const id of nodeIds) {
|
|
13618
|
+
if (!visited.has(id)) {
|
|
13619
|
+
bfsFrom(id);
|
|
13620
|
+
rootsOrder.push(id);
|
|
13621
|
+
}
|
|
13622
|
+
}
|
|
13623
|
+
let leafCounter = 0;
|
|
13624
|
+
const xSlot = /* @__PURE__ */ new Map();
|
|
13625
|
+
const assignXSlot = (u) => {
|
|
13626
|
+
const children = treeChildren.get(u) ?? [];
|
|
13627
|
+
if (children.length === 0) {
|
|
13628
|
+
const slot = leafCounter++;
|
|
13629
|
+
xSlot.set(u, slot);
|
|
13630
|
+
return slot;
|
|
13631
|
+
}
|
|
13632
|
+
const childSlots = children.map(assignXSlot);
|
|
13633
|
+
const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
|
|
13634
|
+
xSlot.set(u, avg);
|
|
13635
|
+
return avg;
|
|
13636
|
+
};
|
|
13637
|
+
for (const r of rootsOrder) assignXSlot(r);
|
|
13638
|
+
let maxDepth = 0;
|
|
13639
|
+
for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
|
|
13640
|
+
const colWidth = width / Math.max(1, leafCounter);
|
|
13641
|
+
const rowHeight = height / (maxDepth + 1);
|
|
13642
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13643
|
+
for (const id of nodeIds) {
|
|
13644
|
+
const slot = xSlot.get(id) ?? 0;
|
|
13645
|
+
const d = depth.get(id) ?? 0;
|
|
13646
|
+
positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
|
|
13647
|
+
}
|
|
13648
|
+
return positions;
|
|
13649
|
+
}
|
|
13650
|
+
function layoutLayered(nodes, edges, width, height) {
|
|
13651
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
13652
|
+
const idSet = new Set(nodeIds);
|
|
13653
|
+
const adj = /* @__PURE__ */ new Map();
|
|
13654
|
+
const remainingIndegree = /* @__PURE__ */ new Map();
|
|
13655
|
+
for (const id of nodeIds) remainingIndegree.set(id, 0);
|
|
13656
|
+
for (const e of edges) {
|
|
13657
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
13658
|
+
const list = adj.get(e.from) ?? [];
|
|
13659
|
+
list.push(e.to);
|
|
13660
|
+
adj.set(e.from, list);
|
|
13661
|
+
remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
|
|
13662
|
+
}
|
|
13663
|
+
const layer = /* @__PURE__ */ new Map();
|
|
13664
|
+
const dequeued = /* @__PURE__ */ new Set();
|
|
13665
|
+
const queue = [];
|
|
13666
|
+
for (const id of nodeIds) {
|
|
13667
|
+
if ((remainingIndegree.get(id) ?? 0) === 0) {
|
|
13668
|
+
layer.set(id, 0);
|
|
13669
|
+
queue.push(id);
|
|
13670
|
+
}
|
|
13671
|
+
}
|
|
13672
|
+
while (queue.length > 0) {
|
|
13673
|
+
const u = queue.shift();
|
|
13674
|
+
dequeued.add(u);
|
|
13675
|
+
for (const v of adj.get(u) ?? []) {
|
|
13676
|
+
const candidate = (layer.get(u) ?? 0) + 1;
|
|
13677
|
+
layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
|
|
13678
|
+
remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
|
|
13679
|
+
if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
|
|
13680
|
+
queue.push(v);
|
|
13681
|
+
}
|
|
13682
|
+
}
|
|
13683
|
+
}
|
|
13684
|
+
let baseMaxLayer = 0;
|
|
13685
|
+
for (const id of nodeIds) {
|
|
13686
|
+
if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
|
|
13687
|
+
}
|
|
13688
|
+
const cycleLayer = baseMaxLayer + 1;
|
|
13689
|
+
let maxLayer = baseMaxLayer;
|
|
13690
|
+
for (const id of nodeIds) {
|
|
13691
|
+
if (!dequeued.has(id)) {
|
|
13692
|
+
layer.set(id, cycleLayer);
|
|
13693
|
+
maxLayer = cycleLayer;
|
|
13694
|
+
}
|
|
13695
|
+
}
|
|
13696
|
+
const colWidth = width / Math.max(1, maxLayer + 1);
|
|
13697
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
13698
|
+
for (const id of nodeIds) {
|
|
13699
|
+
const l = layer.get(id) ?? 0;
|
|
13700
|
+
const list = byLayer.get(l) ?? [];
|
|
13701
|
+
list.push(id);
|
|
13702
|
+
byLayer.set(l, list);
|
|
13703
|
+
}
|
|
13704
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13705
|
+
for (const [l, ids] of byLayer) {
|
|
13706
|
+
const rowHeight = height / ids.length;
|
|
13707
|
+
ids.forEach((id, i) => {
|
|
13708
|
+
positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
|
|
13709
|
+
});
|
|
13710
|
+
}
|
|
13711
|
+
return positions;
|
|
13712
|
+
}
|
|
13713
|
+
function computePositions(nodes, edges, layout, root, width, height) {
|
|
13714
|
+
switch (layout) {
|
|
13715
|
+
case "circle":
|
|
13716
|
+
return layoutCircle(nodes, width, height);
|
|
13717
|
+
case "tree":
|
|
13718
|
+
return layoutTree(nodes, edges, root, width, height);
|
|
13719
|
+
case "layered":
|
|
13720
|
+
return layoutLayered(nodes, edges, width, height);
|
|
13721
|
+
case "manual":
|
|
13722
|
+
default: {
|
|
13723
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13724
|
+
for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
|
|
13725
|
+
return positions;
|
|
13726
|
+
}
|
|
13727
|
+
}
|
|
13728
|
+
}
|
|
13729
|
+
var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS, AlgoGraphCanvas;
|
|
13730
|
+
var init_AlgoGraphCanvas = __esm({
|
|
13731
|
+
"components/learning/molecules/AlgoGraphCanvas.tsx"() {
|
|
13732
|
+
"use client";
|
|
13733
|
+
init_atoms();
|
|
13734
|
+
init_Stack();
|
|
13735
|
+
init_LearningCanvas();
|
|
13736
|
+
NODE_STATE_COLOR = {
|
|
13737
|
+
unvisited: "#cbd5e1",
|
|
13738
|
+
frontier: "#f59e0b",
|
|
13739
|
+
current: "#ef4444",
|
|
13740
|
+
visited: "#22c55e",
|
|
13741
|
+
goal: "#8b5cf6",
|
|
13742
|
+
path: "#0ea5e9"
|
|
13743
|
+
};
|
|
13744
|
+
EDGE_STATE_COLOR = {
|
|
13745
|
+
default: "#9ca3af",
|
|
13746
|
+
tree: "#16a34a",
|
|
13747
|
+
relaxed: "#f59e0b",
|
|
13748
|
+
candidate: "#38bdf8",
|
|
13749
|
+
path: "#dc2626"
|
|
13750
|
+
};
|
|
13751
|
+
DEFAULT_NODE_RADIUS = 18;
|
|
13752
|
+
AlgoGraphCanvas = ({
|
|
13753
|
+
className,
|
|
13754
|
+
width = 600,
|
|
13755
|
+
height = 400,
|
|
13756
|
+
title,
|
|
13757
|
+
backgroundColor,
|
|
13758
|
+
nodes = [],
|
|
13759
|
+
edges = [],
|
|
13760
|
+
layout = "manual",
|
|
13761
|
+
root,
|
|
13762
|
+
shapes = [],
|
|
13763
|
+
interactive = false,
|
|
13764
|
+
animate = false,
|
|
13765
|
+
onShapeClick,
|
|
13766
|
+
onNodeClick,
|
|
13767
|
+
isLoading,
|
|
13768
|
+
error
|
|
13769
|
+
}) => {
|
|
13770
|
+
const nodeById = useMemo(() => {
|
|
13771
|
+
const m = /* @__PURE__ */ new Map();
|
|
13772
|
+
for (const n of nodes) m.set(n.id, n);
|
|
13773
|
+
return m;
|
|
13774
|
+
}, [nodes]);
|
|
13775
|
+
const nodeIndexById = useMemo(() => {
|
|
13776
|
+
const m = /* @__PURE__ */ new Map();
|
|
13777
|
+
nodes.forEach((n, i) => m.set(n.id, i));
|
|
13778
|
+
return m;
|
|
13779
|
+
}, [nodes]);
|
|
13780
|
+
const derivedShapes = useMemo(() => {
|
|
13781
|
+
const out = [];
|
|
13782
|
+
const positions = computePositions(nodes, edges, layout, root, width, height);
|
|
13783
|
+
const edgeGeoms = [];
|
|
13784
|
+
for (const e of edges) {
|
|
13785
|
+
const a = nodeById.get(e.from);
|
|
13786
|
+
const b = nodeById.get(e.to);
|
|
13787
|
+
const posA = positions.get(e.from);
|
|
13788
|
+
const posB = positions.get(e.to);
|
|
13789
|
+
if (!a || !b || !posA || !posB) continue;
|
|
13790
|
+
const rA = a.radius ?? DEFAULT_NODE_RADIUS;
|
|
13791
|
+
const rB = b.radius ?? DEFAULT_NODE_RADIUS;
|
|
13792
|
+
const dx = posB.x - posA.x;
|
|
13793
|
+
const dy = posB.y - posA.y;
|
|
13794
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
13795
|
+
const ux = dx / dist;
|
|
13796
|
+
const uy = dy / dist;
|
|
13797
|
+
edgeGeoms.push({
|
|
13798
|
+
directed: e.directed ?? false,
|
|
13799
|
+
x1: posA.x + ux * rA,
|
|
13800
|
+
y1: posA.y + uy * rA,
|
|
13801
|
+
x2: posB.x - ux * rB,
|
|
13802
|
+
y2: posB.y - uy * rB,
|
|
13803
|
+
color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
|
|
13804
|
+
label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
|
|
13805
|
+
});
|
|
13806
|
+
}
|
|
13807
|
+
for (const g of edgeGeoms) {
|
|
13808
|
+
out.push({
|
|
13809
|
+
type: g.directed ? "arrow" : "line",
|
|
13810
|
+
x1: g.x1,
|
|
13811
|
+
y1: g.y1,
|
|
13812
|
+
x2: g.x2,
|
|
13813
|
+
y2: g.y2,
|
|
13814
|
+
color: g.color,
|
|
13815
|
+
lineWidth: 2
|
|
13816
|
+
});
|
|
13817
|
+
}
|
|
13818
|
+
for (const g of edgeGeoms) {
|
|
13819
|
+
if (g.label === void 0) continue;
|
|
13820
|
+
const midX = (g.x1 + g.x2) / 2;
|
|
13821
|
+
const midY = (g.y1 + g.y2) / 2;
|
|
13822
|
+
const dx = g.x2 - g.x1;
|
|
13823
|
+
const dy = g.y2 - g.y1;
|
|
13824
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
13825
|
+
const perpX = -(dy / dist);
|
|
13826
|
+
const perpY = dx / dist;
|
|
13827
|
+
out.push({
|
|
13828
|
+
type: "text",
|
|
13829
|
+
x: midX + perpX * 10,
|
|
13830
|
+
y: midY + perpY * 10,
|
|
13831
|
+
text: g.label,
|
|
13832
|
+
fontSize: 11,
|
|
13833
|
+
align: "center",
|
|
13834
|
+
color: "#374151"
|
|
13835
|
+
});
|
|
13836
|
+
}
|
|
13837
|
+
const nodeGeoms = [];
|
|
13838
|
+
for (const n of nodes) {
|
|
13839
|
+
const pos = positions.get(n.id);
|
|
13840
|
+
if (!pos) continue;
|
|
13841
|
+
nodeGeoms.push({
|
|
13842
|
+
id: n.id,
|
|
13843
|
+
x: pos.x,
|
|
13844
|
+
y: pos.y,
|
|
13845
|
+
radius: n.radius ?? DEFAULT_NODE_RADIUS,
|
|
13846
|
+
color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
|
|
13847
|
+
label: n.label,
|
|
13848
|
+
badge: n.badge
|
|
13849
|
+
});
|
|
13850
|
+
}
|
|
13851
|
+
for (const g of nodeGeoms) {
|
|
13852
|
+
out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
|
|
13853
|
+
}
|
|
13854
|
+
const badgeGeoms = [];
|
|
13855
|
+
for (const g of nodeGeoms) {
|
|
13856
|
+
if (!g.badge) continue;
|
|
13857
|
+
const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
|
|
13858
|
+
badgeGeoms.push({
|
|
13859
|
+
cx: g.x + g.radius * 0.75,
|
|
13860
|
+
cy: g.y - g.radius * 0.75,
|
|
13861
|
+
w,
|
|
13862
|
+
h: 14,
|
|
13863
|
+
// Borderless pill: same color drives both stroke and fill.
|
|
13864
|
+
color: g.badge.color ?? "#1e293b",
|
|
13865
|
+
text: g.badge.text
|
|
13866
|
+
});
|
|
13867
|
+
}
|
|
13868
|
+
for (const b of badgeGeoms) {
|
|
13869
|
+
out.push({ type: "rect", x: b.cx - b.w / 2, y: b.cy - b.h / 2, width: b.w, height: b.h, color: b.color, fill: b.color });
|
|
13870
|
+
}
|
|
13871
|
+
for (const b of badgeGeoms) {
|
|
13872
|
+
out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
|
|
13873
|
+
}
|
|
13874
|
+
for (const g of nodeGeoms) {
|
|
13875
|
+
if (g.label === void 0) continue;
|
|
13876
|
+
out.push({
|
|
13877
|
+
type: "text",
|
|
13878
|
+
x: g.x,
|
|
13879
|
+
y: g.y + g.radius + 14,
|
|
13880
|
+
text: g.label,
|
|
13881
|
+
fontSize: 12,
|
|
13882
|
+
align: "center",
|
|
13883
|
+
color: "#111827"
|
|
13884
|
+
});
|
|
13885
|
+
}
|
|
13886
|
+
out.push(...shapes);
|
|
13887
|
+
return out;
|
|
13888
|
+
}, [nodes, edges, layout, root, width, height, nodeById, shapes]);
|
|
13889
|
+
const handleShapeClick = useCallback(
|
|
13890
|
+
(payload) => {
|
|
13891
|
+
if (payload.type === "circle" && payload.id) {
|
|
13892
|
+
const node = nodeById.get(payload.id);
|
|
13893
|
+
const idx = nodeIndexById.get(payload.id);
|
|
13894
|
+
if (node && idx !== void 0) {
|
|
13895
|
+
onNodeClick?.({ id: node.id, label: node.label, index: idx });
|
|
13896
|
+
}
|
|
13897
|
+
}
|
|
13898
|
+
onShapeClick?.(payload);
|
|
13899
|
+
},
|
|
13900
|
+
[nodeById, nodeIndexById, onNodeClick, onShapeClick]
|
|
13901
|
+
);
|
|
13902
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
13903
|
+
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13904
|
+
/* @__PURE__ */ jsx(
|
|
13905
|
+
LearningCanvas,
|
|
13906
|
+
{
|
|
13907
|
+
width,
|
|
13908
|
+
height,
|
|
13909
|
+
backgroundColor,
|
|
13910
|
+
shapes: derivedShapes,
|
|
13911
|
+
interactive,
|
|
13912
|
+
animate,
|
|
13913
|
+
onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
|
|
13914
|
+
isLoading,
|
|
13915
|
+
error
|
|
13916
|
+
}
|
|
13917
|
+
)
|
|
13918
|
+
] }) });
|
|
13919
|
+
};
|
|
13920
|
+
}
|
|
13921
|
+
});
|
|
13922
|
+
var DEFAULT_BAR_COLOR, DEFAULT_CELL_COLOR, DEFAULT_POINTER_COLOR, POINTER_BAND, TOP_PAD, PANEL_FAMILY_ORDER, RANGE_COLOR_DEFAULT, RANGE_FILL_OPACITY, BRACKET_TOP_OFFSET, BRACKET_ROW_H, BRACKET_TICK_H, BRACKET_LABEL_OFFSET, SLOT_EMPTY_FILL, SLOT_EMPTY_STROKE, SLOT_FILLED_STROKE, SLOT_HIGHLIGHT_DEFAULT, SLOT_VALUE_TEXT_COLOR, FRAME_ACTIVE_COLOR, FRAME_RETURNING_COLOR, FRAME_DONE_COLOR, FRAME_LABEL_COLOR, FRAME_DETAIL_COLOR, FRAME_TWO_LINE_MIN_H, BUCKET_INDEX_FILL, BUCKET_INDEX_STROKE, BUCKET_INDEX_TEXT, BUCKET_ENTRY_TEXT, BUCKET_ENTRY_DEFAULT, BUCKET_ENTRY_HIGHLIGHT, BUCKET_ENTRY_PROBING, BUCKET_ENTRY_MIN_W, BUCKET_ENTRY_MAX_W, AXIS_LABEL_COLOR, AXIS_LABEL_FONT_SIZE, CORNER_TEXT_COLOR, CORNER_FONT_SIZE, CORNER_MIN_CELL, CORNER_INSET_X, CORNER_INSET_Y, AUX_PRIMARY_RATIO, AUX_LABEL_BAND, AUX_BASELINE_PAD, AlgorithmCanvas;
|
|
13426
13923
|
var init_AlgorithmCanvas = __esm({
|
|
13427
13924
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
13428
13925
|
"use client";
|
|
@@ -13434,6 +13931,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13434
13931
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
13435
13932
|
POINTER_BAND = 34;
|
|
13436
13933
|
TOP_PAD = 26;
|
|
13934
|
+
PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
|
|
13935
|
+
RANGE_COLOR_DEFAULT = "#3b82f6";
|
|
13936
|
+
RANGE_FILL_OPACITY = 0.15;
|
|
13937
|
+
BRACKET_TOP_OFFSET = 16;
|
|
13938
|
+
BRACKET_ROW_H = 14;
|
|
13939
|
+
BRACKET_TICK_H = 6;
|
|
13940
|
+
BRACKET_LABEL_OFFSET = 6;
|
|
13941
|
+
SLOT_EMPTY_FILL = "#f1f5f9";
|
|
13942
|
+
SLOT_EMPTY_STROKE = "#cbd5e1";
|
|
13943
|
+
SLOT_FILLED_STROKE = "#9ca3af";
|
|
13944
|
+
SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
|
|
13945
|
+
SLOT_VALUE_TEXT_COLOR = "#ffffff";
|
|
13946
|
+
FRAME_ACTIVE_COLOR = "#3b82f6";
|
|
13947
|
+
FRAME_RETURNING_COLOR = "#f59e0b";
|
|
13948
|
+
FRAME_DONE_COLOR = "#94a3b8";
|
|
13949
|
+
FRAME_LABEL_COLOR = "#ffffff";
|
|
13950
|
+
FRAME_DETAIL_COLOR = "#e2e8f0";
|
|
13951
|
+
FRAME_TWO_LINE_MIN_H = 22;
|
|
13952
|
+
BUCKET_INDEX_FILL = "#e2e8f0";
|
|
13953
|
+
BUCKET_INDEX_STROKE = "#9ca3af";
|
|
13954
|
+
BUCKET_INDEX_TEXT = "#374151";
|
|
13955
|
+
BUCKET_ENTRY_TEXT = "#ffffff";
|
|
13956
|
+
BUCKET_ENTRY_DEFAULT = "#3b82f6";
|
|
13957
|
+
BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
|
|
13958
|
+
BUCKET_ENTRY_PROBING = "#38bdf8";
|
|
13959
|
+
BUCKET_ENTRY_MIN_W = 24;
|
|
13960
|
+
BUCKET_ENTRY_MAX_W = 64;
|
|
13961
|
+
AXIS_LABEL_COLOR = "#6b7280";
|
|
13962
|
+
AXIS_LABEL_FONT_SIZE = 10;
|
|
13963
|
+
CORNER_TEXT_COLOR = "#111827";
|
|
13964
|
+
CORNER_FONT_SIZE = 7;
|
|
13965
|
+
CORNER_MIN_CELL = 28;
|
|
13966
|
+
CORNER_INSET_X = 3;
|
|
13967
|
+
CORNER_INSET_Y = 6;
|
|
13968
|
+
AUX_PRIMARY_RATIO = 0.6;
|
|
13969
|
+
AUX_LABEL_BAND = 18;
|
|
13970
|
+
AUX_BASELINE_PAD = 8;
|
|
13437
13971
|
AlgorithmCanvas = ({
|
|
13438
13972
|
className,
|
|
13439
13973
|
width = 600,
|
|
@@ -13443,6 +13977,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13443
13977
|
bars = [],
|
|
13444
13978
|
cells = [],
|
|
13445
13979
|
pointers = [],
|
|
13980
|
+
ranges = [],
|
|
13981
|
+
slots = [],
|
|
13982
|
+
slotOrientation = "horizontal",
|
|
13983
|
+
frames = [],
|
|
13984
|
+
buckets = [],
|
|
13985
|
+
auxBars = [],
|
|
13986
|
+
rowLabels = [],
|
|
13987
|
+
colLabels = [],
|
|
13446
13988
|
shapes = [],
|
|
13447
13989
|
interactive = false,
|
|
13448
13990
|
animate = false,
|
|
@@ -13452,12 +13994,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13452
13994
|
}) => {
|
|
13453
13995
|
const derivedShapes = useMemo(() => {
|
|
13454
13996
|
const out = [];
|
|
13997
|
+
const presence = {
|
|
13998
|
+
bars: bars.length > 0,
|
|
13999
|
+
slots: slots.length > 0,
|
|
14000
|
+
cells: cells.length > 0,
|
|
14001
|
+
buckets: buckets.length > 0,
|
|
14002
|
+
frames: frames.length > 0
|
|
14003
|
+
};
|
|
14004
|
+
const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
|
|
14005
|
+
const panelHeight = height / Math.max(1, panelCount);
|
|
14006
|
+
const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
|
|
14007
|
+
let compactIndex = 0;
|
|
14008
|
+
PANEL_FAMILY_ORDER.forEach((f3) => {
|
|
14009
|
+
if (presence[f3]) {
|
|
14010
|
+
panelY[f3] = compactIndex * panelHeight;
|
|
14011
|
+
compactIndex += 1;
|
|
14012
|
+
}
|
|
14013
|
+
});
|
|
13455
14014
|
if (bars.length > 0) {
|
|
14015
|
+
const panelYBars = panelY.bars;
|
|
13456
14016
|
const slot = width / bars.length;
|
|
13457
14017
|
const barW = slot * 0.8;
|
|
13458
14018
|
const gap = slot * 0.1;
|
|
13459
|
-
const
|
|
13460
|
-
const
|
|
14019
|
+
const bracketRanges = ranges.filter((r) => r.kind === "bracket");
|
|
14020
|
+
const bracketCount = bracketRanges.length;
|
|
14021
|
+
const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
|
|
14022
|
+
const hasAux = auxBars.length > 0;
|
|
14023
|
+
const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
|
|
14024
|
+
const baseline = panelYBars + primaryH - POINTER_BAND;
|
|
14025
|
+
const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
|
|
13461
14026
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
13462
14027
|
bars.forEach((bar, i) => {
|
|
13463
14028
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -13487,6 +14052,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13487
14052
|
});
|
|
13488
14053
|
}
|
|
13489
14054
|
});
|
|
14055
|
+
ranges.forEach((r) => {
|
|
14056
|
+
const kind = r.kind ?? "fill";
|
|
14057
|
+
if (kind !== "fill") return;
|
|
14058
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14059
|
+
out.push({
|
|
14060
|
+
type: "rect",
|
|
14061
|
+
x: r.from * slot,
|
|
14062
|
+
y: panelYBars,
|
|
14063
|
+
width: (r.to - r.from + 1) * slot,
|
|
14064
|
+
height: primaryH,
|
|
14065
|
+
color,
|
|
14066
|
+
fill: color,
|
|
14067
|
+
opacity: RANGE_FILL_OPACITY
|
|
14068
|
+
});
|
|
14069
|
+
if (r.label) {
|
|
14070
|
+
out.push({
|
|
14071
|
+
type: "text",
|
|
14072
|
+
x: r.from * slot + 4,
|
|
14073
|
+
// Sits below the bracket block (if any) so fill and bracket labels never collide.
|
|
14074
|
+
y: panelYBars + 10 + bracketHeadroom,
|
|
14075
|
+
text: r.label,
|
|
14076
|
+
color,
|
|
14077
|
+
fontSize: 10,
|
|
14078
|
+
align: "left"
|
|
14079
|
+
});
|
|
14080
|
+
}
|
|
14081
|
+
});
|
|
14082
|
+
bracketRanges.forEach((r, i) => {
|
|
14083
|
+
const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
|
|
14084
|
+
const x1 = r.from * slot + slot * 0.1;
|
|
14085
|
+
const x2 = (r.to + 1) * slot - slot * 0.1;
|
|
14086
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14087
|
+
out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
|
|
14088
|
+
out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14089
|
+
out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14090
|
+
if (r.label) {
|
|
14091
|
+
out.push({
|
|
14092
|
+
type: "text",
|
|
14093
|
+
x: (x1 + x2) / 2,
|
|
14094
|
+
y: bracketY - BRACKET_LABEL_OFFSET,
|
|
14095
|
+
text: r.label,
|
|
14096
|
+
color,
|
|
14097
|
+
fontSize: 10,
|
|
14098
|
+
align: "center"
|
|
14099
|
+
});
|
|
14100
|
+
}
|
|
14101
|
+
});
|
|
14102
|
+
if (hasAux) {
|
|
14103
|
+
const auxH = panelHeight - primaryH;
|
|
14104
|
+
const slot2 = width / auxBars.length;
|
|
14105
|
+
const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
|
|
14106
|
+
const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
|
|
14107
|
+
const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
14108
|
+
auxBars.forEach((bar, i) => {
|
|
14109
|
+
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
14110
|
+
const bh = Math.max(0, v / maxAuxV * auxUsableH);
|
|
14111
|
+
const x = i * slot2 + slot2 * 0.1;
|
|
14112
|
+
const w = slot2 * 0.8;
|
|
14113
|
+
const color = bar.color ?? DEFAULT_BAR_COLOR;
|
|
14114
|
+
out.push({
|
|
14115
|
+
type: "rect",
|
|
14116
|
+
id: `auxbar-${i}`,
|
|
14117
|
+
x,
|
|
14118
|
+
y: auxBaseline - bh,
|
|
14119
|
+
width: w,
|
|
14120
|
+
height: bh,
|
|
14121
|
+
color,
|
|
14122
|
+
fill: color
|
|
14123
|
+
});
|
|
14124
|
+
const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
|
|
14125
|
+
if (label) {
|
|
14126
|
+
out.push({
|
|
14127
|
+
type: "text",
|
|
14128
|
+
x: x + w / 2,
|
|
14129
|
+
y: auxBaseline - bh - 8,
|
|
14130
|
+
text: label,
|
|
14131
|
+
color: "#374151",
|
|
14132
|
+
fontSize: 11,
|
|
14133
|
+
align: "center"
|
|
14134
|
+
});
|
|
14135
|
+
}
|
|
14136
|
+
});
|
|
14137
|
+
}
|
|
13490
14138
|
pointers.forEach((p) => {
|
|
13491
14139
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
13492
14140
|
const cx = p.index * slot + slot / 2;
|
|
@@ -13494,7 +14142,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13494
14142
|
out.push({
|
|
13495
14143
|
type: "arrow",
|
|
13496
14144
|
x1: cx,
|
|
13497
|
-
y1:
|
|
14145
|
+
y1: panelYBars + primaryH - 18,
|
|
13498
14146
|
x2: cx,
|
|
13499
14147
|
y2: baseline + 4,
|
|
13500
14148
|
color,
|
|
@@ -13504,7 +14152,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13504
14152
|
out.push({
|
|
13505
14153
|
type: "text",
|
|
13506
14154
|
x: cx,
|
|
13507
|
-
y:
|
|
14155
|
+
y: panelYBars + primaryH - 8,
|
|
13508
14156
|
text: p.label,
|
|
13509
14157
|
color,
|
|
13510
14158
|
fontSize: 11,
|
|
@@ -13513,14 +14161,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13513
14161
|
}
|
|
13514
14162
|
});
|
|
13515
14163
|
}
|
|
14164
|
+
if (slots.length > 0) {
|
|
14165
|
+
const panelYSlots = panelY.slots;
|
|
14166
|
+
const n = slots.length;
|
|
14167
|
+
const vertical = slotOrientation === "vertical";
|
|
14168
|
+
const vBoxH = panelHeight / n;
|
|
14169
|
+
const vBoxW = Math.min(width * 0.5, 120);
|
|
14170
|
+
const vBoxX = (width - vBoxW) / 2;
|
|
14171
|
+
const hCellW = width / n;
|
|
14172
|
+
const hBoxW = hCellW * 0.82;
|
|
14173
|
+
const hBoxH = Math.min(panelHeight * 0.6, 48);
|
|
14174
|
+
const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
|
|
14175
|
+
const slotBox = (i) => vertical ? { x: vBoxX, y: panelYSlots + panelHeight - (i + 1) * vBoxH, width: vBoxW, height: vBoxH } : { x: i * hCellW + (hCellW - hBoxW) / 2, y: hBoxY, width: hBoxW, height: hBoxH };
|
|
14176
|
+
slots.forEach((s, i) => {
|
|
14177
|
+
const box = slotBox(i);
|
|
14178
|
+
const state = s.state ?? "filled";
|
|
14179
|
+
const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
|
|
14180
|
+
const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
|
|
14181
|
+
out.push({
|
|
14182
|
+
type: "rect",
|
|
14183
|
+
id: `slot-${i}`,
|
|
14184
|
+
x: box.x,
|
|
14185
|
+
y: box.y,
|
|
14186
|
+
width: box.width,
|
|
14187
|
+
height: box.height,
|
|
14188
|
+
color: stroke,
|
|
14189
|
+
fill
|
|
14190
|
+
});
|
|
14191
|
+
if (s.value != null && state !== "empty") {
|
|
14192
|
+
out.push({
|
|
14193
|
+
type: "text",
|
|
14194
|
+
x: box.x + box.width / 2,
|
|
14195
|
+
y: box.y + box.height / 2,
|
|
14196
|
+
text: String(s.value),
|
|
14197
|
+
color: SLOT_VALUE_TEXT_COLOR,
|
|
14198
|
+
fontSize: 12,
|
|
14199
|
+
align: "center"
|
|
14200
|
+
});
|
|
14201
|
+
}
|
|
14202
|
+
});
|
|
14203
|
+
if (bars.length === 0) {
|
|
14204
|
+
pointers.forEach((p) => {
|
|
14205
|
+
if (p.index < 0 || p.index >= slots.length) return;
|
|
14206
|
+
const box = slotBox(p.index);
|
|
14207
|
+
const color = p.color ?? DEFAULT_POINTER_COLOR;
|
|
14208
|
+
if (vertical) {
|
|
14209
|
+
const cy = box.y + box.height / 2;
|
|
14210
|
+
out.push({
|
|
14211
|
+
type: "arrow",
|
|
14212
|
+
x1: box.x + box.width + 34,
|
|
14213
|
+
y1: cy,
|
|
14214
|
+
x2: box.x + box.width + 4,
|
|
14215
|
+
y2: cy,
|
|
14216
|
+
color,
|
|
14217
|
+
lineWidth: 2
|
|
14218
|
+
});
|
|
14219
|
+
if (p.label) {
|
|
14220
|
+
out.push({
|
|
14221
|
+
type: "text",
|
|
14222
|
+
x: box.x + box.width + 38,
|
|
14223
|
+
y: cy,
|
|
14224
|
+
text: p.label,
|
|
14225
|
+
color,
|
|
14226
|
+
fontSize: 11,
|
|
14227
|
+
align: "left"
|
|
14228
|
+
});
|
|
14229
|
+
}
|
|
14230
|
+
} else {
|
|
14231
|
+
const cx = box.x + box.width / 2;
|
|
14232
|
+
out.push({
|
|
14233
|
+
type: "arrow",
|
|
14234
|
+
x1: cx,
|
|
14235
|
+
y1: panelYSlots + panelHeight - 18,
|
|
14236
|
+
x2: cx,
|
|
14237
|
+
y2: box.y + box.height + 4,
|
|
14238
|
+
color,
|
|
14239
|
+
lineWidth: 2
|
|
14240
|
+
});
|
|
14241
|
+
if (p.label) {
|
|
14242
|
+
out.push({
|
|
14243
|
+
type: "text",
|
|
14244
|
+
x: cx,
|
|
14245
|
+
y: panelYSlots + panelHeight - 8,
|
|
14246
|
+
text: p.label,
|
|
14247
|
+
color,
|
|
14248
|
+
fontSize: 11,
|
|
14249
|
+
align: "center"
|
|
14250
|
+
});
|
|
14251
|
+
}
|
|
14252
|
+
}
|
|
14253
|
+
});
|
|
14254
|
+
}
|
|
14255
|
+
}
|
|
13516
14256
|
if (cells.length > 0) {
|
|
14257
|
+
const panelYCells = panelY.cells;
|
|
13517
14258
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
13518
14259
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
13519
|
-
const
|
|
13520
|
-
const
|
|
14260
|
+
const colLabelH = colLabels.length > 0 ? 16 : 0;
|
|
14261
|
+
const rowLabelW = rowLabels.length > 0 ? 20 : 0;
|
|
14262
|
+
const gridX0 = rowLabelW;
|
|
14263
|
+
const gridY0 = panelYCells + colLabelH;
|
|
14264
|
+
const cw = (width - rowLabelW) / maxCol;
|
|
14265
|
+
const ch = (panelHeight - colLabelH) / maxRow;
|
|
13521
14266
|
cells.forEach((c, i) => {
|
|
13522
|
-
const x = c.col * cw;
|
|
13523
|
-
const y = c.row * ch;
|
|
14267
|
+
const x = gridX0 + c.col * cw;
|
|
14268
|
+
const y = gridY0 + c.row * ch;
|
|
13524
14269
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
13525
14270
|
out.push({
|
|
13526
14271
|
type: "rect",
|
|
@@ -13544,11 +14289,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13544
14289
|
align: "center"
|
|
13545
14290
|
});
|
|
13546
14291
|
}
|
|
14292
|
+
if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
|
|
14293
|
+
const { tl, tr, bl, br } = c.corner;
|
|
14294
|
+
if (tl) {
|
|
14295
|
+
out.push({
|
|
14296
|
+
type: "text",
|
|
14297
|
+
x: x + CORNER_INSET_X,
|
|
14298
|
+
y: y + CORNER_INSET_Y,
|
|
14299
|
+
text: tl,
|
|
14300
|
+
color: CORNER_TEXT_COLOR,
|
|
14301
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14302
|
+
align: "left"
|
|
14303
|
+
});
|
|
14304
|
+
}
|
|
14305
|
+
if (tr) {
|
|
14306
|
+
out.push({
|
|
14307
|
+
type: "text",
|
|
14308
|
+
x: x + cw - CORNER_INSET_X,
|
|
14309
|
+
y: y + CORNER_INSET_Y,
|
|
14310
|
+
text: tr,
|
|
14311
|
+
color: CORNER_TEXT_COLOR,
|
|
14312
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14313
|
+
align: "right"
|
|
14314
|
+
});
|
|
14315
|
+
}
|
|
14316
|
+
if (bl) {
|
|
14317
|
+
out.push({
|
|
14318
|
+
type: "text",
|
|
14319
|
+
x: x + CORNER_INSET_X,
|
|
14320
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14321
|
+
text: bl,
|
|
14322
|
+
color: CORNER_TEXT_COLOR,
|
|
14323
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14324
|
+
align: "left"
|
|
14325
|
+
});
|
|
14326
|
+
}
|
|
14327
|
+
if (br) {
|
|
14328
|
+
out.push({
|
|
14329
|
+
type: "text",
|
|
14330
|
+
x: x + cw - CORNER_INSET_X,
|
|
14331
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14332
|
+
text: br,
|
|
14333
|
+
color: CORNER_TEXT_COLOR,
|
|
14334
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14335
|
+
align: "right"
|
|
14336
|
+
});
|
|
14337
|
+
}
|
|
14338
|
+
}
|
|
14339
|
+
});
|
|
14340
|
+
colLabels.forEach((l) => {
|
|
14341
|
+
out.push({
|
|
14342
|
+
type: "text",
|
|
14343
|
+
x: gridX0 + l.index * cw + cw / 2,
|
|
14344
|
+
y: panelYCells + colLabelH / 2,
|
|
14345
|
+
text: l.text,
|
|
14346
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14347
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14348
|
+
align: "center"
|
|
14349
|
+
});
|
|
14350
|
+
});
|
|
14351
|
+
rowLabels.forEach((l) => {
|
|
14352
|
+
out.push({
|
|
14353
|
+
type: "text",
|
|
14354
|
+
x: rowLabelW - 6,
|
|
14355
|
+
y: gridY0 + l.index * ch + ch / 2,
|
|
14356
|
+
text: l.text,
|
|
14357
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14358
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14359
|
+
align: "right"
|
|
14360
|
+
});
|
|
14361
|
+
});
|
|
14362
|
+
}
|
|
14363
|
+
if (buckets.length > 0) {
|
|
14364
|
+
const panelYBuckets = panelY.buckets;
|
|
14365
|
+
const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
|
|
14366
|
+
const rowH = panelHeight / bucketCount;
|
|
14367
|
+
const indexColW = Math.min(width * 0.12, 40);
|
|
14368
|
+
const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
|
|
14369
|
+
const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
|
|
14370
|
+
const maxVisible = Math.floor((width - indexColW - 4) / entryW);
|
|
14371
|
+
buckets.forEach((b) => {
|
|
14372
|
+
const rowY = panelYBuckets + b.index * rowH;
|
|
14373
|
+
out.push({
|
|
14374
|
+
type: "rect",
|
|
14375
|
+
id: `bucket-index-${b.index}`,
|
|
14376
|
+
x: 2,
|
|
14377
|
+
y: rowY + 2,
|
|
14378
|
+
width: indexColW - 4,
|
|
14379
|
+
height: rowH - 4,
|
|
14380
|
+
color: BUCKET_INDEX_STROKE,
|
|
14381
|
+
fill: BUCKET_INDEX_FILL
|
|
14382
|
+
});
|
|
14383
|
+
out.push({
|
|
14384
|
+
type: "text",
|
|
14385
|
+
x: 2 + (indexColW - 4) / 2,
|
|
14386
|
+
y: rowY + rowH / 2,
|
|
14387
|
+
text: String(b.index),
|
|
14388
|
+
color: BUCKET_INDEX_TEXT,
|
|
14389
|
+
fontSize: 10,
|
|
14390
|
+
align: "center"
|
|
14391
|
+
});
|
|
14392
|
+
const overflow = b.entries.length > maxVisible;
|
|
14393
|
+
const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
|
|
14394
|
+
for (let j = 0; j < visibleCount; j++) {
|
|
14395
|
+
const entry = b.entries[j];
|
|
14396
|
+
const ex = indexColW + 4 + j * entryW;
|
|
14397
|
+
const state = entry.state ?? "default";
|
|
14398
|
+
const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
|
|
14399
|
+
out.push({
|
|
14400
|
+
type: "rect",
|
|
14401
|
+
id: `bucket-${b.index}-${j}`,
|
|
14402
|
+
x: ex,
|
|
14403
|
+
y: rowY + 2,
|
|
14404
|
+
width: entryW - 2,
|
|
14405
|
+
height: rowH - 4,
|
|
14406
|
+
color: fill,
|
|
14407
|
+
fill
|
|
14408
|
+
});
|
|
14409
|
+
if (entryW >= 20 && rowH >= 16) {
|
|
14410
|
+
out.push({
|
|
14411
|
+
type: "text",
|
|
14412
|
+
x: ex + (entryW - 2) / 2,
|
|
14413
|
+
y: rowY + rowH / 2,
|
|
14414
|
+
text: entry.label,
|
|
14415
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14416
|
+
fontSize: 10,
|
|
14417
|
+
align: "center"
|
|
14418
|
+
});
|
|
14419
|
+
}
|
|
14420
|
+
}
|
|
14421
|
+
if (overflow) {
|
|
14422
|
+
const ex = indexColW + 4 + visibleCount * entryW;
|
|
14423
|
+
out.push({
|
|
14424
|
+
type: "rect",
|
|
14425
|
+
id: `bucket-${b.index}-overflow`,
|
|
14426
|
+
x: ex,
|
|
14427
|
+
y: rowY + 2,
|
|
14428
|
+
width: entryW - 2,
|
|
14429
|
+
height: rowH - 4,
|
|
14430
|
+
color: BUCKET_ENTRY_DEFAULT,
|
|
14431
|
+
fill: BUCKET_ENTRY_DEFAULT
|
|
14432
|
+
});
|
|
14433
|
+
out.push({
|
|
14434
|
+
type: "text",
|
|
14435
|
+
x: ex + (entryW - 2) / 2,
|
|
14436
|
+
y: rowY + rowH / 2,
|
|
14437
|
+
text: `+${b.entries.length - visibleCount}`,
|
|
14438
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14439
|
+
fontSize: 10,
|
|
14440
|
+
align: "center"
|
|
14441
|
+
});
|
|
14442
|
+
}
|
|
14443
|
+
});
|
|
14444
|
+
}
|
|
14445
|
+
if (frames.length > 0) {
|
|
14446
|
+
const panelYFrames = panelY.frames;
|
|
14447
|
+
const n = frames.length;
|
|
14448
|
+
const frameH = panelHeight / n;
|
|
14449
|
+
const x = 8;
|
|
14450
|
+
const w = width - 16;
|
|
14451
|
+
frames.forEach((f3, i) => {
|
|
14452
|
+
const y = panelYFrames + panelHeight - (i + 1) * frameH;
|
|
14453
|
+
const state = f3.state ?? "active";
|
|
14454
|
+
const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
|
|
14455
|
+
out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
|
|
14456
|
+
if (frameH >= FRAME_TWO_LINE_MIN_H) {
|
|
14457
|
+
out.push({
|
|
14458
|
+
type: "text",
|
|
14459
|
+
x: 16,
|
|
14460
|
+
y: y + frameH * 0.35,
|
|
14461
|
+
text: f3.label,
|
|
14462
|
+
color: FRAME_LABEL_COLOR,
|
|
14463
|
+
fontSize: 10,
|
|
14464
|
+
align: "left"
|
|
14465
|
+
});
|
|
14466
|
+
if (f3.detail) {
|
|
14467
|
+
out.push({
|
|
14468
|
+
type: "text",
|
|
14469
|
+
x: 16,
|
|
14470
|
+
y: y + frameH * 0.7,
|
|
14471
|
+
text: f3.detail,
|
|
14472
|
+
color: FRAME_DETAIL_COLOR,
|
|
14473
|
+
fontSize: 10,
|
|
14474
|
+
align: "left"
|
|
14475
|
+
});
|
|
14476
|
+
}
|
|
14477
|
+
} else {
|
|
14478
|
+
out.push({
|
|
14479
|
+
type: "text",
|
|
14480
|
+
x: 16,
|
|
14481
|
+
y: y + frameH / 2,
|
|
14482
|
+
text: f3.label,
|
|
14483
|
+
color: FRAME_LABEL_COLOR,
|
|
14484
|
+
fontSize: 10,
|
|
14485
|
+
align: "left"
|
|
14486
|
+
});
|
|
14487
|
+
}
|
|
13547
14488
|
});
|
|
13548
14489
|
}
|
|
13549
14490
|
out.push(...shapes);
|
|
13550
14491
|
return out;
|
|
13551
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
14492
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
13552
14493
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
13553
14494
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13554
14495
|
/* @__PURE__ */ jsx(
|
|
@@ -14562,6 +15503,12 @@ function LearningScene3D({
|
|
|
14562
15503
|
const unitId = event.payload?.unitId;
|
|
14563
15504
|
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
14564
15505
|
});
|
|
15506
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
|
|
15507
|
+
const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
|
|
15508
|
+
if (unknownKeys.length > 0) {
|
|
15509
|
+
sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
|
|
15510
|
+
}
|
|
15511
|
+
}
|
|
14565
15512
|
const props3d = {
|
|
14566
15513
|
drawables,
|
|
14567
15514
|
isLoading,
|
|
@@ -14627,7 +15574,7 @@ function cylinderBetween(from, to, radius, color) {
|
|
|
14627
15574
|
material: { color }
|
|
14628
15575
|
};
|
|
14629
15576
|
}
|
|
14630
|
-
function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
15577
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
14631
15578
|
const len = segmentLength(from, to);
|
|
14632
15579
|
if (len < 1e-6) return null;
|
|
14633
15580
|
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
@@ -14655,6 +15602,7 @@ function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
|
14655
15602
|
};
|
|
14656
15603
|
return {
|
|
14657
15604
|
type: "draw-group",
|
|
15605
|
+
...id !== void 0 ? { id } : {},
|
|
14658
15606
|
position: { x: from[0], y: from[1], z: from[2] },
|
|
14659
15607
|
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
14660
15608
|
};
|
|
@@ -14681,20 +15629,228 @@ function get3DClickPayload(onShapeClick, idToIndex) {
|
|
|
14681
15629
|
if (!onShapeClick) return void 0;
|
|
14682
15630
|
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
14683
15631
|
}
|
|
14684
|
-
|
|
15632
|
+
function polylineTube(points, radius, color, opts) {
|
|
15633
|
+
const maxSegments = opts?.maxSegments ?? 128;
|
|
15634
|
+
let pts = points;
|
|
15635
|
+
if (pts.length - 1 > maxSegments) {
|
|
15636
|
+
const step = (pts.length - 1) / maxSegments;
|
|
15637
|
+
const kept = [pts[0]];
|
|
15638
|
+
for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
|
|
15639
|
+
kept.push(pts[pts.length - 1]);
|
|
15640
|
+
pts = kept;
|
|
15641
|
+
}
|
|
15642
|
+
const out = [];
|
|
15643
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
15644
|
+
const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
|
|
15645
|
+
if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
|
|
15646
|
+
}
|
|
15647
|
+
return out;
|
|
15648
|
+
}
|
|
15649
|
+
function heightFieldMesh(spec) {
|
|
15650
|
+
const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
|
|
15651
|
+
const flatShading = spec.flatShading ?? true;
|
|
15652
|
+
const vertices = [];
|
|
15653
|
+
for (let iy = 0; iy < ny; iy++) {
|
|
15654
|
+
for (let ix = 0; ix < nx; ix++) {
|
|
15655
|
+
vertices.push([
|
|
15656
|
+
x + (ix - (nx - 1) / 2) * spacing,
|
|
15657
|
+
y + (iy - (ny - 1) / 2) * spacing,
|
|
15658
|
+
heights[iy * nx + ix] ?? 0
|
|
15659
|
+
]);
|
|
15660
|
+
}
|
|
15661
|
+
}
|
|
15662
|
+
const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
|
|
15663
|
+
const facesByBand = /* @__PURE__ */ new Map();
|
|
15664
|
+
for (let iy = 0; iy < ny - 1; iy++) {
|
|
15665
|
+
for (let ix = 0; ix < nx - 1; ix++) {
|
|
15666
|
+
const v00 = iy * nx + ix;
|
|
15667
|
+
const v10 = iy * nx + ix + 1;
|
|
15668
|
+
const v01 = (iy + 1) * nx + ix;
|
|
15669
|
+
const v11 = (iy + 1) * nx + ix + 1;
|
|
15670
|
+
for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
|
|
15671
|
+
const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
|
|
15672
|
+
let band = null;
|
|
15673
|
+
for (const b of bands) {
|
|
15674
|
+
if ((b.min ?? -Infinity) <= centroid) band = b;
|
|
15675
|
+
}
|
|
15676
|
+
const key = bands.length > 0 ? band : null;
|
|
15677
|
+
const list = facesByBand.get(key) ?? [];
|
|
15678
|
+
list.push(face);
|
|
15679
|
+
facesByBand.set(key, list);
|
|
15680
|
+
}
|
|
15681
|
+
}
|
|
15682
|
+
}
|
|
15683
|
+
const out = [];
|
|
15684
|
+
for (const [band, faces] of facesByBand) {
|
|
15685
|
+
if (faces.length === 0) continue;
|
|
15686
|
+
out.push({
|
|
15687
|
+
type: "draw-mesh",
|
|
15688
|
+
shape: "polyhedron",
|
|
15689
|
+
position: { x: 0, y: 0, z: 0 },
|
|
15690
|
+
vertices,
|
|
15691
|
+
faces,
|
|
15692
|
+
pivot: "center",
|
|
15693
|
+
material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
|
|
15694
|
+
...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
|
|
15695
|
+
});
|
|
15696
|
+
}
|
|
15697
|
+
return out;
|
|
15698
|
+
}
|
|
15699
|
+
function arrowField(vectors, opts) {
|
|
15700
|
+
const scale = opts?.scale ?? 1;
|
|
15701
|
+
const out = [];
|
|
15702
|
+
for (const v of vectors) {
|
|
15703
|
+
const to = [
|
|
15704
|
+
v.from[0] + v.delta[0] * scale,
|
|
15705
|
+
v.from[1] + v.delta[1] * scale,
|
|
15706
|
+
v.from[2] + v.delta[2] * scale
|
|
15707
|
+
];
|
|
15708
|
+
const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
|
|
15709
|
+
if (arrow) out.push(arrow);
|
|
15710
|
+
if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
|
|
15711
|
+
}
|
|
15712
|
+
return out;
|
|
15713
|
+
}
|
|
15714
|
+
function helixDrawables(spec, opts) {
|
|
15715
|
+
const count = spec.count ?? spec.rungs?.length ?? 0;
|
|
15716
|
+
const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
|
|
15717
|
+
const radius = spec.radius ?? 1;
|
|
15718
|
+
const rise = spec.rise ?? 0.34;
|
|
15719
|
+
const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
|
|
15720
|
+
const strandAColor = spec.strandAColor ?? "#38bdf8";
|
|
15721
|
+
const strandBColor = spec.strandBColor ?? "#fb923c";
|
|
15722
|
+
const backboneRadius = spec.backboneRadius ?? 0.16;
|
|
15723
|
+
const rungRadius = spec.rungRadius ?? 0.12;
|
|
15724
|
+
const cx = spec.x ?? 0;
|
|
15725
|
+
const cy = spec.y ?? 0;
|
|
15726
|
+
const cz = spec.z ?? 0;
|
|
15727
|
+
const unwoundCount = spec.unwoundCount ?? 0;
|
|
15728
|
+
const unwindSpread = spec.unwindSpread ?? 1.8;
|
|
15729
|
+
const strandA = [];
|
|
15730
|
+
const strandB = [];
|
|
15731
|
+
for (let i = 0; i < count; i++) {
|
|
15732
|
+
const yi = cy + (i - (count - 1) / 2) * rise;
|
|
15733
|
+
const theta = i * twistRad;
|
|
15734
|
+
const s = i < unwoundCount ? unwindSpread : 1;
|
|
15735
|
+
strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
|
|
15736
|
+
strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
|
|
15737
|
+
}
|
|
15738
|
+
const out = [];
|
|
15739
|
+
for (let i = 0; i < count; i++) {
|
|
15740
|
+
out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
|
|
15741
|
+
out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
|
|
15742
|
+
if (i > 0) {
|
|
15743
|
+
const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
|
|
15744
|
+
if (segA) out.push(segA);
|
|
15745
|
+
const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
|
|
15746
|
+
if (segB) out.push(segB);
|
|
15747
|
+
}
|
|
15748
|
+
const rung = rungs[i];
|
|
15749
|
+
const rungColor = rung.color ?? "#94a3b8";
|
|
15750
|
+
const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
|
|
15751
|
+
if (rod) out.push(rod);
|
|
15752
|
+
const mid = [
|
|
15753
|
+
(strandA[i][0] + strandB[i][0]) / 2,
|
|
15754
|
+
(strandA[i][1] + strandB[i][1]) / 2,
|
|
15755
|
+
(strandA[i][2] + strandB[i][2]) / 2
|
|
15756
|
+
];
|
|
15757
|
+
const markerRadius = rung.radius ?? rungRadius;
|
|
15758
|
+
out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
|
|
15759
|
+
if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
|
|
15760
|
+
}
|
|
15761
|
+
return out;
|
|
15762
|
+
}
|
|
15763
|
+
function latticeDrawables(spec, opts) {
|
|
15764
|
+
const nx = spec.nx ?? 2;
|
|
15765
|
+
const ny = spec.ny ?? 2;
|
|
15766
|
+
const nz = spec.nz ?? 2;
|
|
15767
|
+
const latticeConstant = spec.latticeConstant ?? 2;
|
|
15768
|
+
const bondRadius = spec.bondRadius ?? 0.06;
|
|
15769
|
+
const highlightCell = spec.highlightCell ?? false;
|
|
15770
|
+
const dimColor = spec.dimColor ?? "#475569";
|
|
15771
|
+
const showLabels = spec.showLabels ?? false;
|
|
15772
|
+
const selectedColor = spec.selectedColor ?? "#f59e0b";
|
|
15773
|
+
const posByKey = /* @__PURE__ */ new Map();
|
|
15774
|
+
const inCellByKey = /* @__PURE__ */ new Map();
|
|
15775
|
+
const out = [];
|
|
15776
|
+
for (const site of spec.basis) {
|
|
15777
|
+
const snx = site.xEdge ? nx + 1 : nx;
|
|
15778
|
+
const sny = site.yEdge ? ny + 1 : ny;
|
|
15779
|
+
const snz = site.zEdge ? nz + 1 : nz;
|
|
15780
|
+
for (let i = 0; i < snx; i++) {
|
|
15781
|
+
for (let j = 0; j < sny; j++) {
|
|
15782
|
+
for (let k = 0; k < snz; k++) {
|
|
15783
|
+
const key = `${site.key}-${i}-${j}-${k}`;
|
|
15784
|
+
const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
|
|
15785
|
+
const pos = [
|
|
15786
|
+
(i + site.dx) * latticeConstant - nx * latticeConstant / 2,
|
|
15787
|
+
(j + site.dy) * latticeConstant - ny * latticeConstant / 2,
|
|
15788
|
+
(k + site.dz) * latticeConstant - nz * latticeConstant / 2
|
|
15789
|
+
];
|
|
15790
|
+
posByKey.set(key, pos);
|
|
15791
|
+
inCellByKey.set(key, inCell);
|
|
15792
|
+
const isSelected = spec.selectedId === `lat-${key}`;
|
|
15793
|
+
const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
|
|
15794
|
+
const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
|
|
15795
|
+
out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
|
|
15796
|
+
if (showLabels && site.element) {
|
|
15797
|
+
out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
|
|
15798
|
+
}
|
|
15799
|
+
}
|
|
15800
|
+
}
|
|
15801
|
+
}
|
|
15802
|
+
}
|
|
15803
|
+
const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
|
|
15804
|
+
for (const bond of spec.bonds ?? []) {
|
|
15805
|
+
const fromSite = basisByKey.get(bond.from);
|
|
15806
|
+
const toSite = basisByKey.get(bond.to);
|
|
15807
|
+
if (!fromSite || !toSite) continue;
|
|
15808
|
+
const fnx = fromSite.xEdge ? nx + 1 : nx;
|
|
15809
|
+
const fny = fromSite.yEdge ? ny + 1 : ny;
|
|
15810
|
+
const fnz = fromSite.zEdge ? nz + 1 : nz;
|
|
15811
|
+
const tnx = toSite.xEdge ? nx + 1 : nx;
|
|
15812
|
+
const tny = toSite.yEdge ? ny + 1 : ny;
|
|
15813
|
+
const tnz = toSite.zEdge ? nz + 1 : nz;
|
|
15814
|
+
const bdx = bond.dx ?? 0;
|
|
15815
|
+
const bdy = bond.dy ?? 0;
|
|
15816
|
+
const bdz = bond.dz ?? 0;
|
|
15817
|
+
for (let i = 0; i < fnx; i++) {
|
|
15818
|
+
for (let j = 0; j < fny; j++) {
|
|
15819
|
+
for (let k = 0; k < fnz; k++) {
|
|
15820
|
+
const ti = i + bdx;
|
|
15821
|
+
const tj = j + bdy;
|
|
15822
|
+
const tk = k + bdz;
|
|
15823
|
+
if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
|
|
15824
|
+
const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
|
|
15825
|
+
const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
|
|
15826
|
+
const fromPos = posByKey.get(fromKey);
|
|
15827
|
+
const toPos = posByKey.get(toKey);
|
|
15828
|
+
if (!fromPos || !toPos) continue;
|
|
15829
|
+
const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
|
|
15830
|
+
const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
|
|
15831
|
+
if (seg) out.push(seg);
|
|
15832
|
+
}
|
|
15833
|
+
}
|
|
15834
|
+
}
|
|
15835
|
+
}
|
|
15836
|
+
return out;
|
|
15837
|
+
}
|
|
15838
|
+
var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost2;
|
|
14685
15839
|
var init_learningScene3D = __esm({
|
|
14686
15840
|
"components/learning/molecules/learningScene3D.tsx"() {
|
|
14687
15841
|
"use client";
|
|
14688
15842
|
init_atoms();
|
|
14689
15843
|
init_Stack();
|
|
14690
15844
|
init_useEventBus();
|
|
15845
|
+
sceneLog = createLogger("almadar:ui:learning-scene-3d");
|
|
15846
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
14691
15847
|
Canvas3DHost2 = lazy(
|
|
14692
15848
|
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
14693
15849
|
);
|
|
14694
15850
|
LearningScene3D.displayName = "LearningScene3D";
|
|
14695
15851
|
}
|
|
14696
15852
|
});
|
|
14697
|
-
var biologyLog, BiologyCanvas;
|
|
15853
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
14698
15854
|
var init_BiologyCanvas = __esm({
|
|
14699
15855
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
14700
15856
|
"use client";
|
|
@@ -14703,6 +15859,17 @@ var init_BiologyCanvas = __esm({
|
|
|
14703
15859
|
init_LearningCanvas();
|
|
14704
15860
|
init_learningScene3D();
|
|
14705
15861
|
biologyLog = createLogger("almadar:ui:biology-canvas");
|
|
15862
|
+
BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
|
|
15863
|
+
BIO_STAGE_FILL = {
|
|
15864
|
+
pending: "#e2e8f0",
|
|
15865
|
+
active: "#3b82f6",
|
|
15866
|
+
done: "#94a3b8"
|
|
15867
|
+
};
|
|
15868
|
+
BIO_STAGE_TEXT = {
|
|
15869
|
+
pending: "#64748b",
|
|
15870
|
+
active: "#ffffff",
|
|
15871
|
+
done: "#ffffff"
|
|
15872
|
+
};
|
|
14706
15873
|
BiologyCanvas = ({
|
|
14707
15874
|
className,
|
|
14708
15875
|
width = 600,
|
|
@@ -14715,7 +15882,15 @@ var init_BiologyCanvas = __esm({
|
|
|
14715
15882
|
post,
|
|
14716
15883
|
nodes = [],
|
|
14717
15884
|
edges = [],
|
|
15885
|
+
compartments = [],
|
|
15886
|
+
bands = [],
|
|
15887
|
+
stages = [],
|
|
15888
|
+
stageStyle = "timeline",
|
|
15889
|
+
helix,
|
|
15890
|
+
helix3d,
|
|
14718
15891
|
shapes = [],
|
|
15892
|
+
readouts,
|
|
15893
|
+
traces,
|
|
14719
15894
|
showGrid,
|
|
14720
15895
|
shadows,
|
|
14721
15896
|
interactive,
|
|
@@ -14730,19 +15905,148 @@ var init_BiologyCanvas = __esm({
|
|
|
14730
15905
|
for (const n of nodes) {
|
|
14731
15906
|
if (n.id) nodeById.set(n.id, n);
|
|
14732
15907
|
}
|
|
15908
|
+
const bandCount = bands.length;
|
|
15909
|
+
for (let i = 0; i < bandCount; i++) {
|
|
15910
|
+
const band = bands[i];
|
|
15911
|
+
const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
|
|
15912
|
+
const bandY = i * height / bandCount;
|
|
15913
|
+
const bandH = height / bandCount;
|
|
15914
|
+
out.push({
|
|
15915
|
+
type: "rect",
|
|
15916
|
+
x: 0,
|
|
15917
|
+
y: bandY,
|
|
15918
|
+
width,
|
|
15919
|
+
height: bandH,
|
|
15920
|
+
color: bandColor,
|
|
15921
|
+
fill: bandColor,
|
|
15922
|
+
opacity: 0.45
|
|
15923
|
+
});
|
|
15924
|
+
if (band.label) {
|
|
15925
|
+
out.push({
|
|
15926
|
+
type: "text",
|
|
15927
|
+
x: 8,
|
|
15928
|
+
y: bandY + 14,
|
|
15929
|
+
text: band.label,
|
|
15930
|
+
color: "#6b7280",
|
|
15931
|
+
fontSize: 10
|
|
15932
|
+
});
|
|
15933
|
+
}
|
|
15934
|
+
}
|
|
15935
|
+
for (const c of compartments) {
|
|
15936
|
+
const color = c.color ?? "#16a34a";
|
|
15937
|
+
out.push({
|
|
15938
|
+
type: "ellipse",
|
|
15939
|
+
x: c.x,
|
|
15940
|
+
y: c.y,
|
|
15941
|
+
width: c.width,
|
|
15942
|
+
height: c.height,
|
|
15943
|
+
color,
|
|
15944
|
+
fill: c.fill ?? `${color}1A`,
|
|
15945
|
+
lineWidth: c.lineWidth ?? 2,
|
|
15946
|
+
...c.dash ? { dash: c.dash } : {}
|
|
15947
|
+
});
|
|
15948
|
+
if (c.label) {
|
|
15949
|
+
out.push({
|
|
15950
|
+
type: "text",
|
|
15951
|
+
x: c.x,
|
|
15952
|
+
y: c.y - c.height / 2 + 14,
|
|
15953
|
+
text: c.label,
|
|
15954
|
+
color: "#111827",
|
|
15955
|
+
fontSize: 11,
|
|
15956
|
+
align: "center"
|
|
15957
|
+
});
|
|
15958
|
+
}
|
|
15959
|
+
}
|
|
15960
|
+
if (helix) {
|
|
15961
|
+
const hx = helix.x ?? 24;
|
|
15962
|
+
const hy = helix.y ?? height * 0.25;
|
|
15963
|
+
const hw = helix.width ?? width - 48;
|
|
15964
|
+
const hh = helix.height ?? height * 0.5;
|
|
15965
|
+
const rungs = helix.rungs;
|
|
15966
|
+
const n = rungs.length;
|
|
15967
|
+
const cy = hy + hh / 2;
|
|
15968
|
+
const colorA = helix.colorA ?? "#2563eb";
|
|
15969
|
+
const colorB = helix.colorB ?? "#dc2626";
|
|
15970
|
+
const rungColor = helix.rungColor ?? "#94a3b8";
|
|
15971
|
+
const fork = helix.fork ?? 0;
|
|
15972
|
+
const maxSep = Math.min(hh - 8, 96);
|
|
15973
|
+
const strandA = [];
|
|
15974
|
+
const strandB = [];
|
|
15975
|
+
const rungGeoms = [];
|
|
15976
|
+
for (let i = 0; i < n; i++) {
|
|
15977
|
+
const rx = hx + (i + 0.5) * hw / n;
|
|
15978
|
+
const t = (i + 0.5) / n;
|
|
15979
|
+
const paired = t >= fork;
|
|
15980
|
+
const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
|
|
15981
|
+
strandA.push({ x: rx, y: cy - sep / 2 });
|
|
15982
|
+
strandB.push({ x: rx, y: cy + sep / 2 });
|
|
15983
|
+
rungGeoms.push({ rx, sep, rung: rungs[i], paired });
|
|
15984
|
+
}
|
|
15985
|
+
for (let i = 1; i < n; i++) {
|
|
15986
|
+
out.push({ type: "line", x1: strandA[i - 1].x, y1: strandA[i - 1].y, x2: strandA[i].x, y2: strandA[i].y, color: colorA, lineWidth: 3 });
|
|
15987
|
+
}
|
|
15988
|
+
for (let i = 1; i < n; i++) {
|
|
15989
|
+
out.push({ type: "line", x1: strandB[i - 1].x, y1: strandB[i - 1].y, x2: strandB[i].x, y2: strandB[i].y, color: colorB, lineWidth: 3 });
|
|
15990
|
+
}
|
|
15991
|
+
for (const g of rungGeoms) {
|
|
15992
|
+
const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
|
|
15993
|
+
const topY = cy - g.sep / 2;
|
|
15994
|
+
const bottomY = cy + g.sep / 2;
|
|
15995
|
+
if (g.paired) {
|
|
15996
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
|
|
15997
|
+
if (g.rung.a) {
|
|
15998
|
+
out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
15999
|
+
}
|
|
16000
|
+
if (g.rung.b) {
|
|
16001
|
+
out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16002
|
+
}
|
|
16003
|
+
} else {
|
|
16004
|
+
const stubTopY = topY + 8;
|
|
16005
|
+
const stubBottomY = bottomY - 8;
|
|
16006
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
|
|
16007
|
+
out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
|
|
16008
|
+
if (g.rung.a) {
|
|
16009
|
+
out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
16010
|
+
}
|
|
16011
|
+
if (g.rung.b) {
|
|
16012
|
+
out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16013
|
+
}
|
|
16014
|
+
}
|
|
16015
|
+
}
|
|
16016
|
+
}
|
|
14733
16017
|
for (const e of edges) {
|
|
14734
16018
|
const a = nodeById.get(e.from);
|
|
14735
16019
|
const b = nodeById.get(e.to);
|
|
14736
16020
|
if (!a || !b) continue;
|
|
14737
|
-
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
16021
|
+
const color = e.color ?? "#9ca3af";
|
|
16022
|
+
if (e.directed) {
|
|
16023
|
+
const rA = a.radius ?? 16;
|
|
16024
|
+
const rB = b.radius ?? 16;
|
|
16025
|
+
const dx = b.x - a.x;
|
|
16026
|
+
const dy = b.y - a.y;
|
|
16027
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16028
|
+
const ux = dx / dist;
|
|
16029
|
+
const uy = dy / dist;
|
|
16030
|
+
out.push({
|
|
16031
|
+
type: "arrow",
|
|
16032
|
+
x1: a.x + ux * rA,
|
|
16033
|
+
y1: a.y + uy * rA,
|
|
16034
|
+
x2: b.x - ux * rB,
|
|
16035
|
+
y2: b.y - uy * rB,
|
|
16036
|
+
color,
|
|
16037
|
+
lineWidth: 2
|
|
16038
|
+
});
|
|
16039
|
+
} else {
|
|
16040
|
+
out.push({
|
|
16041
|
+
type: "line",
|
|
16042
|
+
x1: a.x,
|
|
16043
|
+
y1: a.y,
|
|
16044
|
+
x2: b.x,
|
|
16045
|
+
y2: b.y,
|
|
16046
|
+
color,
|
|
16047
|
+
lineWidth: 2
|
|
16048
|
+
});
|
|
16049
|
+
}
|
|
14746
16050
|
if (e.label) {
|
|
14747
16051
|
out.push({
|
|
14748
16052
|
type: "text",
|
|
@@ -14755,6 +16059,8 @@ var init_BiologyCanvas = __esm({
|
|
|
14755
16059
|
}
|
|
14756
16060
|
}
|
|
14757
16061
|
for (const n of nodes) {
|
|
16062
|
+
const state = n.state ?? "default";
|
|
16063
|
+
const muted = state === "muted";
|
|
14758
16064
|
out.push({
|
|
14759
16065
|
type: "circle",
|
|
14760
16066
|
x: n.x,
|
|
@@ -14762,28 +16068,127 @@ var init_BiologyCanvas = __esm({
|
|
|
14762
16068
|
radius: n.radius ?? 16,
|
|
14763
16069
|
color: n.color ?? "#16a34a",
|
|
14764
16070
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
14765
|
-
id: n.id
|
|
16071
|
+
id: n.id,
|
|
16072
|
+
...muted ? { opacity: 0.35 } : {}
|
|
14766
16073
|
});
|
|
16074
|
+
if (state === "highlight") {
|
|
16075
|
+
out.push({
|
|
16076
|
+
type: "circle",
|
|
16077
|
+
x: n.x,
|
|
16078
|
+
y: n.y,
|
|
16079
|
+
radius: (n.radius ?? 16) + 4,
|
|
16080
|
+
color: "#f59e0b",
|
|
16081
|
+
lineWidth: 2
|
|
16082
|
+
});
|
|
16083
|
+
}
|
|
14767
16084
|
if (n.label) {
|
|
14768
16085
|
out.push({
|
|
14769
16086
|
type: "text",
|
|
14770
16087
|
x: n.x,
|
|
14771
16088
|
y: n.y + (n.radius ?? 16) + 14,
|
|
14772
16089
|
text: n.label,
|
|
16090
|
+
...muted ? { opacity: 0.35 } : {},
|
|
14773
16091
|
color: "#111827",
|
|
14774
16092
|
fontSize: 12,
|
|
14775
16093
|
align: "center"
|
|
14776
16094
|
});
|
|
14777
16095
|
}
|
|
14778
16096
|
}
|
|
16097
|
+
const stageCount = stages.length;
|
|
16098
|
+
if (stageCount > 0) {
|
|
16099
|
+
if (stageStyle === "ring") {
|
|
16100
|
+
const cx = width / 2;
|
|
16101
|
+
const cy = height / 2;
|
|
16102
|
+
const R = Math.min(width, height) / 2 - 48;
|
|
16103
|
+
const ringPoints = [];
|
|
16104
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16105
|
+
const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
|
|
16106
|
+
ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
|
|
16107
|
+
}
|
|
16108
|
+
if (stageCount >= 2) {
|
|
16109
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16110
|
+
const p1 = ringPoints[i];
|
|
16111
|
+
const p2 = ringPoints[i + 1];
|
|
16112
|
+
const dx = p2.x - p1.x;
|
|
16113
|
+
const dy = p2.y - p1.y;
|
|
16114
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16115
|
+
const ux = dx / dist;
|
|
16116
|
+
const uy = dy / dist;
|
|
16117
|
+
out.push({
|
|
16118
|
+
type: "arrow",
|
|
16119
|
+
x1: p1.x + ux * 46,
|
|
16120
|
+
y1: p1.y + uy * 46,
|
|
16121
|
+
x2: p2.x - ux * 46,
|
|
16122
|
+
y2: p2.y - uy * 46,
|
|
16123
|
+
color: "#94a3b8"
|
|
16124
|
+
});
|
|
16125
|
+
}
|
|
16126
|
+
}
|
|
16127
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16128
|
+
const stage = stages[i];
|
|
16129
|
+
const state = stage.state ?? "pending";
|
|
16130
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16131
|
+
const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
|
|
16132
|
+
const h = 18;
|
|
16133
|
+
const p = ringPoints[i];
|
|
16134
|
+
out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
|
|
16135
|
+
out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
|
|
16136
|
+
}
|
|
16137
|
+
} else {
|
|
16138
|
+
const stripY = height - 32;
|
|
16139
|
+
const slotW = (width - 16) / stageCount;
|
|
16140
|
+
const chipGeoms = [];
|
|
16141
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16142
|
+
chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
|
|
16143
|
+
}
|
|
16144
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16145
|
+
const midY = stripY + 13;
|
|
16146
|
+
out.push({
|
|
16147
|
+
type: "arrow",
|
|
16148
|
+
x1: chipGeoms[i].x + chipGeoms[i].w,
|
|
16149
|
+
y1: midY,
|
|
16150
|
+
x2: chipGeoms[i + 1].x,
|
|
16151
|
+
y2: midY,
|
|
16152
|
+
color: "#94a3b8"
|
|
16153
|
+
});
|
|
16154
|
+
}
|
|
16155
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16156
|
+
const stage = stages[i];
|
|
16157
|
+
const state = stage.state ?? "pending";
|
|
16158
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16159
|
+
const g = chipGeoms[i];
|
|
16160
|
+
out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
|
|
16161
|
+
out.push({
|
|
16162
|
+
type: "text",
|
|
16163
|
+
x: g.x + g.w / 2,
|
|
16164
|
+
y: stripY + 13,
|
|
16165
|
+
text: stage.label,
|
|
16166
|
+
color: BIO_STAGE_TEXT[state],
|
|
16167
|
+
fontSize: 10,
|
|
16168
|
+
align: "center"
|
|
16169
|
+
});
|
|
16170
|
+
}
|
|
16171
|
+
}
|
|
16172
|
+
}
|
|
14779
16173
|
out.push(...shapes);
|
|
14780
16174
|
return out;
|
|
14781
|
-
}, [nodes, edges, shapes]);
|
|
16175
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
14782
16176
|
const drawables3D = useMemo(() => {
|
|
14783
16177
|
if (mode !== "3d") return [];
|
|
14784
16178
|
if (shapes.length > 0) {
|
|
14785
16179
|
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
14786
16180
|
}
|
|
16181
|
+
if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
|
|
16182
|
+
biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
|
|
16183
|
+
compartments: compartments.length,
|
|
16184
|
+
bands: bands.length,
|
|
16185
|
+
stages: stages.length,
|
|
16186
|
+
helix: helix != null
|
|
16187
|
+
});
|
|
16188
|
+
}
|
|
16189
|
+
if (animate) {
|
|
16190
|
+
biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
16191
|
+
}
|
|
14787
16192
|
const out = [];
|
|
14788
16193
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
14789
16194
|
const nodeById = /* @__PURE__ */ new Map();
|
|
@@ -14817,15 +16222,21 @@ var init_BiologyCanvas = __esm({
|
|
|
14817
16222
|
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
14818
16223
|
}
|
|
14819
16224
|
}
|
|
16225
|
+
if (helix3d) {
|
|
16226
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
16227
|
+
}
|
|
14820
16228
|
return out;
|
|
14821
|
-
}, [mode, nodes, edges, shapes, backgroundColor]);
|
|
16229
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
14822
16230
|
const nodeIndexById = useMemo(() => {
|
|
14823
16231
|
const m = /* @__PURE__ */ new Map();
|
|
16232
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
16233
|
+
if (rung.id) m.set(rung.id, i);
|
|
16234
|
+
});
|
|
14824
16235
|
nodes.forEach((n, i) => {
|
|
14825
16236
|
if (n.id) m.set(n.id, i);
|
|
14826
16237
|
});
|
|
14827
16238
|
return m;
|
|
14828
|
-
}, [nodes]);
|
|
16239
|
+
}, [nodes, helix3d]);
|
|
14829
16240
|
if (mode === "3d") {
|
|
14830
16241
|
return /* @__PURE__ */ jsx(
|
|
14831
16242
|
LearningScene3D,
|
|
@@ -14857,6 +16268,8 @@ var init_BiologyCanvas = __esm({
|
|
|
14857
16268
|
height,
|
|
14858
16269
|
backgroundColor,
|
|
14859
16270
|
shapes: derivedShapes,
|
|
16271
|
+
readouts,
|
|
16272
|
+
traces,
|
|
14860
16273
|
interactive: interactive ?? false,
|
|
14861
16274
|
animate,
|
|
14862
16275
|
onShapeClick,
|
|
@@ -21589,7 +23002,7 @@ function bondPerpendicular(a, b) {
|
|
|
21589
23002
|
if (len < 1e-6) return [1, 0, 0];
|
|
21590
23003
|
return [px / len, py / len, 0];
|
|
21591
23004
|
}
|
|
21592
|
-
var chemistryLog, ChemistryCanvas;
|
|
23005
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
21593
23006
|
var init_ChemistryCanvas = __esm({
|
|
21594
23007
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
21595
23008
|
"use client";
|
|
@@ -21598,6 +23011,13 @@ var init_ChemistryCanvas = __esm({
|
|
|
21598
23011
|
init_LearningCanvas();
|
|
21599
23012
|
init_learningScene3D();
|
|
21600
23013
|
chemistryLog = createLogger("almadar:ui:chemistry-canvas");
|
|
23014
|
+
CHEM_BOND_STATE_COLOR = {
|
|
23015
|
+
default: "#6b7280",
|
|
23016
|
+
forming: "#16a34a",
|
|
23017
|
+
breaking: "#dc2626",
|
|
23018
|
+
highlight: "#f59e0b"
|
|
23019
|
+
};
|
|
23020
|
+
LONE_PAIR_ANGLES = [-90, 0, 90, 180];
|
|
21601
23021
|
ChemistryCanvas = ({
|
|
21602
23022
|
className,
|
|
21603
23023
|
width = 600,
|
|
@@ -21611,7 +23031,14 @@ var init_ChemistryCanvas = __esm({
|
|
|
21611
23031
|
atoms = [],
|
|
21612
23032
|
bonds = [],
|
|
21613
23033
|
arrows = [],
|
|
23034
|
+
bondStyle = "thick",
|
|
23035
|
+
containers = [],
|
|
23036
|
+
equation,
|
|
23037
|
+
equationColor,
|
|
23038
|
+
lattice3d,
|
|
21614
23039
|
shapes = [],
|
|
23040
|
+
readouts,
|
|
23041
|
+
traces,
|
|
21615
23042
|
showGrid,
|
|
21616
23043
|
shadows,
|
|
21617
23044
|
interactive,
|
|
@@ -21626,21 +23053,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
21626
23053
|
for (const a of atoms) {
|
|
21627
23054
|
if (a.id) atomById.set(a.id, a);
|
|
21628
23055
|
}
|
|
23056
|
+
for (const c of containers) {
|
|
23057
|
+
const color = c.color ?? "#64748b";
|
|
23058
|
+
if (c.level != null) {
|
|
23059
|
+
const lv = c.level;
|
|
23060
|
+
out.push({
|
|
23061
|
+
type: "rect",
|
|
23062
|
+
x: c.x + 1,
|
|
23063
|
+
y: c.y + c.height * (1 - lv),
|
|
23064
|
+
width: c.width - 2,
|
|
23065
|
+
height: c.height * lv - 1,
|
|
23066
|
+
color: c.levelColor ?? "#60a5fa",
|
|
23067
|
+
fill: c.levelColor ?? "#60a5fa",
|
|
23068
|
+
opacity: 0.5
|
|
23069
|
+
});
|
|
23070
|
+
}
|
|
23071
|
+
out.push({
|
|
23072
|
+
type: "rect",
|
|
23073
|
+
x: c.x,
|
|
23074
|
+
y: c.y,
|
|
23075
|
+
width: c.width,
|
|
23076
|
+
height: c.height,
|
|
23077
|
+
color,
|
|
23078
|
+
fill: c.fill,
|
|
23079
|
+
lineWidth: c.lineWidth ?? 2
|
|
23080
|
+
});
|
|
23081
|
+
const divider = c.divider ?? "none";
|
|
23082
|
+
if (divider !== "none") {
|
|
23083
|
+
out.push({
|
|
23084
|
+
type: "line",
|
|
23085
|
+
x1: c.x + c.width / 2,
|
|
23086
|
+
y1: c.y,
|
|
23087
|
+
x2: c.x + c.width / 2,
|
|
23088
|
+
y2: c.y + c.height,
|
|
23089
|
+
color: c.dividerColor ?? color,
|
|
23090
|
+
...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
|
|
23091
|
+
});
|
|
23092
|
+
}
|
|
23093
|
+
if (c.leftLabel) {
|
|
23094
|
+
out.push({
|
|
23095
|
+
type: "text",
|
|
23096
|
+
x: c.x + c.width * 0.25,
|
|
23097
|
+
y: c.y + 12,
|
|
23098
|
+
text: c.leftLabel,
|
|
23099
|
+
color: "#374151",
|
|
23100
|
+
fontSize: 11,
|
|
23101
|
+
align: "center"
|
|
23102
|
+
});
|
|
23103
|
+
}
|
|
23104
|
+
if (c.rightLabel) {
|
|
23105
|
+
out.push({
|
|
23106
|
+
type: "text",
|
|
23107
|
+
x: c.x + c.width * 0.75,
|
|
23108
|
+
y: c.y + 12,
|
|
23109
|
+
text: c.rightLabel,
|
|
23110
|
+
color: "#374151",
|
|
23111
|
+
fontSize: 11,
|
|
23112
|
+
align: "center"
|
|
23113
|
+
});
|
|
23114
|
+
}
|
|
23115
|
+
if (c.label) {
|
|
23116
|
+
out.push({
|
|
23117
|
+
type: "text",
|
|
23118
|
+
x: c.x + c.width / 2,
|
|
23119
|
+
y: c.y + c.height + 12,
|
|
23120
|
+
text: c.label,
|
|
23121
|
+
color: "#111827",
|
|
23122
|
+
fontSize: 12,
|
|
23123
|
+
align: "center"
|
|
23124
|
+
});
|
|
23125
|
+
}
|
|
23126
|
+
}
|
|
21629
23127
|
for (const b of bonds) {
|
|
21630
23128
|
const a = atomById.get(b.from);
|
|
21631
23129
|
const c = atomById.get(b.to);
|
|
21632
23130
|
if (!a || !c) continue;
|
|
21633
|
-
const
|
|
21634
|
-
const
|
|
21635
|
-
|
|
21636
|
-
|
|
21637
|
-
|
|
21638
|
-
|
|
21639
|
-
|
|
21640
|
-
|
|
21641
|
-
|
|
21642
|
-
|
|
21643
|
-
|
|
23131
|
+
const state = b.state ?? "default";
|
|
23132
|
+
const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
|
|
23133
|
+
const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
|
|
23134
|
+
if (bondStyle === "parallel") {
|
|
23135
|
+
const dx = c.x - a.x;
|
|
23136
|
+
const dy = c.y - a.y;
|
|
23137
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
23138
|
+
const ux = dx / dist;
|
|
23139
|
+
const uy = dy / dist;
|
|
23140
|
+
const px = -uy;
|
|
23141
|
+
const py = ux;
|
|
23142
|
+
const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
|
|
23143
|
+
for (const off of offsets) {
|
|
23144
|
+
out.push({
|
|
23145
|
+
type: "line",
|
|
23146
|
+
x1: a.x + px * off,
|
|
23147
|
+
y1: a.y + py * off,
|
|
23148
|
+
x2: c.x + px * off,
|
|
23149
|
+
y2: c.y + py * off,
|
|
23150
|
+
color,
|
|
23151
|
+
lineWidth: 2,
|
|
23152
|
+
...dash ? { dash } : {}
|
|
23153
|
+
});
|
|
23154
|
+
}
|
|
23155
|
+
} else {
|
|
23156
|
+
const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
|
|
23157
|
+
out.push({
|
|
23158
|
+
type: "line",
|
|
23159
|
+
x1: a.x,
|
|
23160
|
+
y1: a.y,
|
|
23161
|
+
x2: c.x,
|
|
23162
|
+
y2: c.y,
|
|
23163
|
+
color,
|
|
23164
|
+
lineWidth: strokeWidth,
|
|
23165
|
+
...dash ? { dash } : {}
|
|
23166
|
+
});
|
|
23167
|
+
}
|
|
21644
23168
|
}
|
|
21645
23169
|
for (const a of arrows) {
|
|
21646
23170
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -21689,15 +23213,62 @@ var init_ChemistryCanvas = __esm({
|
|
|
21689
23213
|
align: "center"
|
|
21690
23214
|
});
|
|
21691
23215
|
}
|
|
23216
|
+
const r = a.radius ?? 14;
|
|
23217
|
+
if (a.charge) {
|
|
23218
|
+
out.push({
|
|
23219
|
+
type: "text",
|
|
23220
|
+
x: a.x + r * 0.85,
|
|
23221
|
+
y: a.y - r * 0.85,
|
|
23222
|
+
text: a.charge,
|
|
23223
|
+
color: "#111827",
|
|
23224
|
+
fontSize: 9,
|
|
23225
|
+
align: "left"
|
|
23226
|
+
});
|
|
23227
|
+
}
|
|
23228
|
+
const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
|
|
23229
|
+
for (let k = 0; k < lonePairs; k++) {
|
|
23230
|
+
const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
|
|
23231
|
+
const cx = a.x + (r + 6) * Math.cos(angleRad);
|
|
23232
|
+
const cy = a.y + (r + 6) * Math.sin(angleRad);
|
|
23233
|
+
const perpX = -Math.sin(angleRad);
|
|
23234
|
+
const perpY = Math.cos(angleRad);
|
|
23235
|
+
for (const sign of [1, -1]) {
|
|
23236
|
+
out.push({
|
|
23237
|
+
type: "circle",
|
|
23238
|
+
x: cx + perpX * 2.5 * sign,
|
|
23239
|
+
y: cy + perpY * 2.5 * sign,
|
|
23240
|
+
radius: 1.5,
|
|
23241
|
+
color: "#374151",
|
|
23242
|
+
fill: "#374151"
|
|
23243
|
+
});
|
|
23244
|
+
}
|
|
23245
|
+
}
|
|
23246
|
+
}
|
|
23247
|
+
if (equation) {
|
|
23248
|
+
out.push({
|
|
23249
|
+
type: "text",
|
|
23250
|
+
x: width / 2,
|
|
23251
|
+
y: 14,
|
|
23252
|
+
text: equation,
|
|
23253
|
+
color: equationColor ?? "#111827",
|
|
23254
|
+
fontSize: 13,
|
|
23255
|
+
align: "center"
|
|
23256
|
+
});
|
|
21692
23257
|
}
|
|
21693
23258
|
out.push(...shapes);
|
|
21694
23259
|
return out;
|
|
21695
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
23260
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
21696
23261
|
const drawables3D = useMemo(() => {
|
|
21697
23262
|
if (mode !== "3d") return [];
|
|
21698
23263
|
if (shapes.length > 0) {
|
|
21699
23264
|
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
21700
23265
|
}
|
|
23266
|
+
if (containers.length > 0) {
|
|
23267
|
+
chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
|
|
23268
|
+
}
|
|
23269
|
+
if (animate) {
|
|
23270
|
+
chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
23271
|
+
}
|
|
21701
23272
|
const out = [];
|
|
21702
23273
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
21703
23274
|
const atomById = /* @__PURE__ */ new Map();
|
|
@@ -21744,8 +23315,11 @@ var init_ChemistryCanvas = __esm({
|
|
|
21744
23315
|
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
21745
23316
|
}
|
|
21746
23317
|
}
|
|
23318
|
+
if (lattice3d) {
|
|
23319
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
23320
|
+
}
|
|
21747
23321
|
return out;
|
|
21748
|
-
}, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
|
|
23322
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
21749
23323
|
const atomIndexById = useMemo(() => {
|
|
21750
23324
|
const m = /* @__PURE__ */ new Map();
|
|
21751
23325
|
atoms.forEach((a, i) => {
|
|
@@ -21784,6 +23358,8 @@ var init_ChemistryCanvas = __esm({
|
|
|
21784
23358
|
height,
|
|
21785
23359
|
backgroundColor,
|
|
21786
23360
|
shapes: derivedShapes,
|
|
23361
|
+
readouts,
|
|
23362
|
+
traces,
|
|
21787
23363
|
interactive: interactive ?? false,
|
|
21788
23364
|
animate,
|
|
21789
23365
|
onShapeClick,
|
|
@@ -27895,6 +29471,10 @@ var init_ProgressDots = __esm({
|
|
|
27895
29471
|
ProgressDots.displayName = "ProgressDots";
|
|
27896
29472
|
}
|
|
27897
29473
|
});
|
|
29474
|
+
function formatTick(v) {
|
|
29475
|
+
if (Number.isInteger(v)) return String(v);
|
|
29476
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
29477
|
+
}
|
|
27898
29478
|
var MathCanvas;
|
|
27899
29479
|
var init_MathCanvas = __esm({
|
|
27900
29480
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -27914,10 +29494,19 @@ var init_MathCanvas = __esm({
|
|
|
27914
29494
|
showAxes = true,
|
|
27915
29495
|
showGrid = true,
|
|
27916
29496
|
gridStep = 1,
|
|
29497
|
+
showTickLabels = false,
|
|
29498
|
+
showCurveLabels = false,
|
|
27917
29499
|
curves = [],
|
|
27918
29500
|
points = [],
|
|
27919
29501
|
vectors = [],
|
|
29502
|
+
regions = [],
|
|
29503
|
+
bars = [],
|
|
29504
|
+
guides = [],
|
|
29505
|
+
angles = [],
|
|
29506
|
+
hops = [],
|
|
27920
29507
|
shapes = [],
|
|
29508
|
+
readouts,
|
|
29509
|
+
traces,
|
|
27921
29510
|
interactive = false,
|
|
27922
29511
|
animate = false,
|
|
27923
29512
|
onShapeClick,
|
|
@@ -27931,6 +29520,8 @@ var init_MathCanvas = __esm({
|
|
|
27931
29520
|
const plotH = height - margin * 2;
|
|
27932
29521
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
27933
29522
|
const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
|
|
29523
|
+
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
29524
|
+
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
27934
29525
|
if (showGrid) {
|
|
27935
29526
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
27936
29527
|
const px = mapX(x);
|
|
@@ -27941,14 +29532,99 @@ var init_MathCanvas = __esm({
|
|
|
27941
29532
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
27942
29533
|
}
|
|
27943
29534
|
}
|
|
29535
|
+
if (showTickLabels) {
|
|
29536
|
+
const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
|
|
29537
|
+
let kx = 0;
|
|
29538
|
+
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
|
|
29539
|
+
if (kx % labelEveryX === 0 && x !== 0) {
|
|
29540
|
+
out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
|
|
29541
|
+
}
|
|
29542
|
+
}
|
|
29543
|
+
const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
|
|
29544
|
+
let ky = 0;
|
|
29545
|
+
for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
|
|
29546
|
+
if (ky % labelEveryY === 0 && y !== 0) {
|
|
29547
|
+
out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
|
|
29548
|
+
}
|
|
29549
|
+
}
|
|
29550
|
+
if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
|
|
29551
|
+
out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
|
|
29552
|
+
}
|
|
29553
|
+
}
|
|
29554
|
+
for (const region of regions) {
|
|
29555
|
+
if (!region.samples || region.samples.length === 0) continue;
|
|
29556
|
+
const baseline = region.baseline ?? 0;
|
|
29557
|
+
const clampedPoint = (p) => ({
|
|
29558
|
+
x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
|
|
29559
|
+
y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
|
|
29560
|
+
});
|
|
29561
|
+
const upper = region.samples.map(clampedPoint);
|
|
29562
|
+
const first = region.samples[0];
|
|
29563
|
+
const last = region.samples[region.samples.length - 1];
|
|
29564
|
+
const closing = region.samples2 && region.samples2.length > 0 ? [...region.samples2].reverse().map(clampedPoint) : [clampedPoint({ x: last.x, y: baseline }), clampedPoint({ x: first.x, y: baseline })];
|
|
29565
|
+
const color = region.color ?? "#2563eb";
|
|
29566
|
+
out.push({
|
|
29567
|
+
type: "polygon",
|
|
29568
|
+
points: [...upper, ...closing],
|
|
29569
|
+
fill: color,
|
|
29570
|
+
color,
|
|
29571
|
+
opacity: region.opacity ?? 0.2,
|
|
29572
|
+
lineWidth: 1
|
|
29573
|
+
});
|
|
29574
|
+
if (region.label) {
|
|
29575
|
+
const mid = Math.floor(region.samples.length / 2);
|
|
29576
|
+
out.push({
|
|
29577
|
+
type: "text",
|
|
29578
|
+
x: mapX((first.x + last.x) / 2),
|
|
29579
|
+
y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
|
|
29580
|
+
text: region.label,
|
|
29581
|
+
color: "#111827",
|
|
29582
|
+
fontSize: 11
|
|
29583
|
+
});
|
|
29584
|
+
}
|
|
29585
|
+
}
|
|
29586
|
+
for (const bar of bars) {
|
|
29587
|
+
if (bar.x + bar.width < xMin || bar.x > xMax) continue;
|
|
29588
|
+
const y0 = bar.y0 ?? 0;
|
|
29589
|
+
const color = bar.color ?? "#93c5fd";
|
|
29590
|
+
out.push({
|
|
29591
|
+
type: "rect",
|
|
29592
|
+
x: mapX(bar.x),
|
|
29593
|
+
y: mapY(Math.max(y0, bar.y1)),
|
|
29594
|
+
width: mapX(bar.x + bar.width) - mapX(bar.x),
|
|
29595
|
+
height: Math.abs(mapY(bar.y1) - mapY(y0)),
|
|
29596
|
+
color,
|
|
29597
|
+
fill: color,
|
|
29598
|
+
opacity: bar.opacity ?? 0.5,
|
|
29599
|
+
lineWidth: 1
|
|
29600
|
+
});
|
|
29601
|
+
}
|
|
27944
29602
|
if (showAxes) {
|
|
27945
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
27946
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
27947
29603
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
27948
29604
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
27949
29605
|
}
|
|
29606
|
+
for (const guide of guides) {
|
|
29607
|
+
const color = guide.color ?? "#9ca3af";
|
|
29608
|
+
const dash = guide.dash ?? "dashed";
|
|
29609
|
+
if (guide.kind === "vline") {
|
|
29610
|
+
if (guide.at < xMin || guide.at > xMax) continue;
|
|
29611
|
+
const px = mapX(guide.at);
|
|
29612
|
+
out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
|
|
29613
|
+
if (guide.label) {
|
|
29614
|
+
out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
|
|
29615
|
+
}
|
|
29616
|
+
} else {
|
|
29617
|
+
if (guide.at < yMin || guide.at > yMax) continue;
|
|
29618
|
+
const py = mapY(guide.at);
|
|
29619
|
+
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
|
|
29620
|
+
if (guide.label) {
|
|
29621
|
+
out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
|
|
29622
|
+
}
|
|
29623
|
+
}
|
|
29624
|
+
}
|
|
27950
29625
|
for (const curve of curves) {
|
|
27951
29626
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
29627
|
+
let lastInRange;
|
|
27952
29628
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
27953
29629
|
const a = curve.samples[i - 1];
|
|
27954
29630
|
const b = curve.samples[i];
|
|
@@ -27960,19 +29636,97 @@ var init_MathCanvas = __esm({
|
|
|
27960
29636
|
x2: mapX(b.x),
|
|
27961
29637
|
y2: mapY(b.y),
|
|
27962
29638
|
color: curve.color ?? "#2563eb",
|
|
27963
|
-
lineWidth: 2
|
|
29639
|
+
lineWidth: 2,
|
|
29640
|
+
dash: curve.dash
|
|
29641
|
+
});
|
|
29642
|
+
lastInRange = b;
|
|
29643
|
+
}
|
|
29644
|
+
if (showCurveLabels && curve.label && lastInRange) {
|
|
29645
|
+
out.push({
|
|
29646
|
+
type: "text",
|
|
29647
|
+
x: mapX(lastInRange.x) + 6,
|
|
29648
|
+
y: mapY(lastInRange.y) - 6,
|
|
29649
|
+
text: curve.label,
|
|
29650
|
+
color: curve.color ?? "#2563eb",
|
|
29651
|
+
fontSize: 11
|
|
29652
|
+
});
|
|
29653
|
+
}
|
|
29654
|
+
}
|
|
29655
|
+
for (const hop of hops) {
|
|
29656
|
+
const x1 = mapX(hop.from);
|
|
29657
|
+
const x2 = mapX(hop.to);
|
|
29658
|
+
const peak = Math.min(36, plotH * 0.3);
|
|
29659
|
+
const color = hop.color ?? "#7c3aed";
|
|
29660
|
+
out.push({
|
|
29661
|
+
type: "ellipse",
|
|
29662
|
+
x: (x1 + x2) / 2,
|
|
29663
|
+
y: xAxisY,
|
|
29664
|
+
width: Math.abs(x2 - x1),
|
|
29665
|
+
height: 2 * peak,
|
|
29666
|
+
startAngle: 180,
|
|
29667
|
+
endAngle: 360,
|
|
29668
|
+
color
|
|
29669
|
+
});
|
|
29670
|
+
const s = Math.sign(hop.to - hop.from);
|
|
29671
|
+
out.push({
|
|
29672
|
+
type: "polygon",
|
|
29673
|
+
points: [
|
|
29674
|
+
{ x: x2, y: xAxisY },
|
|
29675
|
+
{ x: x2 - 4 * s, y: xAxisY - 7 },
|
|
29676
|
+
{ x: x2 + 2 * s, y: xAxisY - 7 }
|
|
29677
|
+
],
|
|
29678
|
+
fill: color,
|
|
29679
|
+
color
|
|
29680
|
+
});
|
|
29681
|
+
if (hop.label) {
|
|
29682
|
+
out.push({
|
|
29683
|
+
type: "text",
|
|
29684
|
+
x: (x1 + x2) / 2,
|
|
29685
|
+
y: xAxisY - peak - 8,
|
|
29686
|
+
text: hop.label,
|
|
29687
|
+
color: "#111827",
|
|
29688
|
+
fontSize: 10,
|
|
29689
|
+
align: "center"
|
|
29690
|
+
});
|
|
29691
|
+
}
|
|
29692
|
+
}
|
|
29693
|
+
for (const angle of angles) {
|
|
29694
|
+
const radius = angle.radius ?? 0.8;
|
|
29695
|
+
const color = angle.color ?? "#0ea5e9";
|
|
29696
|
+
out.push({
|
|
29697
|
+
type: "ellipse",
|
|
29698
|
+
x: mapX(angle.x),
|
|
29699
|
+
y: mapY(angle.y),
|
|
29700
|
+
width: 2 * radius * plotW / (xMax - xMin),
|
|
29701
|
+
height: 2 * radius * plotH / (yMax - yMin),
|
|
29702
|
+
startAngle: -angle.to,
|
|
29703
|
+
endAngle: -angle.from,
|
|
29704
|
+
color
|
|
29705
|
+
});
|
|
29706
|
+
if (angle.label) {
|
|
29707
|
+
const mid = (angle.from + angle.to) / 2;
|
|
29708
|
+
const rad = mid * Math.PI / 180;
|
|
29709
|
+
out.push({
|
|
29710
|
+
type: "text",
|
|
29711
|
+
x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
|
|
29712
|
+
y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
|
|
29713
|
+
text: angle.label,
|
|
29714
|
+
color: "#111827",
|
|
29715
|
+
fontSize: 11,
|
|
29716
|
+
align: "center"
|
|
27964
29717
|
});
|
|
27965
29718
|
}
|
|
27966
29719
|
}
|
|
27967
29720
|
for (const p of points) {
|
|
27968
29721
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
29722
|
+
const isOpen = p.style === "open";
|
|
27969
29723
|
out.push({
|
|
27970
29724
|
type: "circle",
|
|
27971
29725
|
x: mapX(p.x),
|
|
27972
29726
|
y: mapY(p.y),
|
|
27973
29727
|
radius: p.radius ?? 4,
|
|
27974
29728
|
color: p.color ?? "#dc2626",
|
|
27975
|
-
fill: p.color ?? "#dc2626"
|
|
29729
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
27976
29730
|
});
|
|
27977
29731
|
if (p.label) {
|
|
27978
29732
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -27991,7 +29745,28 @@ var init_MathCanvas = __esm({
|
|
|
27991
29745
|
}
|
|
27992
29746
|
out.push(...shapes);
|
|
27993
29747
|
return out;
|
|
27994
|
-
}, [
|
|
29748
|
+
}, [
|
|
29749
|
+
width,
|
|
29750
|
+
height,
|
|
29751
|
+
xMin,
|
|
29752
|
+
xMax,
|
|
29753
|
+
yMin,
|
|
29754
|
+
yMax,
|
|
29755
|
+
showAxes,
|
|
29756
|
+
showGrid,
|
|
29757
|
+
gridStep,
|
|
29758
|
+
showTickLabels,
|
|
29759
|
+
showCurveLabels,
|
|
29760
|
+
curves,
|
|
29761
|
+
points,
|
|
29762
|
+
vectors,
|
|
29763
|
+
regions,
|
|
29764
|
+
bars,
|
|
29765
|
+
guides,
|
|
29766
|
+
angles,
|
|
29767
|
+
hops,
|
|
29768
|
+
shapes
|
|
29769
|
+
]);
|
|
27995
29770
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
27996
29771
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
27997
29772
|
/* @__PURE__ */ jsx(
|
|
@@ -28000,6 +29775,8 @@ var init_MathCanvas = __esm({
|
|
|
28000
29775
|
width,
|
|
28001
29776
|
height,
|
|
28002
29777
|
shapes: derivedShapes,
|
|
29778
|
+
readouts,
|
|
29779
|
+
traces,
|
|
28003
29780
|
interactive,
|
|
28004
29781
|
animate,
|
|
28005
29782
|
onShapeClick,
|
|
@@ -28011,7 +29788,315 @@ var init_MathCanvas = __esm({
|
|
|
28011
29788
|
};
|
|
28012
29789
|
}
|
|
28013
29790
|
});
|
|
28014
|
-
|
|
29791
|
+
function formatMeterValue(v) {
|
|
29792
|
+
return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
|
|
29793
|
+
}
|
|
29794
|
+
function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
|
|
29795
|
+
const out = [];
|
|
29796
|
+
const color = obj.color ?? "#334155";
|
|
29797
|
+
switch (obj.kind) {
|
|
29798
|
+
case "ground": {
|
|
29799
|
+
const xStart = obj.x1 ?? 0;
|
|
29800
|
+
const xEnd = obj.x2 ?? canvasWidth;
|
|
29801
|
+
const y = obj.y ?? 0;
|
|
29802
|
+
out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
|
|
29803
|
+
for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
|
|
29804
|
+
out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
|
|
29805
|
+
}
|
|
29806
|
+
if (obj.label) {
|
|
29807
|
+
out.push({
|
|
29808
|
+
type: "text",
|
|
29809
|
+
x: (xStart + xEnd) / 2,
|
|
29810
|
+
y: y - 10,
|
|
29811
|
+
text: obj.label,
|
|
29812
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29813
|
+
fontSize: 11,
|
|
29814
|
+
align: "center"
|
|
29815
|
+
});
|
|
29816
|
+
}
|
|
29817
|
+
break;
|
|
29818
|
+
}
|
|
29819
|
+
case "wall": {
|
|
29820
|
+
const yStart = obj.y1 ?? 0;
|
|
29821
|
+
const yEnd = obj.y2 ?? canvasHeight;
|
|
29822
|
+
const x = obj.x ?? 0;
|
|
29823
|
+
out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
|
|
29824
|
+
for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
|
|
29825
|
+
out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
|
|
29826
|
+
}
|
|
29827
|
+
if (obj.label) {
|
|
29828
|
+
out.push({
|
|
29829
|
+
type: "text",
|
|
29830
|
+
x: x + 12,
|
|
29831
|
+
y: (yStart + yEnd) / 2,
|
|
29832
|
+
text: obj.label,
|
|
29833
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29834
|
+
fontSize: 11,
|
|
29835
|
+
align: "left"
|
|
29836
|
+
});
|
|
29837
|
+
}
|
|
29838
|
+
break;
|
|
29839
|
+
}
|
|
29840
|
+
case "ramp": {
|
|
29841
|
+
const x1 = obj.x1 ?? 0;
|
|
29842
|
+
const y1 = obj.y1 ?? 0;
|
|
29843
|
+
const x2 = obj.x2 ?? canvasWidth;
|
|
29844
|
+
const y2 = obj.y2 ?? canvasHeight;
|
|
29845
|
+
out.push({
|
|
29846
|
+
type: "polygon",
|
|
29847
|
+
points: [
|
|
29848
|
+
{ x: x1, y: y1 },
|
|
29849
|
+
{ x: x2, y: y2 },
|
|
29850
|
+
{ x: x1, y: y2 }
|
|
29851
|
+
],
|
|
29852
|
+
color,
|
|
29853
|
+
fill: obj.fill ?? "#e2e8f0",
|
|
29854
|
+
lineWidth: 2
|
|
29855
|
+
});
|
|
29856
|
+
if (obj.label) {
|
|
29857
|
+
out.push({
|
|
29858
|
+
type: "text",
|
|
29859
|
+
x: (2 * x1 + x2) / 3,
|
|
29860
|
+
y: (y1 + 2 * y2) / 3,
|
|
29861
|
+
text: obj.label,
|
|
29862
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29863
|
+
fontSize: 11,
|
|
29864
|
+
align: "center"
|
|
29865
|
+
});
|
|
29866
|
+
}
|
|
29867
|
+
break;
|
|
29868
|
+
}
|
|
29869
|
+
case "box": {
|
|
29870
|
+
const x = obj.x ?? 0;
|
|
29871
|
+
const y = obj.y ?? 0;
|
|
29872
|
+
const w = obj.width ?? 40;
|
|
29873
|
+
const h = obj.height ?? 40;
|
|
29874
|
+
out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
|
|
29875
|
+
if (obj.label) {
|
|
29876
|
+
out.push({
|
|
29877
|
+
type: "text",
|
|
29878
|
+
x: x + w / 2,
|
|
29879
|
+
y: y + h / 2,
|
|
29880
|
+
text: obj.label,
|
|
29881
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29882
|
+
fontSize: 11,
|
|
29883
|
+
align: "center"
|
|
29884
|
+
});
|
|
29885
|
+
}
|
|
29886
|
+
break;
|
|
29887
|
+
}
|
|
29888
|
+
case "pivot": {
|
|
29889
|
+
const x = obj.x ?? 0;
|
|
29890
|
+
const y = obj.y ?? 0;
|
|
29891
|
+
out.push({ type: "circle", x, y, radius: 5, color, fill: color });
|
|
29892
|
+
out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
|
|
29893
|
+
for (let k = 0; k < 5; k++) {
|
|
29894
|
+
const hx = x - 14 + 7 * k;
|
|
29895
|
+
out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
|
|
29896
|
+
}
|
|
29897
|
+
if (obj.label) {
|
|
29898
|
+
out.push({
|
|
29899
|
+
type: "text",
|
|
29900
|
+
x,
|
|
29901
|
+
y: y - 20,
|
|
29902
|
+
text: obj.label,
|
|
29903
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29904
|
+
fontSize: 11,
|
|
29905
|
+
align: "center"
|
|
29906
|
+
});
|
|
29907
|
+
}
|
|
29908
|
+
break;
|
|
29909
|
+
}
|
|
29910
|
+
}
|
|
29911
|
+
return out;
|
|
29912
|
+
}
|
|
29913
|
+
function trailShapes(trail) {
|
|
29914
|
+
const n = trail.points.length;
|
|
29915
|
+
if (n < 2) return [];
|
|
29916
|
+
const color = trail.color ?? "#94a3b8";
|
|
29917
|
+
const lineWidth = trail.width ?? 2;
|
|
29918
|
+
const fade = trail.fade ?? true;
|
|
29919
|
+
const globalOpacity = trail.opacity ?? 1;
|
|
29920
|
+
const out = [];
|
|
29921
|
+
for (let i = 0; i < n - 1; i++) {
|
|
29922
|
+
const a = trail.points[i];
|
|
29923
|
+
const b = trail.points[i + 1];
|
|
29924
|
+
const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
|
|
29925
|
+
out.push({
|
|
29926
|
+
type: "line",
|
|
29927
|
+
x1: a.x,
|
|
29928
|
+
y1: a.y,
|
|
29929
|
+
x2: b.x,
|
|
29930
|
+
y2: b.y,
|
|
29931
|
+
color,
|
|
29932
|
+
lineWidth,
|
|
29933
|
+
opacity: segmentOpacity * globalOpacity
|
|
29934
|
+
});
|
|
29935
|
+
}
|
|
29936
|
+
return out;
|
|
29937
|
+
}
|
|
29938
|
+
function constraintShapes(c, a, b) {
|
|
29939
|
+
const color = c.color ?? "#9ca3af";
|
|
29940
|
+
const kind = c.kind ?? "rod";
|
|
29941
|
+
if (kind === "rod") {
|
|
29942
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
|
|
29943
|
+
}
|
|
29944
|
+
if (kind === "string") {
|
|
29945
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
|
|
29946
|
+
}
|
|
29947
|
+
const COILS = 8;
|
|
29948
|
+
const AMP = 7;
|
|
29949
|
+
const LEAD = 10;
|
|
29950
|
+
const dx = b.x - a.x;
|
|
29951
|
+
const dy = b.y - a.y;
|
|
29952
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
29953
|
+
const ux = dx / dist;
|
|
29954
|
+
const uy = dy / dist;
|
|
29955
|
+
const perpX = -uy;
|
|
29956
|
+
const perpY = ux;
|
|
29957
|
+
const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
|
|
29958
|
+
const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
|
|
29959
|
+
const m = 2 * COILS;
|
|
29960
|
+
const polyline = [{ x: a.x, y: a.y }, aPrime];
|
|
29961
|
+
for (let j = 1; j <= m; j++) {
|
|
29962
|
+
const t = j / (m + 1);
|
|
29963
|
+
const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
|
|
29964
|
+
const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
|
|
29965
|
+
const sign = j % 2 === 0 ? 1 : -1;
|
|
29966
|
+
polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
|
|
29967
|
+
}
|
|
29968
|
+
polyline.push(bPrime, { x: b.x, y: b.y });
|
|
29969
|
+
const out = [];
|
|
29970
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
29971
|
+
out.push({
|
|
29972
|
+
type: "line",
|
|
29973
|
+
x1: polyline[i - 1].x,
|
|
29974
|
+
y1: polyline[i - 1].y,
|
|
29975
|
+
x2: polyline[i].x,
|
|
29976
|
+
y2: polyline[i].y,
|
|
29977
|
+
color,
|
|
29978
|
+
lineWidth: 2
|
|
29979
|
+
});
|
|
29980
|
+
}
|
|
29981
|
+
return out;
|
|
29982
|
+
}
|
|
29983
|
+
function vectorShapes(v, bodyById) {
|
|
29984
|
+
let ax;
|
|
29985
|
+
let ay;
|
|
29986
|
+
if (v.body) {
|
|
29987
|
+
const anchor = bodyById.get(v.body);
|
|
29988
|
+
if (!anchor) return [];
|
|
29989
|
+
ax = anchor.x;
|
|
29990
|
+
ay = anchor.y;
|
|
29991
|
+
} else {
|
|
29992
|
+
ax = v.x ?? 0;
|
|
29993
|
+
ay = v.y ?? 0;
|
|
29994
|
+
}
|
|
29995
|
+
const scale = v.scale ?? 1;
|
|
29996
|
+
const color = v.color ?? "#dc2626";
|
|
29997
|
+
const tx = ax + v.dx * scale;
|
|
29998
|
+
const ty = ay + v.dy * scale;
|
|
29999
|
+
const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
|
|
30000
|
+
if (v.label) {
|
|
30001
|
+
const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
|
|
30002
|
+
const ux = (tx - ax) / dist;
|
|
30003
|
+
const uy = (ty - ay) / dist;
|
|
30004
|
+
out.push({
|
|
30005
|
+
type: "text",
|
|
30006
|
+
x: tx + 8 * ux,
|
|
30007
|
+
y: ty + 8 * uy,
|
|
30008
|
+
text: v.label,
|
|
30009
|
+
color,
|
|
30010
|
+
fontSize: 11,
|
|
30011
|
+
align: "center"
|
|
30012
|
+
});
|
|
30013
|
+
}
|
|
30014
|
+
return out;
|
|
30015
|
+
}
|
|
30016
|
+
function angleMarkerShapes(a) {
|
|
30017
|
+
const radius = a.radius ?? 26;
|
|
30018
|
+
const color = a.color ?? "#0ea5e9";
|
|
30019
|
+
const out = [
|
|
30020
|
+
{
|
|
30021
|
+
type: "ellipse",
|
|
30022
|
+
x: a.x,
|
|
30023
|
+
y: a.y,
|
|
30024
|
+
width: radius * 2,
|
|
30025
|
+
height: radius * 2,
|
|
30026
|
+
startAngle: a.from,
|
|
30027
|
+
endAngle: a.to,
|
|
30028
|
+
color,
|
|
30029
|
+
lineWidth: 2
|
|
30030
|
+
}
|
|
30031
|
+
];
|
|
30032
|
+
if (a.label) {
|
|
30033
|
+
const mid = (a.from + a.to) / 2 * (Math.PI / 180);
|
|
30034
|
+
out.push({
|
|
30035
|
+
type: "text",
|
|
30036
|
+
x: a.x + (radius + 13) * Math.cos(mid),
|
|
30037
|
+
y: a.y + (radius + 13) * Math.sin(mid),
|
|
30038
|
+
text: a.label,
|
|
30039
|
+
color,
|
|
30040
|
+
fontSize: 11,
|
|
30041
|
+
align: "center"
|
|
30042
|
+
});
|
|
30043
|
+
}
|
|
30044
|
+
return out;
|
|
30045
|
+
}
|
|
30046
|
+
function fieldShapes(field, canvasWidth, canvasHeight) {
|
|
30047
|
+
const spacing = field.spacing ?? 48;
|
|
30048
|
+
const size = field.size ?? 14;
|
|
30049
|
+
const color = field.color ?? "#94a3b8";
|
|
30050
|
+
const regionX = field.x ?? 0;
|
|
30051
|
+
const regionY = field.y ?? 0;
|
|
30052
|
+
const regionW = field.width ?? canvasWidth;
|
|
30053
|
+
const regionH = field.height ?? canvasHeight;
|
|
30054
|
+
const out = [];
|
|
30055
|
+
for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
|
|
30056
|
+
for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
|
|
30057
|
+
if (field.kind === "arrows") {
|
|
30058
|
+
const rad = (field.angle ?? 0) * Math.PI / 180;
|
|
30059
|
+
const hx = Math.cos(rad) * size / 2;
|
|
30060
|
+
const hy = Math.sin(rad) * size / 2;
|
|
30061
|
+
out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
|
|
30062
|
+
} else if (field.kind === "into") {
|
|
30063
|
+
const r = size / 3;
|
|
30064
|
+
const d = 0.6 * r * Math.SQRT1_2;
|
|
30065
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30066
|
+
out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
|
|
30067
|
+
out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
|
|
30068
|
+
} else {
|
|
30069
|
+
const r = size / 3;
|
|
30070
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30071
|
+
out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
|
|
30072
|
+
}
|
|
30073
|
+
}
|
|
30074
|
+
}
|
|
30075
|
+
return out;
|
|
30076
|
+
}
|
|
30077
|
+
function meterShapes(meters, canvasHeight) {
|
|
30078
|
+
const n = meters.length;
|
|
30079
|
+
const out = [];
|
|
30080
|
+
const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
|
|
30081
|
+
meters.forEach((meter, i) => {
|
|
30082
|
+
const rowY = canvasHeight - 10 - 16 * (n - i);
|
|
30083
|
+
const color = meter.color ?? "#3b82f6";
|
|
30084
|
+
const M = meter.max ?? sharedMax;
|
|
30085
|
+
const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
|
|
30086
|
+
out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
|
|
30087
|
+
out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
|
|
30088
|
+
out.push({
|
|
30089
|
+
type: "text",
|
|
30090
|
+
x: 166,
|
|
30091
|
+
y: rowY + 8,
|
|
30092
|
+
text: formatMeterValue(meter.value),
|
|
30093
|
+
color: "#6b7280",
|
|
30094
|
+
fontSize: 9
|
|
30095
|
+
});
|
|
30096
|
+
});
|
|
30097
|
+
return out;
|
|
30098
|
+
}
|
|
30099
|
+
var physicsLog2, PHYSICS_LABEL_COLOR, PhysicsCanvas;
|
|
28015
30100
|
var init_PhysicsCanvas = __esm({
|
|
28016
30101
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
28017
30102
|
"use client";
|
|
@@ -28020,6 +30105,7 @@ var init_PhysicsCanvas = __esm({
|
|
|
28020
30105
|
init_LearningCanvas();
|
|
28021
30106
|
init_learningScene3D();
|
|
28022
30107
|
physicsLog2 = createLogger("almadar:ui:physics-canvas");
|
|
30108
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
28023
30109
|
PhysicsCanvas = ({
|
|
28024
30110
|
className,
|
|
28025
30111
|
width = 600,
|
|
@@ -28036,7 +30122,18 @@ var init_PhysicsCanvas = __esm({
|
|
|
28036
30122
|
showForces = false,
|
|
28037
30123
|
velocityScale = 20,
|
|
28038
30124
|
forceScale = 20,
|
|
30125
|
+
sceneObjects = [],
|
|
30126
|
+
trails = [],
|
|
30127
|
+
vectors = [],
|
|
30128
|
+
surface3d,
|
|
30129
|
+
vectors3d = [],
|
|
30130
|
+
vectorScale = 1,
|
|
30131
|
+
angles = [],
|
|
30132
|
+
field,
|
|
30133
|
+
meters = [],
|
|
28039
30134
|
shapes = [],
|
|
30135
|
+
readouts,
|
|
30136
|
+
traces,
|
|
28040
30137
|
showGrid,
|
|
28041
30138
|
shadows,
|
|
28042
30139
|
interactive,
|
|
@@ -28051,19 +30148,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
28051
30148
|
for (const b of bodies) {
|
|
28052
30149
|
if (b.id) bodyById.set(b.id, b);
|
|
28053
30150
|
}
|
|
30151
|
+
if (field) out.push(...fieldShapes(field, width, height));
|
|
30152
|
+
for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
|
|
30153
|
+
for (const trail of trails) out.push(...trailShapes(trail));
|
|
28054
30154
|
for (const c of constraints) {
|
|
28055
30155
|
const a = bodyById.get(c.from);
|
|
28056
30156
|
const b = bodyById.get(c.to);
|
|
28057
30157
|
if (!a || !b) continue;
|
|
28058
|
-
out.push(
|
|
28059
|
-
type: "line",
|
|
28060
|
-
x1: a.x,
|
|
28061
|
-
y1: a.y,
|
|
28062
|
-
x2: b.x,
|
|
28063
|
-
y2: b.y,
|
|
28064
|
-
color: c.color ?? "#9ca3af",
|
|
28065
|
-
lineWidth: 2
|
|
28066
|
-
});
|
|
30158
|
+
out.push(...constraintShapes(c, a, b));
|
|
28067
30159
|
}
|
|
28068
30160
|
for (const b of bodies) {
|
|
28069
30161
|
out.push({
|
|
@@ -28108,14 +30200,51 @@ var init_PhysicsCanvas = __esm({
|
|
|
28108
30200
|
});
|
|
28109
30201
|
}
|
|
28110
30202
|
}
|
|
30203
|
+
for (const v of vectors) out.push(...vectorShapes(v, bodyById));
|
|
30204
|
+
for (const a of angles) out.push(...angleMarkerShapes(a));
|
|
30205
|
+
if (meters.length > 0) out.push(...meterShapes(meters, height));
|
|
28111
30206
|
out.push(...shapes);
|
|
28112
30207
|
return out;
|
|
28113
|
-
}, [
|
|
30208
|
+
}, [
|
|
30209
|
+
bodies,
|
|
30210
|
+
constraints,
|
|
30211
|
+
showVelocity,
|
|
30212
|
+
showForces,
|
|
30213
|
+
velocityScale,
|
|
30214
|
+
forceScale,
|
|
30215
|
+
sceneObjects,
|
|
30216
|
+
trails,
|
|
30217
|
+
vectors,
|
|
30218
|
+
angles,
|
|
30219
|
+
field,
|
|
30220
|
+
meters,
|
|
30221
|
+
shapes,
|
|
30222
|
+
width,
|
|
30223
|
+
height
|
|
30224
|
+
]);
|
|
28114
30225
|
const drawables3D = useMemo(() => {
|
|
28115
30226
|
if (mode !== "3d") return [];
|
|
28116
30227
|
if (shapes.length > 0) {
|
|
28117
30228
|
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
28118
30229
|
}
|
|
30230
|
+
if (sceneObjects.length > 0) {
|
|
30231
|
+
physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
|
|
30232
|
+
}
|
|
30233
|
+
if (vectors.length > 0) {
|
|
30234
|
+
physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
|
|
30235
|
+
}
|
|
30236
|
+
if (angles.length > 0) {
|
|
30237
|
+
physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
|
|
30238
|
+
}
|
|
30239
|
+
if (field) {
|
|
30240
|
+
physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
|
|
30241
|
+
}
|
|
30242
|
+
if (meters.length > 0) {
|
|
30243
|
+
physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
|
|
30244
|
+
}
|
|
30245
|
+
if (animate) {
|
|
30246
|
+
physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
30247
|
+
}
|
|
28119
30248
|
const out = [];
|
|
28120
30249
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
28121
30250
|
const bodyById = /* @__PURE__ */ new Map();
|
|
@@ -28163,15 +30292,67 @@ var init_PhysicsCanvas = __esm({
|
|
|
28163
30292
|
if (arrow) out.push(arrow);
|
|
28164
30293
|
}
|
|
28165
30294
|
}
|
|
30295
|
+
for (const trail of trails) {
|
|
30296
|
+
if (trail.fade !== void 0) {
|
|
30297
|
+
physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
|
|
30298
|
+
}
|
|
30299
|
+
const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
|
|
30300
|
+
out.push(
|
|
30301
|
+
...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
|
|
30302
|
+
...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
|
|
30303
|
+
})
|
|
30304
|
+
);
|
|
30305
|
+
}
|
|
30306
|
+
if (surface3d) {
|
|
30307
|
+
out.push(...heightFieldMesh(surface3d));
|
|
30308
|
+
}
|
|
30309
|
+
if (vectors3d.length > 0) {
|
|
30310
|
+
out.push(
|
|
30311
|
+
...arrowField(
|
|
30312
|
+
vectors3d.map((v) => ({
|
|
30313
|
+
id: v.id,
|
|
30314
|
+
from: [v.x, v.y, v.z ?? 0],
|
|
30315
|
+
delta: [v.dx, v.dy, v.dz ?? 0],
|
|
30316
|
+
color: v.color,
|
|
30317
|
+
label: v.label,
|
|
30318
|
+
width: v.width
|
|
30319
|
+
})),
|
|
30320
|
+
{ scale: vectorScale, labelColor }
|
|
30321
|
+
)
|
|
30322
|
+
);
|
|
30323
|
+
}
|
|
28166
30324
|
return out;
|
|
28167
|
-
}, [
|
|
30325
|
+
}, [
|
|
30326
|
+
mode,
|
|
30327
|
+
bodies,
|
|
30328
|
+
constraints,
|
|
30329
|
+
showVelocity,
|
|
30330
|
+
showForces,
|
|
30331
|
+
velocityScale,
|
|
30332
|
+
forceScale,
|
|
30333
|
+
shapes,
|
|
30334
|
+
sceneObjects,
|
|
30335
|
+
trails,
|
|
30336
|
+
vectors,
|
|
30337
|
+
surface3d,
|
|
30338
|
+
vectors3d,
|
|
30339
|
+
vectorScale,
|
|
30340
|
+
angles,
|
|
30341
|
+
field,
|
|
30342
|
+
meters,
|
|
30343
|
+
animate,
|
|
30344
|
+
backgroundColor
|
|
30345
|
+
]);
|
|
28168
30346
|
const bodyIndexById = useMemo(() => {
|
|
28169
30347
|
const m = /* @__PURE__ */ new Map();
|
|
30348
|
+
vectors3d.forEach((v, i) => {
|
|
30349
|
+
if (v.id) m.set(v.id, i);
|
|
30350
|
+
});
|
|
28170
30351
|
bodies.forEach((b, i) => {
|
|
28171
30352
|
if (b.id) m.set(b.id, i);
|
|
28172
30353
|
});
|
|
28173
30354
|
return m;
|
|
28174
|
-
}, [bodies]);
|
|
30355
|
+
}, [bodies, vectors3d]);
|
|
28175
30356
|
if (mode === "3d") {
|
|
28176
30357
|
return /* @__PURE__ */ jsx(
|
|
28177
30358
|
LearningScene3D,
|
|
@@ -28203,6 +30384,8 @@ var init_PhysicsCanvas = __esm({
|
|
|
28203
30384
|
height,
|
|
28204
30385
|
backgroundColor,
|
|
28205
30386
|
shapes: derivedShapes,
|
|
30387
|
+
readouts,
|
|
30388
|
+
traces,
|
|
28206
30389
|
interactive: interactive ?? false,
|
|
28207
30390
|
animate,
|
|
28208
30391
|
onShapeClick,
|
|
@@ -28353,7 +30536,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
28353
30536
|
}
|
|
28354
30537
|
return nodeIds.map((id) => positions.get(id));
|
|
28355
30538
|
}
|
|
28356
|
-
function
|
|
30539
|
+
function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
|
|
28357
30540
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
28358
30541
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
28359
30542
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -28409,7 +30592,7 @@ function computeStaticLayout(mode, input) {
|
|
|
28409
30592
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
28410
30593
|
const roots = findRoots(nodeIds, adjacency);
|
|
28411
30594
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
28412
|
-
if (mode === "tree") return
|
|
30595
|
+
if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
|
|
28413
30596
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
28414
30597
|
}
|
|
28415
30598
|
var init_graphViewLayouts = __esm({
|
|
@@ -29690,8 +31873,8 @@ function TableView({
|
|
|
29690
31873
|
columns,
|
|
29691
31874
|
fields,
|
|
29692
31875
|
itemActions,
|
|
29693
|
-
maxInlineActions,
|
|
29694
|
-
itemClickEvent,
|
|
31876
|
+
maxInlineActions: _maxInlineActions,
|
|
31877
|
+
itemClickEvent = "",
|
|
29695
31878
|
selectable = false,
|
|
29696
31879
|
selectEvent,
|
|
29697
31880
|
selectedIds,
|
|
@@ -29741,7 +31924,6 @@ function TableView({
|
|
|
29741
31924
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
29742
31925
|
const hasRenderProp = typeof children === "function";
|
|
29743
31926
|
const idField = dndItemIdField ?? "id";
|
|
29744
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
29745
31927
|
React85__default.useEffect(() => {
|
|
29746
31928
|
tableViewLog.debug("render", {
|
|
29747
31929
|
rowCount: data.length,
|
|
@@ -29783,21 +31965,14 @@ function TableView({
|
|
|
29783
31965
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
29784
31966
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
29785
31967
|
};
|
|
29786
|
-
const
|
|
29787
|
-
e.stopPropagation();
|
|
29788
|
-
const payload = {
|
|
29789
|
-
id: row.id,
|
|
29790
|
-
row
|
|
29791
|
-
};
|
|
29792
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
29793
|
-
};
|
|
31968
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
29794
31969
|
const handleRowClick = (row) => () => {
|
|
29795
|
-
if (!
|
|
31970
|
+
if (!rowClickEvent) return;
|
|
29796
31971
|
const payload = {
|
|
29797
31972
|
id: row.id,
|
|
29798
31973
|
row
|
|
29799
31974
|
};
|
|
29800
|
-
eventBus.emit(`UI:${
|
|
31975
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
29801
31976
|
};
|
|
29802
31977
|
const colFloors = React85__default.useMemo(
|
|
29803
31978
|
() => colDefs.map((col) => {
|
|
@@ -29813,10 +31988,7 @@ function TableView({
|
|
|
29813
31988
|
const statusNode = isLoading ? /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) }) : error ? /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "error", children: error.message }) }) : data.length === 0 ? /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) }) : null;
|
|
29814
31989
|
const lk = LOOKS[look];
|
|
29815
31990
|
const hasActions = actionDefs.length > 0;
|
|
29816
|
-
const
|
|
29817
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
29818
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
29819
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
31991
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
29820
31992
|
const gridTemplateColumns = [
|
|
29821
31993
|
selectable ? "auto" : null,
|
|
29822
31994
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -29863,7 +32035,7 @@ function TableView({
|
|
|
29863
32035
|
col.key
|
|
29864
32036
|
);
|
|
29865
32037
|
}),
|
|
29866
|
-
hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
|
|
32038
|
+
hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)] border-l border-[var(--color-border)] h-full" })
|
|
29867
32039
|
]
|
|
29868
32040
|
}
|
|
29869
32041
|
);
|
|
@@ -29875,12 +32047,12 @@ function TableView({
|
|
|
29875
32047
|
role: "row",
|
|
29876
32048
|
"data-entity-row": true,
|
|
29877
32049
|
"data-entity-id": id,
|
|
29878
|
-
onClick:
|
|
32050
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
29879
32051
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
29880
32052
|
className: cn(
|
|
29881
32053
|
"group items-center gap-3 transition-colors duration-fast",
|
|
29882
32054
|
hasRenderProp ? "flex" : "grid",
|
|
29883
|
-
|
|
32055
|
+
rowClickEvent && "cursor-pointer",
|
|
29884
32056
|
lk.rowPad,
|
|
29885
32057
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
29886
32058
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -29888,7 +32060,7 @@ function TableView({
|
|
|
29888
32060
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
29889
32061
|
),
|
|
29890
32062
|
children: [
|
|
29891
|
-
selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", onClick:
|
|
32063
|
+
selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsx(
|
|
29892
32064
|
Checkbox,
|
|
29893
32065
|
{
|
|
29894
32066
|
checked: selected.has(id),
|
|
@@ -29909,53 +32081,37 @@ function TableView({
|
|
|
29909
32081
|
}
|
|
29910
32082
|
return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
|
|
29911
32083
|
}),
|
|
29912
|
-
hasActions && /* @__PURE__ */
|
|
32084
|
+
hasActions && /* @__PURE__ */ jsx(
|
|
29913
32085
|
HStack,
|
|
29914
32086
|
{
|
|
29915
32087
|
gap: "xs",
|
|
29916
|
-
onClick:
|
|
32088
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
29917
32089
|
className: cn(
|
|
29918
32090
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
29919
|
-
// scroll container, which
|
|
29920
|
-
// Opaque so
|
|
32091
|
+
// scroll container, which would leave the kebab off-screen.
|
|
32092
|
+
// Opaque + hairline edge so it reads as a pinned column, not a
|
|
32093
|
+
// floating control, while scrolled cells pass underneath.
|
|
29921
32094
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
32095
|
+
"border-l border-[var(--color-border)]",
|
|
29922
32096
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
29923
32097
|
),
|
|
29924
|
-
children:
|
|
29925
|
-
|
|
29926
|
-
|
|
29927
|
-
|
|
29928
|
-
|
|
29929
|
-
|
|
29930
|
-
|
|
29931
|
-
|
|
29932
|
-
|
|
29933
|
-
|
|
29934
|
-
|
|
29935
|
-
|
|
29936
|
-
|
|
29937
|
-
|
|
29938
|
-
}
|
|
29939
|
-
|
|
29940
|
-
|
|
29941
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsx(
|
|
29942
|
-
Menu,
|
|
29943
|
-
{
|
|
29944
|
-
position: "bottom-end",
|
|
29945
|
-
trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
29946
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
29947
|
-
label: action.label,
|
|
29948
|
-
icon: action.icon,
|
|
29949
|
-
event: action.event,
|
|
29950
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
29951
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
29952
|
-
id: row.id,
|
|
29953
|
-
row
|
|
29954
|
-
})
|
|
29955
|
-
}))
|
|
29956
|
-
}
|
|
29957
|
-
)
|
|
29958
|
-
]
|
|
32098
|
+
children: /* @__PURE__ */ jsx(
|
|
32099
|
+
Menu,
|
|
32100
|
+
{
|
|
32101
|
+
position: "bottom-end",
|
|
32102
|
+
trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", "data-row-id": String(row.id), children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
32103
|
+
items: actionDefs.map((action) => ({
|
|
32104
|
+
label: action.label,
|
|
32105
|
+
icon: action.icon,
|
|
32106
|
+
event: action.event,
|
|
32107
|
+
variant: action.variant === "danger" ? "danger" : "default",
|
|
32108
|
+
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
32109
|
+
id: row.id,
|
|
32110
|
+
row
|
|
32111
|
+
})
|
|
32112
|
+
}))
|
|
32113
|
+
}
|
|
32114
|
+
)
|
|
29959
32115
|
}
|
|
29960
32116
|
)
|
|
29961
32117
|
]
|
|
@@ -29998,7 +32154,6 @@ var init_TableView = __esm({
|
|
|
29998
32154
|
init_format();
|
|
29999
32155
|
init_getNestedValue();
|
|
30000
32156
|
init_useEventBus();
|
|
30001
|
-
init_useMediaQuery();
|
|
30002
32157
|
init_Box();
|
|
30003
32158
|
init_Stack();
|
|
30004
32159
|
init_Typography();
|
|
@@ -44938,6 +47093,7 @@ var init_component_registry_generated = __esm({
|
|
|
44938
47093
|
init_ActionTile();
|
|
44939
47094
|
init_ActivationBlock();
|
|
44940
47095
|
init_ComponentPatterns();
|
|
47096
|
+
init_AlgoGraphCanvas();
|
|
44941
47097
|
init_AlgorithmCanvas();
|
|
44942
47098
|
init_AnimatedCounter();
|
|
44943
47099
|
init_AnimatedGraphic();
|
|
@@ -45201,6 +47357,7 @@ var init_component_registry_generated = __esm({
|
|
|
45201
47357
|
"ActivationBlock": ActivationBlock,
|
|
45202
47358
|
"Alert": AlertPattern,
|
|
45203
47359
|
"AlertPattern": AlertPattern,
|
|
47360
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
45204
47361
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
45205
47362
|
"AnimatedCounter": AnimatedCounter,
|
|
45206
47363
|
"AnimatedGraphic": AnimatedGraphic,
|
|
@@ -46527,9 +48684,9 @@ var log3 = createLogger("almadar:ui:effects:client-handlers");
|
|
|
46527
48684
|
function createClientEffectHandlers(options) {
|
|
46528
48685
|
const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
|
|
46529
48686
|
return {
|
|
46530
|
-
emit: (event, payload) => {
|
|
48687
|
+
emit: (event, payload, source) => {
|
|
46531
48688
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
46532
|
-
eventBus.emit(prefixedEvent, payload);
|
|
48689
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
46533
48690
|
},
|
|
46534
48691
|
persist: async () => {
|
|
46535
48692
|
log3.warn("persist is server-side only, ignored on client");
|
|
@@ -46864,7 +49021,7 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
|
|
|
46864
49021
|
}
|
|
46865
49022
|
};
|
|
46866
49023
|
ctx.emit = (event, payload) => {
|
|
46867
|
-
emit(event, payload);
|
|
49024
|
+
emit(event, payload, { trait: traitName, tick: tick.name });
|
|
46868
49025
|
};
|
|
46869
49026
|
if (tick.guard !== void 0 && !evaluateGuard(tick.guard, ctx)) {
|
|
46870
49027
|
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
@@ -47316,6 +49473,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
47316
49473
|
}
|
|
47317
49474
|
const effectContext = {
|
|
47318
49475
|
traitName,
|
|
49476
|
+
orbitalName: orbitalsByTrait?.[traitName],
|
|
47319
49477
|
state: previousState,
|
|
47320
49478
|
transition: `${previousState}->${newState}`,
|
|
47321
49479
|
linkedEntity,
|
|
@@ -47392,7 +49550,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
47392
49550
|
});
|
|
47393
49551
|
}
|
|
47394
49552
|
return emittedDuringExec;
|
|
47395
|
-
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot]);
|
|
49553
|
+
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot, orbitalsByTrait]);
|
|
47396
49554
|
const runTickEffects = useCallback((tick, binding) => {
|
|
47397
49555
|
const traitName = binding.trait.name;
|
|
47398
49556
|
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
@@ -47428,9 +49586,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
47428
49586
|
log: tickLog
|
|
47429
49587
|
});
|
|
47430
49588
|
}, [executeTransitionEffects, sharedEntityStore]);
|
|
47431
|
-
const emitFromSharedWriter = useCallback((event, payload) => {
|
|
49589
|
+
const emitFromSharedWriter = useCallback((event, payload, source) => {
|
|
47432
49590
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
47433
|
-
eventBus.emit(prefixedEvent, payload);
|
|
49591
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
47434
49592
|
}, [eventBus]);
|
|
47435
49593
|
useEffect(() => {
|
|
47436
49594
|
const scheduler = createTickScheduler();
|
|
@@ -48284,11 +50442,11 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeP
|
|
|
48284
50442
|
for (const orb of schema.orbitals) {
|
|
48285
50443
|
for (const pageRef of orb.pages ?? []) {
|
|
48286
50444
|
const name = typeof pageRef === "object" && pageRef !== null ? pageRef.name : void 0;
|
|
48287
|
-
if (name === pageName) return orb.theme;
|
|
50445
|
+
if (name === pageName) return orb.theme ?? schema.theme;
|
|
48288
50446
|
}
|
|
48289
50447
|
}
|
|
48290
50448
|
}
|
|
48291
|
-
return schema.orbitals[0]?.theme;
|
|
50449
|
+
return schema.orbitals[0]?.theme ?? schema.theme;
|
|
48292
50450
|
}, [schema, pageName]);
|
|
48293
50451
|
const inner = /* @__PURE__ */ jsx(VerificationProvider, { enabled: true, children: /* @__PURE__ */ jsx(
|
|
48294
50452
|
EntitySchemaProvider,
|