@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.cjs
CHANGED
|
@@ -209,7 +209,7 @@ function useEventBus() {
|
|
|
209
209
|
return {
|
|
210
210
|
...baseBus,
|
|
211
211
|
emit: (type, payload, source) => {
|
|
212
|
-
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
|
|
212
|
+
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
|
|
213
213
|
scopeLog.warn("emit:bare-key-no-scope", { type });
|
|
214
214
|
}
|
|
215
215
|
baseBus.emit(type, payload, source);
|
|
@@ -613,26 +613,6 @@ var init_useDragReorder = __esm({
|
|
|
613
613
|
"use client";
|
|
614
614
|
}
|
|
615
615
|
});
|
|
616
|
-
function useMediaQuery(query) {
|
|
617
|
-
const subscribe = React85.useCallback(
|
|
618
|
-
(onChange) => {
|
|
619
|
-
const mql = window.matchMedia(query);
|
|
620
|
-
mql.addEventListener("change", onChange);
|
|
621
|
-
return () => mql.removeEventListener("change", onChange);
|
|
622
|
-
},
|
|
623
|
-
[query]
|
|
624
|
-
);
|
|
625
|
-
return React85.useSyncExternalStore(
|
|
626
|
-
subscribe,
|
|
627
|
-
() => window.matchMedia(query).matches,
|
|
628
|
-
() => false
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
var init_useMediaQuery = __esm({
|
|
632
|
-
"hooks/useMediaQuery.ts"() {
|
|
633
|
-
"use client";
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
616
|
function useInfiniteScroll(onLoadMore, options = {}) {
|
|
637
617
|
const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
|
|
638
618
|
const observerRef = React85.useRef(null);
|
|
@@ -11733,6 +11713,14 @@ function shapeBounds(shape) {
|
|
|
11733
11713
|
w: shape.radius * 2 + 8,
|
|
11734
11714
|
h: shape.radius * 2 + 8
|
|
11735
11715
|
};
|
|
11716
|
+
case "ellipse":
|
|
11717
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11718
|
+
return {
|
|
11719
|
+
x: shape.x - shape.width / 2 - 4,
|
|
11720
|
+
y: shape.y - shape.height / 2 - 4,
|
|
11721
|
+
w: shape.width + 8,
|
|
11722
|
+
h: shape.height + 8
|
|
11723
|
+
};
|
|
11736
11724
|
case "rect":
|
|
11737
11725
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
11738
11726
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -11766,13 +11754,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
11766
11754
|
ctx.closePath();
|
|
11767
11755
|
ctx.fill();
|
|
11768
11756
|
}
|
|
11769
|
-
function drawShape(ctx, shape, width, height) {
|
|
11757
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
11770
11758
|
ctx.save();
|
|
11771
11759
|
const opacity = shape.opacity ?? 1;
|
|
11772
11760
|
ctx.globalAlpha = opacity;
|
|
11773
11761
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
11774
11762
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
11775
11763
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
11764
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
11776
11765
|
switch (shape.type) {
|
|
11777
11766
|
case "grid": {
|
|
11778
11767
|
const step = shape.step ?? 40;
|
|
@@ -11838,6 +11827,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11838
11827
|
ctx.stroke();
|
|
11839
11828
|
break;
|
|
11840
11829
|
}
|
|
11830
|
+
case "ellipse": {
|
|
11831
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11832
|
+
const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
|
|
11833
|
+
const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
|
|
11834
|
+
ctx.beginPath();
|
|
11835
|
+
ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
|
|
11836
|
+
if (fill) {
|
|
11837
|
+
ctx.fillStyle = fill;
|
|
11838
|
+
ctx.fill();
|
|
11839
|
+
}
|
|
11840
|
+
ctx.strokeStyle = stroke;
|
|
11841
|
+
ctx.stroke();
|
|
11842
|
+
break;
|
|
11843
|
+
}
|
|
11841
11844
|
case "rect": {
|
|
11842
11845
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
11843
11846
|
if (fill) {
|
|
@@ -11884,21 +11887,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
11884
11887
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
11885
11888
|
break;
|
|
11886
11889
|
}
|
|
11890
|
+
case "venn-region": {
|
|
11891
|
+
const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
|
|
11892
|
+
const c = allShapes.find((s) => s.type === "circle" && s.id === id);
|
|
11893
|
+
return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
|
|
11894
|
+
});
|
|
11895
|
+
const inside = resolveCircles(shape.inside);
|
|
11896
|
+
if (inside.length === 0) break;
|
|
11897
|
+
const outside = resolveCircles(shape.outside);
|
|
11898
|
+
const off = document.createElement("canvas");
|
|
11899
|
+
off.width = ctx.canvas.width;
|
|
11900
|
+
off.height = ctx.canvas.height;
|
|
11901
|
+
const octx = off.getContext("2d");
|
|
11902
|
+
if (!octx) break;
|
|
11903
|
+
octx.setTransform(ctx.getTransform());
|
|
11904
|
+
for (const c of inside) {
|
|
11905
|
+
const p = new Path2D();
|
|
11906
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11907
|
+
octx.clip(p);
|
|
11908
|
+
}
|
|
11909
|
+
octx.fillStyle = fill ?? stroke;
|
|
11910
|
+
octx.fillRect(0, 0, width, height);
|
|
11911
|
+
octx.globalCompositeOperation = "destination-out";
|
|
11912
|
+
for (const c of outside) {
|
|
11913
|
+
const p = new Path2D();
|
|
11914
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
11915
|
+
octx.fill(p);
|
|
11916
|
+
}
|
|
11917
|
+
ctx.save();
|
|
11918
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
11919
|
+
ctx.drawImage(off, 0, 0);
|
|
11920
|
+
ctx.restore();
|
|
11921
|
+
break;
|
|
11922
|
+
}
|
|
11887
11923
|
}
|
|
11888
11924
|
ctx.restore();
|
|
11889
11925
|
}
|
|
11890
|
-
|
|
11926
|
+
function readoutShapes(readouts, width) {
|
|
11927
|
+
const out = [];
|
|
11928
|
+
const chipH = 18;
|
|
11929
|
+
const gap = 6;
|
|
11930
|
+
let rightEdge = width - 6;
|
|
11931
|
+
let rowY = 6;
|
|
11932
|
+
for (const readout of readouts) {
|
|
11933
|
+
const text = `${readout.label}: ${String(readout.value)}`;
|
|
11934
|
+
const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
|
|
11935
|
+
let chipX = rightEdge - chipW;
|
|
11936
|
+
if (chipX < 4) {
|
|
11937
|
+
rowY += chipH + 4;
|
|
11938
|
+
rightEdge = width - 6;
|
|
11939
|
+
chipX = rightEdge - chipW;
|
|
11940
|
+
}
|
|
11941
|
+
const color = readout.color ?? "#334155";
|
|
11942
|
+
out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
|
|
11943
|
+
out.push({
|
|
11944
|
+
type: "text",
|
|
11945
|
+
x: chipX + chipW / 2,
|
|
11946
|
+
y: rowY + chipH / 2,
|
|
11947
|
+
text,
|
|
11948
|
+
color: "#ffffff",
|
|
11949
|
+
fontSize: 10,
|
|
11950
|
+
align: "center"
|
|
11951
|
+
});
|
|
11952
|
+
rightEdge = chipX - gap;
|
|
11953
|
+
}
|
|
11954
|
+
return out;
|
|
11955
|
+
}
|
|
11956
|
+
function traceShapes(panel, k, width, height) {
|
|
11957
|
+
const w = panel.width ?? Math.round(width * 0.32);
|
|
11958
|
+
const h = panel.height ?? Math.round(height * 0.28);
|
|
11959
|
+
const x = panel.x ?? width - w - 8;
|
|
11960
|
+
const y = panel.y ?? height - h - 8 - k * (h + 8);
|
|
11961
|
+
const allSamples = panel.series.flatMap((series) => series.samples);
|
|
11962
|
+
let xLo = Math.min(...allSamples.map((p) => p.x));
|
|
11963
|
+
let xHi = Math.max(...allSamples.map((p) => p.x));
|
|
11964
|
+
let yLo = Math.min(...allSamples.map((p) => p.y));
|
|
11965
|
+
let yHi = Math.max(...allSamples.map((p) => p.y));
|
|
11966
|
+
if (xLo === xHi) {
|
|
11967
|
+
xLo -= 1;
|
|
11968
|
+
xHi += 1;
|
|
11969
|
+
}
|
|
11970
|
+
if (yLo === yHi) {
|
|
11971
|
+
yLo -= 1;
|
|
11972
|
+
yHi += 1;
|
|
11973
|
+
}
|
|
11974
|
+
const backgroundColor = panel.backgroundColor ?? "#ffffff";
|
|
11975
|
+
const frameColor = panel.frameColor ?? "#94a3b8";
|
|
11976
|
+
const out = [];
|
|
11977
|
+
out.push({
|
|
11978
|
+
type: "rect",
|
|
11979
|
+
x,
|
|
11980
|
+
y,
|
|
11981
|
+
width: w,
|
|
11982
|
+
height: h,
|
|
11983
|
+
color: backgroundColor,
|
|
11984
|
+
fill: backgroundColor,
|
|
11985
|
+
opacity: panel.backgroundOpacity ?? 0.85
|
|
11986
|
+
});
|
|
11987
|
+
out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
|
|
11988
|
+
panel.series.forEach((series, j) => {
|
|
11989
|
+
const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
|
|
11990
|
+
const mapped = series.samples.map((p) => ({
|
|
11991
|
+
x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
|
|
11992
|
+
y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
|
|
11993
|
+
}));
|
|
11994
|
+
for (let i = 1; i < mapped.length; i++) {
|
|
11995
|
+
out.push({
|
|
11996
|
+
type: "line",
|
|
11997
|
+
x1: mapped[i - 1].x,
|
|
11998
|
+
y1: mapped[i - 1].y,
|
|
11999
|
+
x2: mapped[i].x,
|
|
12000
|
+
y2: mapped[i].y,
|
|
12001
|
+
color,
|
|
12002
|
+
lineWidth: 1.5
|
|
12003
|
+
});
|
|
12004
|
+
}
|
|
12005
|
+
if (mapped.length > 0) {
|
|
12006
|
+
const last = mapped[mapped.length - 1];
|
|
12007
|
+
out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
|
|
12008
|
+
}
|
|
12009
|
+
if (series.label) {
|
|
12010
|
+
out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
|
|
12011
|
+
}
|
|
12012
|
+
});
|
|
12013
|
+
if (panel.yLabel) {
|
|
12014
|
+
out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
12015
|
+
}
|
|
12016
|
+
if (panel.xLabel) {
|
|
12017
|
+
out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
12018
|
+
}
|
|
12019
|
+
return out;
|
|
12020
|
+
}
|
|
12021
|
+
var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
|
|
11891
12022
|
var init_LearningCanvas = __esm({
|
|
11892
12023
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
11893
12024
|
"use client";
|
|
11894
12025
|
init_cn();
|
|
11895
12026
|
init_useEventBus();
|
|
12027
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
12028
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
11896
12029
|
LearningCanvas = ({
|
|
11897
12030
|
className,
|
|
11898
12031
|
width = 600,
|
|
11899
12032
|
height = 400,
|
|
11900
12033
|
backgroundColor,
|
|
11901
12034
|
shapes = [],
|
|
12035
|
+
readouts,
|
|
12036
|
+
traces,
|
|
11902
12037
|
interactive = false,
|
|
11903
12038
|
animate = false,
|
|
11904
12039
|
onShapeClick,
|
|
@@ -11924,6 +12059,12 @@ var init_LearningCanvas = __esm({
|
|
|
11924
12059
|
}
|
|
11925
12060
|
return -1;
|
|
11926
12061
|
}, [shapes]);
|
|
12062
|
+
const derivedShapes = React85.useMemo(() => {
|
|
12063
|
+
if (!traces?.length && !readouts?.length) return shapes;
|
|
12064
|
+
const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
|
|
12065
|
+
const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
|
|
12066
|
+
return [...shapes, ...traceOut, ...readoutOut];
|
|
12067
|
+
}, [shapes, traces, readouts, width, height]);
|
|
11927
12068
|
const draw = React85.useCallback(() => {
|
|
11928
12069
|
const canvas = canvasRef.current;
|
|
11929
12070
|
if (!canvas) return;
|
|
@@ -11940,13 +12081,13 @@ var init_LearningCanvas = __esm({
|
|
|
11940
12081
|
ctx.fillStyle = backgroundColor;
|
|
11941
12082
|
ctx.fillRect(0, 0, width, height);
|
|
11942
12083
|
}
|
|
11943
|
-
for (const shape of
|
|
11944
|
-
if (shape.type !== "text") drawShape(ctx, shape, width, height);
|
|
12084
|
+
for (const shape of derivedShapes) {
|
|
12085
|
+
if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11945
12086
|
}
|
|
11946
|
-
for (const shape of
|
|
11947
|
-
if (shape.type === "text") drawShape(ctx, shape, width, height);
|
|
12087
|
+
for (const shape of derivedShapes) {
|
|
12088
|
+
if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
11948
12089
|
}
|
|
11949
|
-
}, [width, height, backgroundColor,
|
|
12090
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
11950
12091
|
React85.useEffect(() => {
|
|
11951
12092
|
draw();
|
|
11952
12093
|
}, [draw]);
|
|
@@ -13496,7 +13637,363 @@ var init_ComponentPatterns = __esm({
|
|
|
13496
13637
|
AlertPattern.displayName = "AlertPattern";
|
|
13497
13638
|
}
|
|
13498
13639
|
});
|
|
13499
|
-
|
|
13640
|
+
function layoutCircle(nodes, width, height) {
|
|
13641
|
+
const cx = width / 2;
|
|
13642
|
+
const cy = height / 2;
|
|
13643
|
+
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
|
13644
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13645
|
+
const n = nodes.length;
|
|
13646
|
+
nodes.forEach((node, i) => {
|
|
13647
|
+
const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
|
|
13648
|
+
positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
|
|
13649
|
+
});
|
|
13650
|
+
return positions;
|
|
13651
|
+
}
|
|
13652
|
+
function layoutTree(nodes, edges, root, width, height) {
|
|
13653
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
13654
|
+
const idSet = new Set(nodeIds);
|
|
13655
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
13656
|
+
const hasIncoming = /* @__PURE__ */ new Set();
|
|
13657
|
+
for (const e of edges) {
|
|
13658
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
13659
|
+
const list = childrenOf.get(e.from) ?? [];
|
|
13660
|
+
list.push(e.to);
|
|
13661
|
+
childrenOf.set(e.from, list);
|
|
13662
|
+
hasIncoming.add(e.to);
|
|
13663
|
+
}
|
|
13664
|
+
const depth = /* @__PURE__ */ new Map();
|
|
13665
|
+
const treeChildren = /* @__PURE__ */ new Map();
|
|
13666
|
+
const visited = /* @__PURE__ */ new Set();
|
|
13667
|
+
const bfsFrom = (start) => {
|
|
13668
|
+
if (visited.has(start)) return;
|
|
13669
|
+
visited.add(start);
|
|
13670
|
+
depth.set(start, 0);
|
|
13671
|
+
const queue = [start];
|
|
13672
|
+
while (queue.length > 0) {
|
|
13673
|
+
const u = queue.shift();
|
|
13674
|
+
for (const v of childrenOf.get(u) ?? []) {
|
|
13675
|
+
if (visited.has(v)) continue;
|
|
13676
|
+
visited.add(v);
|
|
13677
|
+
depth.set(v, (depth.get(u) ?? 0) + 1);
|
|
13678
|
+
const list = treeChildren.get(u) ?? [];
|
|
13679
|
+
list.push(v);
|
|
13680
|
+
treeChildren.set(u, list);
|
|
13681
|
+
queue.push(v);
|
|
13682
|
+
}
|
|
13683
|
+
}
|
|
13684
|
+
};
|
|
13685
|
+
const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
|
|
13686
|
+
const rootsOrder = [];
|
|
13687
|
+
if (primaryRoot !== void 0) {
|
|
13688
|
+
bfsFrom(primaryRoot);
|
|
13689
|
+
rootsOrder.push(primaryRoot);
|
|
13690
|
+
}
|
|
13691
|
+
for (const id of nodeIds) {
|
|
13692
|
+
if (!visited.has(id)) {
|
|
13693
|
+
bfsFrom(id);
|
|
13694
|
+
rootsOrder.push(id);
|
|
13695
|
+
}
|
|
13696
|
+
}
|
|
13697
|
+
let leafCounter = 0;
|
|
13698
|
+
const xSlot = /* @__PURE__ */ new Map();
|
|
13699
|
+
const assignXSlot = (u) => {
|
|
13700
|
+
const children = treeChildren.get(u) ?? [];
|
|
13701
|
+
if (children.length === 0) {
|
|
13702
|
+
const slot = leafCounter++;
|
|
13703
|
+
xSlot.set(u, slot);
|
|
13704
|
+
return slot;
|
|
13705
|
+
}
|
|
13706
|
+
const childSlots = children.map(assignXSlot);
|
|
13707
|
+
const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
|
|
13708
|
+
xSlot.set(u, avg);
|
|
13709
|
+
return avg;
|
|
13710
|
+
};
|
|
13711
|
+
for (const r of rootsOrder) assignXSlot(r);
|
|
13712
|
+
let maxDepth = 0;
|
|
13713
|
+
for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
|
|
13714
|
+
const colWidth = width / Math.max(1, leafCounter);
|
|
13715
|
+
const rowHeight = height / (maxDepth + 1);
|
|
13716
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13717
|
+
for (const id of nodeIds) {
|
|
13718
|
+
const slot = xSlot.get(id) ?? 0;
|
|
13719
|
+
const d = depth.get(id) ?? 0;
|
|
13720
|
+
positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
|
|
13721
|
+
}
|
|
13722
|
+
return positions;
|
|
13723
|
+
}
|
|
13724
|
+
function layoutLayered(nodes, edges, width, height) {
|
|
13725
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
13726
|
+
const idSet = new Set(nodeIds);
|
|
13727
|
+
const adj = /* @__PURE__ */ new Map();
|
|
13728
|
+
const remainingIndegree = /* @__PURE__ */ new Map();
|
|
13729
|
+
for (const id of nodeIds) remainingIndegree.set(id, 0);
|
|
13730
|
+
for (const e of edges) {
|
|
13731
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
13732
|
+
const list = adj.get(e.from) ?? [];
|
|
13733
|
+
list.push(e.to);
|
|
13734
|
+
adj.set(e.from, list);
|
|
13735
|
+
remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
|
|
13736
|
+
}
|
|
13737
|
+
const layer = /* @__PURE__ */ new Map();
|
|
13738
|
+
const dequeued = /* @__PURE__ */ new Set();
|
|
13739
|
+
const queue = [];
|
|
13740
|
+
for (const id of nodeIds) {
|
|
13741
|
+
if ((remainingIndegree.get(id) ?? 0) === 0) {
|
|
13742
|
+
layer.set(id, 0);
|
|
13743
|
+
queue.push(id);
|
|
13744
|
+
}
|
|
13745
|
+
}
|
|
13746
|
+
while (queue.length > 0) {
|
|
13747
|
+
const u = queue.shift();
|
|
13748
|
+
dequeued.add(u);
|
|
13749
|
+
for (const v of adj.get(u) ?? []) {
|
|
13750
|
+
const candidate = (layer.get(u) ?? 0) + 1;
|
|
13751
|
+
layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
|
|
13752
|
+
remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
|
|
13753
|
+
if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
|
|
13754
|
+
queue.push(v);
|
|
13755
|
+
}
|
|
13756
|
+
}
|
|
13757
|
+
}
|
|
13758
|
+
let baseMaxLayer = 0;
|
|
13759
|
+
for (const id of nodeIds) {
|
|
13760
|
+
if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
|
|
13761
|
+
}
|
|
13762
|
+
const cycleLayer = baseMaxLayer + 1;
|
|
13763
|
+
let maxLayer = baseMaxLayer;
|
|
13764
|
+
for (const id of nodeIds) {
|
|
13765
|
+
if (!dequeued.has(id)) {
|
|
13766
|
+
layer.set(id, cycleLayer);
|
|
13767
|
+
maxLayer = cycleLayer;
|
|
13768
|
+
}
|
|
13769
|
+
}
|
|
13770
|
+
const colWidth = width / Math.max(1, maxLayer + 1);
|
|
13771
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
13772
|
+
for (const id of nodeIds) {
|
|
13773
|
+
const l = layer.get(id) ?? 0;
|
|
13774
|
+
const list = byLayer.get(l) ?? [];
|
|
13775
|
+
list.push(id);
|
|
13776
|
+
byLayer.set(l, list);
|
|
13777
|
+
}
|
|
13778
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13779
|
+
for (const [l, ids] of byLayer) {
|
|
13780
|
+
const rowHeight = height / ids.length;
|
|
13781
|
+
ids.forEach((id, i) => {
|
|
13782
|
+
positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
|
|
13783
|
+
});
|
|
13784
|
+
}
|
|
13785
|
+
return positions;
|
|
13786
|
+
}
|
|
13787
|
+
function computePositions(nodes, edges, layout, root, width, height) {
|
|
13788
|
+
switch (layout) {
|
|
13789
|
+
case "circle":
|
|
13790
|
+
return layoutCircle(nodes, width, height);
|
|
13791
|
+
case "tree":
|
|
13792
|
+
return layoutTree(nodes, edges, root, width, height);
|
|
13793
|
+
case "layered":
|
|
13794
|
+
return layoutLayered(nodes, edges, width, height);
|
|
13795
|
+
case "manual":
|
|
13796
|
+
default: {
|
|
13797
|
+
const positions = /* @__PURE__ */ new Map();
|
|
13798
|
+
for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
|
|
13799
|
+
return positions;
|
|
13800
|
+
}
|
|
13801
|
+
}
|
|
13802
|
+
}
|
|
13803
|
+
var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS, AlgoGraphCanvas;
|
|
13804
|
+
var init_AlgoGraphCanvas = __esm({
|
|
13805
|
+
"components/learning/molecules/AlgoGraphCanvas.tsx"() {
|
|
13806
|
+
"use client";
|
|
13807
|
+
init_atoms();
|
|
13808
|
+
init_Stack();
|
|
13809
|
+
init_LearningCanvas();
|
|
13810
|
+
NODE_STATE_COLOR = {
|
|
13811
|
+
unvisited: "#cbd5e1",
|
|
13812
|
+
frontier: "#f59e0b",
|
|
13813
|
+
current: "#ef4444",
|
|
13814
|
+
visited: "#22c55e",
|
|
13815
|
+
goal: "#8b5cf6",
|
|
13816
|
+
path: "#0ea5e9"
|
|
13817
|
+
};
|
|
13818
|
+
EDGE_STATE_COLOR = {
|
|
13819
|
+
default: "#9ca3af",
|
|
13820
|
+
tree: "#16a34a",
|
|
13821
|
+
relaxed: "#f59e0b",
|
|
13822
|
+
candidate: "#38bdf8",
|
|
13823
|
+
path: "#dc2626"
|
|
13824
|
+
};
|
|
13825
|
+
DEFAULT_NODE_RADIUS = 18;
|
|
13826
|
+
AlgoGraphCanvas = ({
|
|
13827
|
+
className,
|
|
13828
|
+
width = 600,
|
|
13829
|
+
height = 400,
|
|
13830
|
+
title,
|
|
13831
|
+
backgroundColor,
|
|
13832
|
+
nodes = [],
|
|
13833
|
+
edges = [],
|
|
13834
|
+
layout = "manual",
|
|
13835
|
+
root,
|
|
13836
|
+
shapes = [],
|
|
13837
|
+
interactive = false,
|
|
13838
|
+
animate = false,
|
|
13839
|
+
onShapeClick,
|
|
13840
|
+
onNodeClick,
|
|
13841
|
+
isLoading,
|
|
13842
|
+
error
|
|
13843
|
+
}) => {
|
|
13844
|
+
const nodeById = React85.useMemo(() => {
|
|
13845
|
+
const m = /* @__PURE__ */ new Map();
|
|
13846
|
+
for (const n of nodes) m.set(n.id, n);
|
|
13847
|
+
return m;
|
|
13848
|
+
}, [nodes]);
|
|
13849
|
+
const nodeIndexById = React85.useMemo(() => {
|
|
13850
|
+
const m = /* @__PURE__ */ new Map();
|
|
13851
|
+
nodes.forEach((n, i) => m.set(n.id, i));
|
|
13852
|
+
return m;
|
|
13853
|
+
}, [nodes]);
|
|
13854
|
+
const derivedShapes = React85.useMemo(() => {
|
|
13855
|
+
const out = [];
|
|
13856
|
+
const positions = computePositions(nodes, edges, layout, root, width, height);
|
|
13857
|
+
const edgeGeoms = [];
|
|
13858
|
+
for (const e of edges) {
|
|
13859
|
+
const a = nodeById.get(e.from);
|
|
13860
|
+
const b = nodeById.get(e.to);
|
|
13861
|
+
const posA = positions.get(e.from);
|
|
13862
|
+
const posB = positions.get(e.to);
|
|
13863
|
+
if (!a || !b || !posA || !posB) continue;
|
|
13864
|
+
const rA = a.radius ?? DEFAULT_NODE_RADIUS;
|
|
13865
|
+
const rB = b.radius ?? DEFAULT_NODE_RADIUS;
|
|
13866
|
+
const dx = posB.x - posA.x;
|
|
13867
|
+
const dy = posB.y - posA.y;
|
|
13868
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
13869
|
+
const ux = dx / dist;
|
|
13870
|
+
const uy = dy / dist;
|
|
13871
|
+
edgeGeoms.push({
|
|
13872
|
+
directed: e.directed ?? false,
|
|
13873
|
+
x1: posA.x + ux * rA,
|
|
13874
|
+
y1: posA.y + uy * rA,
|
|
13875
|
+
x2: posB.x - ux * rB,
|
|
13876
|
+
y2: posB.y - uy * rB,
|
|
13877
|
+
color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
|
|
13878
|
+
label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
|
|
13879
|
+
});
|
|
13880
|
+
}
|
|
13881
|
+
for (const g of edgeGeoms) {
|
|
13882
|
+
out.push({
|
|
13883
|
+
type: g.directed ? "arrow" : "line",
|
|
13884
|
+
x1: g.x1,
|
|
13885
|
+
y1: g.y1,
|
|
13886
|
+
x2: g.x2,
|
|
13887
|
+
y2: g.y2,
|
|
13888
|
+
color: g.color,
|
|
13889
|
+
lineWidth: 2
|
|
13890
|
+
});
|
|
13891
|
+
}
|
|
13892
|
+
for (const g of edgeGeoms) {
|
|
13893
|
+
if (g.label === void 0) continue;
|
|
13894
|
+
const midX = (g.x1 + g.x2) / 2;
|
|
13895
|
+
const midY = (g.y1 + g.y2) / 2;
|
|
13896
|
+
const dx = g.x2 - g.x1;
|
|
13897
|
+
const dy = g.y2 - g.y1;
|
|
13898
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
13899
|
+
const perpX = -(dy / dist);
|
|
13900
|
+
const perpY = dx / dist;
|
|
13901
|
+
out.push({
|
|
13902
|
+
type: "text",
|
|
13903
|
+
x: midX + perpX * 10,
|
|
13904
|
+
y: midY + perpY * 10,
|
|
13905
|
+
text: g.label,
|
|
13906
|
+
fontSize: 11,
|
|
13907
|
+
align: "center",
|
|
13908
|
+
color: "#374151"
|
|
13909
|
+
});
|
|
13910
|
+
}
|
|
13911
|
+
const nodeGeoms = [];
|
|
13912
|
+
for (const n of nodes) {
|
|
13913
|
+
const pos = positions.get(n.id);
|
|
13914
|
+
if (!pos) continue;
|
|
13915
|
+
nodeGeoms.push({
|
|
13916
|
+
id: n.id,
|
|
13917
|
+
x: pos.x,
|
|
13918
|
+
y: pos.y,
|
|
13919
|
+
radius: n.radius ?? DEFAULT_NODE_RADIUS,
|
|
13920
|
+
color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
|
|
13921
|
+
label: n.label,
|
|
13922
|
+
badge: n.badge
|
|
13923
|
+
});
|
|
13924
|
+
}
|
|
13925
|
+
for (const g of nodeGeoms) {
|
|
13926
|
+
out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
|
|
13927
|
+
}
|
|
13928
|
+
const badgeGeoms = [];
|
|
13929
|
+
for (const g of nodeGeoms) {
|
|
13930
|
+
if (!g.badge) continue;
|
|
13931
|
+
const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
|
|
13932
|
+
badgeGeoms.push({
|
|
13933
|
+
cx: g.x + g.radius * 0.75,
|
|
13934
|
+
cy: g.y - g.radius * 0.75,
|
|
13935
|
+
w,
|
|
13936
|
+
h: 14,
|
|
13937
|
+
// Borderless pill: same color drives both stroke and fill.
|
|
13938
|
+
color: g.badge.color ?? "#1e293b",
|
|
13939
|
+
text: g.badge.text
|
|
13940
|
+
});
|
|
13941
|
+
}
|
|
13942
|
+
for (const b of badgeGeoms) {
|
|
13943
|
+
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 });
|
|
13944
|
+
}
|
|
13945
|
+
for (const b of badgeGeoms) {
|
|
13946
|
+
out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
|
|
13947
|
+
}
|
|
13948
|
+
for (const g of nodeGeoms) {
|
|
13949
|
+
if (g.label === void 0) continue;
|
|
13950
|
+
out.push({
|
|
13951
|
+
type: "text",
|
|
13952
|
+
x: g.x,
|
|
13953
|
+
y: g.y + g.radius + 14,
|
|
13954
|
+
text: g.label,
|
|
13955
|
+
fontSize: 12,
|
|
13956
|
+
align: "center",
|
|
13957
|
+
color: "#111827"
|
|
13958
|
+
});
|
|
13959
|
+
}
|
|
13960
|
+
out.push(...shapes);
|
|
13961
|
+
return out;
|
|
13962
|
+
}, [nodes, edges, layout, root, width, height, nodeById, shapes]);
|
|
13963
|
+
const handleShapeClick = React85.useCallback(
|
|
13964
|
+
(payload) => {
|
|
13965
|
+
if (payload.type === "circle" && payload.id) {
|
|
13966
|
+
const node = nodeById.get(payload.id);
|
|
13967
|
+
const idx = nodeIndexById.get(payload.id);
|
|
13968
|
+
if (node && idx !== void 0) {
|
|
13969
|
+
onNodeClick?.({ id: node.id, label: node.label, index: idx });
|
|
13970
|
+
}
|
|
13971
|
+
}
|
|
13972
|
+
onShapeClick?.(payload);
|
|
13973
|
+
},
|
|
13974
|
+
[nodeById, nodeIndexById, onNodeClick, onShapeClick]
|
|
13975
|
+
);
|
|
13976
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
13977
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13978
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13979
|
+
LearningCanvas,
|
|
13980
|
+
{
|
|
13981
|
+
width,
|
|
13982
|
+
height,
|
|
13983
|
+
backgroundColor,
|
|
13984
|
+
shapes: derivedShapes,
|
|
13985
|
+
interactive,
|
|
13986
|
+
animate,
|
|
13987
|
+
onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
|
|
13988
|
+
isLoading,
|
|
13989
|
+
error
|
|
13990
|
+
}
|
|
13991
|
+
)
|
|
13992
|
+
] }) });
|
|
13993
|
+
};
|
|
13994
|
+
}
|
|
13995
|
+
});
|
|
13996
|
+
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;
|
|
13500
13997
|
var init_AlgorithmCanvas = __esm({
|
|
13501
13998
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
13502
13999
|
"use client";
|
|
@@ -13508,6 +14005,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13508
14005
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
13509
14006
|
POINTER_BAND = 34;
|
|
13510
14007
|
TOP_PAD = 26;
|
|
14008
|
+
PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
|
|
14009
|
+
RANGE_COLOR_DEFAULT = "#3b82f6";
|
|
14010
|
+
RANGE_FILL_OPACITY = 0.15;
|
|
14011
|
+
BRACKET_TOP_OFFSET = 16;
|
|
14012
|
+
BRACKET_ROW_H = 14;
|
|
14013
|
+
BRACKET_TICK_H = 6;
|
|
14014
|
+
BRACKET_LABEL_OFFSET = 6;
|
|
14015
|
+
SLOT_EMPTY_FILL = "#f1f5f9";
|
|
14016
|
+
SLOT_EMPTY_STROKE = "#cbd5e1";
|
|
14017
|
+
SLOT_FILLED_STROKE = "#9ca3af";
|
|
14018
|
+
SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
|
|
14019
|
+
SLOT_VALUE_TEXT_COLOR = "#ffffff";
|
|
14020
|
+
FRAME_ACTIVE_COLOR = "#3b82f6";
|
|
14021
|
+
FRAME_RETURNING_COLOR = "#f59e0b";
|
|
14022
|
+
FRAME_DONE_COLOR = "#94a3b8";
|
|
14023
|
+
FRAME_LABEL_COLOR = "#ffffff";
|
|
14024
|
+
FRAME_DETAIL_COLOR = "#e2e8f0";
|
|
14025
|
+
FRAME_TWO_LINE_MIN_H = 22;
|
|
14026
|
+
BUCKET_INDEX_FILL = "#e2e8f0";
|
|
14027
|
+
BUCKET_INDEX_STROKE = "#9ca3af";
|
|
14028
|
+
BUCKET_INDEX_TEXT = "#374151";
|
|
14029
|
+
BUCKET_ENTRY_TEXT = "#ffffff";
|
|
14030
|
+
BUCKET_ENTRY_DEFAULT = "#3b82f6";
|
|
14031
|
+
BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
|
|
14032
|
+
BUCKET_ENTRY_PROBING = "#38bdf8";
|
|
14033
|
+
BUCKET_ENTRY_MIN_W = 24;
|
|
14034
|
+
BUCKET_ENTRY_MAX_W = 64;
|
|
14035
|
+
AXIS_LABEL_COLOR = "#6b7280";
|
|
14036
|
+
AXIS_LABEL_FONT_SIZE = 10;
|
|
14037
|
+
CORNER_TEXT_COLOR = "#111827";
|
|
14038
|
+
CORNER_FONT_SIZE = 7;
|
|
14039
|
+
CORNER_MIN_CELL = 28;
|
|
14040
|
+
CORNER_INSET_X = 3;
|
|
14041
|
+
CORNER_INSET_Y = 6;
|
|
14042
|
+
AUX_PRIMARY_RATIO = 0.6;
|
|
14043
|
+
AUX_LABEL_BAND = 18;
|
|
14044
|
+
AUX_BASELINE_PAD = 8;
|
|
13511
14045
|
AlgorithmCanvas = ({
|
|
13512
14046
|
className,
|
|
13513
14047
|
width = 600,
|
|
@@ -13517,6 +14051,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13517
14051
|
bars = [],
|
|
13518
14052
|
cells = [],
|
|
13519
14053
|
pointers = [],
|
|
14054
|
+
ranges = [],
|
|
14055
|
+
slots = [],
|
|
14056
|
+
slotOrientation = "horizontal",
|
|
14057
|
+
frames = [],
|
|
14058
|
+
buckets = [],
|
|
14059
|
+
auxBars = [],
|
|
14060
|
+
rowLabels = [],
|
|
14061
|
+
colLabels = [],
|
|
13520
14062
|
shapes = [],
|
|
13521
14063
|
interactive = false,
|
|
13522
14064
|
animate = false,
|
|
@@ -13526,12 +14068,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13526
14068
|
}) => {
|
|
13527
14069
|
const derivedShapes = React85.useMemo(() => {
|
|
13528
14070
|
const out = [];
|
|
14071
|
+
const presence = {
|
|
14072
|
+
bars: bars.length > 0,
|
|
14073
|
+
slots: slots.length > 0,
|
|
14074
|
+
cells: cells.length > 0,
|
|
14075
|
+
buckets: buckets.length > 0,
|
|
14076
|
+
frames: frames.length > 0
|
|
14077
|
+
};
|
|
14078
|
+
const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
|
|
14079
|
+
const panelHeight = height / Math.max(1, panelCount);
|
|
14080
|
+
const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
|
|
14081
|
+
let compactIndex = 0;
|
|
14082
|
+
PANEL_FAMILY_ORDER.forEach((f3) => {
|
|
14083
|
+
if (presence[f3]) {
|
|
14084
|
+
panelY[f3] = compactIndex * panelHeight;
|
|
14085
|
+
compactIndex += 1;
|
|
14086
|
+
}
|
|
14087
|
+
});
|
|
13529
14088
|
if (bars.length > 0) {
|
|
14089
|
+
const panelYBars = panelY.bars;
|
|
13530
14090
|
const slot = width / bars.length;
|
|
13531
14091
|
const barW = slot * 0.8;
|
|
13532
14092
|
const gap = slot * 0.1;
|
|
13533
|
-
const
|
|
13534
|
-
const
|
|
14093
|
+
const bracketRanges = ranges.filter((r) => r.kind === "bracket");
|
|
14094
|
+
const bracketCount = bracketRanges.length;
|
|
14095
|
+
const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
|
|
14096
|
+
const hasAux = auxBars.length > 0;
|
|
14097
|
+
const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
|
|
14098
|
+
const baseline = panelYBars + primaryH - POINTER_BAND;
|
|
14099
|
+
const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
|
|
13535
14100
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
13536
14101
|
bars.forEach((bar, i) => {
|
|
13537
14102
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -13561,6 +14126,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13561
14126
|
});
|
|
13562
14127
|
}
|
|
13563
14128
|
});
|
|
14129
|
+
ranges.forEach((r) => {
|
|
14130
|
+
const kind = r.kind ?? "fill";
|
|
14131
|
+
if (kind !== "fill") return;
|
|
14132
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14133
|
+
out.push({
|
|
14134
|
+
type: "rect",
|
|
14135
|
+
x: r.from * slot,
|
|
14136
|
+
y: panelYBars,
|
|
14137
|
+
width: (r.to - r.from + 1) * slot,
|
|
14138
|
+
height: primaryH,
|
|
14139
|
+
color,
|
|
14140
|
+
fill: color,
|
|
14141
|
+
opacity: RANGE_FILL_OPACITY
|
|
14142
|
+
});
|
|
14143
|
+
if (r.label) {
|
|
14144
|
+
out.push({
|
|
14145
|
+
type: "text",
|
|
14146
|
+
x: r.from * slot + 4,
|
|
14147
|
+
// Sits below the bracket block (if any) so fill and bracket labels never collide.
|
|
14148
|
+
y: panelYBars + 10 + bracketHeadroom,
|
|
14149
|
+
text: r.label,
|
|
14150
|
+
color,
|
|
14151
|
+
fontSize: 10,
|
|
14152
|
+
align: "left"
|
|
14153
|
+
});
|
|
14154
|
+
}
|
|
14155
|
+
});
|
|
14156
|
+
bracketRanges.forEach((r, i) => {
|
|
14157
|
+
const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
|
|
14158
|
+
const x1 = r.from * slot + slot * 0.1;
|
|
14159
|
+
const x2 = (r.to + 1) * slot - slot * 0.1;
|
|
14160
|
+
const color = r.color ?? RANGE_COLOR_DEFAULT;
|
|
14161
|
+
out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
|
|
14162
|
+
out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14163
|
+
out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
14164
|
+
if (r.label) {
|
|
14165
|
+
out.push({
|
|
14166
|
+
type: "text",
|
|
14167
|
+
x: (x1 + x2) / 2,
|
|
14168
|
+
y: bracketY - BRACKET_LABEL_OFFSET,
|
|
14169
|
+
text: r.label,
|
|
14170
|
+
color,
|
|
14171
|
+
fontSize: 10,
|
|
14172
|
+
align: "center"
|
|
14173
|
+
});
|
|
14174
|
+
}
|
|
14175
|
+
});
|
|
14176
|
+
if (hasAux) {
|
|
14177
|
+
const auxH = panelHeight - primaryH;
|
|
14178
|
+
const slot2 = width / auxBars.length;
|
|
14179
|
+
const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
|
|
14180
|
+
const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
|
|
14181
|
+
const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
14182
|
+
auxBars.forEach((bar, i) => {
|
|
14183
|
+
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
14184
|
+
const bh = Math.max(0, v / maxAuxV * auxUsableH);
|
|
14185
|
+
const x = i * slot2 + slot2 * 0.1;
|
|
14186
|
+
const w = slot2 * 0.8;
|
|
14187
|
+
const color = bar.color ?? DEFAULT_BAR_COLOR;
|
|
14188
|
+
out.push({
|
|
14189
|
+
type: "rect",
|
|
14190
|
+
id: `auxbar-${i}`,
|
|
14191
|
+
x,
|
|
14192
|
+
y: auxBaseline - bh,
|
|
14193
|
+
width: w,
|
|
14194
|
+
height: bh,
|
|
14195
|
+
color,
|
|
14196
|
+
fill: color
|
|
14197
|
+
});
|
|
14198
|
+
const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
|
|
14199
|
+
if (label) {
|
|
14200
|
+
out.push({
|
|
14201
|
+
type: "text",
|
|
14202
|
+
x: x + w / 2,
|
|
14203
|
+
y: auxBaseline - bh - 8,
|
|
14204
|
+
text: label,
|
|
14205
|
+
color: "#374151",
|
|
14206
|
+
fontSize: 11,
|
|
14207
|
+
align: "center"
|
|
14208
|
+
});
|
|
14209
|
+
}
|
|
14210
|
+
});
|
|
14211
|
+
}
|
|
13564
14212
|
pointers.forEach((p) => {
|
|
13565
14213
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
13566
14214
|
const cx = p.index * slot + slot / 2;
|
|
@@ -13568,7 +14216,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13568
14216
|
out.push({
|
|
13569
14217
|
type: "arrow",
|
|
13570
14218
|
x1: cx,
|
|
13571
|
-
y1:
|
|
14219
|
+
y1: panelYBars + primaryH - 18,
|
|
13572
14220
|
x2: cx,
|
|
13573
14221
|
y2: baseline + 4,
|
|
13574
14222
|
color,
|
|
@@ -13578,7 +14226,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13578
14226
|
out.push({
|
|
13579
14227
|
type: "text",
|
|
13580
14228
|
x: cx,
|
|
13581
|
-
y:
|
|
14229
|
+
y: panelYBars + primaryH - 8,
|
|
13582
14230
|
text: p.label,
|
|
13583
14231
|
color,
|
|
13584
14232
|
fontSize: 11,
|
|
@@ -13587,14 +14235,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13587
14235
|
}
|
|
13588
14236
|
});
|
|
13589
14237
|
}
|
|
14238
|
+
if (slots.length > 0) {
|
|
14239
|
+
const panelYSlots = panelY.slots;
|
|
14240
|
+
const n = slots.length;
|
|
14241
|
+
const vertical = slotOrientation === "vertical";
|
|
14242
|
+
const vBoxH = panelHeight / n;
|
|
14243
|
+
const vBoxW = Math.min(width * 0.5, 120);
|
|
14244
|
+
const vBoxX = (width - vBoxW) / 2;
|
|
14245
|
+
const hCellW = width / n;
|
|
14246
|
+
const hBoxW = hCellW * 0.82;
|
|
14247
|
+
const hBoxH = Math.min(panelHeight * 0.6, 48);
|
|
14248
|
+
const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
|
|
14249
|
+
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 };
|
|
14250
|
+
slots.forEach((s, i) => {
|
|
14251
|
+
const box = slotBox(i);
|
|
14252
|
+
const state = s.state ?? "filled";
|
|
14253
|
+
const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
|
|
14254
|
+
const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
|
|
14255
|
+
out.push({
|
|
14256
|
+
type: "rect",
|
|
14257
|
+
id: `slot-${i}`,
|
|
14258
|
+
x: box.x,
|
|
14259
|
+
y: box.y,
|
|
14260
|
+
width: box.width,
|
|
14261
|
+
height: box.height,
|
|
14262
|
+
color: stroke,
|
|
14263
|
+
fill
|
|
14264
|
+
});
|
|
14265
|
+
if (s.value != null && state !== "empty") {
|
|
14266
|
+
out.push({
|
|
14267
|
+
type: "text",
|
|
14268
|
+
x: box.x + box.width / 2,
|
|
14269
|
+
y: box.y + box.height / 2,
|
|
14270
|
+
text: String(s.value),
|
|
14271
|
+
color: SLOT_VALUE_TEXT_COLOR,
|
|
14272
|
+
fontSize: 12,
|
|
14273
|
+
align: "center"
|
|
14274
|
+
});
|
|
14275
|
+
}
|
|
14276
|
+
});
|
|
14277
|
+
if (bars.length === 0) {
|
|
14278
|
+
pointers.forEach((p) => {
|
|
14279
|
+
if (p.index < 0 || p.index >= slots.length) return;
|
|
14280
|
+
const box = slotBox(p.index);
|
|
14281
|
+
const color = p.color ?? DEFAULT_POINTER_COLOR;
|
|
14282
|
+
if (vertical) {
|
|
14283
|
+
const cy = box.y + box.height / 2;
|
|
14284
|
+
out.push({
|
|
14285
|
+
type: "arrow",
|
|
14286
|
+
x1: box.x + box.width + 34,
|
|
14287
|
+
y1: cy,
|
|
14288
|
+
x2: box.x + box.width + 4,
|
|
14289
|
+
y2: cy,
|
|
14290
|
+
color,
|
|
14291
|
+
lineWidth: 2
|
|
14292
|
+
});
|
|
14293
|
+
if (p.label) {
|
|
14294
|
+
out.push({
|
|
14295
|
+
type: "text",
|
|
14296
|
+
x: box.x + box.width + 38,
|
|
14297
|
+
y: cy,
|
|
14298
|
+
text: p.label,
|
|
14299
|
+
color,
|
|
14300
|
+
fontSize: 11,
|
|
14301
|
+
align: "left"
|
|
14302
|
+
});
|
|
14303
|
+
}
|
|
14304
|
+
} else {
|
|
14305
|
+
const cx = box.x + box.width / 2;
|
|
14306
|
+
out.push({
|
|
14307
|
+
type: "arrow",
|
|
14308
|
+
x1: cx,
|
|
14309
|
+
y1: panelYSlots + panelHeight - 18,
|
|
14310
|
+
x2: cx,
|
|
14311
|
+
y2: box.y + box.height + 4,
|
|
14312
|
+
color,
|
|
14313
|
+
lineWidth: 2
|
|
14314
|
+
});
|
|
14315
|
+
if (p.label) {
|
|
14316
|
+
out.push({
|
|
14317
|
+
type: "text",
|
|
14318
|
+
x: cx,
|
|
14319
|
+
y: panelYSlots + panelHeight - 8,
|
|
14320
|
+
text: p.label,
|
|
14321
|
+
color,
|
|
14322
|
+
fontSize: 11,
|
|
14323
|
+
align: "center"
|
|
14324
|
+
});
|
|
14325
|
+
}
|
|
14326
|
+
}
|
|
14327
|
+
});
|
|
14328
|
+
}
|
|
14329
|
+
}
|
|
13590
14330
|
if (cells.length > 0) {
|
|
14331
|
+
const panelYCells = panelY.cells;
|
|
13591
14332
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
13592
14333
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
13593
|
-
const
|
|
13594
|
-
const
|
|
14334
|
+
const colLabelH = colLabels.length > 0 ? 16 : 0;
|
|
14335
|
+
const rowLabelW = rowLabels.length > 0 ? 20 : 0;
|
|
14336
|
+
const gridX0 = rowLabelW;
|
|
14337
|
+
const gridY0 = panelYCells + colLabelH;
|
|
14338
|
+
const cw = (width - rowLabelW) / maxCol;
|
|
14339
|
+
const ch = (panelHeight - colLabelH) / maxRow;
|
|
13595
14340
|
cells.forEach((c, i) => {
|
|
13596
|
-
const x = c.col * cw;
|
|
13597
|
-
const y = c.row * ch;
|
|
14341
|
+
const x = gridX0 + c.col * cw;
|
|
14342
|
+
const y = gridY0 + c.row * ch;
|
|
13598
14343
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
13599
14344
|
out.push({
|
|
13600
14345
|
type: "rect",
|
|
@@ -13618,11 +14363,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
13618
14363
|
align: "center"
|
|
13619
14364
|
});
|
|
13620
14365
|
}
|
|
14366
|
+
if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
|
|
14367
|
+
const { tl, tr, bl, br } = c.corner;
|
|
14368
|
+
if (tl) {
|
|
14369
|
+
out.push({
|
|
14370
|
+
type: "text",
|
|
14371
|
+
x: x + CORNER_INSET_X,
|
|
14372
|
+
y: y + CORNER_INSET_Y,
|
|
14373
|
+
text: tl,
|
|
14374
|
+
color: CORNER_TEXT_COLOR,
|
|
14375
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14376
|
+
align: "left"
|
|
14377
|
+
});
|
|
14378
|
+
}
|
|
14379
|
+
if (tr) {
|
|
14380
|
+
out.push({
|
|
14381
|
+
type: "text",
|
|
14382
|
+
x: x + cw - CORNER_INSET_X,
|
|
14383
|
+
y: y + CORNER_INSET_Y,
|
|
14384
|
+
text: tr,
|
|
14385
|
+
color: CORNER_TEXT_COLOR,
|
|
14386
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14387
|
+
align: "right"
|
|
14388
|
+
});
|
|
14389
|
+
}
|
|
14390
|
+
if (bl) {
|
|
14391
|
+
out.push({
|
|
14392
|
+
type: "text",
|
|
14393
|
+
x: x + CORNER_INSET_X,
|
|
14394
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14395
|
+
text: bl,
|
|
14396
|
+
color: CORNER_TEXT_COLOR,
|
|
14397
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14398
|
+
align: "left"
|
|
14399
|
+
});
|
|
14400
|
+
}
|
|
14401
|
+
if (br) {
|
|
14402
|
+
out.push({
|
|
14403
|
+
type: "text",
|
|
14404
|
+
x: x + cw - CORNER_INSET_X,
|
|
14405
|
+
y: y + ch - CORNER_INSET_Y,
|
|
14406
|
+
text: br,
|
|
14407
|
+
color: CORNER_TEXT_COLOR,
|
|
14408
|
+
fontSize: CORNER_FONT_SIZE,
|
|
14409
|
+
align: "right"
|
|
14410
|
+
});
|
|
14411
|
+
}
|
|
14412
|
+
}
|
|
14413
|
+
});
|
|
14414
|
+
colLabels.forEach((l) => {
|
|
14415
|
+
out.push({
|
|
14416
|
+
type: "text",
|
|
14417
|
+
x: gridX0 + l.index * cw + cw / 2,
|
|
14418
|
+
y: panelYCells + colLabelH / 2,
|
|
14419
|
+
text: l.text,
|
|
14420
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14421
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14422
|
+
align: "center"
|
|
14423
|
+
});
|
|
14424
|
+
});
|
|
14425
|
+
rowLabels.forEach((l) => {
|
|
14426
|
+
out.push({
|
|
14427
|
+
type: "text",
|
|
14428
|
+
x: rowLabelW - 6,
|
|
14429
|
+
y: gridY0 + l.index * ch + ch / 2,
|
|
14430
|
+
text: l.text,
|
|
14431
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
14432
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
14433
|
+
align: "right"
|
|
14434
|
+
});
|
|
14435
|
+
});
|
|
14436
|
+
}
|
|
14437
|
+
if (buckets.length > 0) {
|
|
14438
|
+
const panelYBuckets = panelY.buckets;
|
|
14439
|
+
const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
|
|
14440
|
+
const rowH = panelHeight / bucketCount;
|
|
14441
|
+
const indexColW = Math.min(width * 0.12, 40);
|
|
14442
|
+
const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
|
|
14443
|
+
const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
|
|
14444
|
+
const maxVisible = Math.floor((width - indexColW - 4) / entryW);
|
|
14445
|
+
buckets.forEach((b) => {
|
|
14446
|
+
const rowY = panelYBuckets + b.index * rowH;
|
|
14447
|
+
out.push({
|
|
14448
|
+
type: "rect",
|
|
14449
|
+
id: `bucket-index-${b.index}`,
|
|
14450
|
+
x: 2,
|
|
14451
|
+
y: rowY + 2,
|
|
14452
|
+
width: indexColW - 4,
|
|
14453
|
+
height: rowH - 4,
|
|
14454
|
+
color: BUCKET_INDEX_STROKE,
|
|
14455
|
+
fill: BUCKET_INDEX_FILL
|
|
14456
|
+
});
|
|
14457
|
+
out.push({
|
|
14458
|
+
type: "text",
|
|
14459
|
+
x: 2 + (indexColW - 4) / 2,
|
|
14460
|
+
y: rowY + rowH / 2,
|
|
14461
|
+
text: String(b.index),
|
|
14462
|
+
color: BUCKET_INDEX_TEXT,
|
|
14463
|
+
fontSize: 10,
|
|
14464
|
+
align: "center"
|
|
14465
|
+
});
|
|
14466
|
+
const overflow = b.entries.length > maxVisible;
|
|
14467
|
+
const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
|
|
14468
|
+
for (let j = 0; j < visibleCount; j++) {
|
|
14469
|
+
const entry = b.entries[j];
|
|
14470
|
+
const ex = indexColW + 4 + j * entryW;
|
|
14471
|
+
const state = entry.state ?? "default";
|
|
14472
|
+
const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
|
|
14473
|
+
out.push({
|
|
14474
|
+
type: "rect",
|
|
14475
|
+
id: `bucket-${b.index}-${j}`,
|
|
14476
|
+
x: ex,
|
|
14477
|
+
y: rowY + 2,
|
|
14478
|
+
width: entryW - 2,
|
|
14479
|
+
height: rowH - 4,
|
|
14480
|
+
color: fill,
|
|
14481
|
+
fill
|
|
14482
|
+
});
|
|
14483
|
+
if (entryW >= 20 && rowH >= 16) {
|
|
14484
|
+
out.push({
|
|
14485
|
+
type: "text",
|
|
14486
|
+
x: ex + (entryW - 2) / 2,
|
|
14487
|
+
y: rowY + rowH / 2,
|
|
14488
|
+
text: entry.label,
|
|
14489
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14490
|
+
fontSize: 10,
|
|
14491
|
+
align: "center"
|
|
14492
|
+
});
|
|
14493
|
+
}
|
|
14494
|
+
}
|
|
14495
|
+
if (overflow) {
|
|
14496
|
+
const ex = indexColW + 4 + visibleCount * entryW;
|
|
14497
|
+
out.push({
|
|
14498
|
+
type: "rect",
|
|
14499
|
+
id: `bucket-${b.index}-overflow`,
|
|
14500
|
+
x: ex,
|
|
14501
|
+
y: rowY + 2,
|
|
14502
|
+
width: entryW - 2,
|
|
14503
|
+
height: rowH - 4,
|
|
14504
|
+
color: BUCKET_ENTRY_DEFAULT,
|
|
14505
|
+
fill: BUCKET_ENTRY_DEFAULT
|
|
14506
|
+
});
|
|
14507
|
+
out.push({
|
|
14508
|
+
type: "text",
|
|
14509
|
+
x: ex + (entryW - 2) / 2,
|
|
14510
|
+
y: rowY + rowH / 2,
|
|
14511
|
+
text: `+${b.entries.length - visibleCount}`,
|
|
14512
|
+
color: BUCKET_ENTRY_TEXT,
|
|
14513
|
+
fontSize: 10,
|
|
14514
|
+
align: "center"
|
|
14515
|
+
});
|
|
14516
|
+
}
|
|
14517
|
+
});
|
|
14518
|
+
}
|
|
14519
|
+
if (frames.length > 0) {
|
|
14520
|
+
const panelYFrames = panelY.frames;
|
|
14521
|
+
const n = frames.length;
|
|
14522
|
+
const frameH = panelHeight / n;
|
|
14523
|
+
const x = 8;
|
|
14524
|
+
const w = width - 16;
|
|
14525
|
+
frames.forEach((f3, i) => {
|
|
14526
|
+
const y = panelYFrames + panelHeight - (i + 1) * frameH;
|
|
14527
|
+
const state = f3.state ?? "active";
|
|
14528
|
+
const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
|
|
14529
|
+
out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
|
|
14530
|
+
if (frameH >= FRAME_TWO_LINE_MIN_H) {
|
|
14531
|
+
out.push({
|
|
14532
|
+
type: "text",
|
|
14533
|
+
x: 16,
|
|
14534
|
+
y: y + frameH * 0.35,
|
|
14535
|
+
text: f3.label,
|
|
14536
|
+
color: FRAME_LABEL_COLOR,
|
|
14537
|
+
fontSize: 10,
|
|
14538
|
+
align: "left"
|
|
14539
|
+
});
|
|
14540
|
+
if (f3.detail) {
|
|
14541
|
+
out.push({
|
|
14542
|
+
type: "text",
|
|
14543
|
+
x: 16,
|
|
14544
|
+
y: y + frameH * 0.7,
|
|
14545
|
+
text: f3.detail,
|
|
14546
|
+
color: FRAME_DETAIL_COLOR,
|
|
14547
|
+
fontSize: 10,
|
|
14548
|
+
align: "left"
|
|
14549
|
+
});
|
|
14550
|
+
}
|
|
14551
|
+
} else {
|
|
14552
|
+
out.push({
|
|
14553
|
+
type: "text",
|
|
14554
|
+
x: 16,
|
|
14555
|
+
y: y + frameH / 2,
|
|
14556
|
+
text: f3.label,
|
|
14557
|
+
color: FRAME_LABEL_COLOR,
|
|
14558
|
+
fontSize: 10,
|
|
14559
|
+
align: "left"
|
|
14560
|
+
});
|
|
14561
|
+
}
|
|
13621
14562
|
});
|
|
13622
14563
|
}
|
|
13623
14564
|
out.push(...shapes);
|
|
13624
14565
|
return out;
|
|
13625
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
14566
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
13626
14567
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
13627
14568
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
13628
14569
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -14636,6 +15577,12 @@ function LearningScene3D({
|
|
|
14636
15577
|
const unitId = event.payload?.unitId;
|
|
14637
15578
|
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
14638
15579
|
});
|
|
15580
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
|
|
15581
|
+
const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
|
|
15582
|
+
if (unknownKeys.length > 0) {
|
|
15583
|
+
sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
|
|
15584
|
+
}
|
|
15585
|
+
}
|
|
14639
15586
|
const props3d = {
|
|
14640
15587
|
drawables,
|
|
14641
15588
|
isLoading,
|
|
@@ -14701,7 +15648,7 @@ function cylinderBetween(from, to, radius, color) {
|
|
|
14701
15648
|
material: { color }
|
|
14702
15649
|
};
|
|
14703
15650
|
}
|
|
14704
|
-
function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
15651
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
14705
15652
|
const len = segmentLength(from, to);
|
|
14706
15653
|
if (len < 1e-6) return null;
|
|
14707
15654
|
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
@@ -14729,6 +15676,7 @@ function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
|
14729
15676
|
};
|
|
14730
15677
|
return {
|
|
14731
15678
|
type: "draw-group",
|
|
15679
|
+
...id !== void 0 ? { id } : {},
|
|
14732
15680
|
position: { x: from[0], y: from[1], z: from[2] },
|
|
14733
15681
|
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
14734
15682
|
};
|
|
@@ -14755,20 +15703,228 @@ function get3DClickPayload(onShapeClick, idToIndex) {
|
|
|
14755
15703
|
if (!onShapeClick) return void 0;
|
|
14756
15704
|
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
14757
15705
|
}
|
|
14758
|
-
|
|
15706
|
+
function polylineTube(points, radius, color, opts) {
|
|
15707
|
+
const maxSegments = opts?.maxSegments ?? 128;
|
|
15708
|
+
let pts = points;
|
|
15709
|
+
if (pts.length - 1 > maxSegments) {
|
|
15710
|
+
const step = (pts.length - 1) / maxSegments;
|
|
15711
|
+
const kept = [pts[0]];
|
|
15712
|
+
for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
|
|
15713
|
+
kept.push(pts[pts.length - 1]);
|
|
15714
|
+
pts = kept;
|
|
15715
|
+
}
|
|
15716
|
+
const out = [];
|
|
15717
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
15718
|
+
const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
|
|
15719
|
+
if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
|
|
15720
|
+
}
|
|
15721
|
+
return out;
|
|
15722
|
+
}
|
|
15723
|
+
function heightFieldMesh(spec) {
|
|
15724
|
+
const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
|
|
15725
|
+
const flatShading = spec.flatShading ?? true;
|
|
15726
|
+
const vertices = [];
|
|
15727
|
+
for (let iy = 0; iy < ny; iy++) {
|
|
15728
|
+
for (let ix = 0; ix < nx; ix++) {
|
|
15729
|
+
vertices.push([
|
|
15730
|
+
x + (ix - (nx - 1) / 2) * spacing,
|
|
15731
|
+
y + (iy - (ny - 1) / 2) * spacing,
|
|
15732
|
+
heights[iy * nx + ix] ?? 0
|
|
15733
|
+
]);
|
|
15734
|
+
}
|
|
15735
|
+
}
|
|
15736
|
+
const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
|
|
15737
|
+
const facesByBand = /* @__PURE__ */ new Map();
|
|
15738
|
+
for (let iy = 0; iy < ny - 1; iy++) {
|
|
15739
|
+
for (let ix = 0; ix < nx - 1; ix++) {
|
|
15740
|
+
const v00 = iy * nx + ix;
|
|
15741
|
+
const v10 = iy * nx + ix + 1;
|
|
15742
|
+
const v01 = (iy + 1) * nx + ix;
|
|
15743
|
+
const v11 = (iy + 1) * nx + ix + 1;
|
|
15744
|
+
for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
|
|
15745
|
+
const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
|
|
15746
|
+
let band = null;
|
|
15747
|
+
for (const b of bands) {
|
|
15748
|
+
if ((b.min ?? -Infinity) <= centroid) band = b;
|
|
15749
|
+
}
|
|
15750
|
+
const key = bands.length > 0 ? band : null;
|
|
15751
|
+
const list = facesByBand.get(key) ?? [];
|
|
15752
|
+
list.push(face);
|
|
15753
|
+
facesByBand.set(key, list);
|
|
15754
|
+
}
|
|
15755
|
+
}
|
|
15756
|
+
}
|
|
15757
|
+
const out = [];
|
|
15758
|
+
for (const [band, faces] of facesByBand) {
|
|
15759
|
+
if (faces.length === 0) continue;
|
|
15760
|
+
out.push({
|
|
15761
|
+
type: "draw-mesh",
|
|
15762
|
+
shape: "polyhedron",
|
|
15763
|
+
position: { x: 0, y: 0, z: 0 },
|
|
15764
|
+
vertices,
|
|
15765
|
+
faces,
|
|
15766
|
+
pivot: "center",
|
|
15767
|
+
material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
|
|
15768
|
+
...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
|
|
15769
|
+
});
|
|
15770
|
+
}
|
|
15771
|
+
return out;
|
|
15772
|
+
}
|
|
15773
|
+
function arrowField(vectors, opts) {
|
|
15774
|
+
const scale = opts?.scale ?? 1;
|
|
15775
|
+
const out = [];
|
|
15776
|
+
for (const v of vectors) {
|
|
15777
|
+
const to = [
|
|
15778
|
+
v.from[0] + v.delta[0] * scale,
|
|
15779
|
+
v.from[1] + v.delta[1] * scale,
|
|
15780
|
+
v.from[2] + v.delta[2] * scale
|
|
15781
|
+
];
|
|
15782
|
+
const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
|
|
15783
|
+
if (arrow) out.push(arrow);
|
|
15784
|
+
if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
|
|
15785
|
+
}
|
|
15786
|
+
return out;
|
|
15787
|
+
}
|
|
15788
|
+
function helixDrawables(spec, opts) {
|
|
15789
|
+
const count = spec.count ?? spec.rungs?.length ?? 0;
|
|
15790
|
+
const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
|
|
15791
|
+
const radius = spec.radius ?? 1;
|
|
15792
|
+
const rise = spec.rise ?? 0.34;
|
|
15793
|
+
const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
|
|
15794
|
+
const strandAColor = spec.strandAColor ?? "#38bdf8";
|
|
15795
|
+
const strandBColor = spec.strandBColor ?? "#fb923c";
|
|
15796
|
+
const backboneRadius = spec.backboneRadius ?? 0.16;
|
|
15797
|
+
const rungRadius = spec.rungRadius ?? 0.12;
|
|
15798
|
+
const cx = spec.x ?? 0;
|
|
15799
|
+
const cy = spec.y ?? 0;
|
|
15800
|
+
const cz = spec.z ?? 0;
|
|
15801
|
+
const unwoundCount = spec.unwoundCount ?? 0;
|
|
15802
|
+
const unwindSpread = spec.unwindSpread ?? 1.8;
|
|
15803
|
+
const strandA = [];
|
|
15804
|
+
const strandB = [];
|
|
15805
|
+
for (let i = 0; i < count; i++) {
|
|
15806
|
+
const yi = cy + (i - (count - 1) / 2) * rise;
|
|
15807
|
+
const theta = i * twistRad;
|
|
15808
|
+
const s = i < unwoundCount ? unwindSpread : 1;
|
|
15809
|
+
strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
|
|
15810
|
+
strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
|
|
15811
|
+
}
|
|
15812
|
+
const out = [];
|
|
15813
|
+
for (let i = 0; i < count; i++) {
|
|
15814
|
+
out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
|
|
15815
|
+
out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
|
|
15816
|
+
if (i > 0) {
|
|
15817
|
+
const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
|
|
15818
|
+
if (segA) out.push(segA);
|
|
15819
|
+
const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
|
|
15820
|
+
if (segB) out.push(segB);
|
|
15821
|
+
}
|
|
15822
|
+
const rung = rungs[i];
|
|
15823
|
+
const rungColor = rung.color ?? "#94a3b8";
|
|
15824
|
+
const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
|
|
15825
|
+
if (rod) out.push(rod);
|
|
15826
|
+
const mid = [
|
|
15827
|
+
(strandA[i][0] + strandB[i][0]) / 2,
|
|
15828
|
+
(strandA[i][1] + strandB[i][1]) / 2,
|
|
15829
|
+
(strandA[i][2] + strandB[i][2]) / 2
|
|
15830
|
+
];
|
|
15831
|
+
const markerRadius = rung.radius ?? rungRadius;
|
|
15832
|
+
out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
|
|
15833
|
+
if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
|
|
15834
|
+
}
|
|
15835
|
+
return out;
|
|
15836
|
+
}
|
|
15837
|
+
function latticeDrawables(spec, opts) {
|
|
15838
|
+
const nx = spec.nx ?? 2;
|
|
15839
|
+
const ny = spec.ny ?? 2;
|
|
15840
|
+
const nz = spec.nz ?? 2;
|
|
15841
|
+
const latticeConstant = spec.latticeConstant ?? 2;
|
|
15842
|
+
const bondRadius = spec.bondRadius ?? 0.06;
|
|
15843
|
+
const highlightCell = spec.highlightCell ?? false;
|
|
15844
|
+
const dimColor = spec.dimColor ?? "#475569";
|
|
15845
|
+
const showLabels = spec.showLabels ?? false;
|
|
15846
|
+
const selectedColor = spec.selectedColor ?? "#f59e0b";
|
|
15847
|
+
const posByKey = /* @__PURE__ */ new Map();
|
|
15848
|
+
const inCellByKey = /* @__PURE__ */ new Map();
|
|
15849
|
+
const out = [];
|
|
15850
|
+
for (const site of spec.basis) {
|
|
15851
|
+
const snx = site.xEdge ? nx + 1 : nx;
|
|
15852
|
+
const sny = site.yEdge ? ny + 1 : ny;
|
|
15853
|
+
const snz = site.zEdge ? nz + 1 : nz;
|
|
15854
|
+
for (let i = 0; i < snx; i++) {
|
|
15855
|
+
for (let j = 0; j < sny; j++) {
|
|
15856
|
+
for (let k = 0; k < snz; k++) {
|
|
15857
|
+
const key = `${site.key}-${i}-${j}-${k}`;
|
|
15858
|
+
const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
|
|
15859
|
+
const pos = [
|
|
15860
|
+
(i + site.dx) * latticeConstant - nx * latticeConstant / 2,
|
|
15861
|
+
(j + site.dy) * latticeConstant - ny * latticeConstant / 2,
|
|
15862
|
+
(k + site.dz) * latticeConstant - nz * latticeConstant / 2
|
|
15863
|
+
];
|
|
15864
|
+
posByKey.set(key, pos);
|
|
15865
|
+
inCellByKey.set(key, inCell);
|
|
15866
|
+
const isSelected = spec.selectedId === `lat-${key}`;
|
|
15867
|
+
const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
|
|
15868
|
+
const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
|
|
15869
|
+
out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
|
|
15870
|
+
if (showLabels && site.element) {
|
|
15871
|
+
out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
|
|
15872
|
+
}
|
|
15873
|
+
}
|
|
15874
|
+
}
|
|
15875
|
+
}
|
|
15876
|
+
}
|
|
15877
|
+
const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
|
|
15878
|
+
for (const bond of spec.bonds ?? []) {
|
|
15879
|
+
const fromSite = basisByKey.get(bond.from);
|
|
15880
|
+
const toSite = basisByKey.get(bond.to);
|
|
15881
|
+
if (!fromSite || !toSite) continue;
|
|
15882
|
+
const fnx = fromSite.xEdge ? nx + 1 : nx;
|
|
15883
|
+
const fny = fromSite.yEdge ? ny + 1 : ny;
|
|
15884
|
+
const fnz = fromSite.zEdge ? nz + 1 : nz;
|
|
15885
|
+
const tnx = toSite.xEdge ? nx + 1 : nx;
|
|
15886
|
+
const tny = toSite.yEdge ? ny + 1 : ny;
|
|
15887
|
+
const tnz = toSite.zEdge ? nz + 1 : nz;
|
|
15888
|
+
const bdx = bond.dx ?? 0;
|
|
15889
|
+
const bdy = bond.dy ?? 0;
|
|
15890
|
+
const bdz = bond.dz ?? 0;
|
|
15891
|
+
for (let i = 0; i < fnx; i++) {
|
|
15892
|
+
for (let j = 0; j < fny; j++) {
|
|
15893
|
+
for (let k = 0; k < fnz; k++) {
|
|
15894
|
+
const ti = i + bdx;
|
|
15895
|
+
const tj = j + bdy;
|
|
15896
|
+
const tk = k + bdz;
|
|
15897
|
+
if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
|
|
15898
|
+
const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
|
|
15899
|
+
const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
|
|
15900
|
+
const fromPos = posByKey.get(fromKey);
|
|
15901
|
+
const toPos = posByKey.get(toKey);
|
|
15902
|
+
if (!fromPos || !toPos) continue;
|
|
15903
|
+
const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
|
|
15904
|
+
const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
|
|
15905
|
+
if (seg) out.push(seg);
|
|
15906
|
+
}
|
|
15907
|
+
}
|
|
15908
|
+
}
|
|
15909
|
+
}
|
|
15910
|
+
return out;
|
|
15911
|
+
}
|
|
15912
|
+
var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost2;
|
|
14759
15913
|
var init_learningScene3D = __esm({
|
|
14760
15914
|
"components/learning/molecules/learningScene3D.tsx"() {
|
|
14761
15915
|
"use client";
|
|
14762
15916
|
init_atoms();
|
|
14763
15917
|
init_Stack();
|
|
14764
15918
|
init_useEventBus();
|
|
15919
|
+
sceneLog = logger.createLogger("almadar:ui:learning-scene-3d");
|
|
15920
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
14765
15921
|
Canvas3DHost2 = React85.lazy(
|
|
14766
15922
|
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
14767
15923
|
);
|
|
14768
15924
|
LearningScene3D.displayName = "LearningScene3D";
|
|
14769
15925
|
}
|
|
14770
15926
|
});
|
|
14771
|
-
var biologyLog, BiologyCanvas;
|
|
15927
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
14772
15928
|
var init_BiologyCanvas = __esm({
|
|
14773
15929
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
14774
15930
|
"use client";
|
|
@@ -14777,6 +15933,17 @@ var init_BiologyCanvas = __esm({
|
|
|
14777
15933
|
init_LearningCanvas();
|
|
14778
15934
|
init_learningScene3D();
|
|
14779
15935
|
biologyLog = logger.createLogger("almadar:ui:biology-canvas");
|
|
15936
|
+
BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
|
|
15937
|
+
BIO_STAGE_FILL = {
|
|
15938
|
+
pending: "#e2e8f0",
|
|
15939
|
+
active: "#3b82f6",
|
|
15940
|
+
done: "#94a3b8"
|
|
15941
|
+
};
|
|
15942
|
+
BIO_STAGE_TEXT = {
|
|
15943
|
+
pending: "#64748b",
|
|
15944
|
+
active: "#ffffff",
|
|
15945
|
+
done: "#ffffff"
|
|
15946
|
+
};
|
|
14780
15947
|
BiologyCanvas = ({
|
|
14781
15948
|
className,
|
|
14782
15949
|
width = 600,
|
|
@@ -14789,7 +15956,15 @@ var init_BiologyCanvas = __esm({
|
|
|
14789
15956
|
post,
|
|
14790
15957
|
nodes = [],
|
|
14791
15958
|
edges = [],
|
|
15959
|
+
compartments = [],
|
|
15960
|
+
bands = [],
|
|
15961
|
+
stages = [],
|
|
15962
|
+
stageStyle = "timeline",
|
|
15963
|
+
helix,
|
|
15964
|
+
helix3d,
|
|
14792
15965
|
shapes = [],
|
|
15966
|
+
readouts,
|
|
15967
|
+
traces,
|
|
14793
15968
|
showGrid,
|
|
14794
15969
|
shadows,
|
|
14795
15970
|
interactive,
|
|
@@ -14804,19 +15979,148 @@ var init_BiologyCanvas = __esm({
|
|
|
14804
15979
|
for (const n of nodes) {
|
|
14805
15980
|
if (n.id) nodeById.set(n.id, n);
|
|
14806
15981
|
}
|
|
15982
|
+
const bandCount = bands.length;
|
|
15983
|
+
for (let i = 0; i < bandCount; i++) {
|
|
15984
|
+
const band = bands[i];
|
|
15985
|
+
const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
|
|
15986
|
+
const bandY = i * height / bandCount;
|
|
15987
|
+
const bandH = height / bandCount;
|
|
15988
|
+
out.push({
|
|
15989
|
+
type: "rect",
|
|
15990
|
+
x: 0,
|
|
15991
|
+
y: bandY,
|
|
15992
|
+
width,
|
|
15993
|
+
height: bandH,
|
|
15994
|
+
color: bandColor,
|
|
15995
|
+
fill: bandColor,
|
|
15996
|
+
opacity: 0.45
|
|
15997
|
+
});
|
|
15998
|
+
if (band.label) {
|
|
15999
|
+
out.push({
|
|
16000
|
+
type: "text",
|
|
16001
|
+
x: 8,
|
|
16002
|
+
y: bandY + 14,
|
|
16003
|
+
text: band.label,
|
|
16004
|
+
color: "#6b7280",
|
|
16005
|
+
fontSize: 10
|
|
16006
|
+
});
|
|
16007
|
+
}
|
|
16008
|
+
}
|
|
16009
|
+
for (const c of compartments) {
|
|
16010
|
+
const color = c.color ?? "#16a34a";
|
|
16011
|
+
out.push({
|
|
16012
|
+
type: "ellipse",
|
|
16013
|
+
x: c.x,
|
|
16014
|
+
y: c.y,
|
|
16015
|
+
width: c.width,
|
|
16016
|
+
height: c.height,
|
|
16017
|
+
color,
|
|
16018
|
+
fill: c.fill ?? `${color}1A`,
|
|
16019
|
+
lineWidth: c.lineWidth ?? 2,
|
|
16020
|
+
...c.dash ? { dash: c.dash } : {}
|
|
16021
|
+
});
|
|
16022
|
+
if (c.label) {
|
|
16023
|
+
out.push({
|
|
16024
|
+
type: "text",
|
|
16025
|
+
x: c.x,
|
|
16026
|
+
y: c.y - c.height / 2 + 14,
|
|
16027
|
+
text: c.label,
|
|
16028
|
+
color: "#111827",
|
|
16029
|
+
fontSize: 11,
|
|
16030
|
+
align: "center"
|
|
16031
|
+
});
|
|
16032
|
+
}
|
|
16033
|
+
}
|
|
16034
|
+
if (helix) {
|
|
16035
|
+
const hx = helix.x ?? 24;
|
|
16036
|
+
const hy = helix.y ?? height * 0.25;
|
|
16037
|
+
const hw = helix.width ?? width - 48;
|
|
16038
|
+
const hh = helix.height ?? height * 0.5;
|
|
16039
|
+
const rungs = helix.rungs;
|
|
16040
|
+
const n = rungs.length;
|
|
16041
|
+
const cy = hy + hh / 2;
|
|
16042
|
+
const colorA = helix.colorA ?? "#2563eb";
|
|
16043
|
+
const colorB = helix.colorB ?? "#dc2626";
|
|
16044
|
+
const rungColor = helix.rungColor ?? "#94a3b8";
|
|
16045
|
+
const fork = helix.fork ?? 0;
|
|
16046
|
+
const maxSep = Math.min(hh - 8, 96);
|
|
16047
|
+
const strandA = [];
|
|
16048
|
+
const strandB = [];
|
|
16049
|
+
const rungGeoms = [];
|
|
16050
|
+
for (let i = 0; i < n; i++) {
|
|
16051
|
+
const rx = hx + (i + 0.5) * hw / n;
|
|
16052
|
+
const t = (i + 0.5) / n;
|
|
16053
|
+
const paired = t >= fork;
|
|
16054
|
+
const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
|
|
16055
|
+
strandA.push({ x: rx, y: cy - sep / 2 });
|
|
16056
|
+
strandB.push({ x: rx, y: cy + sep / 2 });
|
|
16057
|
+
rungGeoms.push({ rx, sep, rung: rungs[i], paired });
|
|
16058
|
+
}
|
|
16059
|
+
for (let i = 1; i < n; i++) {
|
|
16060
|
+
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 });
|
|
16061
|
+
}
|
|
16062
|
+
for (let i = 1; i < n; i++) {
|
|
16063
|
+
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 });
|
|
16064
|
+
}
|
|
16065
|
+
for (const g of rungGeoms) {
|
|
16066
|
+
const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
|
|
16067
|
+
const topY = cy - g.sep / 2;
|
|
16068
|
+
const bottomY = cy + g.sep / 2;
|
|
16069
|
+
if (g.paired) {
|
|
16070
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
|
|
16071
|
+
if (g.rung.a) {
|
|
16072
|
+
out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
16073
|
+
}
|
|
16074
|
+
if (g.rung.b) {
|
|
16075
|
+
out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16076
|
+
}
|
|
16077
|
+
} else {
|
|
16078
|
+
const stubTopY = topY + 8;
|
|
16079
|
+
const stubBottomY = bottomY - 8;
|
|
16080
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
|
|
16081
|
+
out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
|
|
16082
|
+
if (g.rung.a) {
|
|
16083
|
+
out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
16084
|
+
}
|
|
16085
|
+
if (g.rung.b) {
|
|
16086
|
+
out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
16087
|
+
}
|
|
16088
|
+
}
|
|
16089
|
+
}
|
|
16090
|
+
}
|
|
14807
16091
|
for (const e of edges) {
|
|
14808
16092
|
const a = nodeById.get(e.from);
|
|
14809
16093
|
const b = nodeById.get(e.to);
|
|
14810
16094
|
if (!a || !b) continue;
|
|
14811
|
-
|
|
14812
|
-
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
|
|
14817
|
-
|
|
14818
|
-
|
|
14819
|
-
|
|
16095
|
+
const color = e.color ?? "#9ca3af";
|
|
16096
|
+
if (e.directed) {
|
|
16097
|
+
const rA = a.radius ?? 16;
|
|
16098
|
+
const rB = b.radius ?? 16;
|
|
16099
|
+
const dx = b.x - a.x;
|
|
16100
|
+
const dy = b.y - a.y;
|
|
16101
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16102
|
+
const ux = dx / dist;
|
|
16103
|
+
const uy = dy / dist;
|
|
16104
|
+
out.push({
|
|
16105
|
+
type: "arrow",
|
|
16106
|
+
x1: a.x + ux * rA,
|
|
16107
|
+
y1: a.y + uy * rA,
|
|
16108
|
+
x2: b.x - ux * rB,
|
|
16109
|
+
y2: b.y - uy * rB,
|
|
16110
|
+
color,
|
|
16111
|
+
lineWidth: 2
|
|
16112
|
+
});
|
|
16113
|
+
} else {
|
|
16114
|
+
out.push({
|
|
16115
|
+
type: "line",
|
|
16116
|
+
x1: a.x,
|
|
16117
|
+
y1: a.y,
|
|
16118
|
+
x2: b.x,
|
|
16119
|
+
y2: b.y,
|
|
16120
|
+
color,
|
|
16121
|
+
lineWidth: 2
|
|
16122
|
+
});
|
|
16123
|
+
}
|
|
14820
16124
|
if (e.label) {
|
|
14821
16125
|
out.push({
|
|
14822
16126
|
type: "text",
|
|
@@ -14829,6 +16133,8 @@ var init_BiologyCanvas = __esm({
|
|
|
14829
16133
|
}
|
|
14830
16134
|
}
|
|
14831
16135
|
for (const n of nodes) {
|
|
16136
|
+
const state = n.state ?? "default";
|
|
16137
|
+
const muted = state === "muted";
|
|
14832
16138
|
out.push({
|
|
14833
16139
|
type: "circle",
|
|
14834
16140
|
x: n.x,
|
|
@@ -14836,28 +16142,127 @@ var init_BiologyCanvas = __esm({
|
|
|
14836
16142
|
radius: n.radius ?? 16,
|
|
14837
16143
|
color: n.color ?? "#16a34a",
|
|
14838
16144
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
14839
|
-
id: n.id
|
|
16145
|
+
id: n.id,
|
|
16146
|
+
...muted ? { opacity: 0.35 } : {}
|
|
14840
16147
|
});
|
|
16148
|
+
if (state === "highlight") {
|
|
16149
|
+
out.push({
|
|
16150
|
+
type: "circle",
|
|
16151
|
+
x: n.x,
|
|
16152
|
+
y: n.y,
|
|
16153
|
+
radius: (n.radius ?? 16) + 4,
|
|
16154
|
+
color: "#f59e0b",
|
|
16155
|
+
lineWidth: 2
|
|
16156
|
+
});
|
|
16157
|
+
}
|
|
14841
16158
|
if (n.label) {
|
|
14842
16159
|
out.push({
|
|
14843
16160
|
type: "text",
|
|
14844
16161
|
x: n.x,
|
|
14845
16162
|
y: n.y + (n.radius ?? 16) + 14,
|
|
14846
16163
|
text: n.label,
|
|
16164
|
+
...muted ? { opacity: 0.35 } : {},
|
|
14847
16165
|
color: "#111827",
|
|
14848
16166
|
fontSize: 12,
|
|
14849
16167
|
align: "center"
|
|
14850
16168
|
});
|
|
14851
16169
|
}
|
|
14852
16170
|
}
|
|
16171
|
+
const stageCount = stages.length;
|
|
16172
|
+
if (stageCount > 0) {
|
|
16173
|
+
if (stageStyle === "ring") {
|
|
16174
|
+
const cx = width / 2;
|
|
16175
|
+
const cy = height / 2;
|
|
16176
|
+
const R = Math.min(width, height) / 2 - 48;
|
|
16177
|
+
const ringPoints = [];
|
|
16178
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16179
|
+
const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
|
|
16180
|
+
ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
|
|
16181
|
+
}
|
|
16182
|
+
if (stageCount >= 2) {
|
|
16183
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16184
|
+
const p1 = ringPoints[i];
|
|
16185
|
+
const p2 = ringPoints[i + 1];
|
|
16186
|
+
const dx = p2.x - p1.x;
|
|
16187
|
+
const dy = p2.y - p1.y;
|
|
16188
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
16189
|
+
const ux = dx / dist;
|
|
16190
|
+
const uy = dy / dist;
|
|
16191
|
+
out.push({
|
|
16192
|
+
type: "arrow",
|
|
16193
|
+
x1: p1.x + ux * 46,
|
|
16194
|
+
y1: p1.y + uy * 46,
|
|
16195
|
+
x2: p2.x - ux * 46,
|
|
16196
|
+
y2: p2.y - uy * 46,
|
|
16197
|
+
color: "#94a3b8"
|
|
16198
|
+
});
|
|
16199
|
+
}
|
|
16200
|
+
}
|
|
16201
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16202
|
+
const stage = stages[i];
|
|
16203
|
+
const state = stage.state ?? "pending";
|
|
16204
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16205
|
+
const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
|
|
16206
|
+
const h = 18;
|
|
16207
|
+
const p = ringPoints[i];
|
|
16208
|
+
out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
|
|
16209
|
+
out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
|
|
16210
|
+
}
|
|
16211
|
+
} else {
|
|
16212
|
+
const stripY = height - 32;
|
|
16213
|
+
const slotW = (width - 16) / stageCount;
|
|
16214
|
+
const chipGeoms = [];
|
|
16215
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16216
|
+
chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
|
|
16217
|
+
}
|
|
16218
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
16219
|
+
const midY = stripY + 13;
|
|
16220
|
+
out.push({
|
|
16221
|
+
type: "arrow",
|
|
16222
|
+
x1: chipGeoms[i].x + chipGeoms[i].w,
|
|
16223
|
+
y1: midY,
|
|
16224
|
+
x2: chipGeoms[i + 1].x,
|
|
16225
|
+
y2: midY,
|
|
16226
|
+
color: "#94a3b8"
|
|
16227
|
+
});
|
|
16228
|
+
}
|
|
16229
|
+
for (let i = 0; i < stageCount; i++) {
|
|
16230
|
+
const stage = stages[i];
|
|
16231
|
+
const state = stage.state ?? "pending";
|
|
16232
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
16233
|
+
const g = chipGeoms[i];
|
|
16234
|
+
out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
|
|
16235
|
+
out.push({
|
|
16236
|
+
type: "text",
|
|
16237
|
+
x: g.x + g.w / 2,
|
|
16238
|
+
y: stripY + 13,
|
|
16239
|
+
text: stage.label,
|
|
16240
|
+
color: BIO_STAGE_TEXT[state],
|
|
16241
|
+
fontSize: 10,
|
|
16242
|
+
align: "center"
|
|
16243
|
+
});
|
|
16244
|
+
}
|
|
16245
|
+
}
|
|
16246
|
+
}
|
|
14853
16247
|
out.push(...shapes);
|
|
14854
16248
|
return out;
|
|
14855
|
-
}, [nodes, edges, shapes]);
|
|
16249
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
14856
16250
|
const drawables3D = React85.useMemo(() => {
|
|
14857
16251
|
if (mode !== "3d") return [];
|
|
14858
16252
|
if (shapes.length > 0) {
|
|
14859
16253
|
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
14860
16254
|
}
|
|
16255
|
+
if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
|
|
16256
|
+
biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
|
|
16257
|
+
compartments: compartments.length,
|
|
16258
|
+
bands: bands.length,
|
|
16259
|
+
stages: stages.length,
|
|
16260
|
+
helix: helix != null
|
|
16261
|
+
});
|
|
16262
|
+
}
|
|
16263
|
+
if (animate) {
|
|
16264
|
+
biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
16265
|
+
}
|
|
14861
16266
|
const out = [];
|
|
14862
16267
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
14863
16268
|
const nodeById = /* @__PURE__ */ new Map();
|
|
@@ -14891,15 +16296,21 @@ var init_BiologyCanvas = __esm({
|
|
|
14891
16296
|
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
14892
16297
|
}
|
|
14893
16298
|
}
|
|
16299
|
+
if (helix3d) {
|
|
16300
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
16301
|
+
}
|
|
14894
16302
|
return out;
|
|
14895
|
-
}, [mode, nodes, edges, shapes, backgroundColor]);
|
|
16303
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
14896
16304
|
const nodeIndexById = React85.useMemo(() => {
|
|
14897
16305
|
const m = /* @__PURE__ */ new Map();
|
|
16306
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
16307
|
+
if (rung.id) m.set(rung.id, i);
|
|
16308
|
+
});
|
|
14898
16309
|
nodes.forEach((n, i) => {
|
|
14899
16310
|
if (n.id) m.set(n.id, i);
|
|
14900
16311
|
});
|
|
14901
16312
|
return m;
|
|
14902
|
-
}, [nodes]);
|
|
16313
|
+
}, [nodes, helix3d]);
|
|
14903
16314
|
if (mode === "3d") {
|
|
14904
16315
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14905
16316
|
LearningScene3D,
|
|
@@ -14931,6 +16342,8 @@ var init_BiologyCanvas = __esm({
|
|
|
14931
16342
|
height,
|
|
14932
16343
|
backgroundColor,
|
|
14933
16344
|
shapes: derivedShapes,
|
|
16345
|
+
readouts,
|
|
16346
|
+
traces,
|
|
14934
16347
|
interactive: interactive ?? false,
|
|
14935
16348
|
animate,
|
|
14936
16349
|
onShapeClick,
|
|
@@ -21663,7 +23076,7 @@ function bondPerpendicular(a, b) {
|
|
|
21663
23076
|
if (len < 1e-6) return [1, 0, 0];
|
|
21664
23077
|
return [px / len, py / len, 0];
|
|
21665
23078
|
}
|
|
21666
|
-
var chemistryLog, ChemistryCanvas;
|
|
23079
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
21667
23080
|
var init_ChemistryCanvas = __esm({
|
|
21668
23081
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
21669
23082
|
"use client";
|
|
@@ -21672,6 +23085,13 @@ var init_ChemistryCanvas = __esm({
|
|
|
21672
23085
|
init_LearningCanvas();
|
|
21673
23086
|
init_learningScene3D();
|
|
21674
23087
|
chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
|
|
23088
|
+
CHEM_BOND_STATE_COLOR = {
|
|
23089
|
+
default: "#6b7280",
|
|
23090
|
+
forming: "#16a34a",
|
|
23091
|
+
breaking: "#dc2626",
|
|
23092
|
+
highlight: "#f59e0b"
|
|
23093
|
+
};
|
|
23094
|
+
LONE_PAIR_ANGLES = [-90, 0, 90, 180];
|
|
21675
23095
|
ChemistryCanvas = ({
|
|
21676
23096
|
className,
|
|
21677
23097
|
width = 600,
|
|
@@ -21685,7 +23105,14 @@ var init_ChemistryCanvas = __esm({
|
|
|
21685
23105
|
atoms = [],
|
|
21686
23106
|
bonds = [],
|
|
21687
23107
|
arrows = [],
|
|
23108
|
+
bondStyle = "thick",
|
|
23109
|
+
containers = [],
|
|
23110
|
+
equation,
|
|
23111
|
+
equationColor,
|
|
23112
|
+
lattice3d,
|
|
21688
23113
|
shapes = [],
|
|
23114
|
+
readouts,
|
|
23115
|
+
traces,
|
|
21689
23116
|
showGrid,
|
|
21690
23117
|
shadows,
|
|
21691
23118
|
interactive,
|
|
@@ -21700,21 +23127,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
21700
23127
|
for (const a of atoms) {
|
|
21701
23128
|
if (a.id) atomById.set(a.id, a);
|
|
21702
23129
|
}
|
|
23130
|
+
for (const c of containers) {
|
|
23131
|
+
const color = c.color ?? "#64748b";
|
|
23132
|
+
if (c.level != null) {
|
|
23133
|
+
const lv = c.level;
|
|
23134
|
+
out.push({
|
|
23135
|
+
type: "rect",
|
|
23136
|
+
x: c.x + 1,
|
|
23137
|
+
y: c.y + c.height * (1 - lv),
|
|
23138
|
+
width: c.width - 2,
|
|
23139
|
+
height: c.height * lv - 1,
|
|
23140
|
+
color: c.levelColor ?? "#60a5fa",
|
|
23141
|
+
fill: c.levelColor ?? "#60a5fa",
|
|
23142
|
+
opacity: 0.5
|
|
23143
|
+
});
|
|
23144
|
+
}
|
|
23145
|
+
out.push({
|
|
23146
|
+
type: "rect",
|
|
23147
|
+
x: c.x,
|
|
23148
|
+
y: c.y,
|
|
23149
|
+
width: c.width,
|
|
23150
|
+
height: c.height,
|
|
23151
|
+
color,
|
|
23152
|
+
fill: c.fill,
|
|
23153
|
+
lineWidth: c.lineWidth ?? 2
|
|
23154
|
+
});
|
|
23155
|
+
const divider = c.divider ?? "none";
|
|
23156
|
+
if (divider !== "none") {
|
|
23157
|
+
out.push({
|
|
23158
|
+
type: "line",
|
|
23159
|
+
x1: c.x + c.width / 2,
|
|
23160
|
+
y1: c.y,
|
|
23161
|
+
x2: c.x + c.width / 2,
|
|
23162
|
+
y2: c.y + c.height,
|
|
23163
|
+
color: c.dividerColor ?? color,
|
|
23164
|
+
...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
|
|
23165
|
+
});
|
|
23166
|
+
}
|
|
23167
|
+
if (c.leftLabel) {
|
|
23168
|
+
out.push({
|
|
23169
|
+
type: "text",
|
|
23170
|
+
x: c.x + c.width * 0.25,
|
|
23171
|
+
y: c.y + 12,
|
|
23172
|
+
text: c.leftLabel,
|
|
23173
|
+
color: "#374151",
|
|
23174
|
+
fontSize: 11,
|
|
23175
|
+
align: "center"
|
|
23176
|
+
});
|
|
23177
|
+
}
|
|
23178
|
+
if (c.rightLabel) {
|
|
23179
|
+
out.push({
|
|
23180
|
+
type: "text",
|
|
23181
|
+
x: c.x + c.width * 0.75,
|
|
23182
|
+
y: c.y + 12,
|
|
23183
|
+
text: c.rightLabel,
|
|
23184
|
+
color: "#374151",
|
|
23185
|
+
fontSize: 11,
|
|
23186
|
+
align: "center"
|
|
23187
|
+
});
|
|
23188
|
+
}
|
|
23189
|
+
if (c.label) {
|
|
23190
|
+
out.push({
|
|
23191
|
+
type: "text",
|
|
23192
|
+
x: c.x + c.width / 2,
|
|
23193
|
+
y: c.y + c.height + 12,
|
|
23194
|
+
text: c.label,
|
|
23195
|
+
color: "#111827",
|
|
23196
|
+
fontSize: 12,
|
|
23197
|
+
align: "center"
|
|
23198
|
+
});
|
|
23199
|
+
}
|
|
23200
|
+
}
|
|
21703
23201
|
for (const b of bonds) {
|
|
21704
23202
|
const a = atomById.get(b.from);
|
|
21705
23203
|
const c = atomById.get(b.to);
|
|
21706
23204
|
if (!a || !c) continue;
|
|
21707
|
-
const
|
|
21708
|
-
const
|
|
21709
|
-
|
|
21710
|
-
|
|
21711
|
-
|
|
21712
|
-
|
|
21713
|
-
|
|
21714
|
-
|
|
21715
|
-
|
|
21716
|
-
|
|
21717
|
-
|
|
23205
|
+
const state = b.state ?? "default";
|
|
23206
|
+
const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
|
|
23207
|
+
const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
|
|
23208
|
+
if (bondStyle === "parallel") {
|
|
23209
|
+
const dx = c.x - a.x;
|
|
23210
|
+
const dy = c.y - a.y;
|
|
23211
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
23212
|
+
const ux = dx / dist;
|
|
23213
|
+
const uy = dy / dist;
|
|
23214
|
+
const px = -uy;
|
|
23215
|
+
const py = ux;
|
|
23216
|
+
const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
|
|
23217
|
+
for (const off of offsets) {
|
|
23218
|
+
out.push({
|
|
23219
|
+
type: "line",
|
|
23220
|
+
x1: a.x + px * off,
|
|
23221
|
+
y1: a.y + py * off,
|
|
23222
|
+
x2: c.x + px * off,
|
|
23223
|
+
y2: c.y + py * off,
|
|
23224
|
+
color,
|
|
23225
|
+
lineWidth: 2,
|
|
23226
|
+
...dash ? { dash } : {}
|
|
23227
|
+
});
|
|
23228
|
+
}
|
|
23229
|
+
} else {
|
|
23230
|
+
const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
|
|
23231
|
+
out.push({
|
|
23232
|
+
type: "line",
|
|
23233
|
+
x1: a.x,
|
|
23234
|
+
y1: a.y,
|
|
23235
|
+
x2: c.x,
|
|
23236
|
+
y2: c.y,
|
|
23237
|
+
color,
|
|
23238
|
+
lineWidth: strokeWidth,
|
|
23239
|
+
...dash ? { dash } : {}
|
|
23240
|
+
});
|
|
23241
|
+
}
|
|
21718
23242
|
}
|
|
21719
23243
|
for (const a of arrows) {
|
|
21720
23244
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -21763,15 +23287,62 @@ var init_ChemistryCanvas = __esm({
|
|
|
21763
23287
|
align: "center"
|
|
21764
23288
|
});
|
|
21765
23289
|
}
|
|
23290
|
+
const r = a.radius ?? 14;
|
|
23291
|
+
if (a.charge) {
|
|
23292
|
+
out.push({
|
|
23293
|
+
type: "text",
|
|
23294
|
+
x: a.x + r * 0.85,
|
|
23295
|
+
y: a.y - r * 0.85,
|
|
23296
|
+
text: a.charge,
|
|
23297
|
+
color: "#111827",
|
|
23298
|
+
fontSize: 9,
|
|
23299
|
+
align: "left"
|
|
23300
|
+
});
|
|
23301
|
+
}
|
|
23302
|
+
const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
|
|
23303
|
+
for (let k = 0; k < lonePairs; k++) {
|
|
23304
|
+
const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
|
|
23305
|
+
const cx = a.x + (r + 6) * Math.cos(angleRad);
|
|
23306
|
+
const cy = a.y + (r + 6) * Math.sin(angleRad);
|
|
23307
|
+
const perpX = -Math.sin(angleRad);
|
|
23308
|
+
const perpY = Math.cos(angleRad);
|
|
23309
|
+
for (const sign of [1, -1]) {
|
|
23310
|
+
out.push({
|
|
23311
|
+
type: "circle",
|
|
23312
|
+
x: cx + perpX * 2.5 * sign,
|
|
23313
|
+
y: cy + perpY * 2.5 * sign,
|
|
23314
|
+
radius: 1.5,
|
|
23315
|
+
color: "#374151",
|
|
23316
|
+
fill: "#374151"
|
|
23317
|
+
});
|
|
23318
|
+
}
|
|
23319
|
+
}
|
|
23320
|
+
}
|
|
23321
|
+
if (equation) {
|
|
23322
|
+
out.push({
|
|
23323
|
+
type: "text",
|
|
23324
|
+
x: width / 2,
|
|
23325
|
+
y: 14,
|
|
23326
|
+
text: equation,
|
|
23327
|
+
color: equationColor ?? "#111827",
|
|
23328
|
+
fontSize: 13,
|
|
23329
|
+
align: "center"
|
|
23330
|
+
});
|
|
21766
23331
|
}
|
|
21767
23332
|
out.push(...shapes);
|
|
21768
23333
|
return out;
|
|
21769
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
23334
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
21770
23335
|
const drawables3D = React85.useMemo(() => {
|
|
21771
23336
|
if (mode !== "3d") return [];
|
|
21772
23337
|
if (shapes.length > 0) {
|
|
21773
23338
|
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
21774
23339
|
}
|
|
23340
|
+
if (containers.length > 0) {
|
|
23341
|
+
chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
|
|
23342
|
+
}
|
|
23343
|
+
if (animate) {
|
|
23344
|
+
chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
23345
|
+
}
|
|
21775
23346
|
const out = [];
|
|
21776
23347
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
21777
23348
|
const atomById = /* @__PURE__ */ new Map();
|
|
@@ -21818,8 +23389,11 @@ var init_ChemistryCanvas = __esm({
|
|
|
21818
23389
|
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
21819
23390
|
}
|
|
21820
23391
|
}
|
|
23392
|
+
if (lattice3d) {
|
|
23393
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
23394
|
+
}
|
|
21821
23395
|
return out;
|
|
21822
|
-
}, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
|
|
23396
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
21823
23397
|
const atomIndexById = React85.useMemo(() => {
|
|
21824
23398
|
const m = /* @__PURE__ */ new Map();
|
|
21825
23399
|
atoms.forEach((a, i) => {
|
|
@@ -21858,6 +23432,8 @@ var init_ChemistryCanvas = __esm({
|
|
|
21858
23432
|
height,
|
|
21859
23433
|
backgroundColor,
|
|
21860
23434
|
shapes: derivedShapes,
|
|
23435
|
+
readouts,
|
|
23436
|
+
traces,
|
|
21861
23437
|
interactive: interactive ?? false,
|
|
21862
23438
|
animate,
|
|
21863
23439
|
onShapeClick,
|
|
@@ -27969,6 +29545,10 @@ var init_ProgressDots = __esm({
|
|
|
27969
29545
|
ProgressDots.displayName = "ProgressDots";
|
|
27970
29546
|
}
|
|
27971
29547
|
});
|
|
29548
|
+
function formatTick(v) {
|
|
29549
|
+
if (Number.isInteger(v)) return String(v);
|
|
29550
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
29551
|
+
}
|
|
27972
29552
|
var MathCanvas;
|
|
27973
29553
|
var init_MathCanvas = __esm({
|
|
27974
29554
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -27988,10 +29568,19 @@ var init_MathCanvas = __esm({
|
|
|
27988
29568
|
showAxes = true,
|
|
27989
29569
|
showGrid = true,
|
|
27990
29570
|
gridStep = 1,
|
|
29571
|
+
showTickLabels = false,
|
|
29572
|
+
showCurveLabels = false,
|
|
27991
29573
|
curves = [],
|
|
27992
29574
|
points = [],
|
|
27993
29575
|
vectors = [],
|
|
29576
|
+
regions = [],
|
|
29577
|
+
bars = [],
|
|
29578
|
+
guides = [],
|
|
29579
|
+
angles = [],
|
|
29580
|
+
hops = [],
|
|
27994
29581
|
shapes = [],
|
|
29582
|
+
readouts,
|
|
29583
|
+
traces,
|
|
27995
29584
|
interactive = false,
|
|
27996
29585
|
animate = false,
|
|
27997
29586
|
onShapeClick,
|
|
@@ -28005,6 +29594,8 @@ var init_MathCanvas = __esm({
|
|
|
28005
29594
|
const plotH = height - margin * 2;
|
|
28006
29595
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
28007
29596
|
const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
|
|
29597
|
+
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
29598
|
+
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
28008
29599
|
if (showGrid) {
|
|
28009
29600
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
28010
29601
|
const px = mapX(x);
|
|
@@ -28015,14 +29606,99 @@ var init_MathCanvas = __esm({
|
|
|
28015
29606
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
28016
29607
|
}
|
|
28017
29608
|
}
|
|
29609
|
+
if (showTickLabels) {
|
|
29610
|
+
const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
|
|
29611
|
+
let kx = 0;
|
|
29612
|
+
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
|
|
29613
|
+
if (kx % labelEveryX === 0 && x !== 0) {
|
|
29614
|
+
out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
|
|
29615
|
+
}
|
|
29616
|
+
}
|
|
29617
|
+
const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
|
|
29618
|
+
let ky = 0;
|
|
29619
|
+
for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
|
|
29620
|
+
if (ky % labelEveryY === 0 && y !== 0) {
|
|
29621
|
+
out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
|
|
29622
|
+
}
|
|
29623
|
+
}
|
|
29624
|
+
if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
|
|
29625
|
+
out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
|
|
29626
|
+
}
|
|
29627
|
+
}
|
|
29628
|
+
for (const region of regions) {
|
|
29629
|
+
if (!region.samples || region.samples.length === 0) continue;
|
|
29630
|
+
const baseline = region.baseline ?? 0;
|
|
29631
|
+
const clampedPoint = (p) => ({
|
|
29632
|
+
x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
|
|
29633
|
+
y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
|
|
29634
|
+
});
|
|
29635
|
+
const upper = region.samples.map(clampedPoint);
|
|
29636
|
+
const first = region.samples[0];
|
|
29637
|
+
const last = region.samples[region.samples.length - 1];
|
|
29638
|
+
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 })];
|
|
29639
|
+
const color = region.color ?? "#2563eb";
|
|
29640
|
+
out.push({
|
|
29641
|
+
type: "polygon",
|
|
29642
|
+
points: [...upper, ...closing],
|
|
29643
|
+
fill: color,
|
|
29644
|
+
color,
|
|
29645
|
+
opacity: region.opacity ?? 0.2,
|
|
29646
|
+
lineWidth: 1
|
|
29647
|
+
});
|
|
29648
|
+
if (region.label) {
|
|
29649
|
+
const mid = Math.floor(region.samples.length / 2);
|
|
29650
|
+
out.push({
|
|
29651
|
+
type: "text",
|
|
29652
|
+
x: mapX((first.x + last.x) / 2),
|
|
29653
|
+
y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
|
|
29654
|
+
text: region.label,
|
|
29655
|
+
color: "#111827",
|
|
29656
|
+
fontSize: 11
|
|
29657
|
+
});
|
|
29658
|
+
}
|
|
29659
|
+
}
|
|
29660
|
+
for (const bar of bars) {
|
|
29661
|
+
if (bar.x + bar.width < xMin || bar.x > xMax) continue;
|
|
29662
|
+
const y0 = bar.y0 ?? 0;
|
|
29663
|
+
const color = bar.color ?? "#93c5fd";
|
|
29664
|
+
out.push({
|
|
29665
|
+
type: "rect",
|
|
29666
|
+
x: mapX(bar.x),
|
|
29667
|
+
y: mapY(Math.max(y0, bar.y1)),
|
|
29668
|
+
width: mapX(bar.x + bar.width) - mapX(bar.x),
|
|
29669
|
+
height: Math.abs(mapY(bar.y1) - mapY(y0)),
|
|
29670
|
+
color,
|
|
29671
|
+
fill: color,
|
|
29672
|
+
opacity: bar.opacity ?? 0.5,
|
|
29673
|
+
lineWidth: 1
|
|
29674
|
+
});
|
|
29675
|
+
}
|
|
28018
29676
|
if (showAxes) {
|
|
28019
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
28020
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
28021
29677
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
28022
29678
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
28023
29679
|
}
|
|
29680
|
+
for (const guide of guides) {
|
|
29681
|
+
const color = guide.color ?? "#9ca3af";
|
|
29682
|
+
const dash = guide.dash ?? "dashed";
|
|
29683
|
+
if (guide.kind === "vline") {
|
|
29684
|
+
if (guide.at < xMin || guide.at > xMax) continue;
|
|
29685
|
+
const px = mapX(guide.at);
|
|
29686
|
+
out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
|
|
29687
|
+
if (guide.label) {
|
|
29688
|
+
out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
|
|
29689
|
+
}
|
|
29690
|
+
} else {
|
|
29691
|
+
if (guide.at < yMin || guide.at > yMax) continue;
|
|
29692
|
+
const py = mapY(guide.at);
|
|
29693
|
+
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
|
|
29694
|
+
if (guide.label) {
|
|
29695
|
+
out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
|
|
29696
|
+
}
|
|
29697
|
+
}
|
|
29698
|
+
}
|
|
28024
29699
|
for (const curve of curves) {
|
|
28025
29700
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
29701
|
+
let lastInRange;
|
|
28026
29702
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
28027
29703
|
const a = curve.samples[i - 1];
|
|
28028
29704
|
const b = curve.samples[i];
|
|
@@ -28034,19 +29710,97 @@ var init_MathCanvas = __esm({
|
|
|
28034
29710
|
x2: mapX(b.x),
|
|
28035
29711
|
y2: mapY(b.y),
|
|
28036
29712
|
color: curve.color ?? "#2563eb",
|
|
28037
|
-
lineWidth: 2
|
|
29713
|
+
lineWidth: 2,
|
|
29714
|
+
dash: curve.dash
|
|
29715
|
+
});
|
|
29716
|
+
lastInRange = b;
|
|
29717
|
+
}
|
|
29718
|
+
if (showCurveLabels && curve.label && lastInRange) {
|
|
29719
|
+
out.push({
|
|
29720
|
+
type: "text",
|
|
29721
|
+
x: mapX(lastInRange.x) + 6,
|
|
29722
|
+
y: mapY(lastInRange.y) - 6,
|
|
29723
|
+
text: curve.label,
|
|
29724
|
+
color: curve.color ?? "#2563eb",
|
|
29725
|
+
fontSize: 11
|
|
29726
|
+
});
|
|
29727
|
+
}
|
|
29728
|
+
}
|
|
29729
|
+
for (const hop of hops) {
|
|
29730
|
+
const x1 = mapX(hop.from);
|
|
29731
|
+
const x2 = mapX(hop.to);
|
|
29732
|
+
const peak = Math.min(36, plotH * 0.3);
|
|
29733
|
+
const color = hop.color ?? "#7c3aed";
|
|
29734
|
+
out.push({
|
|
29735
|
+
type: "ellipse",
|
|
29736
|
+
x: (x1 + x2) / 2,
|
|
29737
|
+
y: xAxisY,
|
|
29738
|
+
width: Math.abs(x2 - x1),
|
|
29739
|
+
height: 2 * peak,
|
|
29740
|
+
startAngle: 180,
|
|
29741
|
+
endAngle: 360,
|
|
29742
|
+
color
|
|
29743
|
+
});
|
|
29744
|
+
const s = Math.sign(hop.to - hop.from);
|
|
29745
|
+
out.push({
|
|
29746
|
+
type: "polygon",
|
|
29747
|
+
points: [
|
|
29748
|
+
{ x: x2, y: xAxisY },
|
|
29749
|
+
{ x: x2 - 4 * s, y: xAxisY - 7 },
|
|
29750
|
+
{ x: x2 + 2 * s, y: xAxisY - 7 }
|
|
29751
|
+
],
|
|
29752
|
+
fill: color,
|
|
29753
|
+
color
|
|
29754
|
+
});
|
|
29755
|
+
if (hop.label) {
|
|
29756
|
+
out.push({
|
|
29757
|
+
type: "text",
|
|
29758
|
+
x: (x1 + x2) / 2,
|
|
29759
|
+
y: xAxisY - peak - 8,
|
|
29760
|
+
text: hop.label,
|
|
29761
|
+
color: "#111827",
|
|
29762
|
+
fontSize: 10,
|
|
29763
|
+
align: "center"
|
|
29764
|
+
});
|
|
29765
|
+
}
|
|
29766
|
+
}
|
|
29767
|
+
for (const angle of angles) {
|
|
29768
|
+
const radius = angle.radius ?? 0.8;
|
|
29769
|
+
const color = angle.color ?? "#0ea5e9";
|
|
29770
|
+
out.push({
|
|
29771
|
+
type: "ellipse",
|
|
29772
|
+
x: mapX(angle.x),
|
|
29773
|
+
y: mapY(angle.y),
|
|
29774
|
+
width: 2 * radius * plotW / (xMax - xMin),
|
|
29775
|
+
height: 2 * radius * plotH / (yMax - yMin),
|
|
29776
|
+
startAngle: -angle.to,
|
|
29777
|
+
endAngle: -angle.from,
|
|
29778
|
+
color
|
|
29779
|
+
});
|
|
29780
|
+
if (angle.label) {
|
|
29781
|
+
const mid = (angle.from + angle.to) / 2;
|
|
29782
|
+
const rad = mid * Math.PI / 180;
|
|
29783
|
+
out.push({
|
|
29784
|
+
type: "text",
|
|
29785
|
+
x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
|
|
29786
|
+
y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
|
|
29787
|
+
text: angle.label,
|
|
29788
|
+
color: "#111827",
|
|
29789
|
+
fontSize: 11,
|
|
29790
|
+
align: "center"
|
|
28038
29791
|
});
|
|
28039
29792
|
}
|
|
28040
29793
|
}
|
|
28041
29794
|
for (const p of points) {
|
|
28042
29795
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
29796
|
+
const isOpen = p.style === "open";
|
|
28043
29797
|
out.push({
|
|
28044
29798
|
type: "circle",
|
|
28045
29799
|
x: mapX(p.x),
|
|
28046
29800
|
y: mapY(p.y),
|
|
28047
29801
|
radius: p.radius ?? 4,
|
|
28048
29802
|
color: p.color ?? "#dc2626",
|
|
28049
|
-
fill: p.color ?? "#dc2626"
|
|
29803
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
28050
29804
|
});
|
|
28051
29805
|
if (p.label) {
|
|
28052
29806
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -28065,7 +29819,28 @@ var init_MathCanvas = __esm({
|
|
|
28065
29819
|
}
|
|
28066
29820
|
out.push(...shapes);
|
|
28067
29821
|
return out;
|
|
28068
|
-
}, [
|
|
29822
|
+
}, [
|
|
29823
|
+
width,
|
|
29824
|
+
height,
|
|
29825
|
+
xMin,
|
|
29826
|
+
xMax,
|
|
29827
|
+
yMin,
|
|
29828
|
+
yMax,
|
|
29829
|
+
showAxes,
|
|
29830
|
+
showGrid,
|
|
29831
|
+
gridStep,
|
|
29832
|
+
showTickLabels,
|
|
29833
|
+
showCurveLabels,
|
|
29834
|
+
curves,
|
|
29835
|
+
points,
|
|
29836
|
+
vectors,
|
|
29837
|
+
regions,
|
|
29838
|
+
bars,
|
|
29839
|
+
guides,
|
|
29840
|
+
angles,
|
|
29841
|
+
hops,
|
|
29842
|
+
shapes
|
|
29843
|
+
]);
|
|
28069
29844
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
28070
29845
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
28071
29846
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -28074,6 +29849,8 @@ var init_MathCanvas = __esm({
|
|
|
28074
29849
|
width,
|
|
28075
29850
|
height,
|
|
28076
29851
|
shapes: derivedShapes,
|
|
29852
|
+
readouts,
|
|
29853
|
+
traces,
|
|
28077
29854
|
interactive,
|
|
28078
29855
|
animate,
|
|
28079
29856
|
onShapeClick,
|
|
@@ -28085,7 +29862,315 @@ var init_MathCanvas = __esm({
|
|
|
28085
29862
|
};
|
|
28086
29863
|
}
|
|
28087
29864
|
});
|
|
28088
|
-
|
|
29865
|
+
function formatMeterValue(v) {
|
|
29866
|
+
return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
|
|
29867
|
+
}
|
|
29868
|
+
function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
|
|
29869
|
+
const out = [];
|
|
29870
|
+
const color = obj.color ?? "#334155";
|
|
29871
|
+
switch (obj.kind) {
|
|
29872
|
+
case "ground": {
|
|
29873
|
+
const xStart = obj.x1 ?? 0;
|
|
29874
|
+
const xEnd = obj.x2 ?? canvasWidth;
|
|
29875
|
+
const y = obj.y ?? 0;
|
|
29876
|
+
out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
|
|
29877
|
+
for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
|
|
29878
|
+
out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
|
|
29879
|
+
}
|
|
29880
|
+
if (obj.label) {
|
|
29881
|
+
out.push({
|
|
29882
|
+
type: "text",
|
|
29883
|
+
x: (xStart + xEnd) / 2,
|
|
29884
|
+
y: y - 10,
|
|
29885
|
+
text: obj.label,
|
|
29886
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29887
|
+
fontSize: 11,
|
|
29888
|
+
align: "center"
|
|
29889
|
+
});
|
|
29890
|
+
}
|
|
29891
|
+
break;
|
|
29892
|
+
}
|
|
29893
|
+
case "wall": {
|
|
29894
|
+
const yStart = obj.y1 ?? 0;
|
|
29895
|
+
const yEnd = obj.y2 ?? canvasHeight;
|
|
29896
|
+
const x = obj.x ?? 0;
|
|
29897
|
+
out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
|
|
29898
|
+
for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
|
|
29899
|
+
out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
|
|
29900
|
+
}
|
|
29901
|
+
if (obj.label) {
|
|
29902
|
+
out.push({
|
|
29903
|
+
type: "text",
|
|
29904
|
+
x: x + 12,
|
|
29905
|
+
y: (yStart + yEnd) / 2,
|
|
29906
|
+
text: obj.label,
|
|
29907
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29908
|
+
fontSize: 11,
|
|
29909
|
+
align: "left"
|
|
29910
|
+
});
|
|
29911
|
+
}
|
|
29912
|
+
break;
|
|
29913
|
+
}
|
|
29914
|
+
case "ramp": {
|
|
29915
|
+
const x1 = obj.x1 ?? 0;
|
|
29916
|
+
const y1 = obj.y1 ?? 0;
|
|
29917
|
+
const x2 = obj.x2 ?? canvasWidth;
|
|
29918
|
+
const y2 = obj.y2 ?? canvasHeight;
|
|
29919
|
+
out.push({
|
|
29920
|
+
type: "polygon",
|
|
29921
|
+
points: [
|
|
29922
|
+
{ x: x1, y: y1 },
|
|
29923
|
+
{ x: x2, y: y2 },
|
|
29924
|
+
{ x: x1, y: y2 }
|
|
29925
|
+
],
|
|
29926
|
+
color,
|
|
29927
|
+
fill: obj.fill ?? "#e2e8f0",
|
|
29928
|
+
lineWidth: 2
|
|
29929
|
+
});
|
|
29930
|
+
if (obj.label) {
|
|
29931
|
+
out.push({
|
|
29932
|
+
type: "text",
|
|
29933
|
+
x: (2 * x1 + x2) / 3,
|
|
29934
|
+
y: (y1 + 2 * y2) / 3,
|
|
29935
|
+
text: obj.label,
|
|
29936
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29937
|
+
fontSize: 11,
|
|
29938
|
+
align: "center"
|
|
29939
|
+
});
|
|
29940
|
+
}
|
|
29941
|
+
break;
|
|
29942
|
+
}
|
|
29943
|
+
case "box": {
|
|
29944
|
+
const x = obj.x ?? 0;
|
|
29945
|
+
const y = obj.y ?? 0;
|
|
29946
|
+
const w = obj.width ?? 40;
|
|
29947
|
+
const h = obj.height ?? 40;
|
|
29948
|
+
out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
|
|
29949
|
+
if (obj.label) {
|
|
29950
|
+
out.push({
|
|
29951
|
+
type: "text",
|
|
29952
|
+
x: x + w / 2,
|
|
29953
|
+
y: y + h / 2,
|
|
29954
|
+
text: obj.label,
|
|
29955
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29956
|
+
fontSize: 11,
|
|
29957
|
+
align: "center"
|
|
29958
|
+
});
|
|
29959
|
+
}
|
|
29960
|
+
break;
|
|
29961
|
+
}
|
|
29962
|
+
case "pivot": {
|
|
29963
|
+
const x = obj.x ?? 0;
|
|
29964
|
+
const y = obj.y ?? 0;
|
|
29965
|
+
out.push({ type: "circle", x, y, radius: 5, color, fill: color });
|
|
29966
|
+
out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
|
|
29967
|
+
for (let k = 0; k < 5; k++) {
|
|
29968
|
+
const hx = x - 14 + 7 * k;
|
|
29969
|
+
out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
|
|
29970
|
+
}
|
|
29971
|
+
if (obj.label) {
|
|
29972
|
+
out.push({
|
|
29973
|
+
type: "text",
|
|
29974
|
+
x,
|
|
29975
|
+
y: y - 20,
|
|
29976
|
+
text: obj.label,
|
|
29977
|
+
color: PHYSICS_LABEL_COLOR,
|
|
29978
|
+
fontSize: 11,
|
|
29979
|
+
align: "center"
|
|
29980
|
+
});
|
|
29981
|
+
}
|
|
29982
|
+
break;
|
|
29983
|
+
}
|
|
29984
|
+
}
|
|
29985
|
+
return out;
|
|
29986
|
+
}
|
|
29987
|
+
function trailShapes(trail) {
|
|
29988
|
+
const n = trail.points.length;
|
|
29989
|
+
if (n < 2) return [];
|
|
29990
|
+
const color = trail.color ?? "#94a3b8";
|
|
29991
|
+
const lineWidth = trail.width ?? 2;
|
|
29992
|
+
const fade = trail.fade ?? true;
|
|
29993
|
+
const globalOpacity = trail.opacity ?? 1;
|
|
29994
|
+
const out = [];
|
|
29995
|
+
for (let i = 0; i < n - 1; i++) {
|
|
29996
|
+
const a = trail.points[i];
|
|
29997
|
+
const b = trail.points[i + 1];
|
|
29998
|
+
const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
|
|
29999
|
+
out.push({
|
|
30000
|
+
type: "line",
|
|
30001
|
+
x1: a.x,
|
|
30002
|
+
y1: a.y,
|
|
30003
|
+
x2: b.x,
|
|
30004
|
+
y2: b.y,
|
|
30005
|
+
color,
|
|
30006
|
+
lineWidth,
|
|
30007
|
+
opacity: segmentOpacity * globalOpacity
|
|
30008
|
+
});
|
|
30009
|
+
}
|
|
30010
|
+
return out;
|
|
30011
|
+
}
|
|
30012
|
+
function constraintShapes(c, a, b) {
|
|
30013
|
+
const color = c.color ?? "#9ca3af";
|
|
30014
|
+
const kind = c.kind ?? "rod";
|
|
30015
|
+
if (kind === "rod") {
|
|
30016
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
|
|
30017
|
+
}
|
|
30018
|
+
if (kind === "string") {
|
|
30019
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
|
|
30020
|
+
}
|
|
30021
|
+
const COILS = 8;
|
|
30022
|
+
const AMP = 7;
|
|
30023
|
+
const LEAD = 10;
|
|
30024
|
+
const dx = b.x - a.x;
|
|
30025
|
+
const dy = b.y - a.y;
|
|
30026
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
30027
|
+
const ux = dx / dist;
|
|
30028
|
+
const uy = dy / dist;
|
|
30029
|
+
const perpX = -uy;
|
|
30030
|
+
const perpY = ux;
|
|
30031
|
+
const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
|
|
30032
|
+
const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
|
|
30033
|
+
const m = 2 * COILS;
|
|
30034
|
+
const polyline = [{ x: a.x, y: a.y }, aPrime];
|
|
30035
|
+
for (let j = 1; j <= m; j++) {
|
|
30036
|
+
const t = j / (m + 1);
|
|
30037
|
+
const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
|
|
30038
|
+
const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
|
|
30039
|
+
const sign = j % 2 === 0 ? 1 : -1;
|
|
30040
|
+
polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
|
|
30041
|
+
}
|
|
30042
|
+
polyline.push(bPrime, { x: b.x, y: b.y });
|
|
30043
|
+
const out = [];
|
|
30044
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
30045
|
+
out.push({
|
|
30046
|
+
type: "line",
|
|
30047
|
+
x1: polyline[i - 1].x,
|
|
30048
|
+
y1: polyline[i - 1].y,
|
|
30049
|
+
x2: polyline[i].x,
|
|
30050
|
+
y2: polyline[i].y,
|
|
30051
|
+
color,
|
|
30052
|
+
lineWidth: 2
|
|
30053
|
+
});
|
|
30054
|
+
}
|
|
30055
|
+
return out;
|
|
30056
|
+
}
|
|
30057
|
+
function vectorShapes(v, bodyById) {
|
|
30058
|
+
let ax;
|
|
30059
|
+
let ay;
|
|
30060
|
+
if (v.body) {
|
|
30061
|
+
const anchor = bodyById.get(v.body);
|
|
30062
|
+
if (!anchor) return [];
|
|
30063
|
+
ax = anchor.x;
|
|
30064
|
+
ay = anchor.y;
|
|
30065
|
+
} else {
|
|
30066
|
+
ax = v.x ?? 0;
|
|
30067
|
+
ay = v.y ?? 0;
|
|
30068
|
+
}
|
|
30069
|
+
const scale = v.scale ?? 1;
|
|
30070
|
+
const color = v.color ?? "#dc2626";
|
|
30071
|
+
const tx = ax + v.dx * scale;
|
|
30072
|
+
const ty = ay + v.dy * scale;
|
|
30073
|
+
const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
|
|
30074
|
+
if (v.label) {
|
|
30075
|
+
const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
|
|
30076
|
+
const ux = (tx - ax) / dist;
|
|
30077
|
+
const uy = (ty - ay) / dist;
|
|
30078
|
+
out.push({
|
|
30079
|
+
type: "text",
|
|
30080
|
+
x: tx + 8 * ux,
|
|
30081
|
+
y: ty + 8 * uy,
|
|
30082
|
+
text: v.label,
|
|
30083
|
+
color,
|
|
30084
|
+
fontSize: 11,
|
|
30085
|
+
align: "center"
|
|
30086
|
+
});
|
|
30087
|
+
}
|
|
30088
|
+
return out;
|
|
30089
|
+
}
|
|
30090
|
+
function angleMarkerShapes(a) {
|
|
30091
|
+
const radius = a.radius ?? 26;
|
|
30092
|
+
const color = a.color ?? "#0ea5e9";
|
|
30093
|
+
const out = [
|
|
30094
|
+
{
|
|
30095
|
+
type: "ellipse",
|
|
30096
|
+
x: a.x,
|
|
30097
|
+
y: a.y,
|
|
30098
|
+
width: radius * 2,
|
|
30099
|
+
height: radius * 2,
|
|
30100
|
+
startAngle: a.from,
|
|
30101
|
+
endAngle: a.to,
|
|
30102
|
+
color,
|
|
30103
|
+
lineWidth: 2
|
|
30104
|
+
}
|
|
30105
|
+
];
|
|
30106
|
+
if (a.label) {
|
|
30107
|
+
const mid = (a.from + a.to) / 2 * (Math.PI / 180);
|
|
30108
|
+
out.push({
|
|
30109
|
+
type: "text",
|
|
30110
|
+
x: a.x + (radius + 13) * Math.cos(mid),
|
|
30111
|
+
y: a.y + (radius + 13) * Math.sin(mid),
|
|
30112
|
+
text: a.label,
|
|
30113
|
+
color,
|
|
30114
|
+
fontSize: 11,
|
|
30115
|
+
align: "center"
|
|
30116
|
+
});
|
|
30117
|
+
}
|
|
30118
|
+
return out;
|
|
30119
|
+
}
|
|
30120
|
+
function fieldShapes(field, canvasWidth, canvasHeight) {
|
|
30121
|
+
const spacing = field.spacing ?? 48;
|
|
30122
|
+
const size = field.size ?? 14;
|
|
30123
|
+
const color = field.color ?? "#94a3b8";
|
|
30124
|
+
const regionX = field.x ?? 0;
|
|
30125
|
+
const regionY = field.y ?? 0;
|
|
30126
|
+
const regionW = field.width ?? canvasWidth;
|
|
30127
|
+
const regionH = field.height ?? canvasHeight;
|
|
30128
|
+
const out = [];
|
|
30129
|
+
for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
|
|
30130
|
+
for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
|
|
30131
|
+
if (field.kind === "arrows") {
|
|
30132
|
+
const rad = (field.angle ?? 0) * Math.PI / 180;
|
|
30133
|
+
const hx = Math.cos(rad) * size / 2;
|
|
30134
|
+
const hy = Math.sin(rad) * size / 2;
|
|
30135
|
+
out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
|
|
30136
|
+
} else if (field.kind === "into") {
|
|
30137
|
+
const r = size / 3;
|
|
30138
|
+
const d = 0.6 * r * Math.SQRT1_2;
|
|
30139
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30140
|
+
out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
|
|
30141
|
+
out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
|
|
30142
|
+
} else {
|
|
30143
|
+
const r = size / 3;
|
|
30144
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r, color });
|
|
30145
|
+
out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
|
|
30146
|
+
}
|
|
30147
|
+
}
|
|
30148
|
+
}
|
|
30149
|
+
return out;
|
|
30150
|
+
}
|
|
30151
|
+
function meterShapes(meters, canvasHeight) {
|
|
30152
|
+
const n = meters.length;
|
|
30153
|
+
const out = [];
|
|
30154
|
+
const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
|
|
30155
|
+
meters.forEach((meter, i) => {
|
|
30156
|
+
const rowY = canvasHeight - 10 - 16 * (n - i);
|
|
30157
|
+
const color = meter.color ?? "#3b82f6";
|
|
30158
|
+
const M = meter.max ?? sharedMax;
|
|
30159
|
+
const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
|
|
30160
|
+
out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
|
|
30161
|
+
out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
|
|
30162
|
+
out.push({
|
|
30163
|
+
type: "text",
|
|
30164
|
+
x: 166,
|
|
30165
|
+
y: rowY + 8,
|
|
30166
|
+
text: formatMeterValue(meter.value),
|
|
30167
|
+
color: "#6b7280",
|
|
30168
|
+
fontSize: 9
|
|
30169
|
+
});
|
|
30170
|
+
});
|
|
30171
|
+
return out;
|
|
30172
|
+
}
|
|
30173
|
+
var physicsLog2, PHYSICS_LABEL_COLOR, PhysicsCanvas;
|
|
28089
30174
|
var init_PhysicsCanvas = __esm({
|
|
28090
30175
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
28091
30176
|
"use client";
|
|
@@ -28094,6 +30179,7 @@ var init_PhysicsCanvas = __esm({
|
|
|
28094
30179
|
init_LearningCanvas();
|
|
28095
30180
|
init_learningScene3D();
|
|
28096
30181
|
physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
|
|
30182
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
28097
30183
|
PhysicsCanvas = ({
|
|
28098
30184
|
className,
|
|
28099
30185
|
width = 600,
|
|
@@ -28110,7 +30196,18 @@ var init_PhysicsCanvas = __esm({
|
|
|
28110
30196
|
showForces = false,
|
|
28111
30197
|
velocityScale = 20,
|
|
28112
30198
|
forceScale = 20,
|
|
30199
|
+
sceneObjects = [],
|
|
30200
|
+
trails = [],
|
|
30201
|
+
vectors = [],
|
|
30202
|
+
surface3d,
|
|
30203
|
+
vectors3d = [],
|
|
30204
|
+
vectorScale = 1,
|
|
30205
|
+
angles = [],
|
|
30206
|
+
field,
|
|
30207
|
+
meters = [],
|
|
28113
30208
|
shapes = [],
|
|
30209
|
+
readouts,
|
|
30210
|
+
traces,
|
|
28114
30211
|
showGrid,
|
|
28115
30212
|
shadows,
|
|
28116
30213
|
interactive,
|
|
@@ -28125,19 +30222,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
28125
30222
|
for (const b of bodies) {
|
|
28126
30223
|
if (b.id) bodyById.set(b.id, b);
|
|
28127
30224
|
}
|
|
30225
|
+
if (field) out.push(...fieldShapes(field, width, height));
|
|
30226
|
+
for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
|
|
30227
|
+
for (const trail of trails) out.push(...trailShapes(trail));
|
|
28128
30228
|
for (const c of constraints) {
|
|
28129
30229
|
const a = bodyById.get(c.from);
|
|
28130
30230
|
const b = bodyById.get(c.to);
|
|
28131
30231
|
if (!a || !b) continue;
|
|
28132
|
-
out.push(
|
|
28133
|
-
type: "line",
|
|
28134
|
-
x1: a.x,
|
|
28135
|
-
y1: a.y,
|
|
28136
|
-
x2: b.x,
|
|
28137
|
-
y2: b.y,
|
|
28138
|
-
color: c.color ?? "#9ca3af",
|
|
28139
|
-
lineWidth: 2
|
|
28140
|
-
});
|
|
30232
|
+
out.push(...constraintShapes(c, a, b));
|
|
28141
30233
|
}
|
|
28142
30234
|
for (const b of bodies) {
|
|
28143
30235
|
out.push({
|
|
@@ -28182,14 +30274,51 @@ var init_PhysicsCanvas = __esm({
|
|
|
28182
30274
|
});
|
|
28183
30275
|
}
|
|
28184
30276
|
}
|
|
30277
|
+
for (const v of vectors) out.push(...vectorShapes(v, bodyById));
|
|
30278
|
+
for (const a of angles) out.push(...angleMarkerShapes(a));
|
|
30279
|
+
if (meters.length > 0) out.push(...meterShapes(meters, height));
|
|
28185
30280
|
out.push(...shapes);
|
|
28186
30281
|
return out;
|
|
28187
|
-
}, [
|
|
30282
|
+
}, [
|
|
30283
|
+
bodies,
|
|
30284
|
+
constraints,
|
|
30285
|
+
showVelocity,
|
|
30286
|
+
showForces,
|
|
30287
|
+
velocityScale,
|
|
30288
|
+
forceScale,
|
|
30289
|
+
sceneObjects,
|
|
30290
|
+
trails,
|
|
30291
|
+
vectors,
|
|
30292
|
+
angles,
|
|
30293
|
+
field,
|
|
30294
|
+
meters,
|
|
30295
|
+
shapes,
|
|
30296
|
+
width,
|
|
30297
|
+
height
|
|
30298
|
+
]);
|
|
28188
30299
|
const drawables3D = React85.useMemo(() => {
|
|
28189
30300
|
if (mode !== "3d") return [];
|
|
28190
30301
|
if (shapes.length > 0) {
|
|
28191
30302
|
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
28192
30303
|
}
|
|
30304
|
+
if (sceneObjects.length > 0) {
|
|
30305
|
+
physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
|
|
30306
|
+
}
|
|
30307
|
+
if (vectors.length > 0) {
|
|
30308
|
+
physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
|
|
30309
|
+
}
|
|
30310
|
+
if (angles.length > 0) {
|
|
30311
|
+
physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
|
|
30312
|
+
}
|
|
30313
|
+
if (field) {
|
|
30314
|
+
physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
|
|
30315
|
+
}
|
|
30316
|
+
if (meters.length > 0) {
|
|
30317
|
+
physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
|
|
30318
|
+
}
|
|
30319
|
+
if (animate) {
|
|
30320
|
+
physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
30321
|
+
}
|
|
28193
30322
|
const out = [];
|
|
28194
30323
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
28195
30324
|
const bodyById = /* @__PURE__ */ new Map();
|
|
@@ -28237,15 +30366,67 @@ var init_PhysicsCanvas = __esm({
|
|
|
28237
30366
|
if (arrow) out.push(arrow);
|
|
28238
30367
|
}
|
|
28239
30368
|
}
|
|
30369
|
+
for (const trail of trails) {
|
|
30370
|
+
if (trail.fade !== void 0) {
|
|
30371
|
+
physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
|
|
30372
|
+
}
|
|
30373
|
+
const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
|
|
30374
|
+
out.push(
|
|
30375
|
+
...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
|
|
30376
|
+
...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
|
|
30377
|
+
})
|
|
30378
|
+
);
|
|
30379
|
+
}
|
|
30380
|
+
if (surface3d) {
|
|
30381
|
+
out.push(...heightFieldMesh(surface3d));
|
|
30382
|
+
}
|
|
30383
|
+
if (vectors3d.length > 0) {
|
|
30384
|
+
out.push(
|
|
30385
|
+
...arrowField(
|
|
30386
|
+
vectors3d.map((v) => ({
|
|
30387
|
+
id: v.id,
|
|
30388
|
+
from: [v.x, v.y, v.z ?? 0],
|
|
30389
|
+
delta: [v.dx, v.dy, v.dz ?? 0],
|
|
30390
|
+
color: v.color,
|
|
30391
|
+
label: v.label,
|
|
30392
|
+
width: v.width
|
|
30393
|
+
})),
|
|
30394
|
+
{ scale: vectorScale, labelColor }
|
|
30395
|
+
)
|
|
30396
|
+
);
|
|
30397
|
+
}
|
|
28240
30398
|
return out;
|
|
28241
|
-
}, [
|
|
30399
|
+
}, [
|
|
30400
|
+
mode,
|
|
30401
|
+
bodies,
|
|
30402
|
+
constraints,
|
|
30403
|
+
showVelocity,
|
|
30404
|
+
showForces,
|
|
30405
|
+
velocityScale,
|
|
30406
|
+
forceScale,
|
|
30407
|
+
shapes,
|
|
30408
|
+
sceneObjects,
|
|
30409
|
+
trails,
|
|
30410
|
+
vectors,
|
|
30411
|
+
surface3d,
|
|
30412
|
+
vectors3d,
|
|
30413
|
+
vectorScale,
|
|
30414
|
+
angles,
|
|
30415
|
+
field,
|
|
30416
|
+
meters,
|
|
30417
|
+
animate,
|
|
30418
|
+
backgroundColor
|
|
30419
|
+
]);
|
|
28242
30420
|
const bodyIndexById = React85.useMemo(() => {
|
|
28243
30421
|
const m = /* @__PURE__ */ new Map();
|
|
30422
|
+
vectors3d.forEach((v, i) => {
|
|
30423
|
+
if (v.id) m.set(v.id, i);
|
|
30424
|
+
});
|
|
28244
30425
|
bodies.forEach((b, i) => {
|
|
28245
30426
|
if (b.id) m.set(b.id, i);
|
|
28246
30427
|
});
|
|
28247
30428
|
return m;
|
|
28248
|
-
}, [bodies]);
|
|
30429
|
+
}, [bodies, vectors3d]);
|
|
28249
30430
|
if (mode === "3d") {
|
|
28250
30431
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
28251
30432
|
LearningScene3D,
|
|
@@ -28277,6 +30458,8 @@ var init_PhysicsCanvas = __esm({
|
|
|
28277
30458
|
height,
|
|
28278
30459
|
backgroundColor,
|
|
28279
30460
|
shapes: derivedShapes,
|
|
30461
|
+
readouts,
|
|
30462
|
+
traces,
|
|
28280
30463
|
interactive: interactive ?? false,
|
|
28281
30464
|
animate,
|
|
28282
30465
|
onShapeClick,
|
|
@@ -28427,7 +30610,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
28427
30610
|
}
|
|
28428
30611
|
return nodeIds.map((id) => positions.get(id));
|
|
28429
30612
|
}
|
|
28430
|
-
function
|
|
30613
|
+
function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
|
|
28431
30614
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
28432
30615
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
28433
30616
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -28483,7 +30666,7 @@ function computeStaticLayout(mode, input) {
|
|
|
28483
30666
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
28484
30667
|
const roots = findRoots(nodeIds, adjacency);
|
|
28485
30668
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
28486
|
-
if (mode === "tree") return
|
|
30669
|
+
if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
|
|
28487
30670
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
28488
30671
|
}
|
|
28489
30672
|
var init_graphViewLayouts = __esm({
|
|
@@ -29764,8 +31947,8 @@ function TableView({
|
|
|
29764
31947
|
columns,
|
|
29765
31948
|
fields,
|
|
29766
31949
|
itemActions,
|
|
29767
|
-
maxInlineActions,
|
|
29768
|
-
itemClickEvent,
|
|
31950
|
+
maxInlineActions: _maxInlineActions,
|
|
31951
|
+
itemClickEvent = "",
|
|
29769
31952
|
selectable = false,
|
|
29770
31953
|
selectEvent,
|
|
29771
31954
|
selectedIds,
|
|
@@ -29815,7 +31998,6 @@ function TableView({
|
|
|
29815
31998
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
29816
31999
|
const hasRenderProp = typeof children === "function";
|
|
29817
32000
|
const idField = dndItemIdField ?? "id";
|
|
29818
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
29819
32001
|
React85__namespace.default.useEffect(() => {
|
|
29820
32002
|
tableViewLog.debug("render", {
|
|
29821
32003
|
rowCount: data.length,
|
|
@@ -29857,21 +32039,14 @@ function TableView({
|
|
|
29857
32039
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
29858
32040
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
29859
32041
|
};
|
|
29860
|
-
const
|
|
29861
|
-
e.stopPropagation();
|
|
29862
|
-
const payload = {
|
|
29863
|
-
id: row.id,
|
|
29864
|
-
row
|
|
29865
|
-
};
|
|
29866
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
29867
|
-
};
|
|
32042
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
29868
32043
|
const handleRowClick = (row) => () => {
|
|
29869
|
-
if (!
|
|
32044
|
+
if (!rowClickEvent) return;
|
|
29870
32045
|
const payload = {
|
|
29871
32046
|
id: row.id,
|
|
29872
32047
|
row
|
|
29873
32048
|
};
|
|
29874
|
-
eventBus.emit(`UI:${
|
|
32049
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
29875
32050
|
};
|
|
29876
32051
|
const colFloors = React85__namespace.default.useMemo(
|
|
29877
32052
|
() => colDefs.map((col) => {
|
|
@@ -29887,10 +32062,7 @@ function TableView({
|
|
|
29887
32062
|
const statusNode = isLoading ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) }) : error ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "error", children: error.message }) }) : data.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) }) : null;
|
|
29888
32063
|
const lk = LOOKS[look];
|
|
29889
32064
|
const hasActions = actionDefs.length > 0;
|
|
29890
|
-
const
|
|
29891
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
29892
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
29893
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
32065
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
29894
32066
|
const gridTemplateColumns = [
|
|
29895
32067
|
selectable ? "auto" : null,
|
|
29896
32068
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -29937,7 +32109,7 @@ function TableView({
|
|
|
29937
32109
|
col.key
|
|
29938
32110
|
);
|
|
29939
32111
|
}),
|
|
29940
|
-
hasActions && /* @__PURE__ */ jsxRuntime.jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
|
|
32112
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)] border-l border-[var(--color-border)] h-full" })
|
|
29941
32113
|
]
|
|
29942
32114
|
}
|
|
29943
32115
|
);
|
|
@@ -29949,12 +32121,12 @@ function TableView({
|
|
|
29949
32121
|
role: "row",
|
|
29950
32122
|
"data-entity-row": true,
|
|
29951
32123
|
"data-entity-id": id,
|
|
29952
|
-
onClick:
|
|
32124
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
29953
32125
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
29954
32126
|
className: cn(
|
|
29955
32127
|
"group items-center gap-3 transition-colors duration-fast",
|
|
29956
32128
|
hasRenderProp ? "flex" : "grid",
|
|
29957
|
-
|
|
32129
|
+
rowClickEvent && "cursor-pointer",
|
|
29958
32130
|
lk.rowPad,
|
|
29959
32131
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
29960
32132
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -29962,7 +32134,7 @@ function TableView({
|
|
|
29962
32134
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
29963
32135
|
),
|
|
29964
32136
|
children: [
|
|
29965
|
-
selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick:
|
|
32137
|
+
selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
29966
32138
|
Checkbox,
|
|
29967
32139
|
{
|
|
29968
32140
|
checked: selected.has(id),
|
|
@@ -29983,53 +32155,37 @@ function TableView({
|
|
|
29983
32155
|
}
|
|
29984
32156
|
return /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
|
|
29985
32157
|
}),
|
|
29986
|
-
hasActions && /* @__PURE__ */ jsxRuntime.
|
|
32158
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(
|
|
29987
32159
|
HStack,
|
|
29988
32160
|
{
|
|
29989
32161
|
gap: "xs",
|
|
29990
|
-
onClick:
|
|
32162
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
29991
32163
|
className: cn(
|
|
29992
32164
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
29993
|
-
// scroll container, which
|
|
29994
|
-
// Opaque so
|
|
32165
|
+
// scroll container, which would leave the kebab off-screen.
|
|
32166
|
+
// Opaque + hairline edge so it reads as a pinned column, not a
|
|
32167
|
+
// floating control, while scrolled cells pass underneath.
|
|
29995
32168
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
32169
|
+
"border-l border-[var(--color-border)]",
|
|
29996
32170
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
29997
32171
|
),
|
|
29998
|
-
children:
|
|
29999
|
-
|
|
30000
|
-
|
|
30001
|
-
|
|
30002
|
-
|
|
30003
|
-
|
|
30004
|
-
|
|
30005
|
-
|
|
30006
|
-
|
|
30007
|
-
|
|
30008
|
-
|
|
30009
|
-
|
|
30010
|
-
|
|
30011
|
-
|
|
30012
|
-
}
|
|
30013
|
-
|
|
30014
|
-
|
|
30015
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
|
|
30016
|
-
Menu,
|
|
30017
|
-
{
|
|
30018
|
-
position: "bottom-end",
|
|
30019
|
-
trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
30020
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
30021
|
-
label: action.label,
|
|
30022
|
-
icon: action.icon,
|
|
30023
|
-
event: action.event,
|
|
30024
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
30025
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
30026
|
-
id: row.id,
|
|
30027
|
-
row
|
|
30028
|
-
})
|
|
30029
|
-
}))
|
|
30030
|
-
}
|
|
30031
|
-
)
|
|
30032
|
-
]
|
|
32172
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
32173
|
+
Menu,
|
|
32174
|
+
{
|
|
32175
|
+
position: "bottom-end",
|
|
32176
|
+
trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", "data-row-id": String(row.id), children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
32177
|
+
items: actionDefs.map((action) => ({
|
|
32178
|
+
label: action.label,
|
|
32179
|
+
icon: action.icon,
|
|
32180
|
+
event: action.event,
|
|
32181
|
+
variant: action.variant === "danger" ? "danger" : "default",
|
|
32182
|
+
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
32183
|
+
id: row.id,
|
|
32184
|
+
row
|
|
32185
|
+
})
|
|
32186
|
+
}))
|
|
32187
|
+
}
|
|
32188
|
+
)
|
|
30033
32189
|
}
|
|
30034
32190
|
)
|
|
30035
32191
|
]
|
|
@@ -30072,7 +32228,6 @@ var init_TableView = __esm({
|
|
|
30072
32228
|
init_format();
|
|
30073
32229
|
init_getNestedValue();
|
|
30074
32230
|
init_useEventBus();
|
|
30075
|
-
init_useMediaQuery();
|
|
30076
32231
|
init_Box();
|
|
30077
32232
|
init_Stack();
|
|
30078
32233
|
init_Typography();
|
|
@@ -45012,6 +47167,7 @@ var init_component_registry_generated = __esm({
|
|
|
45012
47167
|
init_ActionTile();
|
|
45013
47168
|
init_ActivationBlock();
|
|
45014
47169
|
init_ComponentPatterns();
|
|
47170
|
+
init_AlgoGraphCanvas();
|
|
45015
47171
|
init_AlgorithmCanvas();
|
|
45016
47172
|
init_AnimatedCounter();
|
|
45017
47173
|
init_AnimatedGraphic();
|
|
@@ -45275,6 +47431,7 @@ var init_component_registry_generated = __esm({
|
|
|
45275
47431
|
"ActivationBlock": ActivationBlock,
|
|
45276
47432
|
"Alert": AlertPattern,
|
|
45277
47433
|
"AlertPattern": AlertPattern,
|
|
47434
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
45278
47435
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
45279
47436
|
"AnimatedCounter": AnimatedCounter,
|
|
45280
47437
|
"AnimatedGraphic": AnimatedGraphic,
|
|
@@ -46601,9 +48758,9 @@ var log3 = logger.createLogger("almadar:ui:effects:client-handlers");
|
|
|
46601
48758
|
function createClientEffectHandlers(options) {
|
|
46602
48759
|
const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
|
|
46603
48760
|
return {
|
|
46604
|
-
emit: (event, payload) => {
|
|
48761
|
+
emit: (event, payload, source) => {
|
|
46605
48762
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
46606
|
-
eventBus.emit(prefixedEvent, payload);
|
|
48763
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
46607
48764
|
},
|
|
46608
48765
|
persist: async () => {
|
|
46609
48766
|
log3.warn("persist is server-side only, ignored on client");
|
|
@@ -46938,7 +49095,7 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
|
|
|
46938
49095
|
}
|
|
46939
49096
|
};
|
|
46940
49097
|
ctx.emit = (event, payload) => {
|
|
46941
|
-
emit(event, payload);
|
|
49098
|
+
emit(event, payload, { trait: traitName, tick: tick.name });
|
|
46942
49099
|
};
|
|
46943
49100
|
if (tick.guard !== void 0 && !evaluator.evaluateGuard(tick.guard, ctx)) {
|
|
46944
49101
|
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
@@ -47390,6 +49547,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
47390
49547
|
}
|
|
47391
49548
|
const effectContext = {
|
|
47392
49549
|
traitName,
|
|
49550
|
+
orbitalName: orbitalsByTrait?.[traitName],
|
|
47393
49551
|
state: previousState,
|
|
47394
49552
|
transition: `${previousState}->${newState}`,
|
|
47395
49553
|
linkedEntity,
|
|
@@ -47466,7 +49624,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
47466
49624
|
});
|
|
47467
49625
|
}
|
|
47468
49626
|
return emittedDuringExec;
|
|
47469
|
-
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot]);
|
|
49627
|
+
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot, orbitalsByTrait]);
|
|
47470
49628
|
const runTickEffects = React85.useCallback((tick, binding) => {
|
|
47471
49629
|
const traitName = binding.trait.name;
|
|
47472
49630
|
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
@@ -47502,9 +49660,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
47502
49660
|
log: tickLog
|
|
47503
49661
|
});
|
|
47504
49662
|
}, [executeTransitionEffects, sharedEntityStore]);
|
|
47505
|
-
const emitFromSharedWriter = React85.useCallback((event, payload) => {
|
|
49663
|
+
const emitFromSharedWriter = React85.useCallback((event, payload, source) => {
|
|
47506
49664
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
47507
|
-
eventBus.emit(prefixedEvent, payload);
|
|
49665
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
47508
49666
|
}, [eventBus]);
|
|
47509
49667
|
React85.useEffect(() => {
|
|
47510
49668
|
const scheduler = runtime.createTickScheduler();
|
|
@@ -48358,11 +50516,11 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeP
|
|
|
48358
50516
|
for (const orb of schema.orbitals) {
|
|
48359
50517
|
for (const pageRef of orb.pages ?? []) {
|
|
48360
50518
|
const name = typeof pageRef === "object" && pageRef !== null ? pageRef.name : void 0;
|
|
48361
|
-
if (name === pageName) return orb.theme;
|
|
50519
|
+
if (name === pageName) return orb.theme ?? schema.theme;
|
|
48362
50520
|
}
|
|
48363
50521
|
}
|
|
48364
50522
|
}
|
|
48365
|
-
return schema.orbitals[0]?.theme;
|
|
50523
|
+
return schema.orbitals[0]?.theme ?? schema.theme;
|
|
48366
50524
|
}, [schema, pageName]);
|
|
48367
50525
|
const inner = /* @__PURE__ */ jsxRuntime.jsx(providers.VerificationProvider, { enabled: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
48368
50526
|
providers.EntitySchemaProvider,
|