@almadar/ui 5.149.0 → 5.150.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 +2313 -155
- package/dist/avl/index.js +2313 -155
- 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 +2313 -155
- package/dist/runtime/index.d.cts +2 -2
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2313 -155
- package/package.json +3 -3
package/dist/avl/index.js
CHANGED
|
@@ -3355,7 +3355,7 @@ function useEventBus() {
|
|
|
3355
3355
|
return {
|
|
3356
3356
|
...baseBus,
|
|
3357
3357
|
emit: (type, payload, source) => {
|
|
3358
|
-
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
|
|
3358
|
+
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
|
|
3359
3359
|
scopeLog.warn("emit:bare-key-no-scope", { type });
|
|
3360
3360
|
}
|
|
3361
3361
|
baseBus.emit(type, payload, source);
|
|
@@ -15045,6 +15045,14 @@ function shapeBounds(shape) {
|
|
|
15045
15045
|
w: shape.radius * 2 + 8,
|
|
15046
15046
|
h: shape.radius * 2 + 8
|
|
15047
15047
|
};
|
|
15048
|
+
case "ellipse":
|
|
15049
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
15050
|
+
return {
|
|
15051
|
+
x: shape.x - shape.width / 2 - 4,
|
|
15052
|
+
y: shape.y - shape.height / 2 - 4,
|
|
15053
|
+
w: shape.width + 8,
|
|
15054
|
+
h: shape.height + 8
|
|
15055
|
+
};
|
|
15048
15056
|
case "rect":
|
|
15049
15057
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
15050
15058
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -15078,13 +15086,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
15078
15086
|
ctx.closePath();
|
|
15079
15087
|
ctx.fill();
|
|
15080
15088
|
}
|
|
15081
|
-
function drawShape(ctx, shape, width, height) {
|
|
15089
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
15082
15090
|
ctx.save();
|
|
15083
15091
|
const opacity = shape.opacity ?? 1;
|
|
15084
15092
|
ctx.globalAlpha = opacity;
|
|
15085
15093
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
15086
15094
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
15087
15095
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
15096
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
15088
15097
|
switch (shape.type) {
|
|
15089
15098
|
case "grid": {
|
|
15090
15099
|
const step = shape.step ?? 40;
|
|
@@ -15150,6 +15159,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
15150
15159
|
ctx.stroke();
|
|
15151
15160
|
break;
|
|
15152
15161
|
}
|
|
15162
|
+
case "ellipse": {
|
|
15163
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
15164
|
+
const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
|
|
15165
|
+
const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
|
|
15166
|
+
ctx.beginPath();
|
|
15167
|
+
ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
|
|
15168
|
+
if (fill) {
|
|
15169
|
+
ctx.fillStyle = fill;
|
|
15170
|
+
ctx.fill();
|
|
15171
|
+
}
|
|
15172
|
+
ctx.strokeStyle = stroke;
|
|
15173
|
+
ctx.stroke();
|
|
15174
|
+
break;
|
|
15175
|
+
}
|
|
15153
15176
|
case "rect": {
|
|
15154
15177
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
15155
15178
|
if (fill) {
|
|
@@ -15196,21 +15219,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
15196
15219
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
15197
15220
|
break;
|
|
15198
15221
|
}
|
|
15222
|
+
case "venn-region": {
|
|
15223
|
+
const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
|
|
15224
|
+
const c = allShapes.find((s) => s.type === "circle" && s.id === id);
|
|
15225
|
+
return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
|
|
15226
|
+
});
|
|
15227
|
+
const inside = resolveCircles(shape.inside);
|
|
15228
|
+
if (inside.length === 0) break;
|
|
15229
|
+
const outside = resolveCircles(shape.outside);
|
|
15230
|
+
const off = document.createElement("canvas");
|
|
15231
|
+
off.width = ctx.canvas.width;
|
|
15232
|
+
off.height = ctx.canvas.height;
|
|
15233
|
+
const octx = off.getContext("2d");
|
|
15234
|
+
if (!octx) break;
|
|
15235
|
+
octx.setTransform(ctx.getTransform());
|
|
15236
|
+
for (const c of inside) {
|
|
15237
|
+
const p = new Path2D();
|
|
15238
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
15239
|
+
octx.clip(p);
|
|
15240
|
+
}
|
|
15241
|
+
octx.fillStyle = fill ?? stroke;
|
|
15242
|
+
octx.fillRect(0, 0, width, height);
|
|
15243
|
+
octx.globalCompositeOperation = "destination-out";
|
|
15244
|
+
for (const c of outside) {
|
|
15245
|
+
const p = new Path2D();
|
|
15246
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
15247
|
+
octx.fill(p);
|
|
15248
|
+
}
|
|
15249
|
+
ctx.save();
|
|
15250
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
15251
|
+
ctx.drawImage(off, 0, 0);
|
|
15252
|
+
ctx.restore();
|
|
15253
|
+
break;
|
|
15254
|
+
}
|
|
15199
15255
|
}
|
|
15200
15256
|
ctx.restore();
|
|
15201
15257
|
}
|
|
15202
|
-
|
|
15258
|
+
function readoutShapes(readouts, width) {
|
|
15259
|
+
const out = [];
|
|
15260
|
+
const chipH = 18;
|
|
15261
|
+
const gap = 6;
|
|
15262
|
+
let rightEdge = width - 6;
|
|
15263
|
+
let rowY = 6;
|
|
15264
|
+
for (const readout of readouts) {
|
|
15265
|
+
const text = `${readout.label}: ${String(readout.value)}`;
|
|
15266
|
+
const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
|
|
15267
|
+
let chipX = rightEdge - chipW;
|
|
15268
|
+
if (chipX < 4) {
|
|
15269
|
+
rowY += chipH + 4;
|
|
15270
|
+
rightEdge = width - 6;
|
|
15271
|
+
chipX = rightEdge - chipW;
|
|
15272
|
+
}
|
|
15273
|
+
const color = readout.color ?? "#334155";
|
|
15274
|
+
out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
|
|
15275
|
+
out.push({
|
|
15276
|
+
type: "text",
|
|
15277
|
+
x: chipX + chipW / 2,
|
|
15278
|
+
y: rowY + chipH / 2,
|
|
15279
|
+
text,
|
|
15280
|
+
color: "#ffffff",
|
|
15281
|
+
fontSize: 10,
|
|
15282
|
+
align: "center"
|
|
15283
|
+
});
|
|
15284
|
+
rightEdge = chipX - gap;
|
|
15285
|
+
}
|
|
15286
|
+
return out;
|
|
15287
|
+
}
|
|
15288
|
+
function traceShapes(panel, k, width, height) {
|
|
15289
|
+
const w = panel.width ?? Math.round(width * 0.32);
|
|
15290
|
+
const h = panel.height ?? Math.round(height * 0.28);
|
|
15291
|
+
const x = panel.x ?? width - w - 8;
|
|
15292
|
+
const y = panel.y ?? height - h - 8 - k * (h + 8);
|
|
15293
|
+
const allSamples = panel.series.flatMap((series) => series.samples);
|
|
15294
|
+
let xLo = Math.min(...allSamples.map((p) => p.x));
|
|
15295
|
+
let xHi = Math.max(...allSamples.map((p) => p.x));
|
|
15296
|
+
let yLo = Math.min(...allSamples.map((p) => p.y));
|
|
15297
|
+
let yHi = Math.max(...allSamples.map((p) => p.y));
|
|
15298
|
+
if (xLo === xHi) {
|
|
15299
|
+
xLo -= 1;
|
|
15300
|
+
xHi += 1;
|
|
15301
|
+
}
|
|
15302
|
+
if (yLo === yHi) {
|
|
15303
|
+
yLo -= 1;
|
|
15304
|
+
yHi += 1;
|
|
15305
|
+
}
|
|
15306
|
+
const backgroundColor = panel.backgroundColor ?? "#ffffff";
|
|
15307
|
+
const frameColor = panel.frameColor ?? "#94a3b8";
|
|
15308
|
+
const out = [];
|
|
15309
|
+
out.push({
|
|
15310
|
+
type: "rect",
|
|
15311
|
+
x,
|
|
15312
|
+
y,
|
|
15313
|
+
width: w,
|
|
15314
|
+
height: h,
|
|
15315
|
+
color: backgroundColor,
|
|
15316
|
+
fill: backgroundColor,
|
|
15317
|
+
opacity: panel.backgroundOpacity ?? 0.85
|
|
15318
|
+
});
|
|
15319
|
+
out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
|
|
15320
|
+
panel.series.forEach((series, j) => {
|
|
15321
|
+
const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
|
|
15322
|
+
const mapped = series.samples.map((p) => ({
|
|
15323
|
+
x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
|
|
15324
|
+
y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
|
|
15325
|
+
}));
|
|
15326
|
+
for (let i = 1; i < mapped.length; i++) {
|
|
15327
|
+
out.push({
|
|
15328
|
+
type: "line",
|
|
15329
|
+
x1: mapped[i - 1].x,
|
|
15330
|
+
y1: mapped[i - 1].y,
|
|
15331
|
+
x2: mapped[i].x,
|
|
15332
|
+
y2: mapped[i].y,
|
|
15333
|
+
color,
|
|
15334
|
+
lineWidth: 1.5
|
|
15335
|
+
});
|
|
15336
|
+
}
|
|
15337
|
+
if (mapped.length > 0) {
|
|
15338
|
+
const last = mapped[mapped.length - 1];
|
|
15339
|
+
out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
|
|
15340
|
+
}
|
|
15341
|
+
if (series.label) {
|
|
15342
|
+
out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
|
|
15343
|
+
}
|
|
15344
|
+
});
|
|
15345
|
+
if (panel.yLabel) {
|
|
15346
|
+
out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
15347
|
+
}
|
|
15348
|
+
if (panel.xLabel) {
|
|
15349
|
+
out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
15350
|
+
}
|
|
15351
|
+
return out;
|
|
15352
|
+
}
|
|
15353
|
+
var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
|
|
15203
15354
|
var init_LearningCanvas = __esm({
|
|
15204
15355
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
15205
15356
|
"use client";
|
|
15206
15357
|
init_cn();
|
|
15207
15358
|
init_useEventBus();
|
|
15359
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
15360
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
15208
15361
|
LearningCanvas = ({
|
|
15209
15362
|
className,
|
|
15210
15363
|
width = 600,
|
|
15211
15364
|
height = 400,
|
|
15212
15365
|
backgroundColor,
|
|
15213
15366
|
shapes = [],
|
|
15367
|
+
readouts,
|
|
15368
|
+
traces,
|
|
15214
15369
|
interactive = false,
|
|
15215
15370
|
animate = false,
|
|
15216
15371
|
onShapeClick,
|
|
@@ -15236,6 +15391,12 @@ var init_LearningCanvas = __esm({
|
|
|
15236
15391
|
}
|
|
15237
15392
|
return -1;
|
|
15238
15393
|
}, [shapes]);
|
|
15394
|
+
const derivedShapes = useMemo(() => {
|
|
15395
|
+
if (!traces?.length && !readouts?.length) return shapes;
|
|
15396
|
+
const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
|
|
15397
|
+
const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
|
|
15398
|
+
return [...shapes, ...traceOut, ...readoutOut];
|
|
15399
|
+
}, [shapes, traces, readouts, width, height]);
|
|
15239
15400
|
const draw = useCallback(() => {
|
|
15240
15401
|
const canvas = canvasRef.current;
|
|
15241
15402
|
if (!canvas) return;
|
|
@@ -15252,13 +15413,13 @@ var init_LearningCanvas = __esm({
|
|
|
15252
15413
|
ctx.fillStyle = backgroundColor;
|
|
15253
15414
|
ctx.fillRect(0, 0, width, height);
|
|
15254
15415
|
}
|
|
15255
|
-
for (const shape of
|
|
15256
|
-
if (shape.type !== "text") drawShape(ctx, shape, width, height);
|
|
15416
|
+
for (const shape of derivedShapes) {
|
|
15417
|
+
if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
15257
15418
|
}
|
|
15258
|
-
for (const shape of
|
|
15259
|
-
if (shape.type === "text") drawShape(ctx, shape, width, height);
|
|
15419
|
+
for (const shape of derivedShapes) {
|
|
15420
|
+
if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
15260
15421
|
}
|
|
15261
|
-
}, [width, height, backgroundColor,
|
|
15422
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
15262
15423
|
useEffect(() => {
|
|
15263
15424
|
draw();
|
|
15264
15425
|
}, [draw]);
|
|
@@ -16808,7 +16969,363 @@ var init_ComponentPatterns = __esm({
|
|
|
16808
16969
|
AlertPattern.displayName = "AlertPattern";
|
|
16809
16970
|
}
|
|
16810
16971
|
});
|
|
16811
|
-
|
|
16972
|
+
function layoutCircle(nodes, width, height) {
|
|
16973
|
+
const cx = width / 2;
|
|
16974
|
+
const cy = height / 2;
|
|
16975
|
+
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
|
16976
|
+
const positions = /* @__PURE__ */ new Map();
|
|
16977
|
+
const n = nodes.length;
|
|
16978
|
+
nodes.forEach((node, i) => {
|
|
16979
|
+
const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
|
|
16980
|
+
positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
|
|
16981
|
+
});
|
|
16982
|
+
return positions;
|
|
16983
|
+
}
|
|
16984
|
+
function layoutTree2(nodes, edges, root, width, height) {
|
|
16985
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
16986
|
+
const idSet = new Set(nodeIds);
|
|
16987
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
16988
|
+
const hasIncoming = /* @__PURE__ */ new Set();
|
|
16989
|
+
for (const e of edges) {
|
|
16990
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
16991
|
+
const list = childrenOf.get(e.from) ?? [];
|
|
16992
|
+
list.push(e.to);
|
|
16993
|
+
childrenOf.set(e.from, list);
|
|
16994
|
+
hasIncoming.add(e.to);
|
|
16995
|
+
}
|
|
16996
|
+
const depth = /* @__PURE__ */ new Map();
|
|
16997
|
+
const treeChildren = /* @__PURE__ */ new Map();
|
|
16998
|
+
const visited = /* @__PURE__ */ new Set();
|
|
16999
|
+
const bfsFrom = (start) => {
|
|
17000
|
+
if (visited.has(start)) return;
|
|
17001
|
+
visited.add(start);
|
|
17002
|
+
depth.set(start, 0);
|
|
17003
|
+
const queue = [start];
|
|
17004
|
+
while (queue.length > 0) {
|
|
17005
|
+
const u = queue.shift();
|
|
17006
|
+
for (const v of childrenOf.get(u) ?? []) {
|
|
17007
|
+
if (visited.has(v)) continue;
|
|
17008
|
+
visited.add(v);
|
|
17009
|
+
depth.set(v, (depth.get(u) ?? 0) + 1);
|
|
17010
|
+
const list = treeChildren.get(u) ?? [];
|
|
17011
|
+
list.push(v);
|
|
17012
|
+
treeChildren.set(u, list);
|
|
17013
|
+
queue.push(v);
|
|
17014
|
+
}
|
|
17015
|
+
}
|
|
17016
|
+
};
|
|
17017
|
+
const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
|
|
17018
|
+
const rootsOrder = [];
|
|
17019
|
+
if (primaryRoot !== void 0) {
|
|
17020
|
+
bfsFrom(primaryRoot);
|
|
17021
|
+
rootsOrder.push(primaryRoot);
|
|
17022
|
+
}
|
|
17023
|
+
for (const id of nodeIds) {
|
|
17024
|
+
if (!visited.has(id)) {
|
|
17025
|
+
bfsFrom(id);
|
|
17026
|
+
rootsOrder.push(id);
|
|
17027
|
+
}
|
|
17028
|
+
}
|
|
17029
|
+
let leafCounter = 0;
|
|
17030
|
+
const xSlot = /* @__PURE__ */ new Map();
|
|
17031
|
+
const assignXSlot = (u) => {
|
|
17032
|
+
const children = treeChildren.get(u) ?? [];
|
|
17033
|
+
if (children.length === 0) {
|
|
17034
|
+
const slot = leafCounter++;
|
|
17035
|
+
xSlot.set(u, slot);
|
|
17036
|
+
return slot;
|
|
17037
|
+
}
|
|
17038
|
+
const childSlots = children.map(assignXSlot);
|
|
17039
|
+
const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
|
|
17040
|
+
xSlot.set(u, avg);
|
|
17041
|
+
return avg;
|
|
17042
|
+
};
|
|
17043
|
+
for (const r2 of rootsOrder) assignXSlot(r2);
|
|
17044
|
+
let maxDepth = 0;
|
|
17045
|
+
for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
|
|
17046
|
+
const colWidth = width / Math.max(1, leafCounter);
|
|
17047
|
+
const rowHeight = height / (maxDepth + 1);
|
|
17048
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17049
|
+
for (const id of nodeIds) {
|
|
17050
|
+
const slot = xSlot.get(id) ?? 0;
|
|
17051
|
+
const d = depth.get(id) ?? 0;
|
|
17052
|
+
positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
|
|
17053
|
+
}
|
|
17054
|
+
return positions;
|
|
17055
|
+
}
|
|
17056
|
+
function layoutLayered(nodes, edges, width, height) {
|
|
17057
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
17058
|
+
const idSet = new Set(nodeIds);
|
|
17059
|
+
const adj = /* @__PURE__ */ new Map();
|
|
17060
|
+
const remainingIndegree = /* @__PURE__ */ new Map();
|
|
17061
|
+
for (const id of nodeIds) remainingIndegree.set(id, 0);
|
|
17062
|
+
for (const e of edges) {
|
|
17063
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
17064
|
+
const list = adj.get(e.from) ?? [];
|
|
17065
|
+
list.push(e.to);
|
|
17066
|
+
adj.set(e.from, list);
|
|
17067
|
+
remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
|
|
17068
|
+
}
|
|
17069
|
+
const layer = /* @__PURE__ */ new Map();
|
|
17070
|
+
const dequeued = /* @__PURE__ */ new Set();
|
|
17071
|
+
const queue = [];
|
|
17072
|
+
for (const id of nodeIds) {
|
|
17073
|
+
if ((remainingIndegree.get(id) ?? 0) === 0) {
|
|
17074
|
+
layer.set(id, 0);
|
|
17075
|
+
queue.push(id);
|
|
17076
|
+
}
|
|
17077
|
+
}
|
|
17078
|
+
while (queue.length > 0) {
|
|
17079
|
+
const u = queue.shift();
|
|
17080
|
+
dequeued.add(u);
|
|
17081
|
+
for (const v of adj.get(u) ?? []) {
|
|
17082
|
+
const candidate = (layer.get(u) ?? 0) + 1;
|
|
17083
|
+
layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
|
|
17084
|
+
remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
|
|
17085
|
+
if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
|
|
17086
|
+
queue.push(v);
|
|
17087
|
+
}
|
|
17088
|
+
}
|
|
17089
|
+
}
|
|
17090
|
+
let baseMaxLayer = 0;
|
|
17091
|
+
for (const id of nodeIds) {
|
|
17092
|
+
if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
|
|
17093
|
+
}
|
|
17094
|
+
const cycleLayer = baseMaxLayer + 1;
|
|
17095
|
+
let maxLayer = baseMaxLayer;
|
|
17096
|
+
for (const id of nodeIds) {
|
|
17097
|
+
if (!dequeued.has(id)) {
|
|
17098
|
+
layer.set(id, cycleLayer);
|
|
17099
|
+
maxLayer = cycleLayer;
|
|
17100
|
+
}
|
|
17101
|
+
}
|
|
17102
|
+
const colWidth = width / Math.max(1, maxLayer + 1);
|
|
17103
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
17104
|
+
for (const id of nodeIds) {
|
|
17105
|
+
const l = layer.get(id) ?? 0;
|
|
17106
|
+
const list = byLayer.get(l) ?? [];
|
|
17107
|
+
list.push(id);
|
|
17108
|
+
byLayer.set(l, list);
|
|
17109
|
+
}
|
|
17110
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17111
|
+
for (const [l, ids] of byLayer) {
|
|
17112
|
+
const rowHeight = height / ids.length;
|
|
17113
|
+
ids.forEach((id, i) => {
|
|
17114
|
+
positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
|
|
17115
|
+
});
|
|
17116
|
+
}
|
|
17117
|
+
return positions;
|
|
17118
|
+
}
|
|
17119
|
+
function computePositions(nodes, edges, layout, root, width, height) {
|
|
17120
|
+
switch (layout) {
|
|
17121
|
+
case "circle":
|
|
17122
|
+
return layoutCircle(nodes, width, height);
|
|
17123
|
+
case "tree":
|
|
17124
|
+
return layoutTree2(nodes, edges, root, width, height);
|
|
17125
|
+
case "layered":
|
|
17126
|
+
return layoutLayered(nodes, edges, width, height);
|
|
17127
|
+
case "manual":
|
|
17128
|
+
default: {
|
|
17129
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17130
|
+
for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
|
|
17131
|
+
return positions;
|
|
17132
|
+
}
|
|
17133
|
+
}
|
|
17134
|
+
}
|
|
17135
|
+
var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS, AlgoGraphCanvas;
|
|
17136
|
+
var init_AlgoGraphCanvas = __esm({
|
|
17137
|
+
"components/learning/molecules/AlgoGraphCanvas.tsx"() {
|
|
17138
|
+
"use client";
|
|
17139
|
+
init_atoms();
|
|
17140
|
+
init_Stack();
|
|
17141
|
+
init_LearningCanvas();
|
|
17142
|
+
NODE_STATE_COLOR = {
|
|
17143
|
+
unvisited: "#cbd5e1",
|
|
17144
|
+
frontier: "#f59e0b",
|
|
17145
|
+
current: "#ef4444",
|
|
17146
|
+
visited: "#22c55e",
|
|
17147
|
+
goal: "#8b5cf6",
|
|
17148
|
+
path: "#0ea5e9"
|
|
17149
|
+
};
|
|
17150
|
+
EDGE_STATE_COLOR = {
|
|
17151
|
+
default: "#9ca3af",
|
|
17152
|
+
tree: "#16a34a",
|
|
17153
|
+
relaxed: "#f59e0b",
|
|
17154
|
+
candidate: "#38bdf8",
|
|
17155
|
+
path: "#dc2626"
|
|
17156
|
+
};
|
|
17157
|
+
DEFAULT_NODE_RADIUS = 18;
|
|
17158
|
+
AlgoGraphCanvas = ({
|
|
17159
|
+
className,
|
|
17160
|
+
width = 600,
|
|
17161
|
+
height = 400,
|
|
17162
|
+
title,
|
|
17163
|
+
backgroundColor,
|
|
17164
|
+
nodes = [],
|
|
17165
|
+
edges = [],
|
|
17166
|
+
layout = "manual",
|
|
17167
|
+
root,
|
|
17168
|
+
shapes = [],
|
|
17169
|
+
interactive = false,
|
|
17170
|
+
animate = false,
|
|
17171
|
+
onShapeClick,
|
|
17172
|
+
onNodeClick,
|
|
17173
|
+
isLoading,
|
|
17174
|
+
error
|
|
17175
|
+
}) => {
|
|
17176
|
+
const nodeById = useMemo(() => {
|
|
17177
|
+
const m = /* @__PURE__ */ new Map();
|
|
17178
|
+
for (const n of nodes) m.set(n.id, n);
|
|
17179
|
+
return m;
|
|
17180
|
+
}, [nodes]);
|
|
17181
|
+
const nodeIndexById = useMemo(() => {
|
|
17182
|
+
const m = /* @__PURE__ */ new Map();
|
|
17183
|
+
nodes.forEach((n, i) => m.set(n.id, i));
|
|
17184
|
+
return m;
|
|
17185
|
+
}, [nodes]);
|
|
17186
|
+
const derivedShapes = useMemo(() => {
|
|
17187
|
+
const out = [];
|
|
17188
|
+
const positions = computePositions(nodes, edges, layout, root, width, height);
|
|
17189
|
+
const edgeGeoms = [];
|
|
17190
|
+
for (const e of edges) {
|
|
17191
|
+
const a = nodeById.get(e.from);
|
|
17192
|
+
const b = nodeById.get(e.to);
|
|
17193
|
+
const posA = positions.get(e.from);
|
|
17194
|
+
const posB = positions.get(e.to);
|
|
17195
|
+
if (!a || !b || !posA || !posB) continue;
|
|
17196
|
+
const rA = a.radius ?? DEFAULT_NODE_RADIUS;
|
|
17197
|
+
const rB = b.radius ?? DEFAULT_NODE_RADIUS;
|
|
17198
|
+
const dx = posB.x - posA.x;
|
|
17199
|
+
const dy = posB.y - posA.y;
|
|
17200
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
17201
|
+
const ux = dx / dist;
|
|
17202
|
+
const uy = dy / dist;
|
|
17203
|
+
edgeGeoms.push({
|
|
17204
|
+
directed: e.directed ?? false,
|
|
17205
|
+
x1: posA.x + ux * rA,
|
|
17206
|
+
y1: posA.y + uy * rA,
|
|
17207
|
+
x2: posB.x - ux * rB,
|
|
17208
|
+
y2: posB.y - uy * rB,
|
|
17209
|
+
color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
|
|
17210
|
+
label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
|
|
17211
|
+
});
|
|
17212
|
+
}
|
|
17213
|
+
for (const g of edgeGeoms) {
|
|
17214
|
+
out.push({
|
|
17215
|
+
type: g.directed ? "arrow" : "line",
|
|
17216
|
+
x1: g.x1,
|
|
17217
|
+
y1: g.y1,
|
|
17218
|
+
x2: g.x2,
|
|
17219
|
+
y2: g.y2,
|
|
17220
|
+
color: g.color,
|
|
17221
|
+
lineWidth: 2
|
|
17222
|
+
});
|
|
17223
|
+
}
|
|
17224
|
+
for (const g of edgeGeoms) {
|
|
17225
|
+
if (g.label === void 0) continue;
|
|
17226
|
+
const midX = (g.x1 + g.x2) / 2;
|
|
17227
|
+
const midY = (g.y1 + g.y2) / 2;
|
|
17228
|
+
const dx = g.x2 - g.x1;
|
|
17229
|
+
const dy = g.y2 - g.y1;
|
|
17230
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
17231
|
+
const perpX = -(dy / dist);
|
|
17232
|
+
const perpY = dx / dist;
|
|
17233
|
+
out.push({
|
|
17234
|
+
type: "text",
|
|
17235
|
+
x: midX + perpX * 10,
|
|
17236
|
+
y: midY + perpY * 10,
|
|
17237
|
+
text: g.label,
|
|
17238
|
+
fontSize: 11,
|
|
17239
|
+
align: "center",
|
|
17240
|
+
color: "#374151"
|
|
17241
|
+
});
|
|
17242
|
+
}
|
|
17243
|
+
const nodeGeoms = [];
|
|
17244
|
+
for (const n of nodes) {
|
|
17245
|
+
const pos = positions.get(n.id);
|
|
17246
|
+
if (!pos) continue;
|
|
17247
|
+
nodeGeoms.push({
|
|
17248
|
+
id: n.id,
|
|
17249
|
+
x: pos.x,
|
|
17250
|
+
y: pos.y,
|
|
17251
|
+
radius: n.radius ?? DEFAULT_NODE_RADIUS,
|
|
17252
|
+
color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
|
|
17253
|
+
label: n.label,
|
|
17254
|
+
badge: n.badge
|
|
17255
|
+
});
|
|
17256
|
+
}
|
|
17257
|
+
for (const g of nodeGeoms) {
|
|
17258
|
+
out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
|
|
17259
|
+
}
|
|
17260
|
+
const badgeGeoms = [];
|
|
17261
|
+
for (const g of nodeGeoms) {
|
|
17262
|
+
if (!g.badge) continue;
|
|
17263
|
+
const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
|
|
17264
|
+
badgeGeoms.push({
|
|
17265
|
+
cx: g.x + g.radius * 0.75,
|
|
17266
|
+
cy: g.y - g.radius * 0.75,
|
|
17267
|
+
w,
|
|
17268
|
+
h: 14,
|
|
17269
|
+
// Borderless pill: same color drives both stroke and fill.
|
|
17270
|
+
color: g.badge.color ?? "#1e293b",
|
|
17271
|
+
text: g.badge.text
|
|
17272
|
+
});
|
|
17273
|
+
}
|
|
17274
|
+
for (const b of badgeGeoms) {
|
|
17275
|
+
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 });
|
|
17276
|
+
}
|
|
17277
|
+
for (const b of badgeGeoms) {
|
|
17278
|
+
out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
|
|
17279
|
+
}
|
|
17280
|
+
for (const g of nodeGeoms) {
|
|
17281
|
+
if (g.label === void 0) continue;
|
|
17282
|
+
out.push({
|
|
17283
|
+
type: "text",
|
|
17284
|
+
x: g.x,
|
|
17285
|
+
y: g.y + g.radius + 14,
|
|
17286
|
+
text: g.label,
|
|
17287
|
+
fontSize: 12,
|
|
17288
|
+
align: "center",
|
|
17289
|
+
color: "#111827"
|
|
17290
|
+
});
|
|
17291
|
+
}
|
|
17292
|
+
out.push(...shapes);
|
|
17293
|
+
return out;
|
|
17294
|
+
}, [nodes, edges, layout, root, width, height, nodeById, shapes]);
|
|
17295
|
+
const handleShapeClick = useCallback(
|
|
17296
|
+
(payload) => {
|
|
17297
|
+
if (payload.type === "circle" && payload.id) {
|
|
17298
|
+
const node = nodeById.get(payload.id);
|
|
17299
|
+
const idx = nodeIndexById.get(payload.id);
|
|
17300
|
+
if (node && idx !== void 0) {
|
|
17301
|
+
onNodeClick?.({ id: node.id, label: node.label, index: idx });
|
|
17302
|
+
}
|
|
17303
|
+
}
|
|
17304
|
+
onShapeClick?.(payload);
|
|
17305
|
+
},
|
|
17306
|
+
[nodeById, nodeIndexById, onNodeClick, onShapeClick]
|
|
17307
|
+
);
|
|
17308
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
17309
|
+
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
17310
|
+
/* @__PURE__ */ jsx(
|
|
17311
|
+
LearningCanvas,
|
|
17312
|
+
{
|
|
17313
|
+
width,
|
|
17314
|
+
height,
|
|
17315
|
+
backgroundColor,
|
|
17316
|
+
shapes: derivedShapes,
|
|
17317
|
+
interactive,
|
|
17318
|
+
animate,
|
|
17319
|
+
onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
|
|
17320
|
+
isLoading,
|
|
17321
|
+
error
|
|
17322
|
+
}
|
|
17323
|
+
)
|
|
17324
|
+
] }) });
|
|
17325
|
+
};
|
|
17326
|
+
}
|
|
17327
|
+
});
|
|
17328
|
+
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;
|
|
16812
17329
|
var init_AlgorithmCanvas = __esm({
|
|
16813
17330
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
16814
17331
|
"use client";
|
|
@@ -16820,6 +17337,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16820
17337
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
16821
17338
|
POINTER_BAND = 34;
|
|
16822
17339
|
TOP_PAD = 26;
|
|
17340
|
+
PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
|
|
17341
|
+
RANGE_COLOR_DEFAULT = "#3b82f6";
|
|
17342
|
+
RANGE_FILL_OPACITY = 0.15;
|
|
17343
|
+
BRACKET_TOP_OFFSET = 16;
|
|
17344
|
+
BRACKET_ROW_H = 14;
|
|
17345
|
+
BRACKET_TICK_H = 6;
|
|
17346
|
+
BRACKET_LABEL_OFFSET = 6;
|
|
17347
|
+
SLOT_EMPTY_FILL = "#f1f5f9";
|
|
17348
|
+
SLOT_EMPTY_STROKE = "#cbd5e1";
|
|
17349
|
+
SLOT_FILLED_STROKE = "#9ca3af";
|
|
17350
|
+
SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
|
|
17351
|
+
SLOT_VALUE_TEXT_COLOR = "#ffffff";
|
|
17352
|
+
FRAME_ACTIVE_COLOR = "#3b82f6";
|
|
17353
|
+
FRAME_RETURNING_COLOR = "#f59e0b";
|
|
17354
|
+
FRAME_DONE_COLOR = "#94a3b8";
|
|
17355
|
+
FRAME_LABEL_COLOR = "#ffffff";
|
|
17356
|
+
FRAME_DETAIL_COLOR = "#e2e8f0";
|
|
17357
|
+
FRAME_TWO_LINE_MIN_H = 22;
|
|
17358
|
+
BUCKET_INDEX_FILL = "#e2e8f0";
|
|
17359
|
+
BUCKET_INDEX_STROKE = "#9ca3af";
|
|
17360
|
+
BUCKET_INDEX_TEXT = "#374151";
|
|
17361
|
+
BUCKET_ENTRY_TEXT = "#ffffff";
|
|
17362
|
+
BUCKET_ENTRY_DEFAULT = "#3b82f6";
|
|
17363
|
+
BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
|
|
17364
|
+
BUCKET_ENTRY_PROBING = "#38bdf8";
|
|
17365
|
+
BUCKET_ENTRY_MIN_W = 24;
|
|
17366
|
+
BUCKET_ENTRY_MAX_W = 64;
|
|
17367
|
+
AXIS_LABEL_COLOR = "#6b7280";
|
|
17368
|
+
AXIS_LABEL_FONT_SIZE = 10;
|
|
17369
|
+
CORNER_TEXT_COLOR = "#111827";
|
|
17370
|
+
CORNER_FONT_SIZE = 7;
|
|
17371
|
+
CORNER_MIN_CELL = 28;
|
|
17372
|
+
CORNER_INSET_X = 3;
|
|
17373
|
+
CORNER_INSET_Y = 6;
|
|
17374
|
+
AUX_PRIMARY_RATIO = 0.6;
|
|
17375
|
+
AUX_LABEL_BAND = 18;
|
|
17376
|
+
AUX_BASELINE_PAD = 8;
|
|
16823
17377
|
AlgorithmCanvas = ({
|
|
16824
17378
|
className,
|
|
16825
17379
|
width = 600,
|
|
@@ -16829,6 +17383,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16829
17383
|
bars = [],
|
|
16830
17384
|
cells = [],
|
|
16831
17385
|
pointers = [],
|
|
17386
|
+
ranges = [],
|
|
17387
|
+
slots = [],
|
|
17388
|
+
slotOrientation = "horizontal",
|
|
17389
|
+
frames = [],
|
|
17390
|
+
buckets = [],
|
|
17391
|
+
auxBars = [],
|
|
17392
|
+
rowLabels = [],
|
|
17393
|
+
colLabels = [],
|
|
16832
17394
|
shapes = [],
|
|
16833
17395
|
interactive = false,
|
|
16834
17396
|
animate = false,
|
|
@@ -16838,12 +17400,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16838
17400
|
}) => {
|
|
16839
17401
|
const derivedShapes = useMemo(() => {
|
|
16840
17402
|
const out = [];
|
|
17403
|
+
const presence = {
|
|
17404
|
+
bars: bars.length > 0,
|
|
17405
|
+
slots: slots.length > 0,
|
|
17406
|
+
cells: cells.length > 0,
|
|
17407
|
+
buckets: buckets.length > 0,
|
|
17408
|
+
frames: frames.length > 0
|
|
17409
|
+
};
|
|
17410
|
+
const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
|
|
17411
|
+
const panelHeight = height / Math.max(1, panelCount);
|
|
17412
|
+
const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
|
|
17413
|
+
let compactIndex = 0;
|
|
17414
|
+
PANEL_FAMILY_ORDER.forEach((f3) => {
|
|
17415
|
+
if (presence[f3]) {
|
|
17416
|
+
panelY[f3] = compactIndex * panelHeight;
|
|
17417
|
+
compactIndex += 1;
|
|
17418
|
+
}
|
|
17419
|
+
});
|
|
16841
17420
|
if (bars.length > 0) {
|
|
17421
|
+
const panelYBars = panelY.bars;
|
|
16842
17422
|
const slot = width / bars.length;
|
|
16843
17423
|
const barW = slot * 0.8;
|
|
16844
17424
|
const gap = slot * 0.1;
|
|
16845
|
-
const
|
|
16846
|
-
const
|
|
17425
|
+
const bracketRanges = ranges.filter((r2) => r2.kind === "bracket");
|
|
17426
|
+
const bracketCount = bracketRanges.length;
|
|
17427
|
+
const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
|
|
17428
|
+
const hasAux = auxBars.length > 0;
|
|
17429
|
+
const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
|
|
17430
|
+
const baseline = panelYBars + primaryH - POINTER_BAND;
|
|
17431
|
+
const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
|
|
16847
17432
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
16848
17433
|
bars.forEach((bar, i) => {
|
|
16849
17434
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -16873,6 +17458,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16873
17458
|
});
|
|
16874
17459
|
}
|
|
16875
17460
|
});
|
|
17461
|
+
ranges.forEach((r2) => {
|
|
17462
|
+
const kind = r2.kind ?? "fill";
|
|
17463
|
+
if (kind !== "fill") return;
|
|
17464
|
+
const color = r2.color ?? RANGE_COLOR_DEFAULT;
|
|
17465
|
+
out.push({
|
|
17466
|
+
type: "rect",
|
|
17467
|
+
x: r2.from * slot,
|
|
17468
|
+
y: panelYBars,
|
|
17469
|
+
width: (r2.to - r2.from + 1) * slot,
|
|
17470
|
+
height: primaryH,
|
|
17471
|
+
color,
|
|
17472
|
+
fill: color,
|
|
17473
|
+
opacity: RANGE_FILL_OPACITY
|
|
17474
|
+
});
|
|
17475
|
+
if (r2.label) {
|
|
17476
|
+
out.push({
|
|
17477
|
+
type: "text",
|
|
17478
|
+
x: r2.from * slot + 4,
|
|
17479
|
+
// Sits below the bracket block (if any) so fill and bracket labels never collide.
|
|
17480
|
+
y: panelYBars + 10 + bracketHeadroom,
|
|
17481
|
+
text: r2.label,
|
|
17482
|
+
color,
|
|
17483
|
+
fontSize: 10,
|
|
17484
|
+
align: "left"
|
|
17485
|
+
});
|
|
17486
|
+
}
|
|
17487
|
+
});
|
|
17488
|
+
bracketRanges.forEach((r2, i) => {
|
|
17489
|
+
const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
|
|
17490
|
+
const x1 = r2.from * slot + slot * 0.1;
|
|
17491
|
+
const x2 = (r2.to + 1) * slot - slot * 0.1;
|
|
17492
|
+
const color = r2.color ?? RANGE_COLOR_DEFAULT;
|
|
17493
|
+
out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
|
|
17494
|
+
out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
17495
|
+
out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
17496
|
+
if (r2.label) {
|
|
17497
|
+
out.push({
|
|
17498
|
+
type: "text",
|
|
17499
|
+
x: (x1 + x2) / 2,
|
|
17500
|
+
y: bracketY - BRACKET_LABEL_OFFSET,
|
|
17501
|
+
text: r2.label,
|
|
17502
|
+
color,
|
|
17503
|
+
fontSize: 10,
|
|
17504
|
+
align: "center"
|
|
17505
|
+
});
|
|
17506
|
+
}
|
|
17507
|
+
});
|
|
17508
|
+
if (hasAux) {
|
|
17509
|
+
const auxH = panelHeight - primaryH;
|
|
17510
|
+
const slot2 = width / auxBars.length;
|
|
17511
|
+
const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
|
|
17512
|
+
const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
|
|
17513
|
+
const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
17514
|
+
auxBars.forEach((bar, i) => {
|
|
17515
|
+
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
17516
|
+
const bh = Math.max(0, v / maxAuxV * auxUsableH);
|
|
17517
|
+
const x = i * slot2 + slot2 * 0.1;
|
|
17518
|
+
const w = slot2 * 0.8;
|
|
17519
|
+
const color = bar.color ?? DEFAULT_BAR_COLOR;
|
|
17520
|
+
out.push({
|
|
17521
|
+
type: "rect",
|
|
17522
|
+
id: `auxbar-${i}`,
|
|
17523
|
+
x,
|
|
17524
|
+
y: auxBaseline - bh,
|
|
17525
|
+
width: w,
|
|
17526
|
+
height: bh,
|
|
17527
|
+
color,
|
|
17528
|
+
fill: color
|
|
17529
|
+
});
|
|
17530
|
+
const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
|
|
17531
|
+
if (label) {
|
|
17532
|
+
out.push({
|
|
17533
|
+
type: "text",
|
|
17534
|
+
x: x + w / 2,
|
|
17535
|
+
y: auxBaseline - bh - 8,
|
|
17536
|
+
text: label,
|
|
17537
|
+
color: "#374151",
|
|
17538
|
+
fontSize: 11,
|
|
17539
|
+
align: "center"
|
|
17540
|
+
});
|
|
17541
|
+
}
|
|
17542
|
+
});
|
|
17543
|
+
}
|
|
16876
17544
|
pointers.forEach((p) => {
|
|
16877
17545
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
16878
17546
|
const cx = p.index * slot + slot / 2;
|
|
@@ -16880,7 +17548,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16880
17548
|
out.push({
|
|
16881
17549
|
type: "arrow",
|
|
16882
17550
|
x1: cx,
|
|
16883
|
-
y1:
|
|
17551
|
+
y1: panelYBars + primaryH - 18,
|
|
16884
17552
|
x2: cx,
|
|
16885
17553
|
y2: baseline + 4,
|
|
16886
17554
|
color,
|
|
@@ -16890,7 +17558,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16890
17558
|
out.push({
|
|
16891
17559
|
type: "text",
|
|
16892
17560
|
x: cx,
|
|
16893
|
-
y:
|
|
17561
|
+
y: panelYBars + primaryH - 8,
|
|
16894
17562
|
text: p.label,
|
|
16895
17563
|
color,
|
|
16896
17564
|
fontSize: 11,
|
|
@@ -16899,14 +17567,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16899
17567
|
}
|
|
16900
17568
|
});
|
|
16901
17569
|
}
|
|
17570
|
+
if (slots.length > 0) {
|
|
17571
|
+
const panelYSlots = panelY.slots;
|
|
17572
|
+
const n = slots.length;
|
|
17573
|
+
const vertical = slotOrientation === "vertical";
|
|
17574
|
+
const vBoxH = panelHeight / n;
|
|
17575
|
+
const vBoxW = Math.min(width * 0.5, 120);
|
|
17576
|
+
const vBoxX = (width - vBoxW) / 2;
|
|
17577
|
+
const hCellW = width / n;
|
|
17578
|
+
const hBoxW = hCellW * 0.82;
|
|
17579
|
+
const hBoxH = Math.min(panelHeight * 0.6, 48);
|
|
17580
|
+
const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
|
|
17581
|
+
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 };
|
|
17582
|
+
slots.forEach((s, i) => {
|
|
17583
|
+
const box = slotBox(i);
|
|
17584
|
+
const state = s.state ?? "filled";
|
|
17585
|
+
const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
|
|
17586
|
+
const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
|
|
17587
|
+
out.push({
|
|
17588
|
+
type: "rect",
|
|
17589
|
+
id: `slot-${i}`,
|
|
17590
|
+
x: box.x,
|
|
17591
|
+
y: box.y,
|
|
17592
|
+
width: box.width,
|
|
17593
|
+
height: box.height,
|
|
17594
|
+
color: stroke,
|
|
17595
|
+
fill
|
|
17596
|
+
});
|
|
17597
|
+
if (s.value != null && state !== "empty") {
|
|
17598
|
+
out.push({
|
|
17599
|
+
type: "text",
|
|
17600
|
+
x: box.x + box.width / 2,
|
|
17601
|
+
y: box.y + box.height / 2,
|
|
17602
|
+
text: String(s.value),
|
|
17603
|
+
color: SLOT_VALUE_TEXT_COLOR,
|
|
17604
|
+
fontSize: 12,
|
|
17605
|
+
align: "center"
|
|
17606
|
+
});
|
|
17607
|
+
}
|
|
17608
|
+
});
|
|
17609
|
+
if (bars.length === 0) {
|
|
17610
|
+
pointers.forEach((p) => {
|
|
17611
|
+
if (p.index < 0 || p.index >= slots.length) return;
|
|
17612
|
+
const box = slotBox(p.index);
|
|
17613
|
+
const color = p.color ?? DEFAULT_POINTER_COLOR;
|
|
17614
|
+
if (vertical) {
|
|
17615
|
+
const cy = box.y + box.height / 2;
|
|
17616
|
+
out.push({
|
|
17617
|
+
type: "arrow",
|
|
17618
|
+
x1: box.x + box.width + 34,
|
|
17619
|
+
y1: cy,
|
|
17620
|
+
x2: box.x + box.width + 4,
|
|
17621
|
+
y2: cy,
|
|
17622
|
+
color,
|
|
17623
|
+
lineWidth: 2
|
|
17624
|
+
});
|
|
17625
|
+
if (p.label) {
|
|
17626
|
+
out.push({
|
|
17627
|
+
type: "text",
|
|
17628
|
+
x: box.x + box.width + 38,
|
|
17629
|
+
y: cy,
|
|
17630
|
+
text: p.label,
|
|
17631
|
+
color,
|
|
17632
|
+
fontSize: 11,
|
|
17633
|
+
align: "left"
|
|
17634
|
+
});
|
|
17635
|
+
}
|
|
17636
|
+
} else {
|
|
17637
|
+
const cx = box.x + box.width / 2;
|
|
17638
|
+
out.push({
|
|
17639
|
+
type: "arrow",
|
|
17640
|
+
x1: cx,
|
|
17641
|
+
y1: panelYSlots + panelHeight - 18,
|
|
17642
|
+
x2: cx,
|
|
17643
|
+
y2: box.y + box.height + 4,
|
|
17644
|
+
color,
|
|
17645
|
+
lineWidth: 2
|
|
17646
|
+
});
|
|
17647
|
+
if (p.label) {
|
|
17648
|
+
out.push({
|
|
17649
|
+
type: "text",
|
|
17650
|
+
x: cx,
|
|
17651
|
+
y: panelYSlots + panelHeight - 8,
|
|
17652
|
+
text: p.label,
|
|
17653
|
+
color,
|
|
17654
|
+
fontSize: 11,
|
|
17655
|
+
align: "center"
|
|
17656
|
+
});
|
|
17657
|
+
}
|
|
17658
|
+
}
|
|
17659
|
+
});
|
|
17660
|
+
}
|
|
17661
|
+
}
|
|
16902
17662
|
if (cells.length > 0) {
|
|
17663
|
+
const panelYCells = panelY.cells;
|
|
16903
17664
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
16904
17665
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
16905
|
-
const
|
|
16906
|
-
const
|
|
17666
|
+
const colLabelH = colLabels.length > 0 ? 16 : 0;
|
|
17667
|
+
const rowLabelW = rowLabels.length > 0 ? 20 : 0;
|
|
17668
|
+
const gridX0 = rowLabelW;
|
|
17669
|
+
const gridY0 = panelYCells + colLabelH;
|
|
17670
|
+
const cw = (width - rowLabelW) / maxCol;
|
|
17671
|
+
const ch = (panelHeight - colLabelH) / maxRow;
|
|
16907
17672
|
cells.forEach((c, i) => {
|
|
16908
|
-
const x = c.col * cw;
|
|
16909
|
-
const y = c.row * ch;
|
|
17673
|
+
const x = gridX0 + c.col * cw;
|
|
17674
|
+
const y = gridY0 + c.row * ch;
|
|
16910
17675
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
16911
17676
|
out.push({
|
|
16912
17677
|
type: "rect",
|
|
@@ -16930,11 +17695,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16930
17695
|
align: "center"
|
|
16931
17696
|
});
|
|
16932
17697
|
}
|
|
17698
|
+
if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
|
|
17699
|
+
const { tl, tr, bl, br } = c.corner;
|
|
17700
|
+
if (tl) {
|
|
17701
|
+
out.push({
|
|
17702
|
+
type: "text",
|
|
17703
|
+
x: x + CORNER_INSET_X,
|
|
17704
|
+
y: y + CORNER_INSET_Y,
|
|
17705
|
+
text: tl,
|
|
17706
|
+
color: CORNER_TEXT_COLOR,
|
|
17707
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17708
|
+
align: "left"
|
|
17709
|
+
});
|
|
17710
|
+
}
|
|
17711
|
+
if (tr) {
|
|
17712
|
+
out.push({
|
|
17713
|
+
type: "text",
|
|
17714
|
+
x: x + cw - CORNER_INSET_X,
|
|
17715
|
+
y: y + CORNER_INSET_Y,
|
|
17716
|
+
text: tr,
|
|
17717
|
+
color: CORNER_TEXT_COLOR,
|
|
17718
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17719
|
+
align: "right"
|
|
17720
|
+
});
|
|
17721
|
+
}
|
|
17722
|
+
if (bl) {
|
|
17723
|
+
out.push({
|
|
17724
|
+
type: "text",
|
|
17725
|
+
x: x + CORNER_INSET_X,
|
|
17726
|
+
y: y + ch - CORNER_INSET_Y,
|
|
17727
|
+
text: bl,
|
|
17728
|
+
color: CORNER_TEXT_COLOR,
|
|
17729
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17730
|
+
align: "left"
|
|
17731
|
+
});
|
|
17732
|
+
}
|
|
17733
|
+
if (br) {
|
|
17734
|
+
out.push({
|
|
17735
|
+
type: "text",
|
|
17736
|
+
x: x + cw - CORNER_INSET_X,
|
|
17737
|
+
y: y + ch - CORNER_INSET_Y,
|
|
17738
|
+
text: br,
|
|
17739
|
+
color: CORNER_TEXT_COLOR,
|
|
17740
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17741
|
+
align: "right"
|
|
17742
|
+
});
|
|
17743
|
+
}
|
|
17744
|
+
}
|
|
17745
|
+
});
|
|
17746
|
+
colLabels.forEach((l) => {
|
|
17747
|
+
out.push({
|
|
17748
|
+
type: "text",
|
|
17749
|
+
x: gridX0 + l.index * cw + cw / 2,
|
|
17750
|
+
y: panelYCells + colLabelH / 2,
|
|
17751
|
+
text: l.text,
|
|
17752
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
17753
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
17754
|
+
align: "center"
|
|
17755
|
+
});
|
|
17756
|
+
});
|
|
17757
|
+
rowLabels.forEach((l) => {
|
|
17758
|
+
out.push({
|
|
17759
|
+
type: "text",
|
|
17760
|
+
x: rowLabelW - 6,
|
|
17761
|
+
y: gridY0 + l.index * ch + ch / 2,
|
|
17762
|
+
text: l.text,
|
|
17763
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
17764
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
17765
|
+
align: "right"
|
|
17766
|
+
});
|
|
17767
|
+
});
|
|
17768
|
+
}
|
|
17769
|
+
if (buckets.length > 0) {
|
|
17770
|
+
const panelYBuckets = panelY.buckets;
|
|
17771
|
+
const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
|
|
17772
|
+
const rowH = panelHeight / bucketCount;
|
|
17773
|
+
const indexColW = Math.min(width * 0.12, 40);
|
|
17774
|
+
const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
|
|
17775
|
+
const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
|
|
17776
|
+
const maxVisible = Math.floor((width - indexColW - 4) / entryW);
|
|
17777
|
+
buckets.forEach((b) => {
|
|
17778
|
+
const rowY = panelYBuckets + b.index * rowH;
|
|
17779
|
+
out.push({
|
|
17780
|
+
type: "rect",
|
|
17781
|
+
id: `bucket-index-${b.index}`,
|
|
17782
|
+
x: 2,
|
|
17783
|
+
y: rowY + 2,
|
|
17784
|
+
width: indexColW - 4,
|
|
17785
|
+
height: rowH - 4,
|
|
17786
|
+
color: BUCKET_INDEX_STROKE,
|
|
17787
|
+
fill: BUCKET_INDEX_FILL
|
|
17788
|
+
});
|
|
17789
|
+
out.push({
|
|
17790
|
+
type: "text",
|
|
17791
|
+
x: 2 + (indexColW - 4) / 2,
|
|
17792
|
+
y: rowY + rowH / 2,
|
|
17793
|
+
text: String(b.index),
|
|
17794
|
+
color: BUCKET_INDEX_TEXT,
|
|
17795
|
+
fontSize: 10,
|
|
17796
|
+
align: "center"
|
|
17797
|
+
});
|
|
17798
|
+
const overflow = b.entries.length > maxVisible;
|
|
17799
|
+
const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
|
|
17800
|
+
for (let j = 0; j < visibleCount; j++) {
|
|
17801
|
+
const entry = b.entries[j];
|
|
17802
|
+
const ex = indexColW + 4 + j * entryW;
|
|
17803
|
+
const state = entry.state ?? "default";
|
|
17804
|
+
const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
|
|
17805
|
+
out.push({
|
|
17806
|
+
type: "rect",
|
|
17807
|
+
id: `bucket-${b.index}-${j}`,
|
|
17808
|
+
x: ex,
|
|
17809
|
+
y: rowY + 2,
|
|
17810
|
+
width: entryW - 2,
|
|
17811
|
+
height: rowH - 4,
|
|
17812
|
+
color: fill,
|
|
17813
|
+
fill
|
|
17814
|
+
});
|
|
17815
|
+
if (entryW >= 20 && rowH >= 16) {
|
|
17816
|
+
out.push({
|
|
17817
|
+
type: "text",
|
|
17818
|
+
x: ex + (entryW - 2) / 2,
|
|
17819
|
+
y: rowY + rowH / 2,
|
|
17820
|
+
text: entry.label,
|
|
17821
|
+
color: BUCKET_ENTRY_TEXT,
|
|
17822
|
+
fontSize: 10,
|
|
17823
|
+
align: "center"
|
|
17824
|
+
});
|
|
17825
|
+
}
|
|
17826
|
+
}
|
|
17827
|
+
if (overflow) {
|
|
17828
|
+
const ex = indexColW + 4 + visibleCount * entryW;
|
|
17829
|
+
out.push({
|
|
17830
|
+
type: "rect",
|
|
17831
|
+
id: `bucket-${b.index}-overflow`,
|
|
17832
|
+
x: ex,
|
|
17833
|
+
y: rowY + 2,
|
|
17834
|
+
width: entryW - 2,
|
|
17835
|
+
height: rowH - 4,
|
|
17836
|
+
color: BUCKET_ENTRY_DEFAULT,
|
|
17837
|
+
fill: BUCKET_ENTRY_DEFAULT
|
|
17838
|
+
});
|
|
17839
|
+
out.push({
|
|
17840
|
+
type: "text",
|
|
17841
|
+
x: ex + (entryW - 2) / 2,
|
|
17842
|
+
y: rowY + rowH / 2,
|
|
17843
|
+
text: `+${b.entries.length - visibleCount}`,
|
|
17844
|
+
color: BUCKET_ENTRY_TEXT,
|
|
17845
|
+
fontSize: 10,
|
|
17846
|
+
align: "center"
|
|
17847
|
+
});
|
|
17848
|
+
}
|
|
17849
|
+
});
|
|
17850
|
+
}
|
|
17851
|
+
if (frames.length > 0) {
|
|
17852
|
+
const panelYFrames = panelY.frames;
|
|
17853
|
+
const n = frames.length;
|
|
17854
|
+
const frameH = panelHeight / n;
|
|
17855
|
+
const x = 8;
|
|
17856
|
+
const w = width - 16;
|
|
17857
|
+
frames.forEach((f3, i) => {
|
|
17858
|
+
const y = panelYFrames + panelHeight - (i + 1) * frameH;
|
|
17859
|
+
const state = f3.state ?? "active";
|
|
17860
|
+
const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
|
|
17861
|
+
out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
|
|
17862
|
+
if (frameH >= FRAME_TWO_LINE_MIN_H) {
|
|
17863
|
+
out.push({
|
|
17864
|
+
type: "text",
|
|
17865
|
+
x: 16,
|
|
17866
|
+
y: y + frameH * 0.35,
|
|
17867
|
+
text: f3.label,
|
|
17868
|
+
color: FRAME_LABEL_COLOR,
|
|
17869
|
+
fontSize: 10,
|
|
17870
|
+
align: "left"
|
|
17871
|
+
});
|
|
17872
|
+
if (f3.detail) {
|
|
17873
|
+
out.push({
|
|
17874
|
+
type: "text",
|
|
17875
|
+
x: 16,
|
|
17876
|
+
y: y + frameH * 0.7,
|
|
17877
|
+
text: f3.detail,
|
|
17878
|
+
color: FRAME_DETAIL_COLOR,
|
|
17879
|
+
fontSize: 10,
|
|
17880
|
+
align: "left"
|
|
17881
|
+
});
|
|
17882
|
+
}
|
|
17883
|
+
} else {
|
|
17884
|
+
out.push({
|
|
17885
|
+
type: "text",
|
|
17886
|
+
x: 16,
|
|
17887
|
+
y: y + frameH / 2,
|
|
17888
|
+
text: f3.label,
|
|
17889
|
+
color: FRAME_LABEL_COLOR,
|
|
17890
|
+
fontSize: 10,
|
|
17891
|
+
align: "left"
|
|
17892
|
+
});
|
|
17893
|
+
}
|
|
16933
17894
|
});
|
|
16934
17895
|
}
|
|
16935
17896
|
out.push(...shapes);
|
|
16936
17897
|
return out;
|
|
16937
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
17898
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
16938
17899
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
16939
17900
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
16940
17901
|
/* @__PURE__ */ jsx(
|
|
@@ -17121,6 +18082,12 @@ function LearningScene3D({
|
|
|
17121
18082
|
const unitId = event.payload?.unitId;
|
|
17122
18083
|
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
17123
18084
|
});
|
|
18085
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
|
|
18086
|
+
const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
|
|
18087
|
+
if (unknownKeys.length > 0) {
|
|
18088
|
+
sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
|
|
18089
|
+
}
|
|
18090
|
+
}
|
|
17124
18091
|
const props3d = {
|
|
17125
18092
|
drawables,
|
|
17126
18093
|
isLoading,
|
|
@@ -17186,7 +18153,7 @@ function cylinderBetween(from, to, radius, color) {
|
|
|
17186
18153
|
material: { color }
|
|
17187
18154
|
};
|
|
17188
18155
|
}
|
|
17189
|
-
function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
18156
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
17190
18157
|
const len = segmentLength(from, to);
|
|
17191
18158
|
if (len < 1e-6) return null;
|
|
17192
18159
|
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
@@ -17214,6 +18181,7 @@ function arrowBetween(from, to, color, shaftRadius = 0.08) {
|
|
|
17214
18181
|
};
|
|
17215
18182
|
return {
|
|
17216
18183
|
type: "draw-group",
|
|
18184
|
+
...id !== void 0 ? { id } : {},
|
|
17217
18185
|
position: { x: from[0], y: from[1], z: from[2] },
|
|
17218
18186
|
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
17219
18187
|
};
|
|
@@ -17240,20 +18208,228 @@ function get3DClickPayload(onShapeClick, idToIndex) {
|
|
|
17240
18208
|
if (!onShapeClick) return void 0;
|
|
17241
18209
|
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
17242
18210
|
}
|
|
17243
|
-
|
|
18211
|
+
function polylineTube(points, radius, color, opts) {
|
|
18212
|
+
const maxSegments = opts?.maxSegments ?? 128;
|
|
18213
|
+
let pts = points;
|
|
18214
|
+
if (pts.length - 1 > maxSegments) {
|
|
18215
|
+
const step = (pts.length - 1) / maxSegments;
|
|
18216
|
+
const kept = [pts[0]];
|
|
18217
|
+
for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
|
|
18218
|
+
kept.push(pts[pts.length - 1]);
|
|
18219
|
+
pts = kept;
|
|
18220
|
+
}
|
|
18221
|
+
const out = [];
|
|
18222
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
18223
|
+
const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
|
|
18224
|
+
if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
|
|
18225
|
+
}
|
|
18226
|
+
return out;
|
|
18227
|
+
}
|
|
18228
|
+
function heightFieldMesh(spec) {
|
|
18229
|
+
const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
|
|
18230
|
+
const flatShading = spec.flatShading ?? true;
|
|
18231
|
+
const vertices = [];
|
|
18232
|
+
for (let iy = 0; iy < ny; iy++) {
|
|
18233
|
+
for (let ix = 0; ix < nx; ix++) {
|
|
18234
|
+
vertices.push([
|
|
18235
|
+
x + (ix - (nx - 1) / 2) * spacing,
|
|
18236
|
+
y + (iy - (ny - 1) / 2) * spacing,
|
|
18237
|
+
heights[iy * nx + ix] ?? 0
|
|
18238
|
+
]);
|
|
18239
|
+
}
|
|
18240
|
+
}
|
|
18241
|
+
const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
|
|
18242
|
+
const facesByBand = /* @__PURE__ */ new Map();
|
|
18243
|
+
for (let iy = 0; iy < ny - 1; iy++) {
|
|
18244
|
+
for (let ix = 0; ix < nx - 1; ix++) {
|
|
18245
|
+
const v00 = iy * nx + ix;
|
|
18246
|
+
const v10 = iy * nx + ix + 1;
|
|
18247
|
+
const v01 = (iy + 1) * nx + ix;
|
|
18248
|
+
const v11 = (iy + 1) * nx + ix + 1;
|
|
18249
|
+
for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
|
|
18250
|
+
const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
|
|
18251
|
+
let band = null;
|
|
18252
|
+
for (const b of bands) {
|
|
18253
|
+
if ((b.min ?? -Infinity) <= centroid) band = b;
|
|
18254
|
+
}
|
|
18255
|
+
const key = bands.length > 0 ? band : null;
|
|
18256
|
+
const list = facesByBand.get(key) ?? [];
|
|
18257
|
+
list.push(face);
|
|
18258
|
+
facesByBand.set(key, list);
|
|
18259
|
+
}
|
|
18260
|
+
}
|
|
18261
|
+
}
|
|
18262
|
+
const out = [];
|
|
18263
|
+
for (const [band, faces] of facesByBand) {
|
|
18264
|
+
if (faces.length === 0) continue;
|
|
18265
|
+
out.push({
|
|
18266
|
+
type: "draw-mesh",
|
|
18267
|
+
shape: "polyhedron",
|
|
18268
|
+
position: { x: 0, y: 0, z: 0 },
|
|
18269
|
+
vertices,
|
|
18270
|
+
faces,
|
|
18271
|
+
pivot: "center",
|
|
18272
|
+
material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
|
|
18273
|
+
...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
|
|
18274
|
+
});
|
|
18275
|
+
}
|
|
18276
|
+
return out;
|
|
18277
|
+
}
|
|
18278
|
+
function arrowField(vectors, opts) {
|
|
18279
|
+
const scale = opts?.scale ?? 1;
|
|
18280
|
+
const out = [];
|
|
18281
|
+
for (const v of vectors) {
|
|
18282
|
+
const to = [
|
|
18283
|
+
v.from[0] + v.delta[0] * scale,
|
|
18284
|
+
v.from[1] + v.delta[1] * scale,
|
|
18285
|
+
v.from[2] + v.delta[2] * scale
|
|
18286
|
+
];
|
|
18287
|
+
const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
|
|
18288
|
+
if (arrow) out.push(arrow);
|
|
18289
|
+
if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
|
|
18290
|
+
}
|
|
18291
|
+
return out;
|
|
18292
|
+
}
|
|
18293
|
+
function helixDrawables(spec, opts) {
|
|
18294
|
+
const count = spec.count ?? spec.rungs?.length ?? 0;
|
|
18295
|
+
const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
|
|
18296
|
+
const radius = spec.radius ?? 1;
|
|
18297
|
+
const rise = spec.rise ?? 0.34;
|
|
18298
|
+
const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
|
|
18299
|
+
const strandAColor = spec.strandAColor ?? "#38bdf8";
|
|
18300
|
+
const strandBColor = spec.strandBColor ?? "#fb923c";
|
|
18301
|
+
const backboneRadius = spec.backboneRadius ?? 0.16;
|
|
18302
|
+
const rungRadius = spec.rungRadius ?? 0.12;
|
|
18303
|
+
const cx = spec.x ?? 0;
|
|
18304
|
+
const cy = spec.y ?? 0;
|
|
18305
|
+
const cz = spec.z ?? 0;
|
|
18306
|
+
const unwoundCount = spec.unwoundCount ?? 0;
|
|
18307
|
+
const unwindSpread = spec.unwindSpread ?? 1.8;
|
|
18308
|
+
const strandA = [];
|
|
18309
|
+
const strandB = [];
|
|
18310
|
+
for (let i = 0; i < count; i++) {
|
|
18311
|
+
const yi = cy + (i - (count - 1) / 2) * rise;
|
|
18312
|
+
const theta = i * twistRad;
|
|
18313
|
+
const s = i < unwoundCount ? unwindSpread : 1;
|
|
18314
|
+
strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
|
|
18315
|
+
strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
|
|
18316
|
+
}
|
|
18317
|
+
const out = [];
|
|
18318
|
+
for (let i = 0; i < count; i++) {
|
|
18319
|
+
out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
|
|
18320
|
+
out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
|
|
18321
|
+
if (i > 0) {
|
|
18322
|
+
const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
|
|
18323
|
+
if (segA) out.push(segA);
|
|
18324
|
+
const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
|
|
18325
|
+
if (segB) out.push(segB);
|
|
18326
|
+
}
|
|
18327
|
+
const rung = rungs[i];
|
|
18328
|
+
const rungColor = rung.color ?? "#94a3b8";
|
|
18329
|
+
const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
|
|
18330
|
+
if (rod) out.push(rod);
|
|
18331
|
+
const mid = [
|
|
18332
|
+
(strandA[i][0] + strandB[i][0]) / 2,
|
|
18333
|
+
(strandA[i][1] + strandB[i][1]) / 2,
|
|
18334
|
+
(strandA[i][2] + strandB[i][2]) / 2
|
|
18335
|
+
];
|
|
18336
|
+
const markerRadius = rung.radius ?? rungRadius;
|
|
18337
|
+
out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
|
|
18338
|
+
if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
|
|
18339
|
+
}
|
|
18340
|
+
return out;
|
|
18341
|
+
}
|
|
18342
|
+
function latticeDrawables(spec, opts) {
|
|
18343
|
+
const nx = spec.nx ?? 2;
|
|
18344
|
+
const ny = spec.ny ?? 2;
|
|
18345
|
+
const nz = spec.nz ?? 2;
|
|
18346
|
+
const latticeConstant = spec.latticeConstant ?? 2;
|
|
18347
|
+
const bondRadius = spec.bondRadius ?? 0.06;
|
|
18348
|
+
const highlightCell = spec.highlightCell ?? false;
|
|
18349
|
+
const dimColor = spec.dimColor ?? "#475569";
|
|
18350
|
+
const showLabels = spec.showLabels ?? false;
|
|
18351
|
+
const selectedColor = spec.selectedColor ?? "#f59e0b";
|
|
18352
|
+
const posByKey = /* @__PURE__ */ new Map();
|
|
18353
|
+
const inCellByKey = /* @__PURE__ */ new Map();
|
|
18354
|
+
const out = [];
|
|
18355
|
+
for (const site of spec.basis) {
|
|
18356
|
+
const snx = site.xEdge ? nx + 1 : nx;
|
|
18357
|
+
const sny = site.yEdge ? ny + 1 : ny;
|
|
18358
|
+
const snz = site.zEdge ? nz + 1 : nz;
|
|
18359
|
+
for (let i = 0; i < snx; i++) {
|
|
18360
|
+
for (let j = 0; j < sny; j++) {
|
|
18361
|
+
for (let k = 0; k < snz; k++) {
|
|
18362
|
+
const key = `${site.key}-${i}-${j}-${k}`;
|
|
18363
|
+
const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
|
|
18364
|
+
const pos = [
|
|
18365
|
+
(i + site.dx) * latticeConstant - nx * latticeConstant / 2,
|
|
18366
|
+
(j + site.dy) * latticeConstant - ny * latticeConstant / 2,
|
|
18367
|
+
(k + site.dz) * latticeConstant - nz * latticeConstant / 2
|
|
18368
|
+
];
|
|
18369
|
+
posByKey.set(key, pos);
|
|
18370
|
+
inCellByKey.set(key, inCell);
|
|
18371
|
+
const isSelected = spec.selectedId === `lat-${key}`;
|
|
18372
|
+
const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
|
|
18373
|
+
const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
|
|
18374
|
+
out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
|
|
18375
|
+
if (showLabels && site.element) {
|
|
18376
|
+
out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
|
|
18377
|
+
}
|
|
18378
|
+
}
|
|
18379
|
+
}
|
|
18380
|
+
}
|
|
18381
|
+
}
|
|
18382
|
+
const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
|
|
18383
|
+
for (const bond of spec.bonds ?? []) {
|
|
18384
|
+
const fromSite = basisByKey.get(bond.from);
|
|
18385
|
+
const toSite = basisByKey.get(bond.to);
|
|
18386
|
+
if (!fromSite || !toSite) continue;
|
|
18387
|
+
const fnx = fromSite.xEdge ? nx + 1 : nx;
|
|
18388
|
+
const fny = fromSite.yEdge ? ny + 1 : ny;
|
|
18389
|
+
const fnz = fromSite.zEdge ? nz + 1 : nz;
|
|
18390
|
+
const tnx = toSite.xEdge ? nx + 1 : nx;
|
|
18391
|
+
const tny = toSite.yEdge ? ny + 1 : ny;
|
|
18392
|
+
const tnz = toSite.zEdge ? nz + 1 : nz;
|
|
18393
|
+
const bdx = bond.dx ?? 0;
|
|
18394
|
+
const bdy = bond.dy ?? 0;
|
|
18395
|
+
const bdz = bond.dz ?? 0;
|
|
18396
|
+
for (let i = 0; i < fnx; i++) {
|
|
18397
|
+
for (let j = 0; j < fny; j++) {
|
|
18398
|
+
for (let k = 0; k < fnz; k++) {
|
|
18399
|
+
const ti = i + bdx;
|
|
18400
|
+
const tj = j + bdy;
|
|
18401
|
+
const tk = k + bdz;
|
|
18402
|
+
if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
|
|
18403
|
+
const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
|
|
18404
|
+
const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
|
|
18405
|
+
const fromPos = posByKey.get(fromKey);
|
|
18406
|
+
const toPos = posByKey.get(toKey);
|
|
18407
|
+
if (!fromPos || !toPos) continue;
|
|
18408
|
+
const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
|
|
18409
|
+
const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
|
|
18410
|
+
if (seg) out.push(seg);
|
|
18411
|
+
}
|
|
18412
|
+
}
|
|
18413
|
+
}
|
|
18414
|
+
}
|
|
18415
|
+
return out;
|
|
18416
|
+
}
|
|
18417
|
+
var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost2;
|
|
17244
18418
|
var init_learningScene3D = __esm({
|
|
17245
18419
|
"components/learning/molecules/learningScene3D.tsx"() {
|
|
17246
18420
|
"use client";
|
|
17247
18421
|
init_atoms();
|
|
17248
18422
|
init_Stack();
|
|
17249
18423
|
init_useEventBus();
|
|
18424
|
+
sceneLog = createLogger("almadar:ui:learning-scene-3d");
|
|
18425
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
17250
18426
|
Canvas3DHost2 = lazy(
|
|
17251
18427
|
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
17252
18428
|
);
|
|
17253
18429
|
LearningScene3D.displayName = "LearningScene3D";
|
|
17254
18430
|
}
|
|
17255
18431
|
});
|
|
17256
|
-
var biologyLog, BiologyCanvas;
|
|
18432
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
17257
18433
|
var init_BiologyCanvas = __esm({
|
|
17258
18434
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
17259
18435
|
"use client";
|
|
@@ -17262,6 +18438,17 @@ var init_BiologyCanvas = __esm({
|
|
|
17262
18438
|
init_LearningCanvas();
|
|
17263
18439
|
init_learningScene3D();
|
|
17264
18440
|
biologyLog = createLogger("almadar:ui:biology-canvas");
|
|
18441
|
+
BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
|
|
18442
|
+
BIO_STAGE_FILL = {
|
|
18443
|
+
pending: "#e2e8f0",
|
|
18444
|
+
active: "#3b82f6",
|
|
18445
|
+
done: "#94a3b8"
|
|
18446
|
+
};
|
|
18447
|
+
BIO_STAGE_TEXT = {
|
|
18448
|
+
pending: "#64748b",
|
|
18449
|
+
active: "#ffffff",
|
|
18450
|
+
done: "#ffffff"
|
|
18451
|
+
};
|
|
17265
18452
|
BiologyCanvas = ({
|
|
17266
18453
|
className,
|
|
17267
18454
|
width = 600,
|
|
@@ -17274,7 +18461,15 @@ var init_BiologyCanvas = __esm({
|
|
|
17274
18461
|
post,
|
|
17275
18462
|
nodes = [],
|
|
17276
18463
|
edges = [],
|
|
18464
|
+
compartments = [],
|
|
18465
|
+
bands = [],
|
|
18466
|
+
stages = [],
|
|
18467
|
+
stageStyle = "timeline",
|
|
18468
|
+
helix,
|
|
18469
|
+
helix3d,
|
|
17277
18470
|
shapes = [],
|
|
18471
|
+
readouts,
|
|
18472
|
+
traces,
|
|
17278
18473
|
showGrid,
|
|
17279
18474
|
shadows,
|
|
17280
18475
|
interactive,
|
|
@@ -17289,19 +18484,148 @@ var init_BiologyCanvas = __esm({
|
|
|
17289
18484
|
for (const n of nodes) {
|
|
17290
18485
|
if (n.id) nodeById.set(n.id, n);
|
|
17291
18486
|
}
|
|
18487
|
+
const bandCount = bands.length;
|
|
18488
|
+
for (let i = 0; i < bandCount; i++) {
|
|
18489
|
+
const band = bands[i];
|
|
18490
|
+
const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
|
|
18491
|
+
const bandY = i * height / bandCount;
|
|
18492
|
+
const bandH = height / bandCount;
|
|
18493
|
+
out.push({
|
|
18494
|
+
type: "rect",
|
|
18495
|
+
x: 0,
|
|
18496
|
+
y: bandY,
|
|
18497
|
+
width,
|
|
18498
|
+
height: bandH,
|
|
18499
|
+
color: bandColor,
|
|
18500
|
+
fill: bandColor,
|
|
18501
|
+
opacity: 0.45
|
|
18502
|
+
});
|
|
18503
|
+
if (band.label) {
|
|
18504
|
+
out.push({
|
|
18505
|
+
type: "text",
|
|
18506
|
+
x: 8,
|
|
18507
|
+
y: bandY + 14,
|
|
18508
|
+
text: band.label,
|
|
18509
|
+
color: "#6b7280",
|
|
18510
|
+
fontSize: 10
|
|
18511
|
+
});
|
|
18512
|
+
}
|
|
18513
|
+
}
|
|
18514
|
+
for (const c of compartments) {
|
|
18515
|
+
const color = c.color ?? "#16a34a";
|
|
18516
|
+
out.push({
|
|
18517
|
+
type: "ellipse",
|
|
18518
|
+
x: c.x,
|
|
18519
|
+
y: c.y,
|
|
18520
|
+
width: c.width,
|
|
18521
|
+
height: c.height,
|
|
18522
|
+
color,
|
|
18523
|
+
fill: c.fill ?? `${color}1A`,
|
|
18524
|
+
lineWidth: c.lineWidth ?? 2,
|
|
18525
|
+
...c.dash ? { dash: c.dash } : {}
|
|
18526
|
+
});
|
|
18527
|
+
if (c.label) {
|
|
18528
|
+
out.push({
|
|
18529
|
+
type: "text",
|
|
18530
|
+
x: c.x,
|
|
18531
|
+
y: c.y - c.height / 2 + 14,
|
|
18532
|
+
text: c.label,
|
|
18533
|
+
color: "#111827",
|
|
18534
|
+
fontSize: 11,
|
|
18535
|
+
align: "center"
|
|
18536
|
+
});
|
|
18537
|
+
}
|
|
18538
|
+
}
|
|
18539
|
+
if (helix) {
|
|
18540
|
+
const hx = helix.x ?? 24;
|
|
18541
|
+
const hy = helix.y ?? height * 0.25;
|
|
18542
|
+
const hw = helix.width ?? width - 48;
|
|
18543
|
+
const hh = helix.height ?? height * 0.5;
|
|
18544
|
+
const rungs = helix.rungs;
|
|
18545
|
+
const n = rungs.length;
|
|
18546
|
+
const cy = hy + hh / 2;
|
|
18547
|
+
const colorA = helix.colorA ?? "#2563eb";
|
|
18548
|
+
const colorB = helix.colorB ?? "#dc2626";
|
|
18549
|
+
const rungColor = helix.rungColor ?? "#94a3b8";
|
|
18550
|
+
const fork = helix.fork ?? 0;
|
|
18551
|
+
const maxSep = Math.min(hh - 8, 96);
|
|
18552
|
+
const strandA = [];
|
|
18553
|
+
const strandB = [];
|
|
18554
|
+
const rungGeoms = [];
|
|
18555
|
+
for (let i = 0; i < n; i++) {
|
|
18556
|
+
const rx = hx + (i + 0.5) * hw / n;
|
|
18557
|
+
const t = (i + 0.5) / n;
|
|
18558
|
+
const paired = t >= fork;
|
|
18559
|
+
const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
|
|
18560
|
+
strandA.push({ x: rx, y: cy - sep / 2 });
|
|
18561
|
+
strandB.push({ x: rx, y: cy + sep / 2 });
|
|
18562
|
+
rungGeoms.push({ rx, sep, rung: rungs[i], paired });
|
|
18563
|
+
}
|
|
18564
|
+
for (let i = 1; i < n; i++) {
|
|
18565
|
+
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 });
|
|
18566
|
+
}
|
|
18567
|
+
for (let i = 1; i < n; i++) {
|
|
18568
|
+
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 });
|
|
18569
|
+
}
|
|
18570
|
+
for (const g of rungGeoms) {
|
|
18571
|
+
const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
|
|
18572
|
+
const topY = cy - g.sep / 2;
|
|
18573
|
+
const bottomY = cy + g.sep / 2;
|
|
18574
|
+
if (g.paired) {
|
|
18575
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
|
|
18576
|
+
if (g.rung.a) {
|
|
18577
|
+
out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
18578
|
+
}
|
|
18579
|
+
if (g.rung.b) {
|
|
18580
|
+
out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
18581
|
+
}
|
|
18582
|
+
} else {
|
|
18583
|
+
const stubTopY = topY + 8;
|
|
18584
|
+
const stubBottomY = bottomY - 8;
|
|
18585
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
|
|
18586
|
+
out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
|
|
18587
|
+
if (g.rung.a) {
|
|
18588
|
+
out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
18589
|
+
}
|
|
18590
|
+
if (g.rung.b) {
|
|
18591
|
+
out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
18592
|
+
}
|
|
18593
|
+
}
|
|
18594
|
+
}
|
|
18595
|
+
}
|
|
17292
18596
|
for (const e of edges) {
|
|
17293
18597
|
const a = nodeById.get(e.from);
|
|
17294
18598
|
const b = nodeById.get(e.to);
|
|
17295
18599
|
if (!a || !b) continue;
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
|
|
17303
|
-
|
|
17304
|
-
|
|
18600
|
+
const color = e.color ?? "#9ca3af";
|
|
18601
|
+
if (e.directed) {
|
|
18602
|
+
const rA = a.radius ?? 16;
|
|
18603
|
+
const rB = b.radius ?? 16;
|
|
18604
|
+
const dx = b.x - a.x;
|
|
18605
|
+
const dy = b.y - a.y;
|
|
18606
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
18607
|
+
const ux = dx / dist;
|
|
18608
|
+
const uy = dy / dist;
|
|
18609
|
+
out.push({
|
|
18610
|
+
type: "arrow",
|
|
18611
|
+
x1: a.x + ux * rA,
|
|
18612
|
+
y1: a.y + uy * rA,
|
|
18613
|
+
x2: b.x - ux * rB,
|
|
18614
|
+
y2: b.y - uy * rB,
|
|
18615
|
+
color,
|
|
18616
|
+
lineWidth: 2
|
|
18617
|
+
});
|
|
18618
|
+
} else {
|
|
18619
|
+
out.push({
|
|
18620
|
+
type: "line",
|
|
18621
|
+
x1: a.x,
|
|
18622
|
+
y1: a.y,
|
|
18623
|
+
x2: b.x,
|
|
18624
|
+
y2: b.y,
|
|
18625
|
+
color,
|
|
18626
|
+
lineWidth: 2
|
|
18627
|
+
});
|
|
18628
|
+
}
|
|
17305
18629
|
if (e.label) {
|
|
17306
18630
|
out.push({
|
|
17307
18631
|
type: "text",
|
|
@@ -17314,6 +18638,8 @@ var init_BiologyCanvas = __esm({
|
|
|
17314
18638
|
}
|
|
17315
18639
|
}
|
|
17316
18640
|
for (const n of nodes) {
|
|
18641
|
+
const state = n.state ?? "default";
|
|
18642
|
+
const muted = state === "muted";
|
|
17317
18643
|
out.push({
|
|
17318
18644
|
type: "circle",
|
|
17319
18645
|
x: n.x,
|
|
@@ -17321,28 +18647,127 @@ var init_BiologyCanvas = __esm({
|
|
|
17321
18647
|
radius: n.radius ?? 16,
|
|
17322
18648
|
color: n.color ?? "#16a34a",
|
|
17323
18649
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
17324
|
-
id: n.id
|
|
18650
|
+
id: n.id,
|
|
18651
|
+
...muted ? { opacity: 0.35 } : {}
|
|
17325
18652
|
});
|
|
18653
|
+
if (state === "highlight") {
|
|
18654
|
+
out.push({
|
|
18655
|
+
type: "circle",
|
|
18656
|
+
x: n.x,
|
|
18657
|
+
y: n.y,
|
|
18658
|
+
radius: (n.radius ?? 16) + 4,
|
|
18659
|
+
color: "#f59e0b",
|
|
18660
|
+
lineWidth: 2
|
|
18661
|
+
});
|
|
18662
|
+
}
|
|
17326
18663
|
if (n.label) {
|
|
17327
18664
|
out.push({
|
|
17328
18665
|
type: "text",
|
|
17329
18666
|
x: n.x,
|
|
17330
18667
|
y: n.y + (n.radius ?? 16) + 14,
|
|
17331
18668
|
text: n.label,
|
|
18669
|
+
...muted ? { opacity: 0.35 } : {},
|
|
17332
18670
|
color: "#111827",
|
|
17333
18671
|
fontSize: 12,
|
|
17334
18672
|
align: "center"
|
|
17335
18673
|
});
|
|
17336
18674
|
}
|
|
17337
18675
|
}
|
|
18676
|
+
const stageCount = stages.length;
|
|
18677
|
+
if (stageCount > 0) {
|
|
18678
|
+
if (stageStyle === "ring") {
|
|
18679
|
+
const cx = width / 2;
|
|
18680
|
+
const cy = height / 2;
|
|
18681
|
+
const R = Math.min(width, height) / 2 - 48;
|
|
18682
|
+
const ringPoints = [];
|
|
18683
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18684
|
+
const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
|
|
18685
|
+
ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
|
|
18686
|
+
}
|
|
18687
|
+
if (stageCount >= 2) {
|
|
18688
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
18689
|
+
const p1 = ringPoints[i];
|
|
18690
|
+
const p2 = ringPoints[i + 1];
|
|
18691
|
+
const dx = p2.x - p1.x;
|
|
18692
|
+
const dy = p2.y - p1.y;
|
|
18693
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
18694
|
+
const ux = dx / dist;
|
|
18695
|
+
const uy = dy / dist;
|
|
18696
|
+
out.push({
|
|
18697
|
+
type: "arrow",
|
|
18698
|
+
x1: p1.x + ux * 46,
|
|
18699
|
+
y1: p1.y + uy * 46,
|
|
18700
|
+
x2: p2.x - ux * 46,
|
|
18701
|
+
y2: p2.y - uy * 46,
|
|
18702
|
+
color: "#94a3b8"
|
|
18703
|
+
});
|
|
18704
|
+
}
|
|
18705
|
+
}
|
|
18706
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18707
|
+
const stage = stages[i];
|
|
18708
|
+
const state = stage.state ?? "pending";
|
|
18709
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
18710
|
+
const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
|
|
18711
|
+
const h = 18;
|
|
18712
|
+
const p = ringPoints[i];
|
|
18713
|
+
out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
|
|
18714
|
+
out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
|
|
18715
|
+
}
|
|
18716
|
+
} else {
|
|
18717
|
+
const stripY = height - 32;
|
|
18718
|
+
const slotW = (width - 16) / stageCount;
|
|
18719
|
+
const chipGeoms = [];
|
|
18720
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18721
|
+
chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
|
|
18722
|
+
}
|
|
18723
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
18724
|
+
const midY = stripY + 13;
|
|
18725
|
+
out.push({
|
|
18726
|
+
type: "arrow",
|
|
18727
|
+
x1: chipGeoms[i].x + chipGeoms[i].w,
|
|
18728
|
+
y1: midY,
|
|
18729
|
+
x2: chipGeoms[i + 1].x,
|
|
18730
|
+
y2: midY,
|
|
18731
|
+
color: "#94a3b8"
|
|
18732
|
+
});
|
|
18733
|
+
}
|
|
18734
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18735
|
+
const stage = stages[i];
|
|
18736
|
+
const state = stage.state ?? "pending";
|
|
18737
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
18738
|
+
const g = chipGeoms[i];
|
|
18739
|
+
out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
|
|
18740
|
+
out.push({
|
|
18741
|
+
type: "text",
|
|
18742
|
+
x: g.x + g.w / 2,
|
|
18743
|
+
y: stripY + 13,
|
|
18744
|
+
text: stage.label,
|
|
18745
|
+
color: BIO_STAGE_TEXT[state],
|
|
18746
|
+
fontSize: 10,
|
|
18747
|
+
align: "center"
|
|
18748
|
+
});
|
|
18749
|
+
}
|
|
18750
|
+
}
|
|
18751
|
+
}
|
|
17338
18752
|
out.push(...shapes);
|
|
17339
18753
|
return out;
|
|
17340
|
-
}, [nodes, edges, shapes]);
|
|
18754
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
17341
18755
|
const drawables3D = useMemo(() => {
|
|
17342
18756
|
if (mode !== "3d") return [];
|
|
17343
18757
|
if (shapes.length > 0) {
|
|
17344
18758
|
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
17345
18759
|
}
|
|
18760
|
+
if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
|
|
18761
|
+
biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
|
|
18762
|
+
compartments: compartments.length,
|
|
18763
|
+
bands: bands.length,
|
|
18764
|
+
stages: stages.length,
|
|
18765
|
+
helix: helix != null
|
|
18766
|
+
});
|
|
18767
|
+
}
|
|
18768
|
+
if (animate) {
|
|
18769
|
+
biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
18770
|
+
}
|
|
17346
18771
|
const out = [];
|
|
17347
18772
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
17348
18773
|
const nodeById = /* @__PURE__ */ new Map();
|
|
@@ -17376,15 +18801,21 @@ var init_BiologyCanvas = __esm({
|
|
|
17376
18801
|
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
17377
18802
|
}
|
|
17378
18803
|
}
|
|
18804
|
+
if (helix3d) {
|
|
18805
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
18806
|
+
}
|
|
17379
18807
|
return out;
|
|
17380
|
-
}, [mode, nodes, edges, shapes, backgroundColor]);
|
|
18808
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
17381
18809
|
const nodeIndexById = useMemo(() => {
|
|
17382
18810
|
const m = /* @__PURE__ */ new Map();
|
|
18811
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
18812
|
+
if (rung.id) m.set(rung.id, i);
|
|
18813
|
+
});
|
|
17383
18814
|
nodes.forEach((n, i) => {
|
|
17384
18815
|
if (n.id) m.set(n.id, i);
|
|
17385
18816
|
});
|
|
17386
18817
|
return m;
|
|
17387
|
-
}, [nodes]);
|
|
18818
|
+
}, [nodes, helix3d]);
|
|
17388
18819
|
if (mode === "3d") {
|
|
17389
18820
|
return /* @__PURE__ */ jsx(
|
|
17390
18821
|
LearningScene3D,
|
|
@@ -17416,6 +18847,8 @@ var init_BiologyCanvas = __esm({
|
|
|
17416
18847
|
height,
|
|
17417
18848
|
backgroundColor,
|
|
17418
18849
|
shapes: derivedShapes,
|
|
18850
|
+
readouts,
|
|
18851
|
+
traces,
|
|
17419
18852
|
interactive: interactive ?? false,
|
|
17420
18853
|
animate,
|
|
17421
18854
|
onShapeClick,
|
|
@@ -24265,7 +25698,7 @@ function bondPerpendicular(a, b) {
|
|
|
24265
25698
|
if (len < 1e-6) return [1, 0, 0];
|
|
24266
25699
|
return [px / len, py / len, 0];
|
|
24267
25700
|
}
|
|
24268
|
-
var chemistryLog, ChemistryCanvas;
|
|
25701
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
24269
25702
|
var init_ChemistryCanvas = __esm({
|
|
24270
25703
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
24271
25704
|
"use client";
|
|
@@ -24274,6 +25707,13 @@ var init_ChemistryCanvas = __esm({
|
|
|
24274
25707
|
init_LearningCanvas();
|
|
24275
25708
|
init_learningScene3D();
|
|
24276
25709
|
chemistryLog = createLogger("almadar:ui:chemistry-canvas");
|
|
25710
|
+
CHEM_BOND_STATE_COLOR = {
|
|
25711
|
+
default: "#6b7280",
|
|
25712
|
+
forming: "#16a34a",
|
|
25713
|
+
breaking: "#dc2626",
|
|
25714
|
+
highlight: "#f59e0b"
|
|
25715
|
+
};
|
|
25716
|
+
LONE_PAIR_ANGLES = [-90, 0, 90, 180];
|
|
24277
25717
|
ChemistryCanvas = ({
|
|
24278
25718
|
className,
|
|
24279
25719
|
width = 600,
|
|
@@ -24287,7 +25727,14 @@ var init_ChemistryCanvas = __esm({
|
|
|
24287
25727
|
atoms = [],
|
|
24288
25728
|
bonds = [],
|
|
24289
25729
|
arrows = [],
|
|
25730
|
+
bondStyle = "thick",
|
|
25731
|
+
containers = [],
|
|
25732
|
+
equation,
|
|
25733
|
+
equationColor,
|
|
25734
|
+
lattice3d,
|
|
24290
25735
|
shapes = [],
|
|
25736
|
+
readouts,
|
|
25737
|
+
traces,
|
|
24291
25738
|
showGrid,
|
|
24292
25739
|
shadows,
|
|
24293
25740
|
interactive,
|
|
@@ -24302,21 +25749,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
24302
25749
|
for (const a of atoms) {
|
|
24303
25750
|
if (a.id) atomById.set(a.id, a);
|
|
24304
25751
|
}
|
|
25752
|
+
for (const c of containers) {
|
|
25753
|
+
const color = c.color ?? "#64748b";
|
|
25754
|
+
if (c.level != null) {
|
|
25755
|
+
const lv = c.level;
|
|
25756
|
+
out.push({
|
|
25757
|
+
type: "rect",
|
|
25758
|
+
x: c.x + 1,
|
|
25759
|
+
y: c.y + c.height * (1 - lv),
|
|
25760
|
+
width: c.width - 2,
|
|
25761
|
+
height: c.height * lv - 1,
|
|
25762
|
+
color: c.levelColor ?? "#60a5fa",
|
|
25763
|
+
fill: c.levelColor ?? "#60a5fa",
|
|
25764
|
+
opacity: 0.5
|
|
25765
|
+
});
|
|
25766
|
+
}
|
|
25767
|
+
out.push({
|
|
25768
|
+
type: "rect",
|
|
25769
|
+
x: c.x,
|
|
25770
|
+
y: c.y,
|
|
25771
|
+
width: c.width,
|
|
25772
|
+
height: c.height,
|
|
25773
|
+
color,
|
|
25774
|
+
fill: c.fill,
|
|
25775
|
+
lineWidth: c.lineWidth ?? 2
|
|
25776
|
+
});
|
|
25777
|
+
const divider = c.divider ?? "none";
|
|
25778
|
+
if (divider !== "none") {
|
|
25779
|
+
out.push({
|
|
25780
|
+
type: "line",
|
|
25781
|
+
x1: c.x + c.width / 2,
|
|
25782
|
+
y1: c.y,
|
|
25783
|
+
x2: c.x + c.width / 2,
|
|
25784
|
+
y2: c.y + c.height,
|
|
25785
|
+
color: c.dividerColor ?? color,
|
|
25786
|
+
...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
|
|
25787
|
+
});
|
|
25788
|
+
}
|
|
25789
|
+
if (c.leftLabel) {
|
|
25790
|
+
out.push({
|
|
25791
|
+
type: "text",
|
|
25792
|
+
x: c.x + c.width * 0.25,
|
|
25793
|
+
y: c.y + 12,
|
|
25794
|
+
text: c.leftLabel,
|
|
25795
|
+
color: "#374151",
|
|
25796
|
+
fontSize: 11,
|
|
25797
|
+
align: "center"
|
|
25798
|
+
});
|
|
25799
|
+
}
|
|
25800
|
+
if (c.rightLabel) {
|
|
25801
|
+
out.push({
|
|
25802
|
+
type: "text",
|
|
25803
|
+
x: c.x + c.width * 0.75,
|
|
25804
|
+
y: c.y + 12,
|
|
25805
|
+
text: c.rightLabel,
|
|
25806
|
+
color: "#374151",
|
|
25807
|
+
fontSize: 11,
|
|
25808
|
+
align: "center"
|
|
25809
|
+
});
|
|
25810
|
+
}
|
|
25811
|
+
if (c.label) {
|
|
25812
|
+
out.push({
|
|
25813
|
+
type: "text",
|
|
25814
|
+
x: c.x + c.width / 2,
|
|
25815
|
+
y: c.y + c.height + 12,
|
|
25816
|
+
text: c.label,
|
|
25817
|
+
color: "#111827",
|
|
25818
|
+
fontSize: 12,
|
|
25819
|
+
align: "center"
|
|
25820
|
+
});
|
|
25821
|
+
}
|
|
25822
|
+
}
|
|
24305
25823
|
for (const b of bonds) {
|
|
24306
25824
|
const a = atomById.get(b.from);
|
|
24307
25825
|
const c = atomById.get(b.to);
|
|
24308
25826
|
if (!a || !c) continue;
|
|
24309
|
-
const
|
|
24310
|
-
const
|
|
24311
|
-
|
|
24312
|
-
|
|
24313
|
-
|
|
24314
|
-
|
|
24315
|
-
|
|
24316
|
-
|
|
24317
|
-
|
|
24318
|
-
|
|
24319
|
-
|
|
25827
|
+
const state = b.state ?? "default";
|
|
25828
|
+
const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
|
|
25829
|
+
const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
|
|
25830
|
+
if (bondStyle === "parallel") {
|
|
25831
|
+
const dx = c.x - a.x;
|
|
25832
|
+
const dy = c.y - a.y;
|
|
25833
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
25834
|
+
const ux = dx / dist;
|
|
25835
|
+
const uy = dy / dist;
|
|
25836
|
+
const px = -uy;
|
|
25837
|
+
const py = ux;
|
|
25838
|
+
const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
|
|
25839
|
+
for (const off of offsets) {
|
|
25840
|
+
out.push({
|
|
25841
|
+
type: "line",
|
|
25842
|
+
x1: a.x + px * off,
|
|
25843
|
+
y1: a.y + py * off,
|
|
25844
|
+
x2: c.x + px * off,
|
|
25845
|
+
y2: c.y + py * off,
|
|
25846
|
+
color,
|
|
25847
|
+
lineWidth: 2,
|
|
25848
|
+
...dash ? { dash } : {}
|
|
25849
|
+
});
|
|
25850
|
+
}
|
|
25851
|
+
} else {
|
|
25852
|
+
const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
|
|
25853
|
+
out.push({
|
|
25854
|
+
type: "line",
|
|
25855
|
+
x1: a.x,
|
|
25856
|
+
y1: a.y,
|
|
25857
|
+
x2: c.x,
|
|
25858
|
+
y2: c.y,
|
|
25859
|
+
color,
|
|
25860
|
+
lineWidth: strokeWidth,
|
|
25861
|
+
...dash ? { dash } : {}
|
|
25862
|
+
});
|
|
25863
|
+
}
|
|
24320
25864
|
}
|
|
24321
25865
|
for (const a of arrows) {
|
|
24322
25866
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -24365,15 +25909,62 @@ var init_ChemistryCanvas = __esm({
|
|
|
24365
25909
|
align: "center"
|
|
24366
25910
|
});
|
|
24367
25911
|
}
|
|
25912
|
+
const r2 = a.radius ?? 14;
|
|
25913
|
+
if (a.charge) {
|
|
25914
|
+
out.push({
|
|
25915
|
+
type: "text",
|
|
25916
|
+
x: a.x + r2 * 0.85,
|
|
25917
|
+
y: a.y - r2 * 0.85,
|
|
25918
|
+
text: a.charge,
|
|
25919
|
+
color: "#111827",
|
|
25920
|
+
fontSize: 9,
|
|
25921
|
+
align: "left"
|
|
25922
|
+
});
|
|
25923
|
+
}
|
|
25924
|
+
const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
|
|
25925
|
+
for (let k = 0; k < lonePairs; k++) {
|
|
25926
|
+
const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
|
|
25927
|
+
const cx = a.x + (r2 + 6) * Math.cos(angleRad);
|
|
25928
|
+
const cy = a.y + (r2 + 6) * Math.sin(angleRad);
|
|
25929
|
+
const perpX = -Math.sin(angleRad);
|
|
25930
|
+
const perpY = Math.cos(angleRad);
|
|
25931
|
+
for (const sign of [1, -1]) {
|
|
25932
|
+
out.push({
|
|
25933
|
+
type: "circle",
|
|
25934
|
+
x: cx + perpX * 2.5 * sign,
|
|
25935
|
+
y: cy + perpY * 2.5 * sign,
|
|
25936
|
+
radius: 1.5,
|
|
25937
|
+
color: "#374151",
|
|
25938
|
+
fill: "#374151"
|
|
25939
|
+
});
|
|
25940
|
+
}
|
|
25941
|
+
}
|
|
25942
|
+
}
|
|
25943
|
+
if (equation) {
|
|
25944
|
+
out.push({
|
|
25945
|
+
type: "text",
|
|
25946
|
+
x: width / 2,
|
|
25947
|
+
y: 14,
|
|
25948
|
+
text: equation,
|
|
25949
|
+
color: equationColor ?? "#111827",
|
|
25950
|
+
fontSize: 13,
|
|
25951
|
+
align: "center"
|
|
25952
|
+
});
|
|
24368
25953
|
}
|
|
24369
25954
|
out.push(...shapes);
|
|
24370
25955
|
return out;
|
|
24371
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
25956
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
24372
25957
|
const drawables3D = useMemo(() => {
|
|
24373
25958
|
if (mode !== "3d") return [];
|
|
24374
25959
|
if (shapes.length > 0) {
|
|
24375
25960
|
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
24376
25961
|
}
|
|
25962
|
+
if (containers.length > 0) {
|
|
25963
|
+
chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
|
|
25964
|
+
}
|
|
25965
|
+
if (animate) {
|
|
25966
|
+
chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
25967
|
+
}
|
|
24377
25968
|
const out = [];
|
|
24378
25969
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
24379
25970
|
const atomById = /* @__PURE__ */ new Map();
|
|
@@ -24420,8 +26011,11 @@ var init_ChemistryCanvas = __esm({
|
|
|
24420
26011
|
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
24421
26012
|
}
|
|
24422
26013
|
}
|
|
26014
|
+
if (lattice3d) {
|
|
26015
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
26016
|
+
}
|
|
24423
26017
|
return out;
|
|
24424
|
-
}, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
|
|
26018
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
24425
26019
|
const atomIndexById = useMemo(() => {
|
|
24426
26020
|
const m = /* @__PURE__ */ new Map();
|
|
24427
26021
|
atoms.forEach((a, i) => {
|
|
@@ -24460,6 +26054,8 @@ var init_ChemistryCanvas = __esm({
|
|
|
24460
26054
|
height,
|
|
24461
26055
|
backgroundColor,
|
|
24462
26056
|
shapes: derivedShapes,
|
|
26057
|
+
readouts,
|
|
26058
|
+
traces,
|
|
24463
26059
|
interactive: interactive ?? false,
|
|
24464
26060
|
animate,
|
|
24465
26061
|
onShapeClick,
|
|
@@ -30649,6 +32245,10 @@ var init_ProgressDots = __esm({
|
|
|
30649
32245
|
ProgressDots.displayName = "ProgressDots";
|
|
30650
32246
|
}
|
|
30651
32247
|
});
|
|
32248
|
+
function formatTick(v) {
|
|
32249
|
+
if (Number.isInteger(v)) return String(v);
|
|
32250
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
32251
|
+
}
|
|
30652
32252
|
var MathCanvas;
|
|
30653
32253
|
var init_MathCanvas = __esm({
|
|
30654
32254
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -30668,10 +32268,19 @@ var init_MathCanvas = __esm({
|
|
|
30668
32268
|
showAxes = true,
|
|
30669
32269
|
showGrid = true,
|
|
30670
32270
|
gridStep = 1,
|
|
32271
|
+
showTickLabels = false,
|
|
32272
|
+
showCurveLabels = false,
|
|
30671
32273
|
curves = [],
|
|
30672
32274
|
points = [],
|
|
30673
32275
|
vectors = [],
|
|
32276
|
+
regions = [],
|
|
32277
|
+
bars = [],
|
|
32278
|
+
guides = [],
|
|
32279
|
+
angles = [],
|
|
32280
|
+
hops = [],
|
|
30674
32281
|
shapes = [],
|
|
32282
|
+
readouts,
|
|
32283
|
+
traces,
|
|
30675
32284
|
interactive = false,
|
|
30676
32285
|
animate = false,
|
|
30677
32286
|
onShapeClick,
|
|
@@ -30685,6 +32294,8 @@ var init_MathCanvas = __esm({
|
|
|
30685
32294
|
const plotH = height - margin * 2;
|
|
30686
32295
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
30687
32296
|
const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
|
|
32297
|
+
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
32298
|
+
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
30688
32299
|
if (showGrid) {
|
|
30689
32300
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
30690
32301
|
const px = mapX(x);
|
|
@@ -30695,14 +32306,99 @@ var init_MathCanvas = __esm({
|
|
|
30695
32306
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
30696
32307
|
}
|
|
30697
32308
|
}
|
|
32309
|
+
if (showTickLabels) {
|
|
32310
|
+
const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
|
|
32311
|
+
let kx = 0;
|
|
32312
|
+
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
|
|
32313
|
+
if (kx % labelEveryX === 0 && x !== 0) {
|
|
32314
|
+
out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
|
|
32315
|
+
}
|
|
32316
|
+
}
|
|
32317
|
+
const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
|
|
32318
|
+
let ky = 0;
|
|
32319
|
+
for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
|
|
32320
|
+
if (ky % labelEveryY === 0 && y !== 0) {
|
|
32321
|
+
out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
|
|
32322
|
+
}
|
|
32323
|
+
}
|
|
32324
|
+
if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
|
|
32325
|
+
out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
|
|
32326
|
+
}
|
|
32327
|
+
}
|
|
32328
|
+
for (const region of regions) {
|
|
32329
|
+
if (!region.samples || region.samples.length === 0) continue;
|
|
32330
|
+
const baseline = region.baseline ?? 0;
|
|
32331
|
+
const clampedPoint = (p) => ({
|
|
32332
|
+
x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
|
|
32333
|
+
y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
|
|
32334
|
+
});
|
|
32335
|
+
const upper = region.samples.map(clampedPoint);
|
|
32336
|
+
const first = region.samples[0];
|
|
32337
|
+
const last = region.samples[region.samples.length - 1];
|
|
32338
|
+
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 })];
|
|
32339
|
+
const color = region.color ?? "#2563eb";
|
|
32340
|
+
out.push({
|
|
32341
|
+
type: "polygon",
|
|
32342
|
+
points: [...upper, ...closing],
|
|
32343
|
+
fill: color,
|
|
32344
|
+
color,
|
|
32345
|
+
opacity: region.opacity ?? 0.2,
|
|
32346
|
+
lineWidth: 1
|
|
32347
|
+
});
|
|
32348
|
+
if (region.label) {
|
|
32349
|
+
const mid = Math.floor(region.samples.length / 2);
|
|
32350
|
+
out.push({
|
|
32351
|
+
type: "text",
|
|
32352
|
+
x: mapX((first.x + last.x) / 2),
|
|
32353
|
+
y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
|
|
32354
|
+
text: region.label,
|
|
32355
|
+
color: "#111827",
|
|
32356
|
+
fontSize: 11
|
|
32357
|
+
});
|
|
32358
|
+
}
|
|
32359
|
+
}
|
|
32360
|
+
for (const bar of bars) {
|
|
32361
|
+
if (bar.x + bar.width < xMin || bar.x > xMax) continue;
|
|
32362
|
+
const y0 = bar.y0 ?? 0;
|
|
32363
|
+
const color = bar.color ?? "#93c5fd";
|
|
32364
|
+
out.push({
|
|
32365
|
+
type: "rect",
|
|
32366
|
+
x: mapX(bar.x),
|
|
32367
|
+
y: mapY(Math.max(y0, bar.y1)),
|
|
32368
|
+
width: mapX(bar.x + bar.width) - mapX(bar.x),
|
|
32369
|
+
height: Math.abs(mapY(bar.y1) - mapY(y0)),
|
|
32370
|
+
color,
|
|
32371
|
+
fill: color,
|
|
32372
|
+
opacity: bar.opacity ?? 0.5,
|
|
32373
|
+
lineWidth: 1
|
|
32374
|
+
});
|
|
32375
|
+
}
|
|
30698
32376
|
if (showAxes) {
|
|
30699
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
30700
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
30701
32377
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
30702
32378
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
30703
32379
|
}
|
|
32380
|
+
for (const guide of guides) {
|
|
32381
|
+
const color = guide.color ?? "#9ca3af";
|
|
32382
|
+
const dash = guide.dash ?? "dashed";
|
|
32383
|
+
if (guide.kind === "vline") {
|
|
32384
|
+
if (guide.at < xMin || guide.at > xMax) continue;
|
|
32385
|
+
const px = mapX(guide.at);
|
|
32386
|
+
out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
|
|
32387
|
+
if (guide.label) {
|
|
32388
|
+
out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
|
|
32389
|
+
}
|
|
32390
|
+
} else {
|
|
32391
|
+
if (guide.at < yMin || guide.at > yMax) continue;
|
|
32392
|
+
const py = mapY(guide.at);
|
|
32393
|
+
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
|
|
32394
|
+
if (guide.label) {
|
|
32395
|
+
out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
|
|
32396
|
+
}
|
|
32397
|
+
}
|
|
32398
|
+
}
|
|
30704
32399
|
for (const curve of curves) {
|
|
30705
32400
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
32401
|
+
let lastInRange;
|
|
30706
32402
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
30707
32403
|
const a = curve.samples[i - 1];
|
|
30708
32404
|
const b = curve.samples[i];
|
|
@@ -30714,19 +32410,97 @@ var init_MathCanvas = __esm({
|
|
|
30714
32410
|
x2: mapX(b.x),
|
|
30715
32411
|
y2: mapY(b.y),
|
|
30716
32412
|
color: curve.color ?? "#2563eb",
|
|
30717
|
-
lineWidth: 2
|
|
32413
|
+
lineWidth: 2,
|
|
32414
|
+
dash: curve.dash
|
|
32415
|
+
});
|
|
32416
|
+
lastInRange = b;
|
|
32417
|
+
}
|
|
32418
|
+
if (showCurveLabels && curve.label && lastInRange) {
|
|
32419
|
+
out.push({
|
|
32420
|
+
type: "text",
|
|
32421
|
+
x: mapX(lastInRange.x) + 6,
|
|
32422
|
+
y: mapY(lastInRange.y) - 6,
|
|
32423
|
+
text: curve.label,
|
|
32424
|
+
color: curve.color ?? "#2563eb",
|
|
32425
|
+
fontSize: 11
|
|
32426
|
+
});
|
|
32427
|
+
}
|
|
32428
|
+
}
|
|
32429
|
+
for (const hop of hops) {
|
|
32430
|
+
const x1 = mapX(hop.from);
|
|
32431
|
+
const x2 = mapX(hop.to);
|
|
32432
|
+
const peak = Math.min(36, plotH * 0.3);
|
|
32433
|
+
const color = hop.color ?? "#7c3aed";
|
|
32434
|
+
out.push({
|
|
32435
|
+
type: "ellipse",
|
|
32436
|
+
x: (x1 + x2) / 2,
|
|
32437
|
+
y: xAxisY,
|
|
32438
|
+
width: Math.abs(x2 - x1),
|
|
32439
|
+
height: 2 * peak,
|
|
32440
|
+
startAngle: 180,
|
|
32441
|
+
endAngle: 360,
|
|
32442
|
+
color
|
|
32443
|
+
});
|
|
32444
|
+
const s = Math.sign(hop.to - hop.from);
|
|
32445
|
+
out.push({
|
|
32446
|
+
type: "polygon",
|
|
32447
|
+
points: [
|
|
32448
|
+
{ x: x2, y: xAxisY },
|
|
32449
|
+
{ x: x2 - 4 * s, y: xAxisY - 7 },
|
|
32450
|
+
{ x: x2 + 2 * s, y: xAxisY - 7 }
|
|
32451
|
+
],
|
|
32452
|
+
fill: color,
|
|
32453
|
+
color
|
|
32454
|
+
});
|
|
32455
|
+
if (hop.label) {
|
|
32456
|
+
out.push({
|
|
32457
|
+
type: "text",
|
|
32458
|
+
x: (x1 + x2) / 2,
|
|
32459
|
+
y: xAxisY - peak - 8,
|
|
32460
|
+
text: hop.label,
|
|
32461
|
+
color: "#111827",
|
|
32462
|
+
fontSize: 10,
|
|
32463
|
+
align: "center"
|
|
32464
|
+
});
|
|
32465
|
+
}
|
|
32466
|
+
}
|
|
32467
|
+
for (const angle of angles) {
|
|
32468
|
+
const radius = angle.radius ?? 0.8;
|
|
32469
|
+
const color = angle.color ?? "#0ea5e9";
|
|
32470
|
+
out.push({
|
|
32471
|
+
type: "ellipse",
|
|
32472
|
+
x: mapX(angle.x),
|
|
32473
|
+
y: mapY(angle.y),
|
|
32474
|
+
width: 2 * radius * plotW / (xMax - xMin),
|
|
32475
|
+
height: 2 * radius * plotH / (yMax - yMin),
|
|
32476
|
+
startAngle: -angle.to,
|
|
32477
|
+
endAngle: -angle.from,
|
|
32478
|
+
color
|
|
32479
|
+
});
|
|
32480
|
+
if (angle.label) {
|
|
32481
|
+
const mid = (angle.from + angle.to) / 2;
|
|
32482
|
+
const rad = mid * Math.PI / 180;
|
|
32483
|
+
out.push({
|
|
32484
|
+
type: "text",
|
|
32485
|
+
x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
|
|
32486
|
+
y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
|
|
32487
|
+
text: angle.label,
|
|
32488
|
+
color: "#111827",
|
|
32489
|
+
fontSize: 11,
|
|
32490
|
+
align: "center"
|
|
30718
32491
|
});
|
|
30719
32492
|
}
|
|
30720
32493
|
}
|
|
30721
32494
|
for (const p of points) {
|
|
30722
32495
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
32496
|
+
const isOpen = p.style === "open";
|
|
30723
32497
|
out.push({
|
|
30724
32498
|
type: "circle",
|
|
30725
32499
|
x: mapX(p.x),
|
|
30726
32500
|
y: mapY(p.y),
|
|
30727
32501
|
radius: p.radius ?? 4,
|
|
30728
32502
|
color: p.color ?? "#dc2626",
|
|
30729
|
-
fill: p.color ?? "#dc2626"
|
|
32503
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
30730
32504
|
});
|
|
30731
32505
|
if (p.label) {
|
|
30732
32506
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -30745,7 +32519,28 @@ var init_MathCanvas = __esm({
|
|
|
30745
32519
|
}
|
|
30746
32520
|
out.push(...shapes);
|
|
30747
32521
|
return out;
|
|
30748
|
-
}, [
|
|
32522
|
+
}, [
|
|
32523
|
+
width,
|
|
32524
|
+
height,
|
|
32525
|
+
xMin,
|
|
32526
|
+
xMax,
|
|
32527
|
+
yMin,
|
|
32528
|
+
yMax,
|
|
32529
|
+
showAxes,
|
|
32530
|
+
showGrid,
|
|
32531
|
+
gridStep,
|
|
32532
|
+
showTickLabels,
|
|
32533
|
+
showCurveLabels,
|
|
32534
|
+
curves,
|
|
32535
|
+
points,
|
|
32536
|
+
vectors,
|
|
32537
|
+
regions,
|
|
32538
|
+
bars,
|
|
32539
|
+
guides,
|
|
32540
|
+
angles,
|
|
32541
|
+
hops,
|
|
32542
|
+
shapes
|
|
32543
|
+
]);
|
|
30749
32544
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
30750
32545
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
30751
32546
|
/* @__PURE__ */ jsx(
|
|
@@ -30754,6 +32549,8 @@ var init_MathCanvas = __esm({
|
|
|
30754
32549
|
width,
|
|
30755
32550
|
height,
|
|
30756
32551
|
shapes: derivedShapes,
|
|
32552
|
+
readouts,
|
|
32553
|
+
traces,
|
|
30757
32554
|
interactive,
|
|
30758
32555
|
animate,
|
|
30759
32556
|
onShapeClick,
|
|
@@ -30765,7 +32562,315 @@ var init_MathCanvas = __esm({
|
|
|
30765
32562
|
};
|
|
30766
32563
|
}
|
|
30767
32564
|
});
|
|
30768
|
-
|
|
32565
|
+
function formatMeterValue(v) {
|
|
32566
|
+
return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
|
|
32567
|
+
}
|
|
32568
|
+
function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
|
|
32569
|
+
const out = [];
|
|
32570
|
+
const color = obj.color ?? "#334155";
|
|
32571
|
+
switch (obj.kind) {
|
|
32572
|
+
case "ground": {
|
|
32573
|
+
const xStart = obj.x1 ?? 0;
|
|
32574
|
+
const xEnd = obj.x2 ?? canvasWidth;
|
|
32575
|
+
const y = obj.y ?? 0;
|
|
32576
|
+
out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
|
|
32577
|
+
for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
|
|
32578
|
+
out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
|
|
32579
|
+
}
|
|
32580
|
+
if (obj.label) {
|
|
32581
|
+
out.push({
|
|
32582
|
+
type: "text",
|
|
32583
|
+
x: (xStart + xEnd) / 2,
|
|
32584
|
+
y: y - 10,
|
|
32585
|
+
text: obj.label,
|
|
32586
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32587
|
+
fontSize: 11,
|
|
32588
|
+
align: "center"
|
|
32589
|
+
});
|
|
32590
|
+
}
|
|
32591
|
+
break;
|
|
32592
|
+
}
|
|
32593
|
+
case "wall": {
|
|
32594
|
+
const yStart = obj.y1 ?? 0;
|
|
32595
|
+
const yEnd = obj.y2 ?? canvasHeight;
|
|
32596
|
+
const x = obj.x ?? 0;
|
|
32597
|
+
out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
|
|
32598
|
+
for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
|
|
32599
|
+
out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
|
|
32600
|
+
}
|
|
32601
|
+
if (obj.label) {
|
|
32602
|
+
out.push({
|
|
32603
|
+
type: "text",
|
|
32604
|
+
x: x + 12,
|
|
32605
|
+
y: (yStart + yEnd) / 2,
|
|
32606
|
+
text: obj.label,
|
|
32607
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32608
|
+
fontSize: 11,
|
|
32609
|
+
align: "left"
|
|
32610
|
+
});
|
|
32611
|
+
}
|
|
32612
|
+
break;
|
|
32613
|
+
}
|
|
32614
|
+
case "ramp": {
|
|
32615
|
+
const x1 = obj.x1 ?? 0;
|
|
32616
|
+
const y1 = obj.y1 ?? 0;
|
|
32617
|
+
const x2 = obj.x2 ?? canvasWidth;
|
|
32618
|
+
const y2 = obj.y2 ?? canvasHeight;
|
|
32619
|
+
out.push({
|
|
32620
|
+
type: "polygon",
|
|
32621
|
+
points: [
|
|
32622
|
+
{ x: x1, y: y1 },
|
|
32623
|
+
{ x: x2, y: y2 },
|
|
32624
|
+
{ x: x1, y: y2 }
|
|
32625
|
+
],
|
|
32626
|
+
color,
|
|
32627
|
+
fill: obj.fill ?? "#e2e8f0",
|
|
32628
|
+
lineWidth: 2
|
|
32629
|
+
});
|
|
32630
|
+
if (obj.label) {
|
|
32631
|
+
out.push({
|
|
32632
|
+
type: "text",
|
|
32633
|
+
x: (2 * x1 + x2) / 3,
|
|
32634
|
+
y: (y1 + 2 * y2) / 3,
|
|
32635
|
+
text: obj.label,
|
|
32636
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32637
|
+
fontSize: 11,
|
|
32638
|
+
align: "center"
|
|
32639
|
+
});
|
|
32640
|
+
}
|
|
32641
|
+
break;
|
|
32642
|
+
}
|
|
32643
|
+
case "box": {
|
|
32644
|
+
const x = obj.x ?? 0;
|
|
32645
|
+
const y = obj.y ?? 0;
|
|
32646
|
+
const w = obj.width ?? 40;
|
|
32647
|
+
const h = obj.height ?? 40;
|
|
32648
|
+
out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
|
|
32649
|
+
if (obj.label) {
|
|
32650
|
+
out.push({
|
|
32651
|
+
type: "text",
|
|
32652
|
+
x: x + w / 2,
|
|
32653
|
+
y: y + h / 2,
|
|
32654
|
+
text: obj.label,
|
|
32655
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32656
|
+
fontSize: 11,
|
|
32657
|
+
align: "center"
|
|
32658
|
+
});
|
|
32659
|
+
}
|
|
32660
|
+
break;
|
|
32661
|
+
}
|
|
32662
|
+
case "pivot": {
|
|
32663
|
+
const x = obj.x ?? 0;
|
|
32664
|
+
const y = obj.y ?? 0;
|
|
32665
|
+
out.push({ type: "circle", x, y, radius: 5, color, fill: color });
|
|
32666
|
+
out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
|
|
32667
|
+
for (let k = 0; k < 5; k++) {
|
|
32668
|
+
const hx = x - 14 + 7 * k;
|
|
32669
|
+
out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
|
|
32670
|
+
}
|
|
32671
|
+
if (obj.label) {
|
|
32672
|
+
out.push({
|
|
32673
|
+
type: "text",
|
|
32674
|
+
x,
|
|
32675
|
+
y: y - 20,
|
|
32676
|
+
text: obj.label,
|
|
32677
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32678
|
+
fontSize: 11,
|
|
32679
|
+
align: "center"
|
|
32680
|
+
});
|
|
32681
|
+
}
|
|
32682
|
+
break;
|
|
32683
|
+
}
|
|
32684
|
+
}
|
|
32685
|
+
return out;
|
|
32686
|
+
}
|
|
32687
|
+
function trailShapes(trail) {
|
|
32688
|
+
const n = trail.points.length;
|
|
32689
|
+
if (n < 2) return [];
|
|
32690
|
+
const color = trail.color ?? "#94a3b8";
|
|
32691
|
+
const lineWidth = trail.width ?? 2;
|
|
32692
|
+
const fade = trail.fade ?? true;
|
|
32693
|
+
const globalOpacity = trail.opacity ?? 1;
|
|
32694
|
+
const out = [];
|
|
32695
|
+
for (let i = 0; i < n - 1; i++) {
|
|
32696
|
+
const a = trail.points[i];
|
|
32697
|
+
const b = trail.points[i + 1];
|
|
32698
|
+
const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
|
|
32699
|
+
out.push({
|
|
32700
|
+
type: "line",
|
|
32701
|
+
x1: a.x,
|
|
32702
|
+
y1: a.y,
|
|
32703
|
+
x2: b.x,
|
|
32704
|
+
y2: b.y,
|
|
32705
|
+
color,
|
|
32706
|
+
lineWidth,
|
|
32707
|
+
opacity: segmentOpacity * globalOpacity
|
|
32708
|
+
});
|
|
32709
|
+
}
|
|
32710
|
+
return out;
|
|
32711
|
+
}
|
|
32712
|
+
function constraintShapes(c, a, b) {
|
|
32713
|
+
const color = c.color ?? "#9ca3af";
|
|
32714
|
+
const kind = c.kind ?? "rod";
|
|
32715
|
+
if (kind === "rod") {
|
|
32716
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
|
|
32717
|
+
}
|
|
32718
|
+
if (kind === "string") {
|
|
32719
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
|
|
32720
|
+
}
|
|
32721
|
+
const COILS = 8;
|
|
32722
|
+
const AMP = 7;
|
|
32723
|
+
const LEAD = 10;
|
|
32724
|
+
const dx = b.x - a.x;
|
|
32725
|
+
const dy = b.y - a.y;
|
|
32726
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
32727
|
+
const ux = dx / dist;
|
|
32728
|
+
const uy = dy / dist;
|
|
32729
|
+
const perpX = -uy;
|
|
32730
|
+
const perpY = ux;
|
|
32731
|
+
const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
|
|
32732
|
+
const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
|
|
32733
|
+
const m = 2 * COILS;
|
|
32734
|
+
const polyline = [{ x: a.x, y: a.y }, aPrime];
|
|
32735
|
+
for (let j = 1; j <= m; j++) {
|
|
32736
|
+
const t = j / (m + 1);
|
|
32737
|
+
const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
|
|
32738
|
+
const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
|
|
32739
|
+
const sign = j % 2 === 0 ? 1 : -1;
|
|
32740
|
+
polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
|
|
32741
|
+
}
|
|
32742
|
+
polyline.push(bPrime, { x: b.x, y: b.y });
|
|
32743
|
+
const out = [];
|
|
32744
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
32745
|
+
out.push({
|
|
32746
|
+
type: "line",
|
|
32747
|
+
x1: polyline[i - 1].x,
|
|
32748
|
+
y1: polyline[i - 1].y,
|
|
32749
|
+
x2: polyline[i].x,
|
|
32750
|
+
y2: polyline[i].y,
|
|
32751
|
+
color,
|
|
32752
|
+
lineWidth: 2
|
|
32753
|
+
});
|
|
32754
|
+
}
|
|
32755
|
+
return out;
|
|
32756
|
+
}
|
|
32757
|
+
function vectorShapes(v, bodyById) {
|
|
32758
|
+
let ax;
|
|
32759
|
+
let ay;
|
|
32760
|
+
if (v.body) {
|
|
32761
|
+
const anchor = bodyById.get(v.body);
|
|
32762
|
+
if (!anchor) return [];
|
|
32763
|
+
ax = anchor.x;
|
|
32764
|
+
ay = anchor.y;
|
|
32765
|
+
} else {
|
|
32766
|
+
ax = v.x ?? 0;
|
|
32767
|
+
ay = v.y ?? 0;
|
|
32768
|
+
}
|
|
32769
|
+
const scale = v.scale ?? 1;
|
|
32770
|
+
const color = v.color ?? "#dc2626";
|
|
32771
|
+
const tx = ax + v.dx * scale;
|
|
32772
|
+
const ty = ay + v.dy * scale;
|
|
32773
|
+
const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
|
|
32774
|
+
if (v.label) {
|
|
32775
|
+
const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
|
|
32776
|
+
const ux = (tx - ax) / dist;
|
|
32777
|
+
const uy = (ty - ay) / dist;
|
|
32778
|
+
out.push({
|
|
32779
|
+
type: "text",
|
|
32780
|
+
x: tx + 8 * ux,
|
|
32781
|
+
y: ty + 8 * uy,
|
|
32782
|
+
text: v.label,
|
|
32783
|
+
color,
|
|
32784
|
+
fontSize: 11,
|
|
32785
|
+
align: "center"
|
|
32786
|
+
});
|
|
32787
|
+
}
|
|
32788
|
+
return out;
|
|
32789
|
+
}
|
|
32790
|
+
function angleMarkerShapes(a) {
|
|
32791
|
+
const radius = a.radius ?? 26;
|
|
32792
|
+
const color = a.color ?? "#0ea5e9";
|
|
32793
|
+
const out = [
|
|
32794
|
+
{
|
|
32795
|
+
type: "ellipse",
|
|
32796
|
+
x: a.x,
|
|
32797
|
+
y: a.y,
|
|
32798
|
+
width: radius * 2,
|
|
32799
|
+
height: radius * 2,
|
|
32800
|
+
startAngle: a.from,
|
|
32801
|
+
endAngle: a.to,
|
|
32802
|
+
color,
|
|
32803
|
+
lineWidth: 2
|
|
32804
|
+
}
|
|
32805
|
+
];
|
|
32806
|
+
if (a.label) {
|
|
32807
|
+
const mid = (a.from + a.to) / 2 * (Math.PI / 180);
|
|
32808
|
+
out.push({
|
|
32809
|
+
type: "text",
|
|
32810
|
+
x: a.x + (radius + 13) * Math.cos(mid),
|
|
32811
|
+
y: a.y + (radius + 13) * Math.sin(mid),
|
|
32812
|
+
text: a.label,
|
|
32813
|
+
color,
|
|
32814
|
+
fontSize: 11,
|
|
32815
|
+
align: "center"
|
|
32816
|
+
});
|
|
32817
|
+
}
|
|
32818
|
+
return out;
|
|
32819
|
+
}
|
|
32820
|
+
function fieldShapes(field, canvasWidth, canvasHeight) {
|
|
32821
|
+
const spacing = field.spacing ?? 48;
|
|
32822
|
+
const size = field.size ?? 14;
|
|
32823
|
+
const color = field.color ?? "#94a3b8";
|
|
32824
|
+
const regionX = field.x ?? 0;
|
|
32825
|
+
const regionY = field.y ?? 0;
|
|
32826
|
+
const regionW = field.width ?? canvasWidth;
|
|
32827
|
+
const regionH = field.height ?? canvasHeight;
|
|
32828
|
+
const out = [];
|
|
32829
|
+
for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
|
|
32830
|
+
for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
|
|
32831
|
+
if (field.kind === "arrows") {
|
|
32832
|
+
const rad = (field.angle ?? 0) * Math.PI / 180;
|
|
32833
|
+
const hx = Math.cos(rad) * size / 2;
|
|
32834
|
+
const hy = Math.sin(rad) * size / 2;
|
|
32835
|
+
out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
|
|
32836
|
+
} else if (field.kind === "into") {
|
|
32837
|
+
const r2 = size / 3;
|
|
32838
|
+
const d = 0.6 * r2 * Math.SQRT1_2;
|
|
32839
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r2, color });
|
|
32840
|
+
out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
|
|
32841
|
+
out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
|
|
32842
|
+
} else {
|
|
32843
|
+
const r2 = size / 3;
|
|
32844
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r2, color });
|
|
32845
|
+
out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
|
|
32846
|
+
}
|
|
32847
|
+
}
|
|
32848
|
+
}
|
|
32849
|
+
return out;
|
|
32850
|
+
}
|
|
32851
|
+
function meterShapes(meters, canvasHeight) {
|
|
32852
|
+
const n = meters.length;
|
|
32853
|
+
const out = [];
|
|
32854
|
+
const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
|
|
32855
|
+
meters.forEach((meter, i) => {
|
|
32856
|
+
const rowY = canvasHeight - 10 - 16 * (n - i);
|
|
32857
|
+
const color = meter.color ?? "#3b82f6";
|
|
32858
|
+
const M = meter.max ?? sharedMax;
|
|
32859
|
+
const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
|
|
32860
|
+
out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
|
|
32861
|
+
out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
|
|
32862
|
+
out.push({
|
|
32863
|
+
type: "text",
|
|
32864
|
+
x: 166,
|
|
32865
|
+
y: rowY + 8,
|
|
32866
|
+
text: formatMeterValue(meter.value),
|
|
32867
|
+
color: "#6b7280",
|
|
32868
|
+
fontSize: 9
|
|
32869
|
+
});
|
|
32870
|
+
});
|
|
32871
|
+
return out;
|
|
32872
|
+
}
|
|
32873
|
+
var physicsLog2, PHYSICS_LABEL_COLOR, PhysicsCanvas;
|
|
30769
32874
|
var init_PhysicsCanvas = __esm({
|
|
30770
32875
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
30771
32876
|
"use client";
|
|
@@ -30774,6 +32879,7 @@ var init_PhysicsCanvas = __esm({
|
|
|
30774
32879
|
init_LearningCanvas();
|
|
30775
32880
|
init_learningScene3D();
|
|
30776
32881
|
physicsLog2 = createLogger("almadar:ui:physics-canvas");
|
|
32882
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
30777
32883
|
PhysicsCanvas = ({
|
|
30778
32884
|
className,
|
|
30779
32885
|
width = 600,
|
|
@@ -30790,7 +32896,18 @@ var init_PhysicsCanvas = __esm({
|
|
|
30790
32896
|
showForces = false,
|
|
30791
32897
|
velocityScale = 20,
|
|
30792
32898
|
forceScale = 20,
|
|
32899
|
+
sceneObjects = [],
|
|
32900
|
+
trails = [],
|
|
32901
|
+
vectors = [],
|
|
32902
|
+
surface3d,
|
|
32903
|
+
vectors3d = [],
|
|
32904
|
+
vectorScale = 1,
|
|
32905
|
+
angles = [],
|
|
32906
|
+
field,
|
|
32907
|
+
meters = [],
|
|
30793
32908
|
shapes = [],
|
|
32909
|
+
readouts,
|
|
32910
|
+
traces,
|
|
30794
32911
|
showGrid,
|
|
30795
32912
|
shadows,
|
|
30796
32913
|
interactive,
|
|
@@ -30805,19 +32922,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
30805
32922
|
for (const b of bodies) {
|
|
30806
32923
|
if (b.id) bodyById.set(b.id, b);
|
|
30807
32924
|
}
|
|
32925
|
+
if (field) out.push(...fieldShapes(field, width, height));
|
|
32926
|
+
for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
|
|
32927
|
+
for (const trail of trails) out.push(...trailShapes(trail));
|
|
30808
32928
|
for (const c of constraints) {
|
|
30809
32929
|
const a = bodyById.get(c.from);
|
|
30810
32930
|
const b = bodyById.get(c.to);
|
|
30811
32931
|
if (!a || !b) continue;
|
|
30812
|
-
out.push(
|
|
30813
|
-
type: "line",
|
|
30814
|
-
x1: a.x,
|
|
30815
|
-
y1: a.y,
|
|
30816
|
-
x2: b.x,
|
|
30817
|
-
y2: b.y,
|
|
30818
|
-
color: c.color ?? "#9ca3af",
|
|
30819
|
-
lineWidth: 2
|
|
30820
|
-
});
|
|
32932
|
+
out.push(...constraintShapes(c, a, b));
|
|
30821
32933
|
}
|
|
30822
32934
|
for (const b of bodies) {
|
|
30823
32935
|
out.push({
|
|
@@ -30862,14 +32974,51 @@ var init_PhysicsCanvas = __esm({
|
|
|
30862
32974
|
});
|
|
30863
32975
|
}
|
|
30864
32976
|
}
|
|
32977
|
+
for (const v of vectors) out.push(...vectorShapes(v, bodyById));
|
|
32978
|
+
for (const a of angles) out.push(...angleMarkerShapes(a));
|
|
32979
|
+
if (meters.length > 0) out.push(...meterShapes(meters, height));
|
|
30865
32980
|
out.push(...shapes);
|
|
30866
32981
|
return out;
|
|
30867
|
-
}, [
|
|
32982
|
+
}, [
|
|
32983
|
+
bodies,
|
|
32984
|
+
constraints,
|
|
32985
|
+
showVelocity,
|
|
32986
|
+
showForces,
|
|
32987
|
+
velocityScale,
|
|
32988
|
+
forceScale,
|
|
32989
|
+
sceneObjects,
|
|
32990
|
+
trails,
|
|
32991
|
+
vectors,
|
|
32992
|
+
angles,
|
|
32993
|
+
field,
|
|
32994
|
+
meters,
|
|
32995
|
+
shapes,
|
|
32996
|
+
width,
|
|
32997
|
+
height
|
|
32998
|
+
]);
|
|
30868
32999
|
const drawables3D = useMemo(() => {
|
|
30869
33000
|
if (mode !== "3d") return [];
|
|
30870
33001
|
if (shapes.length > 0) {
|
|
30871
33002
|
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
30872
33003
|
}
|
|
33004
|
+
if (sceneObjects.length > 0) {
|
|
33005
|
+
physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
|
|
33006
|
+
}
|
|
33007
|
+
if (vectors.length > 0) {
|
|
33008
|
+
physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
|
|
33009
|
+
}
|
|
33010
|
+
if (angles.length > 0) {
|
|
33011
|
+
physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
|
|
33012
|
+
}
|
|
33013
|
+
if (field) {
|
|
33014
|
+
physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
|
|
33015
|
+
}
|
|
33016
|
+
if (meters.length > 0) {
|
|
33017
|
+
physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
|
|
33018
|
+
}
|
|
33019
|
+
if (animate) {
|
|
33020
|
+
physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
33021
|
+
}
|
|
30873
33022
|
const out = [];
|
|
30874
33023
|
const labelColor = labelColorForBackground(backgroundColor);
|
|
30875
33024
|
const bodyById = /* @__PURE__ */ new Map();
|
|
@@ -30917,15 +33066,67 @@ var init_PhysicsCanvas = __esm({
|
|
|
30917
33066
|
if (arrow) out.push(arrow);
|
|
30918
33067
|
}
|
|
30919
33068
|
}
|
|
33069
|
+
for (const trail of trails) {
|
|
33070
|
+
if (trail.fade !== void 0) {
|
|
33071
|
+
physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
|
|
33072
|
+
}
|
|
33073
|
+
const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
|
|
33074
|
+
out.push(
|
|
33075
|
+
...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
|
|
33076
|
+
...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
|
|
33077
|
+
})
|
|
33078
|
+
);
|
|
33079
|
+
}
|
|
33080
|
+
if (surface3d) {
|
|
33081
|
+
out.push(...heightFieldMesh(surface3d));
|
|
33082
|
+
}
|
|
33083
|
+
if (vectors3d.length > 0) {
|
|
33084
|
+
out.push(
|
|
33085
|
+
...arrowField(
|
|
33086
|
+
vectors3d.map((v) => ({
|
|
33087
|
+
id: v.id,
|
|
33088
|
+
from: [v.x, v.y, v.z ?? 0],
|
|
33089
|
+
delta: [v.dx, v.dy, v.dz ?? 0],
|
|
33090
|
+
color: v.color,
|
|
33091
|
+
label: v.label,
|
|
33092
|
+
width: v.width
|
|
33093
|
+
})),
|
|
33094
|
+
{ scale: vectorScale, labelColor }
|
|
33095
|
+
)
|
|
33096
|
+
);
|
|
33097
|
+
}
|
|
30920
33098
|
return out;
|
|
30921
|
-
}, [
|
|
33099
|
+
}, [
|
|
33100
|
+
mode,
|
|
33101
|
+
bodies,
|
|
33102
|
+
constraints,
|
|
33103
|
+
showVelocity,
|
|
33104
|
+
showForces,
|
|
33105
|
+
velocityScale,
|
|
33106
|
+
forceScale,
|
|
33107
|
+
shapes,
|
|
33108
|
+
sceneObjects,
|
|
33109
|
+
trails,
|
|
33110
|
+
vectors,
|
|
33111
|
+
surface3d,
|
|
33112
|
+
vectors3d,
|
|
33113
|
+
vectorScale,
|
|
33114
|
+
angles,
|
|
33115
|
+
field,
|
|
33116
|
+
meters,
|
|
33117
|
+
animate,
|
|
33118
|
+
backgroundColor
|
|
33119
|
+
]);
|
|
30922
33120
|
const bodyIndexById = useMemo(() => {
|
|
30923
33121
|
const m = /* @__PURE__ */ new Map();
|
|
33122
|
+
vectors3d.forEach((v, i) => {
|
|
33123
|
+
if (v.id) m.set(v.id, i);
|
|
33124
|
+
});
|
|
30924
33125
|
bodies.forEach((b, i) => {
|
|
30925
33126
|
if (b.id) m.set(b.id, i);
|
|
30926
33127
|
});
|
|
30927
33128
|
return m;
|
|
30928
|
-
}, [bodies]);
|
|
33129
|
+
}, [bodies, vectors3d]);
|
|
30929
33130
|
if (mode === "3d") {
|
|
30930
33131
|
return /* @__PURE__ */ jsx(
|
|
30931
33132
|
LearningScene3D,
|
|
@@ -30957,6 +33158,8 @@ var init_PhysicsCanvas = __esm({
|
|
|
30957
33158
|
height,
|
|
30958
33159
|
backgroundColor,
|
|
30959
33160
|
shapes: derivedShapes,
|
|
33161
|
+
readouts,
|
|
33162
|
+
traces,
|
|
30960
33163
|
interactive: interactive ?? false,
|
|
30961
33164
|
animate,
|
|
30962
33165
|
onShapeClick,
|
|
@@ -31107,7 +33310,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
31107
33310
|
}
|
|
31108
33311
|
return nodeIds.map((id) => positions.get(id));
|
|
31109
33312
|
}
|
|
31110
|
-
function
|
|
33313
|
+
function layoutTree3(nodeIds, adjacency, roots, width, height, margin) {
|
|
31111
33314
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
31112
33315
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
31113
33316
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -31163,7 +33366,7 @@ function computeStaticLayout(mode, input) {
|
|
|
31163
33366
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
31164
33367
|
const roots = findRoots(nodeIds, adjacency);
|
|
31165
33368
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
31166
|
-
if (mode === "tree") return
|
|
33369
|
+
if (mode === "tree") return layoutTree3(nodeIds, adjacency, roots, width, height, margin);
|
|
31167
33370
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
31168
33371
|
}
|
|
31169
33372
|
var init_graphViewLayouts = __esm({
|
|
@@ -32406,26 +34609,6 @@ var init_Lightbox = __esm({
|
|
|
32406
34609
|
Lightbox.displayName = "Lightbox";
|
|
32407
34610
|
}
|
|
32408
34611
|
});
|
|
32409
|
-
function useMediaQuery(query) {
|
|
32410
|
-
const subscribe = useCallback(
|
|
32411
|
-
(onChange) => {
|
|
32412
|
-
const mql = window.matchMedia(query);
|
|
32413
|
-
mql.addEventListener("change", onChange);
|
|
32414
|
-
return () => mql.removeEventListener("change", onChange);
|
|
32415
|
-
},
|
|
32416
|
-
[query]
|
|
32417
|
-
);
|
|
32418
|
-
return useSyncExternalStore(
|
|
32419
|
-
subscribe,
|
|
32420
|
-
() => window.matchMedia(query).matches,
|
|
32421
|
-
() => false
|
|
32422
|
-
);
|
|
32423
|
-
}
|
|
32424
|
-
var init_useMediaQuery = __esm({
|
|
32425
|
-
"hooks/useMediaQuery.ts"() {
|
|
32426
|
-
"use client";
|
|
32427
|
-
}
|
|
32428
|
-
});
|
|
32429
34612
|
function renderIconInput3(icon, props) {
|
|
32430
34613
|
return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
|
|
32431
34614
|
}
|
|
@@ -32464,8 +34647,8 @@ function TableView({
|
|
|
32464
34647
|
columns,
|
|
32465
34648
|
fields,
|
|
32466
34649
|
itemActions,
|
|
32467
|
-
maxInlineActions,
|
|
32468
|
-
itemClickEvent,
|
|
34650
|
+
maxInlineActions: _maxInlineActions,
|
|
34651
|
+
itemClickEvent = "",
|
|
32469
34652
|
selectable = false,
|
|
32470
34653
|
selectEvent,
|
|
32471
34654
|
selectedIds,
|
|
@@ -32515,7 +34698,6 @@ function TableView({
|
|
|
32515
34698
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
32516
34699
|
const hasRenderProp = typeof children === "function";
|
|
32517
34700
|
const idField = dndItemIdField ?? "id";
|
|
32518
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
32519
34701
|
React94__default.useEffect(() => {
|
|
32520
34702
|
tableViewLog.debug("render", {
|
|
32521
34703
|
rowCount: data.length,
|
|
@@ -32557,21 +34739,14 @@ function TableView({
|
|
|
32557
34739
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
32558
34740
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
32559
34741
|
};
|
|
32560
|
-
const
|
|
32561
|
-
e.stopPropagation();
|
|
32562
|
-
const payload = {
|
|
32563
|
-
id: row.id,
|
|
32564
|
-
row
|
|
32565
|
-
};
|
|
32566
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
32567
|
-
};
|
|
34742
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
32568
34743
|
const handleRowClick = (row) => () => {
|
|
32569
|
-
if (!
|
|
34744
|
+
if (!rowClickEvent) return;
|
|
32570
34745
|
const payload = {
|
|
32571
34746
|
id: row.id,
|
|
32572
34747
|
row
|
|
32573
34748
|
};
|
|
32574
|
-
eventBus.emit(`UI:${
|
|
34749
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
32575
34750
|
};
|
|
32576
34751
|
const colFloors = React94__default.useMemo(
|
|
32577
34752
|
() => colDefs.map((col) => {
|
|
@@ -32587,10 +34762,7 @@ function TableView({
|
|
|
32587
34762
|
const statusNode = isLoading ? /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) }) : error ? /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "error", children: error.message }) }) : data.length === 0 ? /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) }) : null;
|
|
32588
34763
|
const lk = LOOKS[look];
|
|
32589
34764
|
const hasActions = actionDefs.length > 0;
|
|
32590
|
-
const
|
|
32591
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
32592
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
32593
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
34765
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
32594
34766
|
const gridTemplateColumns = [
|
|
32595
34767
|
selectable ? "auto" : null,
|
|
32596
34768
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -32637,7 +34809,7 @@ function TableView({
|
|
|
32637
34809
|
col.key
|
|
32638
34810
|
);
|
|
32639
34811
|
}),
|
|
32640
|
-
hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
|
|
34812
|
+
hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)] border-l border-[var(--color-border)] h-full" })
|
|
32641
34813
|
]
|
|
32642
34814
|
}
|
|
32643
34815
|
);
|
|
@@ -32649,12 +34821,12 @@ function TableView({
|
|
|
32649
34821
|
role: "row",
|
|
32650
34822
|
"data-entity-row": true,
|
|
32651
34823
|
"data-entity-id": id,
|
|
32652
|
-
onClick:
|
|
34824
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
32653
34825
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
32654
34826
|
className: cn(
|
|
32655
34827
|
"group items-center gap-3 transition-colors duration-fast",
|
|
32656
34828
|
hasRenderProp ? "flex" : "grid",
|
|
32657
|
-
|
|
34829
|
+
rowClickEvent && "cursor-pointer",
|
|
32658
34830
|
lk.rowPad,
|
|
32659
34831
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
32660
34832
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -32662,7 +34834,7 @@ function TableView({
|
|
|
32662
34834
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
32663
34835
|
),
|
|
32664
34836
|
children: [
|
|
32665
|
-
selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", onClick:
|
|
34837
|
+
selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsx(
|
|
32666
34838
|
Checkbox,
|
|
32667
34839
|
{
|
|
32668
34840
|
checked: selected.has(id),
|
|
@@ -32683,53 +34855,37 @@ function TableView({
|
|
|
32683
34855
|
}
|
|
32684
34856
|
return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
|
|
32685
34857
|
}),
|
|
32686
|
-
hasActions && /* @__PURE__ */
|
|
34858
|
+
hasActions && /* @__PURE__ */ jsx(
|
|
32687
34859
|
HStack,
|
|
32688
34860
|
{
|
|
32689
34861
|
gap: "xs",
|
|
32690
|
-
onClick:
|
|
34862
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
32691
34863
|
className: cn(
|
|
32692
34864
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
32693
|
-
// scroll container, which
|
|
32694
|
-
// Opaque so
|
|
34865
|
+
// scroll container, which would leave the kebab off-screen.
|
|
34866
|
+
// Opaque + hairline edge so it reads as a pinned column, not a
|
|
34867
|
+
// floating control, while scrolled cells pass underneath.
|
|
32695
34868
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
34869
|
+
"border-l border-[var(--color-border)]",
|
|
32696
34870
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
32697
34871
|
),
|
|
32698
|
-
children:
|
|
32699
|
-
|
|
32700
|
-
|
|
32701
|
-
|
|
32702
|
-
|
|
32703
|
-
|
|
32704
|
-
|
|
32705
|
-
|
|
32706
|
-
|
|
32707
|
-
|
|
32708
|
-
|
|
32709
|
-
|
|
32710
|
-
|
|
32711
|
-
|
|
32712
|
-
}
|
|
32713
|
-
|
|
32714
|
-
|
|
32715
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsx(
|
|
32716
|
-
Menu,
|
|
32717
|
-
{
|
|
32718
|
-
position: "bottom-end",
|
|
32719
|
-
trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
32720
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
32721
|
-
label: action.label,
|
|
32722
|
-
icon: action.icon,
|
|
32723
|
-
event: action.event,
|
|
32724
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
32725
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
32726
|
-
id: row.id,
|
|
32727
|
-
row
|
|
32728
|
-
})
|
|
32729
|
-
}))
|
|
32730
|
-
}
|
|
32731
|
-
)
|
|
32732
|
-
]
|
|
34872
|
+
children: /* @__PURE__ */ jsx(
|
|
34873
|
+
Menu,
|
|
34874
|
+
{
|
|
34875
|
+
position: "bottom-end",
|
|
34876
|
+
trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", "data-row-id": String(row.id), children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
34877
|
+
items: actionDefs.map((action) => ({
|
|
34878
|
+
label: action.label,
|
|
34879
|
+
icon: action.icon,
|
|
34880
|
+
event: action.event,
|
|
34881
|
+
variant: action.variant === "danger" ? "danger" : "default",
|
|
34882
|
+
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
34883
|
+
id: row.id,
|
|
34884
|
+
row
|
|
34885
|
+
})
|
|
34886
|
+
}))
|
|
34887
|
+
}
|
|
34888
|
+
)
|
|
32733
34889
|
}
|
|
32734
34890
|
)
|
|
32735
34891
|
]
|
|
@@ -32772,7 +34928,6 @@ var init_TableView = __esm({
|
|
|
32772
34928
|
init_format();
|
|
32773
34929
|
init_getNestedValue();
|
|
32774
34930
|
init_useEventBus();
|
|
32775
|
-
init_useMediaQuery();
|
|
32776
34931
|
init_Box();
|
|
32777
34932
|
init_Stack();
|
|
32778
34933
|
init_Typography();
|
|
@@ -47441,6 +49596,7 @@ var init_component_registry_generated = __esm({
|
|
|
47441
49596
|
init_ActionTile();
|
|
47442
49597
|
init_ActivationBlock();
|
|
47443
49598
|
init_ComponentPatterns();
|
|
49599
|
+
init_AlgoGraphCanvas();
|
|
47444
49600
|
init_AlgorithmCanvas();
|
|
47445
49601
|
init_AnimatedCounter();
|
|
47446
49602
|
init_AnimatedGraphic();
|
|
@@ -47704,6 +49860,7 @@ var init_component_registry_generated = __esm({
|
|
|
47704
49860
|
"ActivationBlock": ActivationBlock,
|
|
47705
49861
|
"Alert": AlertPattern,
|
|
47706
49862
|
"AlertPattern": AlertPattern,
|
|
49863
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
47707
49864
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
47708
49865
|
"AnimatedCounter": AnimatedCounter,
|
|
47709
49866
|
"AnimatedGraphic": AnimatedGraphic,
|
|
@@ -52576,9 +54733,9 @@ var log8 = createLogger("almadar:ui:effects:client-handlers");
|
|
|
52576
54733
|
function createClientEffectHandlers(options) {
|
|
52577
54734
|
const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
|
|
52578
54735
|
return {
|
|
52579
|
-
emit: (event, payload) => {
|
|
54736
|
+
emit: (event, payload, source) => {
|
|
52580
54737
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
52581
|
-
eventBus.emit(prefixedEvent, payload);
|
|
54738
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
52582
54739
|
},
|
|
52583
54740
|
persist: async () => {
|
|
52584
54741
|
log8.warn("persist is server-side only, ignored on client");
|
|
@@ -52743,7 +54900,7 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
|
|
|
52743
54900
|
}
|
|
52744
54901
|
};
|
|
52745
54902
|
ctx.emit = (event, payload) => {
|
|
52746
|
-
emit(event, payload);
|
|
54903
|
+
emit(event, payload, { trait: traitName, tick: tick.name });
|
|
52747
54904
|
};
|
|
52748
54905
|
if (tick.guard !== void 0 && !evaluateGuard(tick.guard, ctx)) {
|
|
52749
54906
|
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
@@ -53195,6 +55352,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
53195
55352
|
}
|
|
53196
55353
|
const effectContext = {
|
|
53197
55354
|
traitName,
|
|
55355
|
+
orbitalName: orbitalsByTrait?.[traitName],
|
|
53198
55356
|
state: previousState,
|
|
53199
55357
|
transition: `${previousState}->${newState}`,
|
|
53200
55358
|
linkedEntity,
|
|
@@ -53271,7 +55429,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
53271
55429
|
});
|
|
53272
55430
|
}
|
|
53273
55431
|
return emittedDuringExec;
|
|
53274
|
-
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot]);
|
|
55432
|
+
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot, orbitalsByTrait]);
|
|
53275
55433
|
const runTickEffects = useCallback((tick, binding) => {
|
|
53276
55434
|
const traitName = binding.trait.name;
|
|
53277
55435
|
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
@@ -53307,9 +55465,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
53307
55465
|
log: tickLog
|
|
53308
55466
|
});
|
|
53309
55467
|
}, [executeTransitionEffects, sharedEntityStore]);
|
|
53310
|
-
const emitFromSharedWriter = useCallback((event, payload) => {
|
|
55468
|
+
const emitFromSharedWriter = useCallback((event, payload, source) => {
|
|
53311
55469
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
53312
|
-
eventBus.emit(prefixedEvent, payload);
|
|
55470
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
53313
55471
|
}, [eventBus]);
|
|
53314
55472
|
useEffect(() => {
|
|
53315
55473
|
const scheduler = createTickScheduler();
|