@almadar/ui 5.148.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/{GameAudioProvider-CPGwD49P.d.cts → GameAudioProvider-B48iXwz3.d.ts} +2 -50
- package/dist/{GameAudioProvider-CPGwD49P.d.ts → GameAudioProvider-CQAdPreB.d.cts} +2 -50
- package/dist/avl/index.cjs +2741 -158
- package/dist/avl/index.js +2741 -158
- package/dist/{avl-schema-parser-DVmrgdwc.d.cts → avl-schema-parser-CVzkNzg7.d.cts} +18 -9
- package/dist/{avl-schema-parser-BRJ77Yze.d.ts → avl-schema-parser-Cx_4SVg9.d.ts} +18 -9
- package/dist/{cn-BAn68sNO.d.cts → cn-BnAJZcNb.d.cts} +2 -8
- package/dist/{cn-CWxxLkri.d.ts → cn-DJTUjk1M.d.ts} +2 -8
- package/dist/components/index.cjs +2764 -158
- package/dist/components/index.d.cts +1077 -107
- package/dist/components/index.d.ts +1077 -107
- package/dist/components/index.js +2760 -160
- package/dist/hooks/index.cjs +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/lib/drawable/three/index.cjs +57 -30
- package/dist/lib/drawable/three/index.d.cts +4 -3
- package/dist/lib/drawable/three/index.d.ts +4 -3
- package/dist/lib/drawable/three/index.js +57 -30
- package/dist/lib/index.d.cts +3 -2
- package/dist/lib/index.d.ts +3 -2
- package/dist/marketing/index.cjs +1 -1
- package/dist/marketing/index.js +1 -1
- package/dist/{paintDispatch-Cb_hQj4Y.d.ts → paintDispatch-BjZjUbcb.d.cts} +63 -69
- package/dist/{paintDispatch-Cb_hQj4Y.d.cts → paintDispatch-CxdLjHQq.d.ts} +63 -69
- package/dist/providers/index.cjs +2734 -152
- package/dist/providers/index.d.cts +2 -1
- package/dist/providers/index.d.ts +2 -1
- package/dist/providers/index.js +2734 -152
- package/dist/runtime/index.cjs +2741 -158
- package/dist/runtime/index.d.cts +2 -2
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2741 -158
- package/dist/types-B3nHgnMG.d.cts +51 -0
- package/dist/types-B3nHgnMG.d.ts +51 -0
- 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);
|
|
@@ -14041,9 +14041,10 @@ function Canvas({
|
|
|
14041
14041
|
drawables: [...drawables ?? [], ...childDrawables],
|
|
14042
14042
|
isLoading,
|
|
14043
14043
|
cameraMode: to3DCameraMode(camera?.mode),
|
|
14044
|
-
...zoom !== void 0 ? {
|
|
14044
|
+
...zoom !== void 0 ? { zoom } : {},
|
|
14045
14045
|
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
14046
14046
|
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
14047
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
14047
14048
|
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
14048
14049
|
unitScale,
|
|
14049
14050
|
backgroundColor,
|
|
@@ -15044,6 +15045,14 @@ function shapeBounds(shape) {
|
|
|
15044
15045
|
w: shape.radius * 2 + 8,
|
|
15045
15046
|
h: shape.radius * 2 + 8
|
|
15046
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
|
+
};
|
|
15047
15056
|
case "rect":
|
|
15048
15057
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
15049
15058
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -15077,13 +15086,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
15077
15086
|
ctx.closePath();
|
|
15078
15087
|
ctx.fill();
|
|
15079
15088
|
}
|
|
15080
|
-
function drawShape(ctx, shape, width, height) {
|
|
15089
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
15081
15090
|
ctx.save();
|
|
15082
15091
|
const opacity = shape.opacity ?? 1;
|
|
15083
15092
|
ctx.globalAlpha = opacity;
|
|
15084
15093
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
15085
15094
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
15086
15095
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
15096
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
15087
15097
|
switch (shape.type) {
|
|
15088
15098
|
case "grid": {
|
|
15089
15099
|
const step = shape.step ?? 40;
|
|
@@ -15149,6 +15159,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
15149
15159
|
ctx.stroke();
|
|
15150
15160
|
break;
|
|
15151
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
|
+
}
|
|
15152
15176
|
case "rect": {
|
|
15153
15177
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
15154
15178
|
if (fill) {
|
|
@@ -15195,21 +15219,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
15195
15219
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
15196
15220
|
break;
|
|
15197
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
|
+
}
|
|
15198
15255
|
}
|
|
15199
15256
|
ctx.restore();
|
|
15200
15257
|
}
|
|
15201
|
-
|
|
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;
|
|
15202
15354
|
var init_LearningCanvas = __esm({
|
|
15203
15355
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
15204
15356
|
"use client";
|
|
15205
15357
|
init_cn();
|
|
15206
15358
|
init_useEventBus();
|
|
15359
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
15360
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
15207
15361
|
LearningCanvas = ({
|
|
15208
15362
|
className,
|
|
15209
15363
|
width = 600,
|
|
15210
15364
|
height = 400,
|
|
15211
15365
|
backgroundColor,
|
|
15212
15366
|
shapes = [],
|
|
15367
|
+
readouts,
|
|
15368
|
+
traces,
|
|
15213
15369
|
interactive = false,
|
|
15214
15370
|
animate = false,
|
|
15215
15371
|
onShapeClick,
|
|
@@ -15235,6 +15391,12 @@ var init_LearningCanvas = __esm({
|
|
|
15235
15391
|
}
|
|
15236
15392
|
return -1;
|
|
15237
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]);
|
|
15238
15400
|
const draw = useCallback(() => {
|
|
15239
15401
|
const canvas = canvasRef.current;
|
|
15240
15402
|
if (!canvas) return;
|
|
@@ -15251,13 +15413,13 @@ var init_LearningCanvas = __esm({
|
|
|
15251
15413
|
ctx.fillStyle = backgroundColor;
|
|
15252
15414
|
ctx.fillRect(0, 0, width, height);
|
|
15253
15415
|
}
|
|
15254
|
-
for (const shape of
|
|
15255
|
-
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);
|
|
15256
15418
|
}
|
|
15257
|
-
for (const shape of
|
|
15258
|
-
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);
|
|
15259
15421
|
}
|
|
15260
|
-
}, [width, height, backgroundColor,
|
|
15422
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
15261
15423
|
useEffect(() => {
|
|
15262
15424
|
draw();
|
|
15263
15425
|
}, [draw]);
|
|
@@ -16807,7 +16969,363 @@ var init_ComponentPatterns = __esm({
|
|
|
16807
16969
|
AlertPattern.displayName = "AlertPattern";
|
|
16808
16970
|
}
|
|
16809
16971
|
});
|
|
16810
|
-
|
|
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;
|
|
16811
17329
|
var init_AlgorithmCanvas = __esm({
|
|
16812
17330
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
16813
17331
|
"use client";
|
|
@@ -16819,6 +17337,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16819
17337
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
16820
17338
|
POINTER_BAND = 34;
|
|
16821
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;
|
|
16822
17377
|
AlgorithmCanvas = ({
|
|
16823
17378
|
className,
|
|
16824
17379
|
width = 600,
|
|
@@ -16828,6 +17383,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16828
17383
|
bars = [],
|
|
16829
17384
|
cells = [],
|
|
16830
17385
|
pointers = [],
|
|
17386
|
+
ranges = [],
|
|
17387
|
+
slots = [],
|
|
17388
|
+
slotOrientation = "horizontal",
|
|
17389
|
+
frames = [],
|
|
17390
|
+
buckets = [],
|
|
17391
|
+
auxBars = [],
|
|
17392
|
+
rowLabels = [],
|
|
17393
|
+
colLabels = [],
|
|
16831
17394
|
shapes = [],
|
|
16832
17395
|
interactive = false,
|
|
16833
17396
|
animate = false,
|
|
@@ -16837,12 +17400,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16837
17400
|
}) => {
|
|
16838
17401
|
const derivedShapes = useMemo(() => {
|
|
16839
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
|
+
});
|
|
16840
17420
|
if (bars.length > 0) {
|
|
17421
|
+
const panelYBars = panelY.bars;
|
|
16841
17422
|
const slot = width / bars.length;
|
|
16842
17423
|
const barW = slot * 0.8;
|
|
16843
17424
|
const gap = slot * 0.1;
|
|
16844
|
-
const
|
|
16845
|
-
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);
|
|
16846
17432
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
16847
17433
|
bars.forEach((bar, i) => {
|
|
16848
17434
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -16872,6 +17458,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16872
17458
|
});
|
|
16873
17459
|
}
|
|
16874
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
|
+
}
|
|
16875
17544
|
pointers.forEach((p) => {
|
|
16876
17545
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
16877
17546
|
const cx = p.index * slot + slot / 2;
|
|
@@ -16879,7 +17548,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16879
17548
|
out.push({
|
|
16880
17549
|
type: "arrow",
|
|
16881
17550
|
x1: cx,
|
|
16882
|
-
y1:
|
|
17551
|
+
y1: panelYBars + primaryH - 18,
|
|
16883
17552
|
x2: cx,
|
|
16884
17553
|
y2: baseline + 4,
|
|
16885
17554
|
color,
|
|
@@ -16889,7 +17558,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16889
17558
|
out.push({
|
|
16890
17559
|
type: "text",
|
|
16891
17560
|
x: cx,
|
|
16892
|
-
y:
|
|
17561
|
+
y: panelYBars + primaryH - 8,
|
|
16893
17562
|
text: p.label,
|
|
16894
17563
|
color,
|
|
16895
17564
|
fontSize: 11,
|
|
@@ -16898,14 +17567,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16898
17567
|
}
|
|
16899
17568
|
});
|
|
16900
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
|
+
}
|
|
16901
17662
|
if (cells.length > 0) {
|
|
17663
|
+
const panelYCells = panelY.cells;
|
|
16902
17664
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
16903
17665
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
16904
|
-
const
|
|
16905
|
-
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;
|
|
16906
17672
|
cells.forEach((c, i) => {
|
|
16907
|
-
const x = c.col * cw;
|
|
16908
|
-
const y = c.row * ch;
|
|
17673
|
+
const x = gridX0 + c.col * cw;
|
|
17674
|
+
const y = gridY0 + c.row * ch;
|
|
16909
17675
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
16910
17676
|
out.push({
|
|
16911
17677
|
type: "rect",
|
|
@@ -16929,11 +17695,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16929
17695
|
align: "center"
|
|
16930
17696
|
});
|
|
16931
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
|
+
}
|
|
16932
17894
|
});
|
|
16933
17895
|
}
|
|
16934
17896
|
out.push(...shapes);
|
|
16935
17897
|
return out;
|
|
16936
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
17898
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
16937
17899
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
16938
17900
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
16939
17901
|
/* @__PURE__ */ jsx(
|
|
@@ -17095,23 +18057,422 @@ var init_AuthLayout = __esm({
|
|
|
17095
18057
|
AuthLayout.displayName = "AuthLayout";
|
|
17096
18058
|
}
|
|
17097
18059
|
});
|
|
17098
|
-
|
|
18060
|
+
function LearningScene3D({
|
|
18061
|
+
className,
|
|
18062
|
+
width = 600,
|
|
18063
|
+
height = 400,
|
|
18064
|
+
title,
|
|
18065
|
+
backgroundColor,
|
|
18066
|
+
drawables,
|
|
18067
|
+
camera,
|
|
18068
|
+
lighting,
|
|
18069
|
+
post,
|
|
18070
|
+
showGrid = false,
|
|
18071
|
+
shadows,
|
|
18072
|
+
interactive,
|
|
18073
|
+
isLoading,
|
|
18074
|
+
error,
|
|
18075
|
+
onItemClick
|
|
18076
|
+
}) {
|
|
18077
|
+
const instanceId = useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
18078
|
+
const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
|
|
18079
|
+
const onItemClickRef = useRef(onItemClick);
|
|
18080
|
+
onItemClickRef.current = onItemClick;
|
|
18081
|
+
useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
|
|
18082
|
+
const unitId = event.payload?.unitId;
|
|
18083
|
+
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
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
|
+
}
|
|
18091
|
+
const props3d = {
|
|
18092
|
+
drawables,
|
|
18093
|
+
isLoading,
|
|
18094
|
+
error: error ? error.message : null,
|
|
18095
|
+
cameraMode: camera?.mode ?? "perspective",
|
|
18096
|
+
...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
|
|
18097
|
+
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
18098
|
+
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
18099
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
18100
|
+
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
18101
|
+
backgroundColor,
|
|
18102
|
+
showGrid,
|
|
18103
|
+
...shadows !== void 0 ? { shadows } : {},
|
|
18104
|
+
...interactive !== void 0 ? { controlsEnabled: interactive } : {},
|
|
18105
|
+
lighting,
|
|
18106
|
+
post,
|
|
18107
|
+
...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
|
|
18108
|
+
};
|
|
18109
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
18110
|
+
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
18111
|
+
/* @__PURE__ */ jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost2, { ...props3d }) }) })
|
|
18112
|
+
] }) });
|
|
18113
|
+
}
|
|
18114
|
+
function meshSphere(id, x, y, z, radius, color, opts) {
|
|
18115
|
+
return {
|
|
18116
|
+
type: "draw-mesh",
|
|
18117
|
+
...id !== void 0 ? { id } : {},
|
|
18118
|
+
shape: opts?.shape ?? "sphere",
|
|
18119
|
+
position: { x, y, z },
|
|
18120
|
+
radius,
|
|
18121
|
+
pivot: "center",
|
|
18122
|
+
segments: opts?.segments ?? 24,
|
|
18123
|
+
material: { color, ...opts?.material },
|
|
18124
|
+
...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
|
|
18125
|
+
};
|
|
18126
|
+
}
|
|
18127
|
+
function axisRotation(from, to) {
|
|
18128
|
+
const wx = to[0] - from[0];
|
|
18129
|
+
const wy = to[2] - from[2];
|
|
18130
|
+
const wz = to[1] - from[1];
|
|
18131
|
+
const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
|
|
18132
|
+
const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
|
|
18133
|
+
return [0, Math.atan2(wz, -wx), tilt];
|
|
18134
|
+
}
|
|
18135
|
+
function segmentLength(from, to) {
|
|
18136
|
+
const dx = to[0] - from[0];
|
|
18137
|
+
const dy = to[1] - from[1];
|
|
18138
|
+
const dz = to[2] - from[2];
|
|
18139
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
18140
|
+
}
|
|
18141
|
+
function cylinderBetween(from, to, radius, color) {
|
|
18142
|
+
const len = segmentLength(from, to);
|
|
18143
|
+
if (len < 1e-6) return null;
|
|
18144
|
+
return {
|
|
18145
|
+
type: "draw-mesh",
|
|
18146
|
+
shape: "cylinder",
|
|
18147
|
+
position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
|
|
18148
|
+
radius,
|
|
18149
|
+
height: len,
|
|
18150
|
+
rotation: axisRotation(from, to),
|
|
18151
|
+
pivot: "center",
|
|
18152
|
+
segments: 12,
|
|
18153
|
+
material: { color }
|
|
18154
|
+
};
|
|
18155
|
+
}
|
|
18156
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
18157
|
+
const len = segmentLength(from, to);
|
|
18158
|
+
if (len < 1e-6) return null;
|
|
18159
|
+
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
18160
|
+
const k = (len - tipLen) / len;
|
|
18161
|
+
const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
|
|
18162
|
+
const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
|
|
18163
|
+
const tipStart = shaftEnd;
|
|
18164
|
+
const tipEnd = delta;
|
|
18165
|
+
const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
|
|
18166
|
+
const tipLenActual = segmentLength(tipStart, tipEnd);
|
|
18167
|
+
const tip = {
|
|
18168
|
+
type: "draw-mesh",
|
|
18169
|
+
shape: "cone",
|
|
18170
|
+
position: {
|
|
18171
|
+
x: (tipStart[0] + tipEnd[0]) / 2,
|
|
18172
|
+
y: (tipStart[1] + tipEnd[1]) / 2,
|
|
18173
|
+
z: (tipStart[2] + tipEnd[2]) / 2
|
|
18174
|
+
},
|
|
18175
|
+
radius: shaftRadius * 3,
|
|
18176
|
+
height: tipLenActual,
|
|
18177
|
+
rotation: axisRotation(tipStart, tipEnd),
|
|
18178
|
+
pivot: "center",
|
|
18179
|
+
segments: 12,
|
|
18180
|
+
material: { color }
|
|
18181
|
+
};
|
|
18182
|
+
return {
|
|
18183
|
+
type: "draw-group",
|
|
18184
|
+
...id !== void 0 ? { id } : {},
|
|
18185
|
+
position: { x: from[0], y: from[1], z: from[2] },
|
|
18186
|
+
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
18187
|
+
};
|
|
18188
|
+
}
|
|
18189
|
+
function billboardLabel(text, x, y, z, opts) {
|
|
18190
|
+
return {
|
|
18191
|
+
type: "draw-text",
|
|
18192
|
+
text,
|
|
18193
|
+
position: { x, y, z },
|
|
18194
|
+
color: opts?.color ?? "#111827"
|
|
18195
|
+
};
|
|
18196
|
+
}
|
|
18197
|
+
function labelColorForBackground(backgroundColor) {
|
|
18198
|
+
const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
|
|
18199
|
+
if (!m) return "#111827";
|
|
18200
|
+
const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
|
|
18201
|
+
const r2 = parseInt(hex.slice(0, 2), 16) / 255;
|
|
18202
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
18203
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
18204
|
+
const luminance = 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
|
|
18205
|
+
return luminance < 0.5 ? "#e5e7eb" : "#111827";
|
|
18206
|
+
}
|
|
18207
|
+
function get3DClickPayload(onShapeClick, idToIndex) {
|
|
18208
|
+
if (!onShapeClick) return void 0;
|
|
18209
|
+
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
18210
|
+
}
|
|
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;
|
|
18418
|
+
var init_learningScene3D = __esm({
|
|
18419
|
+
"components/learning/molecules/learningScene3D.tsx"() {
|
|
18420
|
+
"use client";
|
|
18421
|
+
init_atoms();
|
|
18422
|
+
init_Stack();
|
|
18423
|
+
init_useEventBus();
|
|
18424
|
+
sceneLog = createLogger("almadar:ui:learning-scene-3d");
|
|
18425
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
18426
|
+
Canvas3DHost2 = lazy(
|
|
18427
|
+
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
18428
|
+
);
|
|
18429
|
+
LearningScene3D.displayName = "LearningScene3D";
|
|
18430
|
+
}
|
|
18431
|
+
});
|
|
18432
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
17099
18433
|
var init_BiologyCanvas = __esm({
|
|
17100
18434
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
17101
18435
|
"use client";
|
|
17102
18436
|
init_atoms();
|
|
17103
18437
|
init_Stack();
|
|
17104
18438
|
init_LearningCanvas();
|
|
18439
|
+
init_learningScene3D();
|
|
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
|
+
};
|
|
17105
18452
|
BiologyCanvas = ({
|
|
17106
18453
|
className,
|
|
17107
18454
|
width = 600,
|
|
17108
18455
|
height = 400,
|
|
17109
18456
|
title,
|
|
17110
18457
|
backgroundColor,
|
|
18458
|
+
mode = "2d",
|
|
18459
|
+
camera,
|
|
18460
|
+
lighting,
|
|
18461
|
+
post,
|
|
17111
18462
|
nodes = [],
|
|
17112
18463
|
edges = [],
|
|
18464
|
+
compartments = [],
|
|
18465
|
+
bands = [],
|
|
18466
|
+
stages = [],
|
|
18467
|
+
stageStyle = "timeline",
|
|
18468
|
+
helix,
|
|
18469
|
+
helix3d,
|
|
17113
18470
|
shapes = [],
|
|
17114
|
-
|
|
18471
|
+
readouts,
|
|
18472
|
+
traces,
|
|
18473
|
+
showGrid,
|
|
18474
|
+
shadows,
|
|
18475
|
+
interactive,
|
|
17115
18476
|
animate = false,
|
|
17116
18477
|
onShapeClick,
|
|
17117
18478
|
isLoading,
|
|
@@ -17123,19 +18484,148 @@ var init_BiologyCanvas = __esm({
|
|
|
17123
18484
|
for (const n of nodes) {
|
|
17124
18485
|
if (n.id) nodeById.set(n.id, n);
|
|
17125
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
|
+
}
|
|
17126
18596
|
for (const e of edges) {
|
|
17127
18597
|
const a = nodeById.get(e.from);
|
|
17128
18598
|
const b = nodeById.get(e.to);
|
|
17129
18599
|
if (!a || !b) continue;
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
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
|
+
}
|
|
17139
18629
|
if (e.label) {
|
|
17140
18630
|
out.push({
|
|
17141
18631
|
type: "text",
|
|
@@ -17148,6 +18638,8 @@ var init_BiologyCanvas = __esm({
|
|
|
17148
18638
|
}
|
|
17149
18639
|
}
|
|
17150
18640
|
for (const n of nodes) {
|
|
18641
|
+
const state = n.state ?? "default";
|
|
18642
|
+
const muted = state === "muted";
|
|
17151
18643
|
out.push({
|
|
17152
18644
|
type: "circle",
|
|
17153
18645
|
x: n.x,
|
|
@@ -17155,23 +18647,197 @@ var init_BiologyCanvas = __esm({
|
|
|
17155
18647
|
radius: n.radius ?? 16,
|
|
17156
18648
|
color: n.color ?? "#16a34a",
|
|
17157
18649
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
17158
|
-
id: n.id
|
|
18650
|
+
id: n.id,
|
|
18651
|
+
...muted ? { opacity: 0.35 } : {}
|
|
17159
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
|
+
}
|
|
17160
18663
|
if (n.label) {
|
|
17161
18664
|
out.push({
|
|
17162
18665
|
type: "text",
|
|
17163
18666
|
x: n.x,
|
|
17164
18667
|
y: n.y + (n.radius ?? 16) + 14,
|
|
17165
18668
|
text: n.label,
|
|
18669
|
+
...muted ? { opacity: 0.35 } : {},
|
|
17166
18670
|
color: "#111827",
|
|
17167
18671
|
fontSize: 12,
|
|
17168
18672
|
align: "center"
|
|
17169
18673
|
});
|
|
17170
18674
|
}
|
|
17171
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
|
+
}
|
|
17172
18752
|
out.push(...shapes);
|
|
17173
18753
|
return out;
|
|
17174
|
-
}, [nodes, edges, shapes]);
|
|
18754
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
18755
|
+
const drawables3D = useMemo(() => {
|
|
18756
|
+
if (mode !== "3d") return [];
|
|
18757
|
+
if (shapes.length > 0) {
|
|
18758
|
+
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
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
|
+
}
|
|
18771
|
+
const out = [];
|
|
18772
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
18773
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
18774
|
+
for (const n of nodes) {
|
|
18775
|
+
if (n.id) nodeById.set(n.id, n);
|
|
18776
|
+
}
|
|
18777
|
+
for (const e of edges) {
|
|
18778
|
+
const a = nodeById.get(e.from);
|
|
18779
|
+
const b = nodeById.get(e.to);
|
|
18780
|
+
if (!a || !b) continue;
|
|
18781
|
+
const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
|
|
18782
|
+
const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
|
|
18783
|
+
if (edge) out.push(edge);
|
|
18784
|
+
if (e.label) {
|
|
18785
|
+
out.push(
|
|
18786
|
+
billboardLabel(
|
|
18787
|
+
e.label,
|
|
18788
|
+
(a.x + b.x) / 2,
|
|
18789
|
+
(a.y + b.y) / 2,
|
|
18790
|
+
((a.z ?? 0) + (b.z ?? 0)) / 2,
|
|
18791
|
+
{ color: labelColor }
|
|
18792
|
+
)
|
|
18793
|
+
);
|
|
18794
|
+
}
|
|
18795
|
+
}
|
|
18796
|
+
for (const n of nodes) {
|
|
18797
|
+
const radius = n.radius ?? 0.5;
|
|
18798
|
+
const nz = n.z ?? 0;
|
|
18799
|
+
out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
|
|
18800
|
+
if (n.label) {
|
|
18801
|
+
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
18802
|
+
}
|
|
18803
|
+
}
|
|
18804
|
+
if (helix3d) {
|
|
18805
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
18806
|
+
}
|
|
18807
|
+
return out;
|
|
18808
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
18809
|
+
const nodeIndexById = useMemo(() => {
|
|
18810
|
+
const m = /* @__PURE__ */ new Map();
|
|
18811
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
18812
|
+
if (rung.id) m.set(rung.id, i);
|
|
18813
|
+
});
|
|
18814
|
+
nodes.forEach((n, i) => {
|
|
18815
|
+
if (n.id) m.set(n.id, i);
|
|
18816
|
+
});
|
|
18817
|
+
return m;
|
|
18818
|
+
}, [nodes, helix3d]);
|
|
18819
|
+
if (mode === "3d") {
|
|
18820
|
+
return /* @__PURE__ */ jsx(
|
|
18821
|
+
LearningScene3D,
|
|
18822
|
+
{
|
|
18823
|
+
className,
|
|
18824
|
+
width,
|
|
18825
|
+
height,
|
|
18826
|
+
title,
|
|
18827
|
+
backgroundColor,
|
|
18828
|
+
drawables: drawables3D,
|
|
18829
|
+
camera,
|
|
18830
|
+
lighting,
|
|
18831
|
+
post,
|
|
18832
|
+
showGrid,
|
|
18833
|
+
shadows,
|
|
18834
|
+
interactive,
|
|
18835
|
+
isLoading,
|
|
18836
|
+
error,
|
|
18837
|
+
onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
|
|
18838
|
+
}
|
|
18839
|
+
);
|
|
18840
|
+
}
|
|
17175
18841
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
17176
18842
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
17177
18843
|
/* @__PURE__ */ jsx(
|
|
@@ -17181,7 +18847,9 @@ var init_BiologyCanvas = __esm({
|
|
|
17181
18847
|
height,
|
|
17182
18848
|
backgroundColor,
|
|
17183
18849
|
shapes: derivedShapes,
|
|
17184
|
-
|
|
18850
|
+
readouts,
|
|
18851
|
+
traces,
|
|
18852
|
+
interactive: interactive ?? false,
|
|
17185
18853
|
animate,
|
|
17186
18854
|
onShapeClick,
|
|
17187
18855
|
isLoading,
|
|
@@ -24021,24 +25689,55 @@ var init_ChatBar = __esm({
|
|
|
24021
25689
|
ChatBar.displayName = "ChatBar";
|
|
24022
25690
|
}
|
|
24023
25691
|
});
|
|
24024
|
-
|
|
25692
|
+
function bondPerpendicular(a, b) {
|
|
25693
|
+
const dx = b[0] - a[0];
|
|
25694
|
+
const dy = b[1] - a[1];
|
|
25695
|
+
const px = dy;
|
|
25696
|
+
const py = -dx;
|
|
25697
|
+
const len = Math.sqrt(px * px + py * py);
|
|
25698
|
+
if (len < 1e-6) return [1, 0, 0];
|
|
25699
|
+
return [px / len, py / len, 0];
|
|
25700
|
+
}
|
|
25701
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
24025
25702
|
var init_ChemistryCanvas = __esm({
|
|
24026
25703
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
24027
25704
|
"use client";
|
|
24028
25705
|
init_atoms();
|
|
24029
25706
|
init_Stack();
|
|
24030
25707
|
init_LearningCanvas();
|
|
25708
|
+
init_learningScene3D();
|
|
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];
|
|
24031
25717
|
ChemistryCanvas = ({
|
|
24032
25718
|
className,
|
|
24033
25719
|
width = 600,
|
|
24034
25720
|
height = 400,
|
|
24035
25721
|
title,
|
|
24036
25722
|
backgroundColor,
|
|
25723
|
+
mode = "2d",
|
|
25724
|
+
camera,
|
|
25725
|
+
lighting,
|
|
25726
|
+
post,
|
|
24037
25727
|
atoms = [],
|
|
24038
25728
|
bonds = [],
|
|
24039
25729
|
arrows = [],
|
|
25730
|
+
bondStyle = "thick",
|
|
25731
|
+
containers = [],
|
|
25732
|
+
equation,
|
|
25733
|
+
equationColor,
|
|
25734
|
+
lattice3d,
|
|
24040
25735
|
shapes = [],
|
|
24041
|
-
|
|
25736
|
+
readouts,
|
|
25737
|
+
traces,
|
|
25738
|
+
showGrid,
|
|
25739
|
+
shadows,
|
|
25740
|
+
interactive,
|
|
24042
25741
|
animate = false,
|
|
24043
25742
|
onShapeClick,
|
|
24044
25743
|
isLoading,
|
|
@@ -24050,21 +25749,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
24050
25749
|
for (const a of atoms) {
|
|
24051
25750
|
if (a.id) atomById.set(a.id, a);
|
|
24052
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
|
+
}
|
|
24053
25823
|
for (const b of bonds) {
|
|
24054
25824
|
const a = atomById.get(b.from);
|
|
24055
25825
|
const c = atomById.get(b.to);
|
|
24056
25826
|
if (!a || !c) continue;
|
|
24057
|
-
const
|
|
24058
|
-
const
|
|
24059
|
-
|
|
24060
|
-
|
|
24061
|
-
|
|
24062
|
-
|
|
24063
|
-
|
|
24064
|
-
|
|
24065
|
-
|
|
24066
|
-
|
|
24067
|
-
|
|
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
|
+
}
|
|
24068
25864
|
}
|
|
24069
25865
|
for (const a of arrows) {
|
|
24070
25866
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -24113,10 +25909,142 @@ var init_ChemistryCanvas = __esm({
|
|
|
24113
25909
|
align: "center"
|
|
24114
25910
|
});
|
|
24115
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
|
+
});
|
|
24116
25953
|
}
|
|
24117
25954
|
out.push(...shapes);
|
|
24118
25955
|
return out;
|
|
24119
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
25956
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
25957
|
+
const drawables3D = useMemo(() => {
|
|
25958
|
+
if (mode !== "3d") return [];
|
|
25959
|
+
if (shapes.length > 0) {
|
|
25960
|
+
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
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
|
+
}
|
|
25968
|
+
const out = [];
|
|
25969
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
25970
|
+
const atomById = /* @__PURE__ */ new Map();
|
|
25971
|
+
for (const a of atoms) {
|
|
25972
|
+
if (a.id) atomById.set(a.id, a);
|
|
25973
|
+
}
|
|
25974
|
+
for (const b of bonds) {
|
|
25975
|
+
const a = atomById.get(b.from);
|
|
25976
|
+
const c = atomById.get(b.to);
|
|
25977
|
+
if (!a || !c) continue;
|
|
25978
|
+
const color = b.color ?? "#6b7280";
|
|
25979
|
+
const from = [a.x, a.y, a.z ?? 0];
|
|
25980
|
+
const to = [c.x, c.y, c.z ?? 0];
|
|
25981
|
+
const perp = bondPerpendicular(from, to);
|
|
25982
|
+
const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
|
|
25983
|
+
const step = bondRadius * 2.2;
|
|
25984
|
+
const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
|
|
25985
|
+
for (const off of offsets) {
|
|
25986
|
+
const bond = cylinderBetween(
|
|
25987
|
+
[from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
|
|
25988
|
+
[to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
|
|
25989
|
+
bondRadius,
|
|
25990
|
+
color
|
|
25991
|
+
);
|
|
25992
|
+
if (bond) out.push(bond);
|
|
25993
|
+
}
|
|
25994
|
+
}
|
|
25995
|
+
for (const a of arrows) {
|
|
25996
|
+
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
25997
|
+
const len = a.length ?? 60;
|
|
25998
|
+
const x2 = a.x + Math.cos(angle) * len;
|
|
25999
|
+
const y2 = a.y + Math.sin(angle) * len;
|
|
26000
|
+
const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
|
|
26001
|
+
if (arrow) out.push(arrow);
|
|
26002
|
+
if (a.label) {
|
|
26003
|
+
out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
|
|
26004
|
+
}
|
|
26005
|
+
}
|
|
26006
|
+
for (const a of atoms) {
|
|
26007
|
+
const radius = a.radius ?? 0.45;
|
|
26008
|
+
const az = a.z ?? 0;
|
|
26009
|
+
out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
|
|
26010
|
+
if (a.element) {
|
|
26011
|
+
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
26012
|
+
}
|
|
26013
|
+
}
|
|
26014
|
+
if (lattice3d) {
|
|
26015
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
26016
|
+
}
|
|
26017
|
+
return out;
|
|
26018
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
26019
|
+
const atomIndexById = useMemo(() => {
|
|
26020
|
+
const m = /* @__PURE__ */ new Map();
|
|
26021
|
+
atoms.forEach((a, i) => {
|
|
26022
|
+
if (a.id) m.set(a.id, i);
|
|
26023
|
+
});
|
|
26024
|
+
return m;
|
|
26025
|
+
}, [atoms]);
|
|
26026
|
+
if (mode === "3d") {
|
|
26027
|
+
return /* @__PURE__ */ jsx(
|
|
26028
|
+
LearningScene3D,
|
|
26029
|
+
{
|
|
26030
|
+
className,
|
|
26031
|
+
width,
|
|
26032
|
+
height,
|
|
26033
|
+
title,
|
|
26034
|
+
backgroundColor,
|
|
26035
|
+
drawables: drawables3D,
|
|
26036
|
+
camera,
|
|
26037
|
+
lighting,
|
|
26038
|
+
post,
|
|
26039
|
+
showGrid,
|
|
26040
|
+
shadows,
|
|
26041
|
+
interactive,
|
|
26042
|
+
isLoading,
|
|
26043
|
+
error,
|
|
26044
|
+
onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
|
|
26045
|
+
}
|
|
26046
|
+
);
|
|
26047
|
+
}
|
|
24120
26048
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
24121
26049
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
24122
26050
|
/* @__PURE__ */ jsx(
|
|
@@ -24126,7 +26054,9 @@ var init_ChemistryCanvas = __esm({
|
|
|
24126
26054
|
height,
|
|
24127
26055
|
backgroundColor,
|
|
24128
26056
|
shapes: derivedShapes,
|
|
24129
|
-
|
|
26057
|
+
readouts,
|
|
26058
|
+
traces,
|
|
26059
|
+
interactive: interactive ?? false,
|
|
24130
26060
|
animate,
|
|
24131
26061
|
onShapeClick,
|
|
24132
26062
|
isLoading,
|
|
@@ -30315,6 +32245,10 @@ var init_ProgressDots = __esm({
|
|
|
30315
32245
|
ProgressDots.displayName = "ProgressDots";
|
|
30316
32246
|
}
|
|
30317
32247
|
});
|
|
32248
|
+
function formatTick(v) {
|
|
32249
|
+
if (Number.isInteger(v)) return String(v);
|
|
32250
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
32251
|
+
}
|
|
30318
32252
|
var MathCanvas;
|
|
30319
32253
|
var init_MathCanvas = __esm({
|
|
30320
32254
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -30334,10 +32268,19 @@ var init_MathCanvas = __esm({
|
|
|
30334
32268
|
showAxes = true,
|
|
30335
32269
|
showGrid = true,
|
|
30336
32270
|
gridStep = 1,
|
|
32271
|
+
showTickLabels = false,
|
|
32272
|
+
showCurveLabels = false,
|
|
30337
32273
|
curves = [],
|
|
30338
32274
|
points = [],
|
|
30339
32275
|
vectors = [],
|
|
32276
|
+
regions = [],
|
|
32277
|
+
bars = [],
|
|
32278
|
+
guides = [],
|
|
32279
|
+
angles = [],
|
|
32280
|
+
hops = [],
|
|
30340
32281
|
shapes = [],
|
|
32282
|
+
readouts,
|
|
32283
|
+
traces,
|
|
30341
32284
|
interactive = false,
|
|
30342
32285
|
animate = false,
|
|
30343
32286
|
onShapeClick,
|
|
@@ -30351,6 +32294,8 @@ var init_MathCanvas = __esm({
|
|
|
30351
32294
|
const plotH = height - margin * 2;
|
|
30352
32295
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
30353
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)));
|
|
30354
32299
|
if (showGrid) {
|
|
30355
32300
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
30356
32301
|
const px = mapX(x);
|
|
@@ -30361,14 +32306,99 @@ var init_MathCanvas = __esm({
|
|
|
30361
32306
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
30362
32307
|
}
|
|
30363
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
|
+
}
|
|
30364
32376
|
if (showAxes) {
|
|
30365
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
30366
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
30367
32377
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
30368
32378
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
30369
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
|
+
}
|
|
30370
32399
|
for (const curve of curves) {
|
|
30371
32400
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
32401
|
+
let lastInRange;
|
|
30372
32402
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
30373
32403
|
const a = curve.samples[i - 1];
|
|
30374
32404
|
const b = curve.samples[i];
|
|
@@ -30380,19 +32410,97 @@ var init_MathCanvas = __esm({
|
|
|
30380
32410
|
x2: mapX(b.x),
|
|
30381
32411
|
y2: mapY(b.y),
|
|
30382
32412
|
color: curve.color ?? "#2563eb",
|
|
30383
|
-
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"
|
|
30384
32491
|
});
|
|
30385
32492
|
}
|
|
30386
32493
|
}
|
|
30387
32494
|
for (const p of points) {
|
|
30388
32495
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
32496
|
+
const isOpen = p.style === "open";
|
|
30389
32497
|
out.push({
|
|
30390
32498
|
type: "circle",
|
|
30391
32499
|
x: mapX(p.x),
|
|
30392
32500
|
y: mapY(p.y),
|
|
30393
32501
|
radius: p.radius ?? 4,
|
|
30394
32502
|
color: p.color ?? "#dc2626",
|
|
30395
|
-
fill: p.color ?? "#dc2626"
|
|
32503
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
30396
32504
|
});
|
|
30397
32505
|
if (p.label) {
|
|
30398
32506
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -30411,7 +32519,28 @@ var init_MathCanvas = __esm({
|
|
|
30411
32519
|
}
|
|
30412
32520
|
out.push(...shapes);
|
|
30413
32521
|
return out;
|
|
30414
|
-
}, [
|
|
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
|
+
]);
|
|
30415
32544
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
30416
32545
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
30417
32546
|
/* @__PURE__ */ jsx(
|
|
@@ -30420,6 +32549,8 @@ var init_MathCanvas = __esm({
|
|
|
30420
32549
|
width,
|
|
30421
32550
|
height,
|
|
30422
32551
|
shapes: derivedShapes,
|
|
32552
|
+
readouts,
|
|
32553
|
+
traces,
|
|
30423
32554
|
interactive,
|
|
30424
32555
|
animate,
|
|
30425
32556
|
onShapeClick,
|
|
@@ -30431,27 +32562,355 @@ var init_MathCanvas = __esm({
|
|
|
30431
32562
|
};
|
|
30432
32563
|
}
|
|
30433
32564
|
});
|
|
30434
|
-
|
|
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;
|
|
30435
32874
|
var init_PhysicsCanvas = __esm({
|
|
30436
32875
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
30437
32876
|
"use client";
|
|
30438
32877
|
init_atoms();
|
|
30439
32878
|
init_Stack();
|
|
30440
32879
|
init_LearningCanvas();
|
|
32880
|
+
init_learningScene3D();
|
|
32881
|
+
physicsLog2 = createLogger("almadar:ui:physics-canvas");
|
|
32882
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
30441
32883
|
PhysicsCanvas = ({
|
|
30442
32884
|
className,
|
|
30443
32885
|
width = 600,
|
|
30444
32886
|
height = 400,
|
|
30445
32887
|
title,
|
|
30446
32888
|
backgroundColor,
|
|
32889
|
+
mode = "2d",
|
|
32890
|
+
camera,
|
|
32891
|
+
lighting,
|
|
32892
|
+
post,
|
|
30447
32893
|
bodies = [],
|
|
30448
32894
|
constraints = [],
|
|
30449
32895
|
showVelocity = true,
|
|
30450
32896
|
showForces = false,
|
|
30451
32897
|
velocityScale = 20,
|
|
30452
32898
|
forceScale = 20,
|
|
32899
|
+
sceneObjects = [],
|
|
32900
|
+
trails = [],
|
|
32901
|
+
vectors = [],
|
|
32902
|
+
surface3d,
|
|
32903
|
+
vectors3d = [],
|
|
32904
|
+
vectorScale = 1,
|
|
32905
|
+
angles = [],
|
|
32906
|
+
field,
|
|
32907
|
+
meters = [],
|
|
30453
32908
|
shapes = [],
|
|
30454
|
-
|
|
32909
|
+
readouts,
|
|
32910
|
+
traces,
|
|
32911
|
+
showGrid,
|
|
32912
|
+
shadows,
|
|
32913
|
+
interactive,
|
|
30455
32914
|
animate = false,
|
|
30456
32915
|
onShapeClick,
|
|
30457
32916
|
isLoading,
|
|
@@ -30463,19 +32922,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
30463
32922
|
for (const b of bodies) {
|
|
30464
32923
|
if (b.id) bodyById.set(b.id, b);
|
|
30465
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));
|
|
30466
32928
|
for (const c of constraints) {
|
|
30467
32929
|
const a = bodyById.get(c.from);
|
|
30468
32930
|
const b = bodyById.get(c.to);
|
|
30469
32931
|
if (!a || !b) continue;
|
|
30470
|
-
out.push(
|
|
30471
|
-
type: "line",
|
|
30472
|
-
x1: a.x,
|
|
30473
|
-
y1: a.y,
|
|
30474
|
-
x2: b.x,
|
|
30475
|
-
y2: b.y,
|
|
30476
|
-
color: c.color ?? "#9ca3af",
|
|
30477
|
-
lineWidth: 2
|
|
30478
|
-
});
|
|
32932
|
+
out.push(...constraintShapes(c, a, b));
|
|
30479
32933
|
}
|
|
30480
32934
|
for (const b of bodies) {
|
|
30481
32935
|
out.push({
|
|
@@ -30520,9 +32974,181 @@ var init_PhysicsCanvas = __esm({
|
|
|
30520
32974
|
});
|
|
30521
32975
|
}
|
|
30522
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));
|
|
30523
32980
|
out.push(...shapes);
|
|
30524
32981
|
return out;
|
|
30525
|
-
}, [
|
|
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
|
+
]);
|
|
32999
|
+
const drawables3D = useMemo(() => {
|
|
33000
|
+
if (mode !== "3d") return [];
|
|
33001
|
+
if (shapes.length > 0) {
|
|
33002
|
+
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
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
|
+
}
|
|
33022
|
+
const out = [];
|
|
33023
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
33024
|
+
const bodyById = /* @__PURE__ */ new Map();
|
|
33025
|
+
for (const b of bodies) {
|
|
33026
|
+
if (b.id) bodyById.set(b.id, b);
|
|
33027
|
+
}
|
|
33028
|
+
for (const c of constraints) {
|
|
33029
|
+
const a = bodyById.get(c.from);
|
|
33030
|
+
const b = bodyById.get(c.to);
|
|
33031
|
+
if (!a || !b) continue;
|
|
33032
|
+
const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
|
|
33033
|
+
const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
|
|
33034
|
+
if (rod) out.push(rod);
|
|
33035
|
+
}
|
|
33036
|
+
for (const b of bodies) {
|
|
33037
|
+
const radius = b.radius ?? 0.5;
|
|
33038
|
+
const bz = b.z ?? 0;
|
|
33039
|
+
out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
|
|
33040
|
+
if (b.label) {
|
|
33041
|
+
out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
|
|
33042
|
+
}
|
|
33043
|
+
const vx = b.vx ?? 0;
|
|
33044
|
+
const vy = b.vy ?? 0;
|
|
33045
|
+
const vz = b.vz ?? 0;
|
|
33046
|
+
const arrowRadius = Math.max(0.05, radius * 0.15);
|
|
33047
|
+
if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
|
|
33048
|
+
const arrow = arrowBetween(
|
|
33049
|
+
[b.x, b.y, bz],
|
|
33050
|
+
[b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
|
|
33051
|
+
"#16a34a",
|
|
33052
|
+
arrowRadius
|
|
33053
|
+
);
|
|
33054
|
+
if (arrow) out.push(arrow);
|
|
33055
|
+
}
|
|
33056
|
+
const fx = b.fx ?? 0;
|
|
33057
|
+
const fy = b.fy ?? 0;
|
|
33058
|
+
const fz = b.fz ?? 0;
|
|
33059
|
+
if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
|
|
33060
|
+
const arrow = arrowBetween(
|
|
33061
|
+
[b.x, b.y, bz],
|
|
33062
|
+
[b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
|
|
33063
|
+
"#dc2626",
|
|
33064
|
+
arrowRadius
|
|
33065
|
+
);
|
|
33066
|
+
if (arrow) out.push(arrow);
|
|
33067
|
+
}
|
|
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
|
+
}
|
|
33098
|
+
return out;
|
|
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
|
+
]);
|
|
33120
|
+
const bodyIndexById = useMemo(() => {
|
|
33121
|
+
const m = /* @__PURE__ */ new Map();
|
|
33122
|
+
vectors3d.forEach((v, i) => {
|
|
33123
|
+
if (v.id) m.set(v.id, i);
|
|
33124
|
+
});
|
|
33125
|
+
bodies.forEach((b, i) => {
|
|
33126
|
+
if (b.id) m.set(b.id, i);
|
|
33127
|
+
});
|
|
33128
|
+
return m;
|
|
33129
|
+
}, [bodies, vectors3d]);
|
|
33130
|
+
if (mode === "3d") {
|
|
33131
|
+
return /* @__PURE__ */ jsx(
|
|
33132
|
+
LearningScene3D,
|
|
33133
|
+
{
|
|
33134
|
+
className,
|
|
33135
|
+
width,
|
|
33136
|
+
height,
|
|
33137
|
+
title,
|
|
33138
|
+
backgroundColor,
|
|
33139
|
+
drawables: drawables3D,
|
|
33140
|
+
camera,
|
|
33141
|
+
lighting,
|
|
33142
|
+
post,
|
|
33143
|
+
showGrid,
|
|
33144
|
+
shadows,
|
|
33145
|
+
interactive,
|
|
33146
|
+
isLoading,
|
|
33147
|
+
error,
|
|
33148
|
+
onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
|
|
33149
|
+
}
|
|
33150
|
+
);
|
|
33151
|
+
}
|
|
30526
33152
|
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
30527
33153
|
title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
|
|
30528
33154
|
/* @__PURE__ */ jsx(
|
|
@@ -30532,7 +33158,9 @@ var init_PhysicsCanvas = __esm({
|
|
|
30532
33158
|
height,
|
|
30533
33159
|
backgroundColor,
|
|
30534
33160
|
shapes: derivedShapes,
|
|
30535
|
-
|
|
33161
|
+
readouts,
|
|
33162
|
+
traces,
|
|
33163
|
+
interactive: interactive ?? false,
|
|
30536
33164
|
animate,
|
|
30537
33165
|
onShapeClick,
|
|
30538
33166
|
isLoading,
|
|
@@ -30682,7 +33310,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
30682
33310
|
}
|
|
30683
33311
|
return nodeIds.map((id) => positions.get(id));
|
|
30684
33312
|
}
|
|
30685
|
-
function
|
|
33313
|
+
function layoutTree3(nodeIds, adjacency, roots, width, height, margin) {
|
|
30686
33314
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
30687
33315
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
30688
33316
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -30738,7 +33366,7 @@ function computeStaticLayout(mode, input) {
|
|
|
30738
33366
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
30739
33367
|
const roots = findRoots(nodeIds, adjacency);
|
|
30740
33368
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
30741
|
-
if (mode === "tree") return
|
|
33369
|
+
if (mode === "tree") return layoutTree3(nodeIds, adjacency, roots, width, height, margin);
|
|
30742
33370
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
30743
33371
|
}
|
|
30744
33372
|
var init_graphViewLayouts = __esm({
|
|
@@ -31150,12 +33778,12 @@ var init_MapView = __esm({
|
|
|
31150
33778
|
shadowSize: [41, 41]
|
|
31151
33779
|
});
|
|
31152
33780
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
31153
|
-
const { useEffect: useEffect64, useRef:
|
|
33781
|
+
const { useEffect: useEffect64, useRef: useRef65, useCallback: useCallback96, useState: useState100 } = React94__default;
|
|
31154
33782
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
31155
33783
|
const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
31156
33784
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
31157
33785
|
const map = useMap();
|
|
31158
|
-
const prevRef =
|
|
33786
|
+
const prevRef = useRef65({ centerLat, centerLng, zoom });
|
|
31159
33787
|
useEffect64(() => {
|
|
31160
33788
|
const prev = prevRef.current;
|
|
31161
33789
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
@@ -31981,26 +34609,6 @@ var init_Lightbox = __esm({
|
|
|
31981
34609
|
Lightbox.displayName = "Lightbox";
|
|
31982
34610
|
}
|
|
31983
34611
|
});
|
|
31984
|
-
function useMediaQuery(query) {
|
|
31985
|
-
const subscribe = useCallback(
|
|
31986
|
-
(onChange) => {
|
|
31987
|
-
const mql = window.matchMedia(query);
|
|
31988
|
-
mql.addEventListener("change", onChange);
|
|
31989
|
-
return () => mql.removeEventListener("change", onChange);
|
|
31990
|
-
},
|
|
31991
|
-
[query]
|
|
31992
|
-
);
|
|
31993
|
-
return useSyncExternalStore(
|
|
31994
|
-
subscribe,
|
|
31995
|
-
() => window.matchMedia(query).matches,
|
|
31996
|
-
() => false
|
|
31997
|
-
);
|
|
31998
|
-
}
|
|
31999
|
-
var init_useMediaQuery = __esm({
|
|
32000
|
-
"hooks/useMediaQuery.ts"() {
|
|
32001
|
-
"use client";
|
|
32002
|
-
}
|
|
32003
|
-
});
|
|
32004
34612
|
function renderIconInput3(icon, props) {
|
|
32005
34613
|
return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
|
|
32006
34614
|
}
|
|
@@ -32039,8 +34647,8 @@ function TableView({
|
|
|
32039
34647
|
columns,
|
|
32040
34648
|
fields,
|
|
32041
34649
|
itemActions,
|
|
32042
|
-
maxInlineActions,
|
|
32043
|
-
itemClickEvent,
|
|
34650
|
+
maxInlineActions: _maxInlineActions,
|
|
34651
|
+
itemClickEvent = "",
|
|
32044
34652
|
selectable = false,
|
|
32045
34653
|
selectEvent,
|
|
32046
34654
|
selectedIds,
|
|
@@ -32090,7 +34698,6 @@ function TableView({
|
|
|
32090
34698
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
32091
34699
|
const hasRenderProp = typeof children === "function";
|
|
32092
34700
|
const idField = dndItemIdField ?? "id";
|
|
32093
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
32094
34701
|
React94__default.useEffect(() => {
|
|
32095
34702
|
tableViewLog.debug("render", {
|
|
32096
34703
|
rowCount: data.length,
|
|
@@ -32132,21 +34739,14 @@ function TableView({
|
|
|
32132
34739
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
32133
34740
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
32134
34741
|
};
|
|
32135
|
-
const
|
|
32136
|
-
e.stopPropagation();
|
|
32137
|
-
const payload = {
|
|
32138
|
-
id: row.id,
|
|
32139
|
-
row
|
|
32140
|
-
};
|
|
32141
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
32142
|
-
};
|
|
34742
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
32143
34743
|
const handleRowClick = (row) => () => {
|
|
32144
|
-
if (!
|
|
34744
|
+
if (!rowClickEvent) return;
|
|
32145
34745
|
const payload = {
|
|
32146
34746
|
id: row.id,
|
|
32147
34747
|
row
|
|
32148
34748
|
};
|
|
32149
|
-
eventBus.emit(`UI:${
|
|
34749
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
32150
34750
|
};
|
|
32151
34751
|
const colFloors = React94__default.useMemo(
|
|
32152
34752
|
() => colDefs.map((col) => {
|
|
@@ -32162,10 +34762,7 @@ function TableView({
|
|
|
32162
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;
|
|
32163
34763
|
const lk = LOOKS[look];
|
|
32164
34764
|
const hasActions = actionDefs.length > 0;
|
|
32165
|
-
const
|
|
32166
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
32167
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
32168
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
34765
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
32169
34766
|
const gridTemplateColumns = [
|
|
32170
34767
|
selectable ? "auto" : null,
|
|
32171
34768
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -32212,7 +34809,7 @@ function TableView({
|
|
|
32212
34809
|
col.key
|
|
32213
34810
|
);
|
|
32214
34811
|
}),
|
|
32215
|
-
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" })
|
|
32216
34813
|
]
|
|
32217
34814
|
}
|
|
32218
34815
|
);
|
|
@@ -32224,12 +34821,12 @@ function TableView({
|
|
|
32224
34821
|
role: "row",
|
|
32225
34822
|
"data-entity-row": true,
|
|
32226
34823
|
"data-entity-id": id,
|
|
32227
|
-
onClick:
|
|
34824
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
32228
34825
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
32229
34826
|
className: cn(
|
|
32230
34827
|
"group items-center gap-3 transition-colors duration-fast",
|
|
32231
34828
|
hasRenderProp ? "flex" : "grid",
|
|
32232
|
-
|
|
34829
|
+
rowClickEvent && "cursor-pointer",
|
|
32233
34830
|
lk.rowPad,
|
|
32234
34831
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
32235
34832
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -32237,7 +34834,7 @@ function TableView({
|
|
|
32237
34834
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
32238
34835
|
),
|
|
32239
34836
|
children: [
|
|
32240
|
-
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(
|
|
32241
34838
|
Checkbox,
|
|
32242
34839
|
{
|
|
32243
34840
|
checked: selected.has(id),
|
|
@@ -32258,53 +34855,37 @@ function TableView({
|
|
|
32258
34855
|
}
|
|
32259
34856
|
return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
|
|
32260
34857
|
}),
|
|
32261
|
-
hasActions && /* @__PURE__ */
|
|
34858
|
+
hasActions && /* @__PURE__ */ jsx(
|
|
32262
34859
|
HStack,
|
|
32263
34860
|
{
|
|
32264
34861
|
gap: "xs",
|
|
32265
|
-
onClick:
|
|
34862
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
32266
34863
|
className: cn(
|
|
32267
34864
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
32268
|
-
// scroll container, which
|
|
32269
|
-
// 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.
|
|
32270
34868
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
34869
|
+
"border-l border-[var(--color-border)]",
|
|
32271
34870
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
32272
34871
|
),
|
|
32273
|
-
children:
|
|
32274
|
-
|
|
32275
|
-
|
|
32276
|
-
|
|
32277
|
-
|
|
32278
|
-
|
|
32279
|
-
|
|
32280
|
-
|
|
32281
|
-
|
|
32282
|
-
|
|
32283
|
-
|
|
32284
|
-
|
|
32285
|
-
|
|
32286
|
-
|
|
32287
|
-
}
|
|
32288
|
-
|
|
32289
|
-
|
|
32290
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsx(
|
|
32291
|
-
Menu,
|
|
32292
|
-
{
|
|
32293
|
-
position: "bottom-end",
|
|
32294
|
-
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" }) }),
|
|
32295
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
32296
|
-
label: action.label,
|
|
32297
|
-
icon: action.icon,
|
|
32298
|
-
event: action.event,
|
|
32299
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
32300
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
32301
|
-
id: row.id,
|
|
32302
|
-
row
|
|
32303
|
-
})
|
|
32304
|
-
}))
|
|
32305
|
-
}
|
|
32306
|
-
)
|
|
32307
|
-
]
|
|
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
|
+
)
|
|
32308
34889
|
}
|
|
32309
34890
|
)
|
|
32310
34891
|
]
|
|
@@ -32347,7 +34928,6 @@ var init_TableView = __esm({
|
|
|
32347
34928
|
init_format();
|
|
32348
34929
|
init_getNestedValue();
|
|
32349
34930
|
init_useEventBus();
|
|
32350
|
-
init_useMediaQuery();
|
|
32351
34931
|
init_Box();
|
|
32352
34932
|
init_Stack();
|
|
32353
34933
|
init_Typography();
|
|
@@ -41569,7 +44149,7 @@ function getEnumOptions(field) {
|
|
|
41569
44149
|
}));
|
|
41570
44150
|
}
|
|
41571
44151
|
const validation = field.validation;
|
|
41572
|
-
if (validation?.enum &&
|
|
44152
|
+
if (validation?.enum && validation.enum.length > 0) {
|
|
41573
44153
|
return validation.enum.map((v) => ({
|
|
41574
44154
|
value: v,
|
|
41575
44155
|
label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
|
|
@@ -47016,6 +49596,7 @@ var init_component_registry_generated = __esm({
|
|
|
47016
49596
|
init_ActionTile();
|
|
47017
49597
|
init_ActivationBlock();
|
|
47018
49598
|
init_ComponentPatterns();
|
|
49599
|
+
init_AlgoGraphCanvas();
|
|
47019
49600
|
init_AlgorithmCanvas();
|
|
47020
49601
|
init_AnimatedCounter();
|
|
47021
49602
|
init_AnimatedGraphic();
|
|
@@ -47279,6 +49860,7 @@ var init_component_registry_generated = __esm({
|
|
|
47279
49860
|
"ActivationBlock": ActivationBlock,
|
|
47280
49861
|
"Alert": AlertPattern,
|
|
47281
49862
|
"AlertPattern": AlertPattern,
|
|
49863
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
47282
49864
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
47283
49865
|
"AnimatedCounter": AnimatedCounter,
|
|
47284
49866
|
"AnimatedGraphic": AnimatedGraphic,
|
|
@@ -52151,9 +54733,9 @@ var log8 = createLogger("almadar:ui:effects:client-handlers");
|
|
|
52151
54733
|
function createClientEffectHandlers(options) {
|
|
52152
54734
|
const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
|
|
52153
54735
|
return {
|
|
52154
|
-
emit: (event, payload) => {
|
|
54736
|
+
emit: (event, payload, source) => {
|
|
52155
54737
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
52156
|
-
eventBus.emit(prefixedEvent, payload);
|
|
54738
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
52157
54739
|
},
|
|
52158
54740
|
persist: async () => {
|
|
52159
54741
|
log8.warn("persist is server-side only, ignored on client");
|
|
@@ -52318,7 +54900,7 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
|
|
|
52318
54900
|
}
|
|
52319
54901
|
};
|
|
52320
54902
|
ctx.emit = (event, payload) => {
|
|
52321
|
-
emit(event, payload);
|
|
54903
|
+
emit(event, payload, { trait: traitName, tick: tick.name });
|
|
52322
54904
|
};
|
|
52323
54905
|
if (tick.guard !== void 0 && !evaluateGuard(tick.guard, ctx)) {
|
|
52324
54906
|
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
@@ -52770,6 +55352,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
52770
55352
|
}
|
|
52771
55353
|
const effectContext = {
|
|
52772
55354
|
traitName,
|
|
55355
|
+
orbitalName: orbitalsByTrait?.[traitName],
|
|
52773
55356
|
state: previousState,
|
|
52774
55357
|
transition: `${previousState}->${newState}`,
|
|
52775
55358
|
linkedEntity,
|
|
@@ -52846,7 +55429,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
52846
55429
|
});
|
|
52847
55430
|
}
|
|
52848
55431
|
return emittedDuringExec;
|
|
52849
|
-
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot]);
|
|
55432
|
+
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot, orbitalsByTrait]);
|
|
52850
55433
|
const runTickEffects = useCallback((tick, binding) => {
|
|
52851
55434
|
const traitName = binding.trait.name;
|
|
52852
55435
|
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
@@ -52882,9 +55465,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
52882
55465
|
log: tickLog
|
|
52883
55466
|
});
|
|
52884
55467
|
}, [executeTransitionEffects, sharedEntityStore]);
|
|
52885
|
-
const emitFromSharedWriter = useCallback((event, payload) => {
|
|
55468
|
+
const emitFromSharedWriter = useCallback((event, payload, source) => {
|
|
52886
55469
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
52887
|
-
eventBus.emit(prefixedEvent, payload);
|
|
55470
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
52888
55471
|
}, [eventBus]);
|
|
52889
55472
|
useEffect(() => {
|
|
52890
55473
|
const scheduler = createTickScheduler();
|