@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.cjs
CHANGED
|
@@ -3431,7 +3431,7 @@ function useEventBus() {
|
|
|
3431
3431
|
return {
|
|
3432
3432
|
...baseBus,
|
|
3433
3433
|
emit: (type, payload, source) => {
|
|
3434
|
-
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
|
|
3434
|
+
if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
|
|
3435
3435
|
scopeLog.warn("emit:bare-key-no-scope", { type });
|
|
3436
3436
|
}
|
|
3437
3437
|
baseBus.emit(type, payload, source);
|
|
@@ -14117,9 +14117,10 @@ function Canvas({
|
|
|
14117
14117
|
drawables: [...drawables ?? [], ...childDrawables],
|
|
14118
14118
|
isLoading,
|
|
14119
14119
|
cameraMode: to3DCameraMode(camera?.mode),
|
|
14120
|
-
...zoom !== void 0 ? {
|
|
14120
|
+
...zoom !== void 0 ? { zoom } : {},
|
|
14121
14121
|
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
14122
14122
|
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
14123
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
14123
14124
|
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
14124
14125
|
unitScale,
|
|
14125
14126
|
backgroundColor,
|
|
@@ -15120,6 +15121,14 @@ function shapeBounds(shape) {
|
|
|
15120
15121
|
w: shape.radius * 2 + 8,
|
|
15121
15122
|
h: shape.radius * 2 + 8
|
|
15122
15123
|
};
|
|
15124
|
+
case "ellipse":
|
|
15125
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
15126
|
+
return {
|
|
15127
|
+
x: shape.x - shape.width / 2 - 4,
|
|
15128
|
+
y: shape.y - shape.height / 2 - 4,
|
|
15129
|
+
w: shape.width + 8,
|
|
15130
|
+
h: shape.height + 8
|
|
15131
|
+
};
|
|
15123
15132
|
case "rect":
|
|
15124
15133
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
|
|
15125
15134
|
return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
|
|
@@ -15153,13 +15162,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
|
|
|
15153
15162
|
ctx.closePath();
|
|
15154
15163
|
ctx.fill();
|
|
15155
15164
|
}
|
|
15156
|
-
function drawShape(ctx, shape, width, height) {
|
|
15165
|
+
function drawShape(ctx, shape, width, height, allShapes) {
|
|
15157
15166
|
ctx.save();
|
|
15158
15167
|
const opacity = shape.opacity ?? 1;
|
|
15159
15168
|
ctx.globalAlpha = opacity;
|
|
15160
15169
|
const stroke = resolveColor2(shape.color, ctx, "#333333");
|
|
15161
15170
|
const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
|
|
15162
15171
|
ctx.lineWidth = shape.lineWidth ?? 2;
|
|
15172
|
+
if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
|
|
15163
15173
|
switch (shape.type) {
|
|
15164
15174
|
case "grid": {
|
|
15165
15175
|
const step = shape.step ?? 40;
|
|
@@ -15225,6 +15235,20 @@ function drawShape(ctx, shape, width, height) {
|
|
|
15225
15235
|
ctx.stroke();
|
|
15226
15236
|
break;
|
|
15227
15237
|
}
|
|
15238
|
+
case "ellipse": {
|
|
15239
|
+
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
15240
|
+
const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
|
|
15241
|
+
const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
|
|
15242
|
+
ctx.beginPath();
|
|
15243
|
+
ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
|
|
15244
|
+
if (fill) {
|
|
15245
|
+
ctx.fillStyle = fill;
|
|
15246
|
+
ctx.fill();
|
|
15247
|
+
}
|
|
15248
|
+
ctx.strokeStyle = stroke;
|
|
15249
|
+
ctx.stroke();
|
|
15250
|
+
break;
|
|
15251
|
+
}
|
|
15228
15252
|
case "rect": {
|
|
15229
15253
|
if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
|
|
15230
15254
|
if (fill) {
|
|
@@ -15271,21 +15295,153 @@ function drawShape(ctx, shape, width, height) {
|
|
|
15271
15295
|
ctx.fillText(shape.text, shape.x, shape.y);
|
|
15272
15296
|
break;
|
|
15273
15297
|
}
|
|
15298
|
+
case "venn-region": {
|
|
15299
|
+
const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
|
|
15300
|
+
const c = allShapes.find((s) => s.type === "circle" && s.id === id);
|
|
15301
|
+
return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
|
|
15302
|
+
});
|
|
15303
|
+
const inside = resolveCircles(shape.inside);
|
|
15304
|
+
if (inside.length === 0) break;
|
|
15305
|
+
const outside = resolveCircles(shape.outside);
|
|
15306
|
+
const off = document.createElement("canvas");
|
|
15307
|
+
off.width = ctx.canvas.width;
|
|
15308
|
+
off.height = ctx.canvas.height;
|
|
15309
|
+
const octx = off.getContext("2d");
|
|
15310
|
+
if (!octx) break;
|
|
15311
|
+
octx.setTransform(ctx.getTransform());
|
|
15312
|
+
for (const c of inside) {
|
|
15313
|
+
const p = new Path2D();
|
|
15314
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
15315
|
+
octx.clip(p);
|
|
15316
|
+
}
|
|
15317
|
+
octx.fillStyle = fill ?? stroke;
|
|
15318
|
+
octx.fillRect(0, 0, width, height);
|
|
15319
|
+
octx.globalCompositeOperation = "destination-out";
|
|
15320
|
+
for (const c of outside) {
|
|
15321
|
+
const p = new Path2D();
|
|
15322
|
+
p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
|
15323
|
+
octx.fill(p);
|
|
15324
|
+
}
|
|
15325
|
+
ctx.save();
|
|
15326
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
15327
|
+
ctx.drawImage(off, 0, 0);
|
|
15328
|
+
ctx.restore();
|
|
15329
|
+
break;
|
|
15330
|
+
}
|
|
15274
15331
|
}
|
|
15275
15332
|
ctx.restore();
|
|
15276
15333
|
}
|
|
15277
|
-
|
|
15334
|
+
function readoutShapes(readouts, width) {
|
|
15335
|
+
const out = [];
|
|
15336
|
+
const chipH = 18;
|
|
15337
|
+
const gap = 6;
|
|
15338
|
+
let rightEdge = width - 6;
|
|
15339
|
+
let rowY = 6;
|
|
15340
|
+
for (const readout of readouts) {
|
|
15341
|
+
const text = `${readout.label}: ${String(readout.value)}`;
|
|
15342
|
+
const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
|
|
15343
|
+
let chipX = rightEdge - chipW;
|
|
15344
|
+
if (chipX < 4) {
|
|
15345
|
+
rowY += chipH + 4;
|
|
15346
|
+
rightEdge = width - 6;
|
|
15347
|
+
chipX = rightEdge - chipW;
|
|
15348
|
+
}
|
|
15349
|
+
const color = readout.color ?? "#334155";
|
|
15350
|
+
out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
|
|
15351
|
+
out.push({
|
|
15352
|
+
type: "text",
|
|
15353
|
+
x: chipX + chipW / 2,
|
|
15354
|
+
y: rowY + chipH / 2,
|
|
15355
|
+
text,
|
|
15356
|
+
color: "#ffffff",
|
|
15357
|
+
fontSize: 10,
|
|
15358
|
+
align: "center"
|
|
15359
|
+
});
|
|
15360
|
+
rightEdge = chipX - gap;
|
|
15361
|
+
}
|
|
15362
|
+
return out;
|
|
15363
|
+
}
|
|
15364
|
+
function traceShapes(panel, k, width, height) {
|
|
15365
|
+
const w = panel.width ?? Math.round(width * 0.32);
|
|
15366
|
+
const h = panel.height ?? Math.round(height * 0.28);
|
|
15367
|
+
const x = panel.x ?? width - w - 8;
|
|
15368
|
+
const y = panel.y ?? height - h - 8 - k * (h + 8);
|
|
15369
|
+
const allSamples = panel.series.flatMap((series) => series.samples);
|
|
15370
|
+
let xLo = Math.min(...allSamples.map((p) => p.x));
|
|
15371
|
+
let xHi = Math.max(...allSamples.map((p) => p.x));
|
|
15372
|
+
let yLo = Math.min(...allSamples.map((p) => p.y));
|
|
15373
|
+
let yHi = Math.max(...allSamples.map((p) => p.y));
|
|
15374
|
+
if (xLo === xHi) {
|
|
15375
|
+
xLo -= 1;
|
|
15376
|
+
xHi += 1;
|
|
15377
|
+
}
|
|
15378
|
+
if (yLo === yHi) {
|
|
15379
|
+
yLo -= 1;
|
|
15380
|
+
yHi += 1;
|
|
15381
|
+
}
|
|
15382
|
+
const backgroundColor = panel.backgroundColor ?? "#ffffff";
|
|
15383
|
+
const frameColor = panel.frameColor ?? "#94a3b8";
|
|
15384
|
+
const out = [];
|
|
15385
|
+
out.push({
|
|
15386
|
+
type: "rect",
|
|
15387
|
+
x,
|
|
15388
|
+
y,
|
|
15389
|
+
width: w,
|
|
15390
|
+
height: h,
|
|
15391
|
+
color: backgroundColor,
|
|
15392
|
+
fill: backgroundColor,
|
|
15393
|
+
opacity: panel.backgroundOpacity ?? 0.85
|
|
15394
|
+
});
|
|
15395
|
+
out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
|
|
15396
|
+
panel.series.forEach((series, j) => {
|
|
15397
|
+
const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
|
|
15398
|
+
const mapped = series.samples.map((p) => ({
|
|
15399
|
+
x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
|
|
15400
|
+
y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
|
|
15401
|
+
}));
|
|
15402
|
+
for (let i = 1; i < mapped.length; i++) {
|
|
15403
|
+
out.push({
|
|
15404
|
+
type: "line",
|
|
15405
|
+
x1: mapped[i - 1].x,
|
|
15406
|
+
y1: mapped[i - 1].y,
|
|
15407
|
+
x2: mapped[i].x,
|
|
15408
|
+
y2: mapped[i].y,
|
|
15409
|
+
color,
|
|
15410
|
+
lineWidth: 1.5
|
|
15411
|
+
});
|
|
15412
|
+
}
|
|
15413
|
+
if (mapped.length > 0) {
|
|
15414
|
+
const last = mapped[mapped.length - 1];
|
|
15415
|
+
out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
|
|
15416
|
+
}
|
|
15417
|
+
if (series.label) {
|
|
15418
|
+
out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
|
|
15419
|
+
}
|
|
15420
|
+
});
|
|
15421
|
+
if (panel.yLabel) {
|
|
15422
|
+
out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
15423
|
+
}
|
|
15424
|
+
if (panel.xLabel) {
|
|
15425
|
+
out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
|
|
15426
|
+
}
|
|
15427
|
+
return out;
|
|
15428
|
+
}
|
|
15429
|
+
var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
|
|
15278
15430
|
var init_LearningCanvas = __esm({
|
|
15279
15431
|
"components/learning/atoms/LearningCanvas.tsx"() {
|
|
15280
15432
|
"use client";
|
|
15281
15433
|
init_cn();
|
|
15282
15434
|
init_useEventBus();
|
|
15435
|
+
DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
|
|
15436
|
+
TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
|
|
15283
15437
|
LearningCanvas = ({
|
|
15284
15438
|
className,
|
|
15285
15439
|
width = 600,
|
|
15286
15440
|
height = 400,
|
|
15287
15441
|
backgroundColor,
|
|
15288
15442
|
shapes = [],
|
|
15443
|
+
readouts,
|
|
15444
|
+
traces,
|
|
15289
15445
|
interactive = false,
|
|
15290
15446
|
animate = false,
|
|
15291
15447
|
onShapeClick,
|
|
@@ -15311,6 +15467,12 @@ var init_LearningCanvas = __esm({
|
|
|
15311
15467
|
}
|
|
15312
15468
|
return -1;
|
|
15313
15469
|
}, [shapes]);
|
|
15470
|
+
const derivedShapes = React94.useMemo(() => {
|
|
15471
|
+
if (!traces?.length && !readouts?.length) return shapes;
|
|
15472
|
+
const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
|
|
15473
|
+
const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
|
|
15474
|
+
return [...shapes, ...traceOut, ...readoutOut];
|
|
15475
|
+
}, [shapes, traces, readouts, width, height]);
|
|
15314
15476
|
const draw = React94.useCallback(() => {
|
|
15315
15477
|
const canvas = canvasRef.current;
|
|
15316
15478
|
if (!canvas) return;
|
|
@@ -15327,13 +15489,13 @@ var init_LearningCanvas = __esm({
|
|
|
15327
15489
|
ctx.fillStyle = backgroundColor;
|
|
15328
15490
|
ctx.fillRect(0, 0, width, height);
|
|
15329
15491
|
}
|
|
15330
|
-
for (const shape of
|
|
15331
|
-
if (shape.type !== "text") drawShape(ctx, shape, width, height);
|
|
15492
|
+
for (const shape of derivedShapes) {
|
|
15493
|
+
if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
15332
15494
|
}
|
|
15333
|
-
for (const shape of
|
|
15334
|
-
if (shape.type === "text") drawShape(ctx, shape, width, height);
|
|
15495
|
+
for (const shape of derivedShapes) {
|
|
15496
|
+
if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
|
|
15335
15497
|
}
|
|
15336
|
-
}, [width, height, backgroundColor,
|
|
15498
|
+
}, [width, height, backgroundColor, derivedShapes]);
|
|
15337
15499
|
React94.useEffect(() => {
|
|
15338
15500
|
draw();
|
|
15339
15501
|
}, [draw]);
|
|
@@ -16883,7 +17045,363 @@ var init_ComponentPatterns = __esm({
|
|
|
16883
17045
|
AlertPattern.displayName = "AlertPattern";
|
|
16884
17046
|
}
|
|
16885
17047
|
});
|
|
16886
|
-
|
|
17048
|
+
function layoutCircle(nodes, width, height) {
|
|
17049
|
+
const cx = width / 2;
|
|
17050
|
+
const cy = height / 2;
|
|
17051
|
+
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
|
17052
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17053
|
+
const n = nodes.length;
|
|
17054
|
+
nodes.forEach((node, i) => {
|
|
17055
|
+
const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
|
|
17056
|
+
positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
|
|
17057
|
+
});
|
|
17058
|
+
return positions;
|
|
17059
|
+
}
|
|
17060
|
+
function layoutTree2(nodes, edges, root, width, height) {
|
|
17061
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
17062
|
+
const idSet = new Set(nodeIds);
|
|
17063
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
17064
|
+
const hasIncoming = /* @__PURE__ */ new Set();
|
|
17065
|
+
for (const e of edges) {
|
|
17066
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
17067
|
+
const list = childrenOf.get(e.from) ?? [];
|
|
17068
|
+
list.push(e.to);
|
|
17069
|
+
childrenOf.set(e.from, list);
|
|
17070
|
+
hasIncoming.add(e.to);
|
|
17071
|
+
}
|
|
17072
|
+
const depth = /* @__PURE__ */ new Map();
|
|
17073
|
+
const treeChildren = /* @__PURE__ */ new Map();
|
|
17074
|
+
const visited = /* @__PURE__ */ new Set();
|
|
17075
|
+
const bfsFrom = (start) => {
|
|
17076
|
+
if (visited.has(start)) return;
|
|
17077
|
+
visited.add(start);
|
|
17078
|
+
depth.set(start, 0);
|
|
17079
|
+
const queue = [start];
|
|
17080
|
+
while (queue.length > 0) {
|
|
17081
|
+
const u = queue.shift();
|
|
17082
|
+
for (const v of childrenOf.get(u) ?? []) {
|
|
17083
|
+
if (visited.has(v)) continue;
|
|
17084
|
+
visited.add(v);
|
|
17085
|
+
depth.set(v, (depth.get(u) ?? 0) + 1);
|
|
17086
|
+
const list = treeChildren.get(u) ?? [];
|
|
17087
|
+
list.push(v);
|
|
17088
|
+
treeChildren.set(u, list);
|
|
17089
|
+
queue.push(v);
|
|
17090
|
+
}
|
|
17091
|
+
}
|
|
17092
|
+
};
|
|
17093
|
+
const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
|
|
17094
|
+
const rootsOrder = [];
|
|
17095
|
+
if (primaryRoot !== void 0) {
|
|
17096
|
+
bfsFrom(primaryRoot);
|
|
17097
|
+
rootsOrder.push(primaryRoot);
|
|
17098
|
+
}
|
|
17099
|
+
for (const id of nodeIds) {
|
|
17100
|
+
if (!visited.has(id)) {
|
|
17101
|
+
bfsFrom(id);
|
|
17102
|
+
rootsOrder.push(id);
|
|
17103
|
+
}
|
|
17104
|
+
}
|
|
17105
|
+
let leafCounter = 0;
|
|
17106
|
+
const xSlot = /* @__PURE__ */ new Map();
|
|
17107
|
+
const assignXSlot = (u) => {
|
|
17108
|
+
const children = treeChildren.get(u) ?? [];
|
|
17109
|
+
if (children.length === 0) {
|
|
17110
|
+
const slot = leafCounter++;
|
|
17111
|
+
xSlot.set(u, slot);
|
|
17112
|
+
return slot;
|
|
17113
|
+
}
|
|
17114
|
+
const childSlots = children.map(assignXSlot);
|
|
17115
|
+
const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
|
|
17116
|
+
xSlot.set(u, avg);
|
|
17117
|
+
return avg;
|
|
17118
|
+
};
|
|
17119
|
+
for (const r2 of rootsOrder) assignXSlot(r2);
|
|
17120
|
+
let maxDepth = 0;
|
|
17121
|
+
for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
|
|
17122
|
+
const colWidth = width / Math.max(1, leafCounter);
|
|
17123
|
+
const rowHeight = height / (maxDepth + 1);
|
|
17124
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17125
|
+
for (const id of nodeIds) {
|
|
17126
|
+
const slot = xSlot.get(id) ?? 0;
|
|
17127
|
+
const d = depth.get(id) ?? 0;
|
|
17128
|
+
positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
|
|
17129
|
+
}
|
|
17130
|
+
return positions;
|
|
17131
|
+
}
|
|
17132
|
+
function layoutLayered(nodes, edges, width, height) {
|
|
17133
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
17134
|
+
const idSet = new Set(nodeIds);
|
|
17135
|
+
const adj = /* @__PURE__ */ new Map();
|
|
17136
|
+
const remainingIndegree = /* @__PURE__ */ new Map();
|
|
17137
|
+
for (const id of nodeIds) remainingIndegree.set(id, 0);
|
|
17138
|
+
for (const e of edges) {
|
|
17139
|
+
if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
|
|
17140
|
+
const list = adj.get(e.from) ?? [];
|
|
17141
|
+
list.push(e.to);
|
|
17142
|
+
adj.set(e.from, list);
|
|
17143
|
+
remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
|
|
17144
|
+
}
|
|
17145
|
+
const layer = /* @__PURE__ */ new Map();
|
|
17146
|
+
const dequeued = /* @__PURE__ */ new Set();
|
|
17147
|
+
const queue = [];
|
|
17148
|
+
for (const id of nodeIds) {
|
|
17149
|
+
if ((remainingIndegree.get(id) ?? 0) === 0) {
|
|
17150
|
+
layer.set(id, 0);
|
|
17151
|
+
queue.push(id);
|
|
17152
|
+
}
|
|
17153
|
+
}
|
|
17154
|
+
while (queue.length > 0) {
|
|
17155
|
+
const u = queue.shift();
|
|
17156
|
+
dequeued.add(u);
|
|
17157
|
+
for (const v of adj.get(u) ?? []) {
|
|
17158
|
+
const candidate = (layer.get(u) ?? 0) + 1;
|
|
17159
|
+
layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
|
|
17160
|
+
remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
|
|
17161
|
+
if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
|
|
17162
|
+
queue.push(v);
|
|
17163
|
+
}
|
|
17164
|
+
}
|
|
17165
|
+
}
|
|
17166
|
+
let baseMaxLayer = 0;
|
|
17167
|
+
for (const id of nodeIds) {
|
|
17168
|
+
if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
|
|
17169
|
+
}
|
|
17170
|
+
const cycleLayer = baseMaxLayer + 1;
|
|
17171
|
+
let maxLayer = baseMaxLayer;
|
|
17172
|
+
for (const id of nodeIds) {
|
|
17173
|
+
if (!dequeued.has(id)) {
|
|
17174
|
+
layer.set(id, cycleLayer);
|
|
17175
|
+
maxLayer = cycleLayer;
|
|
17176
|
+
}
|
|
17177
|
+
}
|
|
17178
|
+
const colWidth = width / Math.max(1, maxLayer + 1);
|
|
17179
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
17180
|
+
for (const id of nodeIds) {
|
|
17181
|
+
const l = layer.get(id) ?? 0;
|
|
17182
|
+
const list = byLayer.get(l) ?? [];
|
|
17183
|
+
list.push(id);
|
|
17184
|
+
byLayer.set(l, list);
|
|
17185
|
+
}
|
|
17186
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17187
|
+
for (const [l, ids] of byLayer) {
|
|
17188
|
+
const rowHeight = height / ids.length;
|
|
17189
|
+
ids.forEach((id, i) => {
|
|
17190
|
+
positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
|
|
17191
|
+
});
|
|
17192
|
+
}
|
|
17193
|
+
return positions;
|
|
17194
|
+
}
|
|
17195
|
+
function computePositions(nodes, edges, layout, root, width, height) {
|
|
17196
|
+
switch (layout) {
|
|
17197
|
+
case "circle":
|
|
17198
|
+
return layoutCircle(nodes, width, height);
|
|
17199
|
+
case "tree":
|
|
17200
|
+
return layoutTree2(nodes, edges, root, width, height);
|
|
17201
|
+
case "layered":
|
|
17202
|
+
return layoutLayered(nodes, edges, width, height);
|
|
17203
|
+
case "manual":
|
|
17204
|
+
default: {
|
|
17205
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17206
|
+
for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
|
|
17207
|
+
return positions;
|
|
17208
|
+
}
|
|
17209
|
+
}
|
|
17210
|
+
}
|
|
17211
|
+
var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS, AlgoGraphCanvas;
|
|
17212
|
+
var init_AlgoGraphCanvas = __esm({
|
|
17213
|
+
"components/learning/molecules/AlgoGraphCanvas.tsx"() {
|
|
17214
|
+
"use client";
|
|
17215
|
+
init_atoms();
|
|
17216
|
+
init_Stack();
|
|
17217
|
+
init_LearningCanvas();
|
|
17218
|
+
NODE_STATE_COLOR = {
|
|
17219
|
+
unvisited: "#cbd5e1",
|
|
17220
|
+
frontier: "#f59e0b",
|
|
17221
|
+
current: "#ef4444",
|
|
17222
|
+
visited: "#22c55e",
|
|
17223
|
+
goal: "#8b5cf6",
|
|
17224
|
+
path: "#0ea5e9"
|
|
17225
|
+
};
|
|
17226
|
+
EDGE_STATE_COLOR = {
|
|
17227
|
+
default: "#9ca3af",
|
|
17228
|
+
tree: "#16a34a",
|
|
17229
|
+
relaxed: "#f59e0b",
|
|
17230
|
+
candidate: "#38bdf8",
|
|
17231
|
+
path: "#dc2626"
|
|
17232
|
+
};
|
|
17233
|
+
DEFAULT_NODE_RADIUS = 18;
|
|
17234
|
+
AlgoGraphCanvas = ({
|
|
17235
|
+
className,
|
|
17236
|
+
width = 600,
|
|
17237
|
+
height = 400,
|
|
17238
|
+
title,
|
|
17239
|
+
backgroundColor,
|
|
17240
|
+
nodes = [],
|
|
17241
|
+
edges = [],
|
|
17242
|
+
layout = "manual",
|
|
17243
|
+
root,
|
|
17244
|
+
shapes = [],
|
|
17245
|
+
interactive = false,
|
|
17246
|
+
animate = false,
|
|
17247
|
+
onShapeClick,
|
|
17248
|
+
onNodeClick,
|
|
17249
|
+
isLoading,
|
|
17250
|
+
error
|
|
17251
|
+
}) => {
|
|
17252
|
+
const nodeById = React94.useMemo(() => {
|
|
17253
|
+
const m = /* @__PURE__ */ new Map();
|
|
17254
|
+
for (const n of nodes) m.set(n.id, n);
|
|
17255
|
+
return m;
|
|
17256
|
+
}, [nodes]);
|
|
17257
|
+
const nodeIndexById = React94.useMemo(() => {
|
|
17258
|
+
const m = /* @__PURE__ */ new Map();
|
|
17259
|
+
nodes.forEach((n, i) => m.set(n.id, i));
|
|
17260
|
+
return m;
|
|
17261
|
+
}, [nodes]);
|
|
17262
|
+
const derivedShapes = React94.useMemo(() => {
|
|
17263
|
+
const out = [];
|
|
17264
|
+
const positions = computePositions(nodes, edges, layout, root, width, height);
|
|
17265
|
+
const edgeGeoms = [];
|
|
17266
|
+
for (const e of edges) {
|
|
17267
|
+
const a = nodeById.get(e.from);
|
|
17268
|
+
const b = nodeById.get(e.to);
|
|
17269
|
+
const posA = positions.get(e.from);
|
|
17270
|
+
const posB = positions.get(e.to);
|
|
17271
|
+
if (!a || !b || !posA || !posB) continue;
|
|
17272
|
+
const rA = a.radius ?? DEFAULT_NODE_RADIUS;
|
|
17273
|
+
const rB = b.radius ?? DEFAULT_NODE_RADIUS;
|
|
17274
|
+
const dx = posB.x - posA.x;
|
|
17275
|
+
const dy = posB.y - posA.y;
|
|
17276
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
17277
|
+
const ux = dx / dist;
|
|
17278
|
+
const uy = dy / dist;
|
|
17279
|
+
edgeGeoms.push({
|
|
17280
|
+
directed: e.directed ?? false,
|
|
17281
|
+
x1: posA.x + ux * rA,
|
|
17282
|
+
y1: posA.y + uy * rA,
|
|
17283
|
+
x2: posB.x - ux * rB,
|
|
17284
|
+
y2: posB.y - uy * rB,
|
|
17285
|
+
color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
|
|
17286
|
+
label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
|
|
17287
|
+
});
|
|
17288
|
+
}
|
|
17289
|
+
for (const g of edgeGeoms) {
|
|
17290
|
+
out.push({
|
|
17291
|
+
type: g.directed ? "arrow" : "line",
|
|
17292
|
+
x1: g.x1,
|
|
17293
|
+
y1: g.y1,
|
|
17294
|
+
x2: g.x2,
|
|
17295
|
+
y2: g.y2,
|
|
17296
|
+
color: g.color,
|
|
17297
|
+
lineWidth: 2
|
|
17298
|
+
});
|
|
17299
|
+
}
|
|
17300
|
+
for (const g of edgeGeoms) {
|
|
17301
|
+
if (g.label === void 0) continue;
|
|
17302
|
+
const midX = (g.x1 + g.x2) / 2;
|
|
17303
|
+
const midY = (g.y1 + g.y2) / 2;
|
|
17304
|
+
const dx = g.x2 - g.x1;
|
|
17305
|
+
const dy = g.y2 - g.y1;
|
|
17306
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
17307
|
+
const perpX = -(dy / dist);
|
|
17308
|
+
const perpY = dx / dist;
|
|
17309
|
+
out.push({
|
|
17310
|
+
type: "text",
|
|
17311
|
+
x: midX + perpX * 10,
|
|
17312
|
+
y: midY + perpY * 10,
|
|
17313
|
+
text: g.label,
|
|
17314
|
+
fontSize: 11,
|
|
17315
|
+
align: "center",
|
|
17316
|
+
color: "#374151"
|
|
17317
|
+
});
|
|
17318
|
+
}
|
|
17319
|
+
const nodeGeoms = [];
|
|
17320
|
+
for (const n of nodes) {
|
|
17321
|
+
const pos = positions.get(n.id);
|
|
17322
|
+
if (!pos) continue;
|
|
17323
|
+
nodeGeoms.push({
|
|
17324
|
+
id: n.id,
|
|
17325
|
+
x: pos.x,
|
|
17326
|
+
y: pos.y,
|
|
17327
|
+
radius: n.radius ?? DEFAULT_NODE_RADIUS,
|
|
17328
|
+
color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
|
|
17329
|
+
label: n.label,
|
|
17330
|
+
badge: n.badge
|
|
17331
|
+
});
|
|
17332
|
+
}
|
|
17333
|
+
for (const g of nodeGeoms) {
|
|
17334
|
+
out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
|
|
17335
|
+
}
|
|
17336
|
+
const badgeGeoms = [];
|
|
17337
|
+
for (const g of nodeGeoms) {
|
|
17338
|
+
if (!g.badge) continue;
|
|
17339
|
+
const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
|
|
17340
|
+
badgeGeoms.push({
|
|
17341
|
+
cx: g.x + g.radius * 0.75,
|
|
17342
|
+
cy: g.y - g.radius * 0.75,
|
|
17343
|
+
w,
|
|
17344
|
+
h: 14,
|
|
17345
|
+
// Borderless pill: same color drives both stroke and fill.
|
|
17346
|
+
color: g.badge.color ?? "#1e293b",
|
|
17347
|
+
text: g.badge.text
|
|
17348
|
+
});
|
|
17349
|
+
}
|
|
17350
|
+
for (const b of badgeGeoms) {
|
|
17351
|
+
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 });
|
|
17352
|
+
}
|
|
17353
|
+
for (const b of badgeGeoms) {
|
|
17354
|
+
out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
|
|
17355
|
+
}
|
|
17356
|
+
for (const g of nodeGeoms) {
|
|
17357
|
+
if (g.label === void 0) continue;
|
|
17358
|
+
out.push({
|
|
17359
|
+
type: "text",
|
|
17360
|
+
x: g.x,
|
|
17361
|
+
y: g.y + g.radius + 14,
|
|
17362
|
+
text: g.label,
|
|
17363
|
+
fontSize: 12,
|
|
17364
|
+
align: "center",
|
|
17365
|
+
color: "#111827"
|
|
17366
|
+
});
|
|
17367
|
+
}
|
|
17368
|
+
out.push(...shapes);
|
|
17369
|
+
return out;
|
|
17370
|
+
}, [nodes, edges, layout, root, width, height, nodeById, shapes]);
|
|
17371
|
+
const handleShapeClick = React94.useCallback(
|
|
17372
|
+
(payload) => {
|
|
17373
|
+
if (payload.type === "circle" && payload.id) {
|
|
17374
|
+
const node = nodeById.get(payload.id);
|
|
17375
|
+
const idx = nodeIndexById.get(payload.id);
|
|
17376
|
+
if (node && idx !== void 0) {
|
|
17377
|
+
onNodeClick?.({ id: node.id, label: node.label, index: idx });
|
|
17378
|
+
}
|
|
17379
|
+
}
|
|
17380
|
+
onShapeClick?.(payload);
|
|
17381
|
+
},
|
|
17382
|
+
[nodeById, nodeIndexById, onNodeClick, onShapeClick]
|
|
17383
|
+
);
|
|
17384
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
17385
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
17386
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17387
|
+
LearningCanvas,
|
|
17388
|
+
{
|
|
17389
|
+
width,
|
|
17390
|
+
height,
|
|
17391
|
+
backgroundColor,
|
|
17392
|
+
shapes: derivedShapes,
|
|
17393
|
+
interactive,
|
|
17394
|
+
animate,
|
|
17395
|
+
onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
|
|
17396
|
+
isLoading,
|
|
17397
|
+
error
|
|
17398
|
+
}
|
|
17399
|
+
)
|
|
17400
|
+
] }) });
|
|
17401
|
+
};
|
|
17402
|
+
}
|
|
17403
|
+
});
|
|
17404
|
+
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;
|
|
16887
17405
|
var init_AlgorithmCanvas = __esm({
|
|
16888
17406
|
"components/learning/molecules/AlgorithmCanvas.tsx"() {
|
|
16889
17407
|
"use client";
|
|
@@ -16895,6 +17413,43 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16895
17413
|
DEFAULT_POINTER_COLOR = "#dc2626";
|
|
16896
17414
|
POINTER_BAND = 34;
|
|
16897
17415
|
TOP_PAD = 26;
|
|
17416
|
+
PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
|
|
17417
|
+
RANGE_COLOR_DEFAULT = "#3b82f6";
|
|
17418
|
+
RANGE_FILL_OPACITY = 0.15;
|
|
17419
|
+
BRACKET_TOP_OFFSET = 16;
|
|
17420
|
+
BRACKET_ROW_H = 14;
|
|
17421
|
+
BRACKET_TICK_H = 6;
|
|
17422
|
+
BRACKET_LABEL_OFFSET = 6;
|
|
17423
|
+
SLOT_EMPTY_FILL = "#f1f5f9";
|
|
17424
|
+
SLOT_EMPTY_STROKE = "#cbd5e1";
|
|
17425
|
+
SLOT_FILLED_STROKE = "#9ca3af";
|
|
17426
|
+
SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
|
|
17427
|
+
SLOT_VALUE_TEXT_COLOR = "#ffffff";
|
|
17428
|
+
FRAME_ACTIVE_COLOR = "#3b82f6";
|
|
17429
|
+
FRAME_RETURNING_COLOR = "#f59e0b";
|
|
17430
|
+
FRAME_DONE_COLOR = "#94a3b8";
|
|
17431
|
+
FRAME_LABEL_COLOR = "#ffffff";
|
|
17432
|
+
FRAME_DETAIL_COLOR = "#e2e8f0";
|
|
17433
|
+
FRAME_TWO_LINE_MIN_H = 22;
|
|
17434
|
+
BUCKET_INDEX_FILL = "#e2e8f0";
|
|
17435
|
+
BUCKET_INDEX_STROKE = "#9ca3af";
|
|
17436
|
+
BUCKET_INDEX_TEXT = "#374151";
|
|
17437
|
+
BUCKET_ENTRY_TEXT = "#ffffff";
|
|
17438
|
+
BUCKET_ENTRY_DEFAULT = "#3b82f6";
|
|
17439
|
+
BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
|
|
17440
|
+
BUCKET_ENTRY_PROBING = "#38bdf8";
|
|
17441
|
+
BUCKET_ENTRY_MIN_W = 24;
|
|
17442
|
+
BUCKET_ENTRY_MAX_W = 64;
|
|
17443
|
+
AXIS_LABEL_COLOR = "#6b7280";
|
|
17444
|
+
AXIS_LABEL_FONT_SIZE = 10;
|
|
17445
|
+
CORNER_TEXT_COLOR = "#111827";
|
|
17446
|
+
CORNER_FONT_SIZE = 7;
|
|
17447
|
+
CORNER_MIN_CELL = 28;
|
|
17448
|
+
CORNER_INSET_X = 3;
|
|
17449
|
+
CORNER_INSET_Y = 6;
|
|
17450
|
+
AUX_PRIMARY_RATIO = 0.6;
|
|
17451
|
+
AUX_LABEL_BAND = 18;
|
|
17452
|
+
AUX_BASELINE_PAD = 8;
|
|
16898
17453
|
AlgorithmCanvas = ({
|
|
16899
17454
|
className,
|
|
16900
17455
|
width = 600,
|
|
@@ -16904,6 +17459,14 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16904
17459
|
bars = [],
|
|
16905
17460
|
cells = [],
|
|
16906
17461
|
pointers = [],
|
|
17462
|
+
ranges = [],
|
|
17463
|
+
slots = [],
|
|
17464
|
+
slotOrientation = "horizontal",
|
|
17465
|
+
frames = [],
|
|
17466
|
+
buckets = [],
|
|
17467
|
+
auxBars = [],
|
|
17468
|
+
rowLabels = [],
|
|
17469
|
+
colLabels = [],
|
|
16907
17470
|
shapes = [],
|
|
16908
17471
|
interactive = false,
|
|
16909
17472
|
animate = false,
|
|
@@ -16913,12 +17476,35 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16913
17476
|
}) => {
|
|
16914
17477
|
const derivedShapes = React94.useMemo(() => {
|
|
16915
17478
|
const out = [];
|
|
17479
|
+
const presence = {
|
|
17480
|
+
bars: bars.length > 0,
|
|
17481
|
+
slots: slots.length > 0,
|
|
17482
|
+
cells: cells.length > 0,
|
|
17483
|
+
buckets: buckets.length > 0,
|
|
17484
|
+
frames: frames.length > 0
|
|
17485
|
+
};
|
|
17486
|
+
const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
|
|
17487
|
+
const panelHeight = height / Math.max(1, panelCount);
|
|
17488
|
+
const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
|
|
17489
|
+
let compactIndex = 0;
|
|
17490
|
+
PANEL_FAMILY_ORDER.forEach((f3) => {
|
|
17491
|
+
if (presence[f3]) {
|
|
17492
|
+
panelY[f3] = compactIndex * panelHeight;
|
|
17493
|
+
compactIndex += 1;
|
|
17494
|
+
}
|
|
17495
|
+
});
|
|
16916
17496
|
if (bars.length > 0) {
|
|
17497
|
+
const panelYBars = panelY.bars;
|
|
16917
17498
|
const slot = width / bars.length;
|
|
16918
17499
|
const barW = slot * 0.8;
|
|
16919
17500
|
const gap = slot * 0.1;
|
|
16920
|
-
const
|
|
16921
|
-
const
|
|
17501
|
+
const bracketRanges = ranges.filter((r2) => r2.kind === "bracket");
|
|
17502
|
+
const bracketCount = bracketRanges.length;
|
|
17503
|
+
const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
|
|
17504
|
+
const hasAux = auxBars.length > 0;
|
|
17505
|
+
const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
|
|
17506
|
+
const baseline = panelYBars + primaryH - POINTER_BAND;
|
|
17507
|
+
const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
|
|
16922
17508
|
const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
16923
17509
|
bars.forEach((bar, i) => {
|
|
16924
17510
|
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
@@ -16948,6 +17534,89 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16948
17534
|
});
|
|
16949
17535
|
}
|
|
16950
17536
|
});
|
|
17537
|
+
ranges.forEach((r2) => {
|
|
17538
|
+
const kind = r2.kind ?? "fill";
|
|
17539
|
+
if (kind !== "fill") return;
|
|
17540
|
+
const color = r2.color ?? RANGE_COLOR_DEFAULT;
|
|
17541
|
+
out.push({
|
|
17542
|
+
type: "rect",
|
|
17543
|
+
x: r2.from * slot,
|
|
17544
|
+
y: panelYBars,
|
|
17545
|
+
width: (r2.to - r2.from + 1) * slot,
|
|
17546
|
+
height: primaryH,
|
|
17547
|
+
color,
|
|
17548
|
+
fill: color,
|
|
17549
|
+
opacity: RANGE_FILL_OPACITY
|
|
17550
|
+
});
|
|
17551
|
+
if (r2.label) {
|
|
17552
|
+
out.push({
|
|
17553
|
+
type: "text",
|
|
17554
|
+
x: r2.from * slot + 4,
|
|
17555
|
+
// Sits below the bracket block (if any) so fill and bracket labels never collide.
|
|
17556
|
+
y: panelYBars + 10 + bracketHeadroom,
|
|
17557
|
+
text: r2.label,
|
|
17558
|
+
color,
|
|
17559
|
+
fontSize: 10,
|
|
17560
|
+
align: "left"
|
|
17561
|
+
});
|
|
17562
|
+
}
|
|
17563
|
+
});
|
|
17564
|
+
bracketRanges.forEach((r2, i) => {
|
|
17565
|
+
const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
|
|
17566
|
+
const x1 = r2.from * slot + slot * 0.1;
|
|
17567
|
+
const x2 = (r2.to + 1) * slot - slot * 0.1;
|
|
17568
|
+
const color = r2.color ?? RANGE_COLOR_DEFAULT;
|
|
17569
|
+
out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
|
|
17570
|
+
out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
17571
|
+
out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
|
|
17572
|
+
if (r2.label) {
|
|
17573
|
+
out.push({
|
|
17574
|
+
type: "text",
|
|
17575
|
+
x: (x1 + x2) / 2,
|
|
17576
|
+
y: bracketY - BRACKET_LABEL_OFFSET,
|
|
17577
|
+
text: r2.label,
|
|
17578
|
+
color,
|
|
17579
|
+
fontSize: 10,
|
|
17580
|
+
align: "center"
|
|
17581
|
+
});
|
|
17582
|
+
}
|
|
17583
|
+
});
|
|
17584
|
+
if (hasAux) {
|
|
17585
|
+
const auxH = panelHeight - primaryH;
|
|
17586
|
+
const slot2 = width / auxBars.length;
|
|
17587
|
+
const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
|
|
17588
|
+
const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
|
|
17589
|
+
const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
|
|
17590
|
+
auxBars.forEach((bar, i) => {
|
|
17591
|
+
const v = Number.isFinite(bar.value) ? bar.value : 0;
|
|
17592
|
+
const bh = Math.max(0, v / maxAuxV * auxUsableH);
|
|
17593
|
+
const x = i * slot2 + slot2 * 0.1;
|
|
17594
|
+
const w = slot2 * 0.8;
|
|
17595
|
+
const color = bar.color ?? DEFAULT_BAR_COLOR;
|
|
17596
|
+
out.push({
|
|
17597
|
+
type: "rect",
|
|
17598
|
+
id: `auxbar-${i}`,
|
|
17599
|
+
x,
|
|
17600
|
+
y: auxBaseline - bh,
|
|
17601
|
+
width: w,
|
|
17602
|
+
height: bh,
|
|
17603
|
+
color,
|
|
17604
|
+
fill: color
|
|
17605
|
+
});
|
|
17606
|
+
const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
|
|
17607
|
+
if (label) {
|
|
17608
|
+
out.push({
|
|
17609
|
+
type: "text",
|
|
17610
|
+
x: x + w / 2,
|
|
17611
|
+
y: auxBaseline - bh - 8,
|
|
17612
|
+
text: label,
|
|
17613
|
+
color: "#374151",
|
|
17614
|
+
fontSize: 11,
|
|
17615
|
+
align: "center"
|
|
17616
|
+
});
|
|
17617
|
+
}
|
|
17618
|
+
});
|
|
17619
|
+
}
|
|
16951
17620
|
pointers.forEach((p) => {
|
|
16952
17621
|
if (p.index < 0 || p.index >= bars.length) return;
|
|
16953
17622
|
const cx = p.index * slot + slot / 2;
|
|
@@ -16955,7 +17624,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16955
17624
|
out.push({
|
|
16956
17625
|
type: "arrow",
|
|
16957
17626
|
x1: cx,
|
|
16958
|
-
y1:
|
|
17627
|
+
y1: panelYBars + primaryH - 18,
|
|
16959
17628
|
x2: cx,
|
|
16960
17629
|
y2: baseline + 4,
|
|
16961
17630
|
color,
|
|
@@ -16965,7 +17634,7 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16965
17634
|
out.push({
|
|
16966
17635
|
type: "text",
|
|
16967
17636
|
x: cx,
|
|
16968
|
-
y:
|
|
17637
|
+
y: panelYBars + primaryH - 8,
|
|
16969
17638
|
text: p.label,
|
|
16970
17639
|
color,
|
|
16971
17640
|
fontSize: 11,
|
|
@@ -16974,14 +17643,111 @@ var init_AlgorithmCanvas = __esm({
|
|
|
16974
17643
|
}
|
|
16975
17644
|
});
|
|
16976
17645
|
}
|
|
17646
|
+
if (slots.length > 0) {
|
|
17647
|
+
const panelYSlots = panelY.slots;
|
|
17648
|
+
const n = slots.length;
|
|
17649
|
+
const vertical = slotOrientation === "vertical";
|
|
17650
|
+
const vBoxH = panelHeight / n;
|
|
17651
|
+
const vBoxW = Math.min(width * 0.5, 120);
|
|
17652
|
+
const vBoxX = (width - vBoxW) / 2;
|
|
17653
|
+
const hCellW = width / n;
|
|
17654
|
+
const hBoxW = hCellW * 0.82;
|
|
17655
|
+
const hBoxH = Math.min(panelHeight * 0.6, 48);
|
|
17656
|
+
const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
|
|
17657
|
+
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 };
|
|
17658
|
+
slots.forEach((s, i) => {
|
|
17659
|
+
const box = slotBox(i);
|
|
17660
|
+
const state = s.state ?? "filled";
|
|
17661
|
+
const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
|
|
17662
|
+
const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
|
|
17663
|
+
out.push({
|
|
17664
|
+
type: "rect",
|
|
17665
|
+
id: `slot-${i}`,
|
|
17666
|
+
x: box.x,
|
|
17667
|
+
y: box.y,
|
|
17668
|
+
width: box.width,
|
|
17669
|
+
height: box.height,
|
|
17670
|
+
color: stroke,
|
|
17671
|
+
fill
|
|
17672
|
+
});
|
|
17673
|
+
if (s.value != null && state !== "empty") {
|
|
17674
|
+
out.push({
|
|
17675
|
+
type: "text",
|
|
17676
|
+
x: box.x + box.width / 2,
|
|
17677
|
+
y: box.y + box.height / 2,
|
|
17678
|
+
text: String(s.value),
|
|
17679
|
+
color: SLOT_VALUE_TEXT_COLOR,
|
|
17680
|
+
fontSize: 12,
|
|
17681
|
+
align: "center"
|
|
17682
|
+
});
|
|
17683
|
+
}
|
|
17684
|
+
});
|
|
17685
|
+
if (bars.length === 0) {
|
|
17686
|
+
pointers.forEach((p) => {
|
|
17687
|
+
if (p.index < 0 || p.index >= slots.length) return;
|
|
17688
|
+
const box = slotBox(p.index);
|
|
17689
|
+
const color = p.color ?? DEFAULT_POINTER_COLOR;
|
|
17690
|
+
if (vertical) {
|
|
17691
|
+
const cy = box.y + box.height / 2;
|
|
17692
|
+
out.push({
|
|
17693
|
+
type: "arrow",
|
|
17694
|
+
x1: box.x + box.width + 34,
|
|
17695
|
+
y1: cy,
|
|
17696
|
+
x2: box.x + box.width + 4,
|
|
17697
|
+
y2: cy,
|
|
17698
|
+
color,
|
|
17699
|
+
lineWidth: 2
|
|
17700
|
+
});
|
|
17701
|
+
if (p.label) {
|
|
17702
|
+
out.push({
|
|
17703
|
+
type: "text",
|
|
17704
|
+
x: box.x + box.width + 38,
|
|
17705
|
+
y: cy,
|
|
17706
|
+
text: p.label,
|
|
17707
|
+
color,
|
|
17708
|
+
fontSize: 11,
|
|
17709
|
+
align: "left"
|
|
17710
|
+
});
|
|
17711
|
+
}
|
|
17712
|
+
} else {
|
|
17713
|
+
const cx = box.x + box.width / 2;
|
|
17714
|
+
out.push({
|
|
17715
|
+
type: "arrow",
|
|
17716
|
+
x1: cx,
|
|
17717
|
+
y1: panelYSlots + panelHeight - 18,
|
|
17718
|
+
x2: cx,
|
|
17719
|
+
y2: box.y + box.height + 4,
|
|
17720
|
+
color,
|
|
17721
|
+
lineWidth: 2
|
|
17722
|
+
});
|
|
17723
|
+
if (p.label) {
|
|
17724
|
+
out.push({
|
|
17725
|
+
type: "text",
|
|
17726
|
+
x: cx,
|
|
17727
|
+
y: panelYSlots + panelHeight - 8,
|
|
17728
|
+
text: p.label,
|
|
17729
|
+
color,
|
|
17730
|
+
fontSize: 11,
|
|
17731
|
+
align: "center"
|
|
17732
|
+
});
|
|
17733
|
+
}
|
|
17734
|
+
}
|
|
17735
|
+
});
|
|
17736
|
+
}
|
|
17737
|
+
}
|
|
16977
17738
|
if (cells.length > 0) {
|
|
17739
|
+
const panelYCells = panelY.cells;
|
|
16978
17740
|
const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
|
|
16979
17741
|
const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
|
|
16980
|
-
const
|
|
16981
|
-
const
|
|
17742
|
+
const colLabelH = colLabels.length > 0 ? 16 : 0;
|
|
17743
|
+
const rowLabelW = rowLabels.length > 0 ? 20 : 0;
|
|
17744
|
+
const gridX0 = rowLabelW;
|
|
17745
|
+
const gridY0 = panelYCells + colLabelH;
|
|
17746
|
+
const cw = (width - rowLabelW) / maxCol;
|
|
17747
|
+
const ch = (panelHeight - colLabelH) / maxRow;
|
|
16982
17748
|
cells.forEach((c, i) => {
|
|
16983
|
-
const x = c.col * cw;
|
|
16984
|
-
const y = c.row * ch;
|
|
17749
|
+
const x = gridX0 + c.col * cw;
|
|
17750
|
+
const y = gridY0 + c.row * ch;
|
|
16985
17751
|
const color = c.color ?? DEFAULT_CELL_COLOR;
|
|
16986
17752
|
out.push({
|
|
16987
17753
|
type: "rect",
|
|
@@ -17005,11 +17771,207 @@ var init_AlgorithmCanvas = __esm({
|
|
|
17005
17771
|
align: "center"
|
|
17006
17772
|
});
|
|
17007
17773
|
}
|
|
17774
|
+
if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
|
|
17775
|
+
const { tl, tr, bl, br } = c.corner;
|
|
17776
|
+
if (tl) {
|
|
17777
|
+
out.push({
|
|
17778
|
+
type: "text",
|
|
17779
|
+
x: x + CORNER_INSET_X,
|
|
17780
|
+
y: y + CORNER_INSET_Y,
|
|
17781
|
+
text: tl,
|
|
17782
|
+
color: CORNER_TEXT_COLOR,
|
|
17783
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17784
|
+
align: "left"
|
|
17785
|
+
});
|
|
17786
|
+
}
|
|
17787
|
+
if (tr) {
|
|
17788
|
+
out.push({
|
|
17789
|
+
type: "text",
|
|
17790
|
+
x: x + cw - CORNER_INSET_X,
|
|
17791
|
+
y: y + CORNER_INSET_Y,
|
|
17792
|
+
text: tr,
|
|
17793
|
+
color: CORNER_TEXT_COLOR,
|
|
17794
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17795
|
+
align: "right"
|
|
17796
|
+
});
|
|
17797
|
+
}
|
|
17798
|
+
if (bl) {
|
|
17799
|
+
out.push({
|
|
17800
|
+
type: "text",
|
|
17801
|
+
x: x + CORNER_INSET_X,
|
|
17802
|
+
y: y + ch - CORNER_INSET_Y,
|
|
17803
|
+
text: bl,
|
|
17804
|
+
color: CORNER_TEXT_COLOR,
|
|
17805
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17806
|
+
align: "left"
|
|
17807
|
+
});
|
|
17808
|
+
}
|
|
17809
|
+
if (br) {
|
|
17810
|
+
out.push({
|
|
17811
|
+
type: "text",
|
|
17812
|
+
x: x + cw - CORNER_INSET_X,
|
|
17813
|
+
y: y + ch - CORNER_INSET_Y,
|
|
17814
|
+
text: br,
|
|
17815
|
+
color: CORNER_TEXT_COLOR,
|
|
17816
|
+
fontSize: CORNER_FONT_SIZE,
|
|
17817
|
+
align: "right"
|
|
17818
|
+
});
|
|
17819
|
+
}
|
|
17820
|
+
}
|
|
17821
|
+
});
|
|
17822
|
+
colLabels.forEach((l) => {
|
|
17823
|
+
out.push({
|
|
17824
|
+
type: "text",
|
|
17825
|
+
x: gridX0 + l.index * cw + cw / 2,
|
|
17826
|
+
y: panelYCells + colLabelH / 2,
|
|
17827
|
+
text: l.text,
|
|
17828
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
17829
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
17830
|
+
align: "center"
|
|
17831
|
+
});
|
|
17832
|
+
});
|
|
17833
|
+
rowLabels.forEach((l) => {
|
|
17834
|
+
out.push({
|
|
17835
|
+
type: "text",
|
|
17836
|
+
x: rowLabelW - 6,
|
|
17837
|
+
y: gridY0 + l.index * ch + ch / 2,
|
|
17838
|
+
text: l.text,
|
|
17839
|
+
color: l.color ?? AXIS_LABEL_COLOR,
|
|
17840
|
+
fontSize: AXIS_LABEL_FONT_SIZE,
|
|
17841
|
+
align: "right"
|
|
17842
|
+
});
|
|
17843
|
+
});
|
|
17844
|
+
}
|
|
17845
|
+
if (buckets.length > 0) {
|
|
17846
|
+
const panelYBuckets = panelY.buckets;
|
|
17847
|
+
const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
|
|
17848
|
+
const rowH = panelHeight / bucketCount;
|
|
17849
|
+
const indexColW = Math.min(width * 0.12, 40);
|
|
17850
|
+
const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
|
|
17851
|
+
const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
|
|
17852
|
+
const maxVisible = Math.floor((width - indexColW - 4) / entryW);
|
|
17853
|
+
buckets.forEach((b) => {
|
|
17854
|
+
const rowY = panelYBuckets + b.index * rowH;
|
|
17855
|
+
out.push({
|
|
17856
|
+
type: "rect",
|
|
17857
|
+
id: `bucket-index-${b.index}`,
|
|
17858
|
+
x: 2,
|
|
17859
|
+
y: rowY + 2,
|
|
17860
|
+
width: indexColW - 4,
|
|
17861
|
+
height: rowH - 4,
|
|
17862
|
+
color: BUCKET_INDEX_STROKE,
|
|
17863
|
+
fill: BUCKET_INDEX_FILL
|
|
17864
|
+
});
|
|
17865
|
+
out.push({
|
|
17866
|
+
type: "text",
|
|
17867
|
+
x: 2 + (indexColW - 4) / 2,
|
|
17868
|
+
y: rowY + rowH / 2,
|
|
17869
|
+
text: String(b.index),
|
|
17870
|
+
color: BUCKET_INDEX_TEXT,
|
|
17871
|
+
fontSize: 10,
|
|
17872
|
+
align: "center"
|
|
17873
|
+
});
|
|
17874
|
+
const overflow = b.entries.length > maxVisible;
|
|
17875
|
+
const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
|
|
17876
|
+
for (let j = 0; j < visibleCount; j++) {
|
|
17877
|
+
const entry = b.entries[j];
|
|
17878
|
+
const ex = indexColW + 4 + j * entryW;
|
|
17879
|
+
const state = entry.state ?? "default";
|
|
17880
|
+
const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
|
|
17881
|
+
out.push({
|
|
17882
|
+
type: "rect",
|
|
17883
|
+
id: `bucket-${b.index}-${j}`,
|
|
17884
|
+
x: ex,
|
|
17885
|
+
y: rowY + 2,
|
|
17886
|
+
width: entryW - 2,
|
|
17887
|
+
height: rowH - 4,
|
|
17888
|
+
color: fill,
|
|
17889
|
+
fill
|
|
17890
|
+
});
|
|
17891
|
+
if (entryW >= 20 && rowH >= 16) {
|
|
17892
|
+
out.push({
|
|
17893
|
+
type: "text",
|
|
17894
|
+
x: ex + (entryW - 2) / 2,
|
|
17895
|
+
y: rowY + rowH / 2,
|
|
17896
|
+
text: entry.label,
|
|
17897
|
+
color: BUCKET_ENTRY_TEXT,
|
|
17898
|
+
fontSize: 10,
|
|
17899
|
+
align: "center"
|
|
17900
|
+
});
|
|
17901
|
+
}
|
|
17902
|
+
}
|
|
17903
|
+
if (overflow) {
|
|
17904
|
+
const ex = indexColW + 4 + visibleCount * entryW;
|
|
17905
|
+
out.push({
|
|
17906
|
+
type: "rect",
|
|
17907
|
+
id: `bucket-${b.index}-overflow`,
|
|
17908
|
+
x: ex,
|
|
17909
|
+
y: rowY + 2,
|
|
17910
|
+
width: entryW - 2,
|
|
17911
|
+
height: rowH - 4,
|
|
17912
|
+
color: BUCKET_ENTRY_DEFAULT,
|
|
17913
|
+
fill: BUCKET_ENTRY_DEFAULT
|
|
17914
|
+
});
|
|
17915
|
+
out.push({
|
|
17916
|
+
type: "text",
|
|
17917
|
+
x: ex + (entryW - 2) / 2,
|
|
17918
|
+
y: rowY + rowH / 2,
|
|
17919
|
+
text: `+${b.entries.length - visibleCount}`,
|
|
17920
|
+
color: BUCKET_ENTRY_TEXT,
|
|
17921
|
+
fontSize: 10,
|
|
17922
|
+
align: "center"
|
|
17923
|
+
});
|
|
17924
|
+
}
|
|
17925
|
+
});
|
|
17926
|
+
}
|
|
17927
|
+
if (frames.length > 0) {
|
|
17928
|
+
const panelYFrames = panelY.frames;
|
|
17929
|
+
const n = frames.length;
|
|
17930
|
+
const frameH = panelHeight / n;
|
|
17931
|
+
const x = 8;
|
|
17932
|
+
const w = width - 16;
|
|
17933
|
+
frames.forEach((f3, i) => {
|
|
17934
|
+
const y = panelYFrames + panelHeight - (i + 1) * frameH;
|
|
17935
|
+
const state = f3.state ?? "active";
|
|
17936
|
+
const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
|
|
17937
|
+
out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
|
|
17938
|
+
if (frameH >= FRAME_TWO_LINE_MIN_H) {
|
|
17939
|
+
out.push({
|
|
17940
|
+
type: "text",
|
|
17941
|
+
x: 16,
|
|
17942
|
+
y: y + frameH * 0.35,
|
|
17943
|
+
text: f3.label,
|
|
17944
|
+
color: FRAME_LABEL_COLOR,
|
|
17945
|
+
fontSize: 10,
|
|
17946
|
+
align: "left"
|
|
17947
|
+
});
|
|
17948
|
+
if (f3.detail) {
|
|
17949
|
+
out.push({
|
|
17950
|
+
type: "text",
|
|
17951
|
+
x: 16,
|
|
17952
|
+
y: y + frameH * 0.7,
|
|
17953
|
+
text: f3.detail,
|
|
17954
|
+
color: FRAME_DETAIL_COLOR,
|
|
17955
|
+
fontSize: 10,
|
|
17956
|
+
align: "left"
|
|
17957
|
+
});
|
|
17958
|
+
}
|
|
17959
|
+
} else {
|
|
17960
|
+
out.push({
|
|
17961
|
+
type: "text",
|
|
17962
|
+
x: 16,
|
|
17963
|
+
y: y + frameH / 2,
|
|
17964
|
+
text: f3.label,
|
|
17965
|
+
color: FRAME_LABEL_COLOR,
|
|
17966
|
+
fontSize: 10,
|
|
17967
|
+
align: "left"
|
|
17968
|
+
});
|
|
17969
|
+
}
|
|
17008
17970
|
});
|
|
17009
17971
|
}
|
|
17010
17972
|
out.push(...shapes);
|
|
17011
17973
|
return out;
|
|
17012
|
-
}, [bars, cells, pointers, shapes, width, height]);
|
|
17974
|
+
}, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
|
|
17013
17975
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
17014
17976
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
17015
17977
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -17171,23 +18133,422 @@ var init_AuthLayout = __esm({
|
|
|
17171
18133
|
AuthLayout.displayName = "AuthLayout";
|
|
17172
18134
|
}
|
|
17173
18135
|
});
|
|
17174
|
-
|
|
18136
|
+
function LearningScene3D({
|
|
18137
|
+
className,
|
|
18138
|
+
width = 600,
|
|
18139
|
+
height = 400,
|
|
18140
|
+
title,
|
|
18141
|
+
backgroundColor,
|
|
18142
|
+
drawables,
|
|
18143
|
+
camera,
|
|
18144
|
+
lighting,
|
|
18145
|
+
post,
|
|
18146
|
+
showGrid = false,
|
|
18147
|
+
shadows,
|
|
18148
|
+
interactive,
|
|
18149
|
+
isLoading,
|
|
18150
|
+
error,
|
|
18151
|
+
onItemClick
|
|
18152
|
+
}) {
|
|
18153
|
+
const instanceId = React94.useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
18154
|
+
const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
|
|
18155
|
+
const onItemClickRef = React94.useRef(onItemClick);
|
|
18156
|
+
onItemClickRef.current = onItemClick;
|
|
18157
|
+
useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
|
|
18158
|
+
const unitId = event.payload?.unitId;
|
|
18159
|
+
if (typeof unitId === "string") onItemClickRef.current?.(unitId);
|
|
18160
|
+
});
|
|
18161
|
+
if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
|
|
18162
|
+
const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
|
|
18163
|
+
if (unknownKeys.length > 0) {
|
|
18164
|
+
sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
|
|
18165
|
+
}
|
|
18166
|
+
}
|
|
18167
|
+
const props3d = {
|
|
18168
|
+
drawables,
|
|
18169
|
+
isLoading,
|
|
18170
|
+
error: error ? error.message : null,
|
|
18171
|
+
cameraMode: camera?.mode ?? "perspective",
|
|
18172
|
+
...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
|
|
18173
|
+
...camera?.fov !== void 0 ? { fov: camera.fov } : {},
|
|
18174
|
+
...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
|
|
18175
|
+
...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
|
|
18176
|
+
...camera?.target !== void 0 ? { followTarget: camera.target } : {},
|
|
18177
|
+
backgroundColor,
|
|
18178
|
+
showGrid,
|
|
18179
|
+
...shadows !== void 0 ? { shadows } : {},
|
|
18180
|
+
...interactive !== void 0 ? { controlsEnabled: interactive } : {},
|
|
18181
|
+
lighting,
|
|
18182
|
+
post,
|
|
18183
|
+
...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
|
|
18184
|
+
};
|
|
18185
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
18186
|
+
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
18187
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(React94.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost2, { ...props3d }) }) })
|
|
18188
|
+
] }) });
|
|
18189
|
+
}
|
|
18190
|
+
function meshSphere(id, x, y, z, radius, color, opts) {
|
|
18191
|
+
return {
|
|
18192
|
+
type: "draw-mesh",
|
|
18193
|
+
...id !== void 0 ? { id } : {},
|
|
18194
|
+
shape: opts?.shape ?? "sphere",
|
|
18195
|
+
position: { x, y, z },
|
|
18196
|
+
radius,
|
|
18197
|
+
pivot: "center",
|
|
18198
|
+
segments: opts?.segments ?? 24,
|
|
18199
|
+
material: { color, ...opts?.material },
|
|
18200
|
+
...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
|
|
18201
|
+
};
|
|
18202
|
+
}
|
|
18203
|
+
function axisRotation(from, to) {
|
|
18204
|
+
const wx = to[0] - from[0];
|
|
18205
|
+
const wy = to[2] - from[2];
|
|
18206
|
+
const wz = to[1] - from[1];
|
|
18207
|
+
const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
|
|
18208
|
+
const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
|
|
18209
|
+
return [0, Math.atan2(wz, -wx), tilt];
|
|
18210
|
+
}
|
|
18211
|
+
function segmentLength(from, to) {
|
|
18212
|
+
const dx = to[0] - from[0];
|
|
18213
|
+
const dy = to[1] - from[1];
|
|
18214
|
+
const dz = to[2] - from[2];
|
|
18215
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
18216
|
+
}
|
|
18217
|
+
function cylinderBetween(from, to, radius, color) {
|
|
18218
|
+
const len = segmentLength(from, to);
|
|
18219
|
+
if (len < 1e-6) return null;
|
|
18220
|
+
return {
|
|
18221
|
+
type: "draw-mesh",
|
|
18222
|
+
shape: "cylinder",
|
|
18223
|
+
position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
|
|
18224
|
+
radius,
|
|
18225
|
+
height: len,
|
|
18226
|
+
rotation: axisRotation(from, to),
|
|
18227
|
+
pivot: "center",
|
|
18228
|
+
segments: 12,
|
|
18229
|
+
material: { color }
|
|
18230
|
+
};
|
|
18231
|
+
}
|
|
18232
|
+
function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
|
|
18233
|
+
const len = segmentLength(from, to);
|
|
18234
|
+
if (len < 1e-6) return null;
|
|
18235
|
+
const tipLen = Math.min(shaftRadius * 8, len * 0.35);
|
|
18236
|
+
const k = (len - tipLen) / len;
|
|
18237
|
+
const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
|
|
18238
|
+
const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
|
|
18239
|
+
const tipStart = shaftEnd;
|
|
18240
|
+
const tipEnd = delta;
|
|
18241
|
+
const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
|
|
18242
|
+
const tipLenActual = segmentLength(tipStart, tipEnd);
|
|
18243
|
+
const tip = {
|
|
18244
|
+
type: "draw-mesh",
|
|
18245
|
+
shape: "cone",
|
|
18246
|
+
position: {
|
|
18247
|
+
x: (tipStart[0] + tipEnd[0]) / 2,
|
|
18248
|
+
y: (tipStart[1] + tipEnd[1]) / 2,
|
|
18249
|
+
z: (tipStart[2] + tipEnd[2]) / 2
|
|
18250
|
+
},
|
|
18251
|
+
radius: shaftRadius * 3,
|
|
18252
|
+
height: tipLenActual,
|
|
18253
|
+
rotation: axisRotation(tipStart, tipEnd),
|
|
18254
|
+
pivot: "center",
|
|
18255
|
+
segments: 12,
|
|
18256
|
+
material: { color }
|
|
18257
|
+
};
|
|
18258
|
+
return {
|
|
18259
|
+
type: "draw-group",
|
|
18260
|
+
...id !== void 0 ? { id } : {},
|
|
18261
|
+
position: { x: from[0], y: from[1], z: from[2] },
|
|
18262
|
+
items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
|
|
18263
|
+
};
|
|
18264
|
+
}
|
|
18265
|
+
function billboardLabel(text, x, y, z, opts) {
|
|
18266
|
+
return {
|
|
18267
|
+
type: "draw-text",
|
|
18268
|
+
text,
|
|
18269
|
+
position: { x, y, z },
|
|
18270
|
+
color: opts?.color ?? "#111827"
|
|
18271
|
+
};
|
|
18272
|
+
}
|
|
18273
|
+
function labelColorForBackground(backgroundColor) {
|
|
18274
|
+
const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
|
|
18275
|
+
if (!m) return "#111827";
|
|
18276
|
+
const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
|
|
18277
|
+
const r2 = parseInt(hex.slice(0, 2), 16) / 255;
|
|
18278
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
18279
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
18280
|
+
const luminance = 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
|
|
18281
|
+
return luminance < 0.5 ? "#e5e7eb" : "#111827";
|
|
18282
|
+
}
|
|
18283
|
+
function get3DClickPayload(onShapeClick, idToIndex) {
|
|
18284
|
+
if (!onShapeClick) return void 0;
|
|
18285
|
+
return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
|
|
18286
|
+
}
|
|
18287
|
+
function polylineTube(points, radius, color, opts) {
|
|
18288
|
+
const maxSegments = opts?.maxSegments ?? 128;
|
|
18289
|
+
let pts = points;
|
|
18290
|
+
if (pts.length - 1 > maxSegments) {
|
|
18291
|
+
const step = (pts.length - 1) / maxSegments;
|
|
18292
|
+
const kept = [pts[0]];
|
|
18293
|
+
for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
|
|
18294
|
+
kept.push(pts[pts.length - 1]);
|
|
18295
|
+
pts = kept;
|
|
18296
|
+
}
|
|
18297
|
+
const out = [];
|
|
18298
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
18299
|
+
const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
|
|
18300
|
+
if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
|
|
18301
|
+
}
|
|
18302
|
+
return out;
|
|
18303
|
+
}
|
|
18304
|
+
function heightFieldMesh(spec) {
|
|
18305
|
+
const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
|
|
18306
|
+
const flatShading = spec.flatShading ?? true;
|
|
18307
|
+
const vertices = [];
|
|
18308
|
+
for (let iy = 0; iy < ny; iy++) {
|
|
18309
|
+
for (let ix = 0; ix < nx; ix++) {
|
|
18310
|
+
vertices.push([
|
|
18311
|
+
x + (ix - (nx - 1) / 2) * spacing,
|
|
18312
|
+
y + (iy - (ny - 1) / 2) * spacing,
|
|
18313
|
+
heights[iy * nx + ix] ?? 0
|
|
18314
|
+
]);
|
|
18315
|
+
}
|
|
18316
|
+
}
|
|
18317
|
+
const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
|
|
18318
|
+
const facesByBand = /* @__PURE__ */ new Map();
|
|
18319
|
+
for (let iy = 0; iy < ny - 1; iy++) {
|
|
18320
|
+
for (let ix = 0; ix < nx - 1; ix++) {
|
|
18321
|
+
const v00 = iy * nx + ix;
|
|
18322
|
+
const v10 = iy * nx + ix + 1;
|
|
18323
|
+
const v01 = (iy + 1) * nx + ix;
|
|
18324
|
+
const v11 = (iy + 1) * nx + ix + 1;
|
|
18325
|
+
for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
|
|
18326
|
+
const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
|
|
18327
|
+
let band = null;
|
|
18328
|
+
for (const b of bands) {
|
|
18329
|
+
if ((b.min ?? -Infinity) <= centroid) band = b;
|
|
18330
|
+
}
|
|
18331
|
+
const key = bands.length > 0 ? band : null;
|
|
18332
|
+
const list = facesByBand.get(key) ?? [];
|
|
18333
|
+
list.push(face);
|
|
18334
|
+
facesByBand.set(key, list);
|
|
18335
|
+
}
|
|
18336
|
+
}
|
|
18337
|
+
}
|
|
18338
|
+
const out = [];
|
|
18339
|
+
for (const [band, faces] of facesByBand) {
|
|
18340
|
+
if (faces.length === 0) continue;
|
|
18341
|
+
out.push({
|
|
18342
|
+
type: "draw-mesh",
|
|
18343
|
+
shape: "polyhedron",
|
|
18344
|
+
position: { x: 0, y: 0, z: 0 },
|
|
18345
|
+
vertices,
|
|
18346
|
+
faces,
|
|
18347
|
+
pivot: "center",
|
|
18348
|
+
material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
|
|
18349
|
+
...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
|
|
18350
|
+
});
|
|
18351
|
+
}
|
|
18352
|
+
return out;
|
|
18353
|
+
}
|
|
18354
|
+
function arrowField(vectors, opts) {
|
|
18355
|
+
const scale = opts?.scale ?? 1;
|
|
18356
|
+
const out = [];
|
|
18357
|
+
for (const v of vectors) {
|
|
18358
|
+
const to = [
|
|
18359
|
+
v.from[0] + v.delta[0] * scale,
|
|
18360
|
+
v.from[1] + v.delta[1] * scale,
|
|
18361
|
+
v.from[2] + v.delta[2] * scale
|
|
18362
|
+
];
|
|
18363
|
+
const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
|
|
18364
|
+
if (arrow) out.push(arrow);
|
|
18365
|
+
if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
|
|
18366
|
+
}
|
|
18367
|
+
return out;
|
|
18368
|
+
}
|
|
18369
|
+
function helixDrawables(spec, opts) {
|
|
18370
|
+
const count = spec.count ?? spec.rungs?.length ?? 0;
|
|
18371
|
+
const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
|
|
18372
|
+
const radius = spec.radius ?? 1;
|
|
18373
|
+
const rise = spec.rise ?? 0.34;
|
|
18374
|
+
const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
|
|
18375
|
+
const strandAColor = spec.strandAColor ?? "#38bdf8";
|
|
18376
|
+
const strandBColor = spec.strandBColor ?? "#fb923c";
|
|
18377
|
+
const backboneRadius = spec.backboneRadius ?? 0.16;
|
|
18378
|
+
const rungRadius = spec.rungRadius ?? 0.12;
|
|
18379
|
+
const cx = spec.x ?? 0;
|
|
18380
|
+
const cy = spec.y ?? 0;
|
|
18381
|
+
const cz = spec.z ?? 0;
|
|
18382
|
+
const unwoundCount = spec.unwoundCount ?? 0;
|
|
18383
|
+
const unwindSpread = spec.unwindSpread ?? 1.8;
|
|
18384
|
+
const strandA = [];
|
|
18385
|
+
const strandB = [];
|
|
18386
|
+
for (let i = 0; i < count; i++) {
|
|
18387
|
+
const yi = cy + (i - (count - 1) / 2) * rise;
|
|
18388
|
+
const theta = i * twistRad;
|
|
18389
|
+
const s = i < unwoundCount ? unwindSpread : 1;
|
|
18390
|
+
strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
|
|
18391
|
+
strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
|
|
18392
|
+
}
|
|
18393
|
+
const out = [];
|
|
18394
|
+
for (let i = 0; i < count; i++) {
|
|
18395
|
+
out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
|
|
18396
|
+
out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
|
|
18397
|
+
if (i > 0) {
|
|
18398
|
+
const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
|
|
18399
|
+
if (segA) out.push(segA);
|
|
18400
|
+
const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
|
|
18401
|
+
if (segB) out.push(segB);
|
|
18402
|
+
}
|
|
18403
|
+
const rung = rungs[i];
|
|
18404
|
+
const rungColor = rung.color ?? "#94a3b8";
|
|
18405
|
+
const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
|
|
18406
|
+
if (rod) out.push(rod);
|
|
18407
|
+
const mid = [
|
|
18408
|
+
(strandA[i][0] + strandB[i][0]) / 2,
|
|
18409
|
+
(strandA[i][1] + strandB[i][1]) / 2,
|
|
18410
|
+
(strandA[i][2] + strandB[i][2]) / 2
|
|
18411
|
+
];
|
|
18412
|
+
const markerRadius = rung.radius ?? rungRadius;
|
|
18413
|
+
out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
|
|
18414
|
+
if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
|
|
18415
|
+
}
|
|
18416
|
+
return out;
|
|
18417
|
+
}
|
|
18418
|
+
function latticeDrawables(spec, opts) {
|
|
18419
|
+
const nx = spec.nx ?? 2;
|
|
18420
|
+
const ny = spec.ny ?? 2;
|
|
18421
|
+
const nz = spec.nz ?? 2;
|
|
18422
|
+
const latticeConstant = spec.latticeConstant ?? 2;
|
|
18423
|
+
const bondRadius = spec.bondRadius ?? 0.06;
|
|
18424
|
+
const highlightCell = spec.highlightCell ?? false;
|
|
18425
|
+
const dimColor = spec.dimColor ?? "#475569";
|
|
18426
|
+
const showLabels = spec.showLabels ?? false;
|
|
18427
|
+
const selectedColor = spec.selectedColor ?? "#f59e0b";
|
|
18428
|
+
const posByKey = /* @__PURE__ */ new Map();
|
|
18429
|
+
const inCellByKey = /* @__PURE__ */ new Map();
|
|
18430
|
+
const out = [];
|
|
18431
|
+
for (const site of spec.basis) {
|
|
18432
|
+
const snx = site.xEdge ? nx + 1 : nx;
|
|
18433
|
+
const sny = site.yEdge ? ny + 1 : ny;
|
|
18434
|
+
const snz = site.zEdge ? nz + 1 : nz;
|
|
18435
|
+
for (let i = 0; i < snx; i++) {
|
|
18436
|
+
for (let j = 0; j < sny; j++) {
|
|
18437
|
+
for (let k = 0; k < snz; k++) {
|
|
18438
|
+
const key = `${site.key}-${i}-${j}-${k}`;
|
|
18439
|
+
const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
|
|
18440
|
+
const pos = [
|
|
18441
|
+
(i + site.dx) * latticeConstant - nx * latticeConstant / 2,
|
|
18442
|
+
(j + site.dy) * latticeConstant - ny * latticeConstant / 2,
|
|
18443
|
+
(k + site.dz) * latticeConstant - nz * latticeConstant / 2
|
|
18444
|
+
];
|
|
18445
|
+
posByKey.set(key, pos);
|
|
18446
|
+
inCellByKey.set(key, inCell);
|
|
18447
|
+
const isSelected = spec.selectedId === `lat-${key}`;
|
|
18448
|
+
const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
|
|
18449
|
+
const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
|
|
18450
|
+
out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
|
|
18451
|
+
if (showLabels && site.element) {
|
|
18452
|
+
out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
|
|
18453
|
+
}
|
|
18454
|
+
}
|
|
18455
|
+
}
|
|
18456
|
+
}
|
|
18457
|
+
}
|
|
18458
|
+
const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
|
|
18459
|
+
for (const bond of spec.bonds ?? []) {
|
|
18460
|
+
const fromSite = basisByKey.get(bond.from);
|
|
18461
|
+
const toSite = basisByKey.get(bond.to);
|
|
18462
|
+
if (!fromSite || !toSite) continue;
|
|
18463
|
+
const fnx = fromSite.xEdge ? nx + 1 : nx;
|
|
18464
|
+
const fny = fromSite.yEdge ? ny + 1 : ny;
|
|
18465
|
+
const fnz = fromSite.zEdge ? nz + 1 : nz;
|
|
18466
|
+
const tnx = toSite.xEdge ? nx + 1 : nx;
|
|
18467
|
+
const tny = toSite.yEdge ? ny + 1 : ny;
|
|
18468
|
+
const tnz = toSite.zEdge ? nz + 1 : nz;
|
|
18469
|
+
const bdx = bond.dx ?? 0;
|
|
18470
|
+
const bdy = bond.dy ?? 0;
|
|
18471
|
+
const bdz = bond.dz ?? 0;
|
|
18472
|
+
for (let i = 0; i < fnx; i++) {
|
|
18473
|
+
for (let j = 0; j < fny; j++) {
|
|
18474
|
+
for (let k = 0; k < fnz; k++) {
|
|
18475
|
+
const ti = i + bdx;
|
|
18476
|
+
const tj = j + bdy;
|
|
18477
|
+
const tk = k + bdz;
|
|
18478
|
+
if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
|
|
18479
|
+
const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
|
|
18480
|
+
const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
|
|
18481
|
+
const fromPos = posByKey.get(fromKey);
|
|
18482
|
+
const toPos = posByKey.get(toKey);
|
|
18483
|
+
if (!fromPos || !toPos) continue;
|
|
18484
|
+
const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
|
|
18485
|
+
const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
|
|
18486
|
+
if (seg) out.push(seg);
|
|
18487
|
+
}
|
|
18488
|
+
}
|
|
18489
|
+
}
|
|
18490
|
+
}
|
|
18491
|
+
return out;
|
|
18492
|
+
}
|
|
18493
|
+
var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost2;
|
|
18494
|
+
var init_learningScene3D = __esm({
|
|
18495
|
+
"components/learning/molecules/learningScene3D.tsx"() {
|
|
18496
|
+
"use client";
|
|
18497
|
+
init_atoms();
|
|
18498
|
+
init_Stack();
|
|
18499
|
+
init_useEventBus();
|
|
18500
|
+
sceneLog = logger.createLogger("almadar:ui:learning-scene-3d");
|
|
18501
|
+
KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
|
|
18502
|
+
Canvas3DHost2 = React94.lazy(
|
|
18503
|
+
() => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
|
|
18504
|
+
);
|
|
18505
|
+
LearningScene3D.displayName = "LearningScene3D";
|
|
18506
|
+
}
|
|
18507
|
+
});
|
|
18508
|
+
var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
|
|
17175
18509
|
var init_BiologyCanvas = __esm({
|
|
17176
18510
|
"components/learning/molecules/BiologyCanvas.tsx"() {
|
|
17177
18511
|
"use client";
|
|
17178
18512
|
init_atoms();
|
|
17179
18513
|
init_Stack();
|
|
17180
18514
|
init_LearningCanvas();
|
|
18515
|
+
init_learningScene3D();
|
|
18516
|
+
biologyLog = logger.createLogger("almadar:ui:biology-canvas");
|
|
18517
|
+
BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
|
|
18518
|
+
BIO_STAGE_FILL = {
|
|
18519
|
+
pending: "#e2e8f0",
|
|
18520
|
+
active: "#3b82f6",
|
|
18521
|
+
done: "#94a3b8"
|
|
18522
|
+
};
|
|
18523
|
+
BIO_STAGE_TEXT = {
|
|
18524
|
+
pending: "#64748b",
|
|
18525
|
+
active: "#ffffff",
|
|
18526
|
+
done: "#ffffff"
|
|
18527
|
+
};
|
|
17181
18528
|
BiologyCanvas = ({
|
|
17182
18529
|
className,
|
|
17183
18530
|
width = 600,
|
|
17184
18531
|
height = 400,
|
|
17185
18532
|
title,
|
|
17186
18533
|
backgroundColor,
|
|
18534
|
+
mode = "2d",
|
|
18535
|
+
camera,
|
|
18536
|
+
lighting,
|
|
18537
|
+
post,
|
|
17187
18538
|
nodes = [],
|
|
17188
18539
|
edges = [],
|
|
18540
|
+
compartments = [],
|
|
18541
|
+
bands = [],
|
|
18542
|
+
stages = [],
|
|
18543
|
+
stageStyle = "timeline",
|
|
18544
|
+
helix,
|
|
18545
|
+
helix3d,
|
|
17189
18546
|
shapes = [],
|
|
17190
|
-
|
|
18547
|
+
readouts,
|
|
18548
|
+
traces,
|
|
18549
|
+
showGrid,
|
|
18550
|
+
shadows,
|
|
18551
|
+
interactive,
|
|
17191
18552
|
animate = false,
|
|
17192
18553
|
onShapeClick,
|
|
17193
18554
|
isLoading,
|
|
@@ -17199,19 +18560,148 @@ var init_BiologyCanvas = __esm({
|
|
|
17199
18560
|
for (const n of nodes) {
|
|
17200
18561
|
if (n.id) nodeById.set(n.id, n);
|
|
17201
18562
|
}
|
|
18563
|
+
const bandCount = bands.length;
|
|
18564
|
+
for (let i = 0; i < bandCount; i++) {
|
|
18565
|
+
const band = bands[i];
|
|
18566
|
+
const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
|
|
18567
|
+
const bandY = i * height / bandCount;
|
|
18568
|
+
const bandH = height / bandCount;
|
|
18569
|
+
out.push({
|
|
18570
|
+
type: "rect",
|
|
18571
|
+
x: 0,
|
|
18572
|
+
y: bandY,
|
|
18573
|
+
width,
|
|
18574
|
+
height: bandH,
|
|
18575
|
+
color: bandColor,
|
|
18576
|
+
fill: bandColor,
|
|
18577
|
+
opacity: 0.45
|
|
18578
|
+
});
|
|
18579
|
+
if (band.label) {
|
|
18580
|
+
out.push({
|
|
18581
|
+
type: "text",
|
|
18582
|
+
x: 8,
|
|
18583
|
+
y: bandY + 14,
|
|
18584
|
+
text: band.label,
|
|
18585
|
+
color: "#6b7280",
|
|
18586
|
+
fontSize: 10
|
|
18587
|
+
});
|
|
18588
|
+
}
|
|
18589
|
+
}
|
|
18590
|
+
for (const c of compartments) {
|
|
18591
|
+
const color = c.color ?? "#16a34a";
|
|
18592
|
+
out.push({
|
|
18593
|
+
type: "ellipse",
|
|
18594
|
+
x: c.x,
|
|
18595
|
+
y: c.y,
|
|
18596
|
+
width: c.width,
|
|
18597
|
+
height: c.height,
|
|
18598
|
+
color,
|
|
18599
|
+
fill: c.fill ?? `${color}1A`,
|
|
18600
|
+
lineWidth: c.lineWidth ?? 2,
|
|
18601
|
+
...c.dash ? { dash: c.dash } : {}
|
|
18602
|
+
});
|
|
18603
|
+
if (c.label) {
|
|
18604
|
+
out.push({
|
|
18605
|
+
type: "text",
|
|
18606
|
+
x: c.x,
|
|
18607
|
+
y: c.y - c.height / 2 + 14,
|
|
18608
|
+
text: c.label,
|
|
18609
|
+
color: "#111827",
|
|
18610
|
+
fontSize: 11,
|
|
18611
|
+
align: "center"
|
|
18612
|
+
});
|
|
18613
|
+
}
|
|
18614
|
+
}
|
|
18615
|
+
if (helix) {
|
|
18616
|
+
const hx = helix.x ?? 24;
|
|
18617
|
+
const hy = helix.y ?? height * 0.25;
|
|
18618
|
+
const hw = helix.width ?? width - 48;
|
|
18619
|
+
const hh = helix.height ?? height * 0.5;
|
|
18620
|
+
const rungs = helix.rungs;
|
|
18621
|
+
const n = rungs.length;
|
|
18622
|
+
const cy = hy + hh / 2;
|
|
18623
|
+
const colorA = helix.colorA ?? "#2563eb";
|
|
18624
|
+
const colorB = helix.colorB ?? "#dc2626";
|
|
18625
|
+
const rungColor = helix.rungColor ?? "#94a3b8";
|
|
18626
|
+
const fork = helix.fork ?? 0;
|
|
18627
|
+
const maxSep = Math.min(hh - 8, 96);
|
|
18628
|
+
const strandA = [];
|
|
18629
|
+
const strandB = [];
|
|
18630
|
+
const rungGeoms = [];
|
|
18631
|
+
for (let i = 0; i < n; i++) {
|
|
18632
|
+
const rx = hx + (i + 0.5) * hw / n;
|
|
18633
|
+
const t = (i + 0.5) / n;
|
|
18634
|
+
const paired = t >= fork;
|
|
18635
|
+
const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
|
|
18636
|
+
strandA.push({ x: rx, y: cy - sep / 2 });
|
|
18637
|
+
strandB.push({ x: rx, y: cy + sep / 2 });
|
|
18638
|
+
rungGeoms.push({ rx, sep, rung: rungs[i], paired });
|
|
18639
|
+
}
|
|
18640
|
+
for (let i = 1; i < n; i++) {
|
|
18641
|
+
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 });
|
|
18642
|
+
}
|
|
18643
|
+
for (let i = 1; i < n; i++) {
|
|
18644
|
+
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 });
|
|
18645
|
+
}
|
|
18646
|
+
for (const g of rungGeoms) {
|
|
18647
|
+
const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
|
|
18648
|
+
const topY = cy - g.sep / 2;
|
|
18649
|
+
const bottomY = cy + g.sep / 2;
|
|
18650
|
+
if (g.paired) {
|
|
18651
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
|
|
18652
|
+
if (g.rung.a) {
|
|
18653
|
+
out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
18654
|
+
}
|
|
18655
|
+
if (g.rung.b) {
|
|
18656
|
+
out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
18657
|
+
}
|
|
18658
|
+
} else {
|
|
18659
|
+
const stubTopY = topY + 8;
|
|
18660
|
+
const stubBottomY = bottomY - 8;
|
|
18661
|
+
out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
|
|
18662
|
+
out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
|
|
18663
|
+
if (g.rung.a) {
|
|
18664
|
+
out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
|
|
18665
|
+
}
|
|
18666
|
+
if (g.rung.b) {
|
|
18667
|
+
out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
|
|
18668
|
+
}
|
|
18669
|
+
}
|
|
18670
|
+
}
|
|
18671
|
+
}
|
|
17202
18672
|
for (const e of edges) {
|
|
17203
18673
|
const a = nodeById.get(e.from);
|
|
17204
18674
|
const b = nodeById.get(e.to);
|
|
17205
18675
|
if (!a || !b) continue;
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
17209
|
-
|
|
17210
|
-
|
|
17211
|
-
|
|
17212
|
-
|
|
17213
|
-
|
|
17214
|
-
|
|
18676
|
+
const color = e.color ?? "#9ca3af";
|
|
18677
|
+
if (e.directed) {
|
|
18678
|
+
const rA = a.radius ?? 16;
|
|
18679
|
+
const rB = b.radius ?? 16;
|
|
18680
|
+
const dx = b.x - a.x;
|
|
18681
|
+
const dy = b.y - a.y;
|
|
18682
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
18683
|
+
const ux = dx / dist;
|
|
18684
|
+
const uy = dy / dist;
|
|
18685
|
+
out.push({
|
|
18686
|
+
type: "arrow",
|
|
18687
|
+
x1: a.x + ux * rA,
|
|
18688
|
+
y1: a.y + uy * rA,
|
|
18689
|
+
x2: b.x - ux * rB,
|
|
18690
|
+
y2: b.y - uy * rB,
|
|
18691
|
+
color,
|
|
18692
|
+
lineWidth: 2
|
|
18693
|
+
});
|
|
18694
|
+
} else {
|
|
18695
|
+
out.push({
|
|
18696
|
+
type: "line",
|
|
18697
|
+
x1: a.x,
|
|
18698
|
+
y1: a.y,
|
|
18699
|
+
x2: b.x,
|
|
18700
|
+
y2: b.y,
|
|
18701
|
+
color,
|
|
18702
|
+
lineWidth: 2
|
|
18703
|
+
});
|
|
18704
|
+
}
|
|
17215
18705
|
if (e.label) {
|
|
17216
18706
|
out.push({
|
|
17217
18707
|
type: "text",
|
|
@@ -17224,6 +18714,8 @@ var init_BiologyCanvas = __esm({
|
|
|
17224
18714
|
}
|
|
17225
18715
|
}
|
|
17226
18716
|
for (const n of nodes) {
|
|
18717
|
+
const state = n.state ?? "default";
|
|
18718
|
+
const muted = state === "muted";
|
|
17227
18719
|
out.push({
|
|
17228
18720
|
type: "circle",
|
|
17229
18721
|
x: n.x,
|
|
@@ -17231,23 +18723,197 @@ var init_BiologyCanvas = __esm({
|
|
|
17231
18723
|
radius: n.radius ?? 16,
|
|
17232
18724
|
color: n.color ?? "#16a34a",
|
|
17233
18725
|
fill: `${n.color ?? "#16a34a"}33`,
|
|
17234
|
-
id: n.id
|
|
18726
|
+
id: n.id,
|
|
18727
|
+
...muted ? { opacity: 0.35 } : {}
|
|
17235
18728
|
});
|
|
18729
|
+
if (state === "highlight") {
|
|
18730
|
+
out.push({
|
|
18731
|
+
type: "circle",
|
|
18732
|
+
x: n.x,
|
|
18733
|
+
y: n.y,
|
|
18734
|
+
radius: (n.radius ?? 16) + 4,
|
|
18735
|
+
color: "#f59e0b",
|
|
18736
|
+
lineWidth: 2
|
|
18737
|
+
});
|
|
18738
|
+
}
|
|
17236
18739
|
if (n.label) {
|
|
17237
18740
|
out.push({
|
|
17238
18741
|
type: "text",
|
|
17239
18742
|
x: n.x,
|
|
17240
18743
|
y: n.y + (n.radius ?? 16) + 14,
|
|
17241
18744
|
text: n.label,
|
|
18745
|
+
...muted ? { opacity: 0.35 } : {},
|
|
17242
18746
|
color: "#111827",
|
|
17243
18747
|
fontSize: 12,
|
|
17244
18748
|
align: "center"
|
|
17245
18749
|
});
|
|
17246
18750
|
}
|
|
17247
18751
|
}
|
|
18752
|
+
const stageCount = stages.length;
|
|
18753
|
+
if (stageCount > 0) {
|
|
18754
|
+
if (stageStyle === "ring") {
|
|
18755
|
+
const cx = width / 2;
|
|
18756
|
+
const cy = height / 2;
|
|
18757
|
+
const R = Math.min(width, height) / 2 - 48;
|
|
18758
|
+
const ringPoints = [];
|
|
18759
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18760
|
+
const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
|
|
18761
|
+
ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
|
|
18762
|
+
}
|
|
18763
|
+
if (stageCount >= 2) {
|
|
18764
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
18765
|
+
const p1 = ringPoints[i];
|
|
18766
|
+
const p2 = ringPoints[i + 1];
|
|
18767
|
+
const dx = p2.x - p1.x;
|
|
18768
|
+
const dy = p2.y - p1.y;
|
|
18769
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
18770
|
+
const ux = dx / dist;
|
|
18771
|
+
const uy = dy / dist;
|
|
18772
|
+
out.push({
|
|
18773
|
+
type: "arrow",
|
|
18774
|
+
x1: p1.x + ux * 46,
|
|
18775
|
+
y1: p1.y + uy * 46,
|
|
18776
|
+
x2: p2.x - ux * 46,
|
|
18777
|
+
y2: p2.y - uy * 46,
|
|
18778
|
+
color: "#94a3b8"
|
|
18779
|
+
});
|
|
18780
|
+
}
|
|
18781
|
+
}
|
|
18782
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18783
|
+
const stage = stages[i];
|
|
18784
|
+
const state = stage.state ?? "pending";
|
|
18785
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
18786
|
+
const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
|
|
18787
|
+
const h = 18;
|
|
18788
|
+
const p = ringPoints[i];
|
|
18789
|
+
out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
|
|
18790
|
+
out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
|
|
18791
|
+
}
|
|
18792
|
+
} else {
|
|
18793
|
+
const stripY = height - 32;
|
|
18794
|
+
const slotW = (width - 16) / stageCount;
|
|
18795
|
+
const chipGeoms = [];
|
|
18796
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18797
|
+
chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
|
|
18798
|
+
}
|
|
18799
|
+
for (let i = 0; i < stageCount - 1; i++) {
|
|
18800
|
+
const midY = stripY + 13;
|
|
18801
|
+
out.push({
|
|
18802
|
+
type: "arrow",
|
|
18803
|
+
x1: chipGeoms[i].x + chipGeoms[i].w,
|
|
18804
|
+
y1: midY,
|
|
18805
|
+
x2: chipGeoms[i + 1].x,
|
|
18806
|
+
y2: midY,
|
|
18807
|
+
color: "#94a3b8"
|
|
18808
|
+
});
|
|
18809
|
+
}
|
|
18810
|
+
for (let i = 0; i < stageCount; i++) {
|
|
18811
|
+
const stage = stages[i];
|
|
18812
|
+
const state = stage.state ?? "pending";
|
|
18813
|
+
const fill = stage.color ?? BIO_STAGE_FILL[state];
|
|
18814
|
+
const g = chipGeoms[i];
|
|
18815
|
+
out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
|
|
18816
|
+
out.push({
|
|
18817
|
+
type: "text",
|
|
18818
|
+
x: g.x + g.w / 2,
|
|
18819
|
+
y: stripY + 13,
|
|
18820
|
+
text: stage.label,
|
|
18821
|
+
color: BIO_STAGE_TEXT[state],
|
|
18822
|
+
fontSize: 10,
|
|
18823
|
+
align: "center"
|
|
18824
|
+
});
|
|
18825
|
+
}
|
|
18826
|
+
}
|
|
18827
|
+
}
|
|
17248
18828
|
out.push(...shapes);
|
|
17249
18829
|
return out;
|
|
17250
|
-
}, [nodes, edges, shapes]);
|
|
18830
|
+
}, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
|
|
18831
|
+
const drawables3D = React94.useMemo(() => {
|
|
18832
|
+
if (mode !== "3d") return [];
|
|
18833
|
+
if (shapes.length > 0) {
|
|
18834
|
+
biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
18835
|
+
}
|
|
18836
|
+
if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
|
|
18837
|
+
biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
|
|
18838
|
+
compartments: compartments.length,
|
|
18839
|
+
bands: bands.length,
|
|
18840
|
+
stages: stages.length,
|
|
18841
|
+
helix: helix != null
|
|
18842
|
+
});
|
|
18843
|
+
}
|
|
18844
|
+
if (animate) {
|
|
18845
|
+
biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
18846
|
+
}
|
|
18847
|
+
const out = [];
|
|
18848
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
18849
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
18850
|
+
for (const n of nodes) {
|
|
18851
|
+
if (n.id) nodeById.set(n.id, n);
|
|
18852
|
+
}
|
|
18853
|
+
for (const e of edges) {
|
|
18854
|
+
const a = nodeById.get(e.from);
|
|
18855
|
+
const b = nodeById.get(e.to);
|
|
18856
|
+
if (!a || !b) continue;
|
|
18857
|
+
const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
|
|
18858
|
+
const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
|
|
18859
|
+
if (edge) out.push(edge);
|
|
18860
|
+
if (e.label) {
|
|
18861
|
+
out.push(
|
|
18862
|
+
billboardLabel(
|
|
18863
|
+
e.label,
|
|
18864
|
+
(a.x + b.x) / 2,
|
|
18865
|
+
(a.y + b.y) / 2,
|
|
18866
|
+
((a.z ?? 0) + (b.z ?? 0)) / 2,
|
|
18867
|
+
{ color: labelColor }
|
|
18868
|
+
)
|
|
18869
|
+
);
|
|
18870
|
+
}
|
|
18871
|
+
}
|
|
18872
|
+
for (const n of nodes) {
|
|
18873
|
+
const radius = n.radius ?? 0.5;
|
|
18874
|
+
const nz = n.z ?? 0;
|
|
18875
|
+
out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
|
|
18876
|
+
if (n.label) {
|
|
18877
|
+
out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
|
|
18878
|
+
}
|
|
18879
|
+
}
|
|
18880
|
+
if (helix3d) {
|
|
18881
|
+
out.push(...helixDrawables(helix3d, { labelColor }));
|
|
18882
|
+
}
|
|
18883
|
+
return out;
|
|
18884
|
+
}, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
|
|
18885
|
+
const nodeIndexById = React94.useMemo(() => {
|
|
18886
|
+
const m = /* @__PURE__ */ new Map();
|
|
18887
|
+
(helix3d?.rungs ?? []).forEach((rung, i) => {
|
|
18888
|
+
if (rung.id) m.set(rung.id, i);
|
|
18889
|
+
});
|
|
18890
|
+
nodes.forEach((n, i) => {
|
|
18891
|
+
if (n.id) m.set(n.id, i);
|
|
18892
|
+
});
|
|
18893
|
+
return m;
|
|
18894
|
+
}, [nodes, helix3d]);
|
|
18895
|
+
if (mode === "3d") {
|
|
18896
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
18897
|
+
LearningScene3D,
|
|
18898
|
+
{
|
|
18899
|
+
className,
|
|
18900
|
+
width,
|
|
18901
|
+
height,
|
|
18902
|
+
title,
|
|
18903
|
+
backgroundColor,
|
|
18904
|
+
drawables: drawables3D,
|
|
18905
|
+
camera,
|
|
18906
|
+
lighting,
|
|
18907
|
+
post,
|
|
18908
|
+
showGrid,
|
|
18909
|
+
shadows,
|
|
18910
|
+
interactive,
|
|
18911
|
+
isLoading,
|
|
18912
|
+
error,
|
|
18913
|
+
onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
|
|
18914
|
+
}
|
|
18915
|
+
);
|
|
18916
|
+
}
|
|
17251
18917
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
17252
18918
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
17253
18919
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -17257,7 +18923,9 @@ var init_BiologyCanvas = __esm({
|
|
|
17257
18923
|
height,
|
|
17258
18924
|
backgroundColor,
|
|
17259
18925
|
shapes: derivedShapes,
|
|
17260
|
-
|
|
18926
|
+
readouts,
|
|
18927
|
+
traces,
|
|
18928
|
+
interactive: interactive ?? false,
|
|
17261
18929
|
animate,
|
|
17262
18930
|
onShapeClick,
|
|
17263
18931
|
isLoading,
|
|
@@ -24097,24 +25765,55 @@ var init_ChatBar = __esm({
|
|
|
24097
25765
|
ChatBar.displayName = "ChatBar";
|
|
24098
25766
|
}
|
|
24099
25767
|
});
|
|
24100
|
-
|
|
25768
|
+
function bondPerpendicular(a, b) {
|
|
25769
|
+
const dx = b[0] - a[0];
|
|
25770
|
+
const dy = b[1] - a[1];
|
|
25771
|
+
const px = dy;
|
|
25772
|
+
const py = -dx;
|
|
25773
|
+
const len = Math.sqrt(px * px + py * py);
|
|
25774
|
+
if (len < 1e-6) return [1, 0, 0];
|
|
25775
|
+
return [px / len, py / len, 0];
|
|
25776
|
+
}
|
|
25777
|
+
var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
|
|
24101
25778
|
var init_ChemistryCanvas = __esm({
|
|
24102
25779
|
"components/learning/molecules/ChemistryCanvas.tsx"() {
|
|
24103
25780
|
"use client";
|
|
24104
25781
|
init_atoms();
|
|
24105
25782
|
init_Stack();
|
|
24106
25783
|
init_LearningCanvas();
|
|
25784
|
+
init_learningScene3D();
|
|
25785
|
+
chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
|
|
25786
|
+
CHEM_BOND_STATE_COLOR = {
|
|
25787
|
+
default: "#6b7280",
|
|
25788
|
+
forming: "#16a34a",
|
|
25789
|
+
breaking: "#dc2626",
|
|
25790
|
+
highlight: "#f59e0b"
|
|
25791
|
+
};
|
|
25792
|
+
LONE_PAIR_ANGLES = [-90, 0, 90, 180];
|
|
24107
25793
|
ChemistryCanvas = ({
|
|
24108
25794
|
className,
|
|
24109
25795
|
width = 600,
|
|
24110
25796
|
height = 400,
|
|
24111
25797
|
title,
|
|
24112
25798
|
backgroundColor,
|
|
25799
|
+
mode = "2d",
|
|
25800
|
+
camera,
|
|
25801
|
+
lighting,
|
|
25802
|
+
post,
|
|
24113
25803
|
atoms = [],
|
|
24114
25804
|
bonds = [],
|
|
24115
25805
|
arrows = [],
|
|
25806
|
+
bondStyle = "thick",
|
|
25807
|
+
containers = [],
|
|
25808
|
+
equation,
|
|
25809
|
+
equationColor,
|
|
25810
|
+
lattice3d,
|
|
24116
25811
|
shapes = [],
|
|
24117
|
-
|
|
25812
|
+
readouts,
|
|
25813
|
+
traces,
|
|
25814
|
+
showGrid,
|
|
25815
|
+
shadows,
|
|
25816
|
+
interactive,
|
|
24118
25817
|
animate = false,
|
|
24119
25818
|
onShapeClick,
|
|
24120
25819
|
isLoading,
|
|
@@ -24126,21 +25825,118 @@ var init_ChemistryCanvas = __esm({
|
|
|
24126
25825
|
for (const a of atoms) {
|
|
24127
25826
|
if (a.id) atomById.set(a.id, a);
|
|
24128
25827
|
}
|
|
25828
|
+
for (const c of containers) {
|
|
25829
|
+
const color = c.color ?? "#64748b";
|
|
25830
|
+
if (c.level != null) {
|
|
25831
|
+
const lv = c.level;
|
|
25832
|
+
out.push({
|
|
25833
|
+
type: "rect",
|
|
25834
|
+
x: c.x + 1,
|
|
25835
|
+
y: c.y + c.height * (1 - lv),
|
|
25836
|
+
width: c.width - 2,
|
|
25837
|
+
height: c.height * lv - 1,
|
|
25838
|
+
color: c.levelColor ?? "#60a5fa",
|
|
25839
|
+
fill: c.levelColor ?? "#60a5fa",
|
|
25840
|
+
opacity: 0.5
|
|
25841
|
+
});
|
|
25842
|
+
}
|
|
25843
|
+
out.push({
|
|
25844
|
+
type: "rect",
|
|
25845
|
+
x: c.x,
|
|
25846
|
+
y: c.y,
|
|
25847
|
+
width: c.width,
|
|
25848
|
+
height: c.height,
|
|
25849
|
+
color,
|
|
25850
|
+
fill: c.fill,
|
|
25851
|
+
lineWidth: c.lineWidth ?? 2
|
|
25852
|
+
});
|
|
25853
|
+
const divider = c.divider ?? "none";
|
|
25854
|
+
if (divider !== "none") {
|
|
25855
|
+
out.push({
|
|
25856
|
+
type: "line",
|
|
25857
|
+
x1: c.x + c.width / 2,
|
|
25858
|
+
y1: c.y,
|
|
25859
|
+
x2: c.x + c.width / 2,
|
|
25860
|
+
y2: c.y + c.height,
|
|
25861
|
+
color: c.dividerColor ?? color,
|
|
25862
|
+
...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
|
|
25863
|
+
});
|
|
25864
|
+
}
|
|
25865
|
+
if (c.leftLabel) {
|
|
25866
|
+
out.push({
|
|
25867
|
+
type: "text",
|
|
25868
|
+
x: c.x + c.width * 0.25,
|
|
25869
|
+
y: c.y + 12,
|
|
25870
|
+
text: c.leftLabel,
|
|
25871
|
+
color: "#374151",
|
|
25872
|
+
fontSize: 11,
|
|
25873
|
+
align: "center"
|
|
25874
|
+
});
|
|
25875
|
+
}
|
|
25876
|
+
if (c.rightLabel) {
|
|
25877
|
+
out.push({
|
|
25878
|
+
type: "text",
|
|
25879
|
+
x: c.x + c.width * 0.75,
|
|
25880
|
+
y: c.y + 12,
|
|
25881
|
+
text: c.rightLabel,
|
|
25882
|
+
color: "#374151",
|
|
25883
|
+
fontSize: 11,
|
|
25884
|
+
align: "center"
|
|
25885
|
+
});
|
|
25886
|
+
}
|
|
25887
|
+
if (c.label) {
|
|
25888
|
+
out.push({
|
|
25889
|
+
type: "text",
|
|
25890
|
+
x: c.x + c.width / 2,
|
|
25891
|
+
y: c.y + c.height + 12,
|
|
25892
|
+
text: c.label,
|
|
25893
|
+
color: "#111827",
|
|
25894
|
+
fontSize: 12,
|
|
25895
|
+
align: "center"
|
|
25896
|
+
});
|
|
25897
|
+
}
|
|
25898
|
+
}
|
|
24129
25899
|
for (const b of bonds) {
|
|
24130
25900
|
const a = atomById.get(b.from);
|
|
24131
25901
|
const c = atomById.get(b.to);
|
|
24132
25902
|
if (!a || !c) continue;
|
|
24133
|
-
const
|
|
24134
|
-
const
|
|
24135
|
-
|
|
24136
|
-
|
|
24137
|
-
|
|
24138
|
-
|
|
24139
|
-
|
|
24140
|
-
|
|
24141
|
-
|
|
24142
|
-
|
|
24143
|
-
|
|
25903
|
+
const state = b.state ?? "default";
|
|
25904
|
+
const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
|
|
25905
|
+
const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
|
|
25906
|
+
if (bondStyle === "parallel") {
|
|
25907
|
+
const dx = c.x - a.x;
|
|
25908
|
+
const dy = c.y - a.y;
|
|
25909
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
25910
|
+
const ux = dx / dist;
|
|
25911
|
+
const uy = dy / dist;
|
|
25912
|
+
const px = -uy;
|
|
25913
|
+
const py = ux;
|
|
25914
|
+
const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
|
|
25915
|
+
for (const off of offsets) {
|
|
25916
|
+
out.push({
|
|
25917
|
+
type: "line",
|
|
25918
|
+
x1: a.x + px * off,
|
|
25919
|
+
y1: a.y + py * off,
|
|
25920
|
+
x2: c.x + px * off,
|
|
25921
|
+
y2: c.y + py * off,
|
|
25922
|
+
color,
|
|
25923
|
+
lineWidth: 2,
|
|
25924
|
+
...dash ? { dash } : {}
|
|
25925
|
+
});
|
|
25926
|
+
}
|
|
25927
|
+
} else {
|
|
25928
|
+
const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
|
|
25929
|
+
out.push({
|
|
25930
|
+
type: "line",
|
|
25931
|
+
x1: a.x,
|
|
25932
|
+
y1: a.y,
|
|
25933
|
+
x2: c.x,
|
|
25934
|
+
y2: c.y,
|
|
25935
|
+
color,
|
|
25936
|
+
lineWidth: strokeWidth,
|
|
25937
|
+
...dash ? { dash } : {}
|
|
25938
|
+
});
|
|
25939
|
+
}
|
|
24144
25940
|
}
|
|
24145
25941
|
for (const a of arrows) {
|
|
24146
25942
|
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
@@ -24189,10 +25985,142 @@ var init_ChemistryCanvas = __esm({
|
|
|
24189
25985
|
align: "center"
|
|
24190
25986
|
});
|
|
24191
25987
|
}
|
|
25988
|
+
const r2 = a.radius ?? 14;
|
|
25989
|
+
if (a.charge) {
|
|
25990
|
+
out.push({
|
|
25991
|
+
type: "text",
|
|
25992
|
+
x: a.x + r2 * 0.85,
|
|
25993
|
+
y: a.y - r2 * 0.85,
|
|
25994
|
+
text: a.charge,
|
|
25995
|
+
color: "#111827",
|
|
25996
|
+
fontSize: 9,
|
|
25997
|
+
align: "left"
|
|
25998
|
+
});
|
|
25999
|
+
}
|
|
26000
|
+
const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
|
|
26001
|
+
for (let k = 0; k < lonePairs; k++) {
|
|
26002
|
+
const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
|
|
26003
|
+
const cx = a.x + (r2 + 6) * Math.cos(angleRad);
|
|
26004
|
+
const cy = a.y + (r2 + 6) * Math.sin(angleRad);
|
|
26005
|
+
const perpX = -Math.sin(angleRad);
|
|
26006
|
+
const perpY = Math.cos(angleRad);
|
|
26007
|
+
for (const sign of [1, -1]) {
|
|
26008
|
+
out.push({
|
|
26009
|
+
type: "circle",
|
|
26010
|
+
x: cx + perpX * 2.5 * sign,
|
|
26011
|
+
y: cy + perpY * 2.5 * sign,
|
|
26012
|
+
radius: 1.5,
|
|
26013
|
+
color: "#374151",
|
|
26014
|
+
fill: "#374151"
|
|
26015
|
+
});
|
|
26016
|
+
}
|
|
26017
|
+
}
|
|
26018
|
+
}
|
|
26019
|
+
if (equation) {
|
|
26020
|
+
out.push({
|
|
26021
|
+
type: "text",
|
|
26022
|
+
x: width / 2,
|
|
26023
|
+
y: 14,
|
|
26024
|
+
text: equation,
|
|
26025
|
+
color: equationColor ?? "#111827",
|
|
26026
|
+
fontSize: 13,
|
|
26027
|
+
align: "center"
|
|
26028
|
+
});
|
|
24192
26029
|
}
|
|
24193
26030
|
out.push(...shapes);
|
|
24194
26031
|
return out;
|
|
24195
|
-
}, [atoms, bonds, arrows, shapes]);
|
|
26032
|
+
}, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
|
|
26033
|
+
const drawables3D = React94.useMemo(() => {
|
|
26034
|
+
if (mode !== "3d") return [];
|
|
26035
|
+
if (shapes.length > 0) {
|
|
26036
|
+
chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
26037
|
+
}
|
|
26038
|
+
if (containers.length > 0) {
|
|
26039
|
+
chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
|
|
26040
|
+
}
|
|
26041
|
+
if (animate) {
|
|
26042
|
+
chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
26043
|
+
}
|
|
26044
|
+
const out = [];
|
|
26045
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
26046
|
+
const atomById = /* @__PURE__ */ new Map();
|
|
26047
|
+
for (const a of atoms) {
|
|
26048
|
+
if (a.id) atomById.set(a.id, a);
|
|
26049
|
+
}
|
|
26050
|
+
for (const b of bonds) {
|
|
26051
|
+
const a = atomById.get(b.from);
|
|
26052
|
+
const c = atomById.get(b.to);
|
|
26053
|
+
if (!a || !c) continue;
|
|
26054
|
+
const color = b.color ?? "#6b7280";
|
|
26055
|
+
const from = [a.x, a.y, a.z ?? 0];
|
|
26056
|
+
const to = [c.x, c.y, c.z ?? 0];
|
|
26057
|
+
const perp = bondPerpendicular(from, to);
|
|
26058
|
+
const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
|
|
26059
|
+
const step = bondRadius * 2.2;
|
|
26060
|
+
const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
|
|
26061
|
+
for (const off of offsets) {
|
|
26062
|
+
const bond = cylinderBetween(
|
|
26063
|
+
[from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
|
|
26064
|
+
[to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
|
|
26065
|
+
bondRadius,
|
|
26066
|
+
color
|
|
26067
|
+
);
|
|
26068
|
+
if (bond) out.push(bond);
|
|
26069
|
+
}
|
|
26070
|
+
}
|
|
26071
|
+
for (const a of arrows) {
|
|
26072
|
+
const angle = (a.angle ?? 0) * (Math.PI / 180);
|
|
26073
|
+
const len = a.length ?? 60;
|
|
26074
|
+
const x2 = a.x + Math.cos(angle) * len;
|
|
26075
|
+
const y2 = a.y + Math.sin(angle) * len;
|
|
26076
|
+
const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
|
|
26077
|
+
if (arrow) out.push(arrow);
|
|
26078
|
+
if (a.label) {
|
|
26079
|
+
out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
|
|
26080
|
+
}
|
|
26081
|
+
}
|
|
26082
|
+
for (const a of atoms) {
|
|
26083
|
+
const radius = a.radius ?? 0.45;
|
|
26084
|
+
const az = a.z ?? 0;
|
|
26085
|
+
out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
|
|
26086
|
+
if (a.element) {
|
|
26087
|
+
out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
|
|
26088
|
+
}
|
|
26089
|
+
}
|
|
26090
|
+
if (lattice3d) {
|
|
26091
|
+
out.push(...latticeDrawables(lattice3d, { labelColor }));
|
|
26092
|
+
}
|
|
26093
|
+
return out;
|
|
26094
|
+
}, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
|
|
26095
|
+
const atomIndexById = React94.useMemo(() => {
|
|
26096
|
+
const m = /* @__PURE__ */ new Map();
|
|
26097
|
+
atoms.forEach((a, i) => {
|
|
26098
|
+
if (a.id) m.set(a.id, i);
|
|
26099
|
+
});
|
|
26100
|
+
return m;
|
|
26101
|
+
}, [atoms]);
|
|
26102
|
+
if (mode === "3d") {
|
|
26103
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
26104
|
+
LearningScene3D,
|
|
26105
|
+
{
|
|
26106
|
+
className,
|
|
26107
|
+
width,
|
|
26108
|
+
height,
|
|
26109
|
+
title,
|
|
26110
|
+
backgroundColor,
|
|
26111
|
+
drawables: drawables3D,
|
|
26112
|
+
camera,
|
|
26113
|
+
lighting,
|
|
26114
|
+
post,
|
|
26115
|
+
showGrid,
|
|
26116
|
+
shadows,
|
|
26117
|
+
interactive,
|
|
26118
|
+
isLoading,
|
|
26119
|
+
error,
|
|
26120
|
+
onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
|
|
26121
|
+
}
|
|
26122
|
+
);
|
|
26123
|
+
}
|
|
24196
26124
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
24197
26125
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
24198
26126
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -24202,7 +26130,9 @@ var init_ChemistryCanvas = __esm({
|
|
|
24202
26130
|
height,
|
|
24203
26131
|
backgroundColor,
|
|
24204
26132
|
shapes: derivedShapes,
|
|
24205
|
-
|
|
26133
|
+
readouts,
|
|
26134
|
+
traces,
|
|
26135
|
+
interactive: interactive ?? false,
|
|
24206
26136
|
animate,
|
|
24207
26137
|
onShapeClick,
|
|
24208
26138
|
isLoading,
|
|
@@ -30391,6 +32321,10 @@ var init_ProgressDots = __esm({
|
|
|
30391
32321
|
ProgressDots.displayName = "ProgressDots";
|
|
30392
32322
|
}
|
|
30393
32323
|
});
|
|
32324
|
+
function formatTick(v) {
|
|
32325
|
+
if (Number.isInteger(v)) return String(v);
|
|
32326
|
+
return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
32327
|
+
}
|
|
30394
32328
|
var MathCanvas;
|
|
30395
32329
|
var init_MathCanvas = __esm({
|
|
30396
32330
|
"components/learning/molecules/MathCanvas.tsx"() {
|
|
@@ -30410,10 +32344,19 @@ var init_MathCanvas = __esm({
|
|
|
30410
32344
|
showAxes = true,
|
|
30411
32345
|
showGrid = true,
|
|
30412
32346
|
gridStep = 1,
|
|
32347
|
+
showTickLabels = false,
|
|
32348
|
+
showCurveLabels = false,
|
|
30413
32349
|
curves = [],
|
|
30414
32350
|
points = [],
|
|
30415
32351
|
vectors = [],
|
|
32352
|
+
regions = [],
|
|
32353
|
+
bars = [],
|
|
32354
|
+
guides = [],
|
|
32355
|
+
angles = [],
|
|
32356
|
+
hops = [],
|
|
30416
32357
|
shapes = [],
|
|
32358
|
+
readouts,
|
|
32359
|
+
traces,
|
|
30417
32360
|
interactive = false,
|
|
30418
32361
|
animate = false,
|
|
30419
32362
|
onShapeClick,
|
|
@@ -30427,6 +32370,8 @@ var init_MathCanvas = __esm({
|
|
|
30427
32370
|
const plotH = height - margin * 2;
|
|
30428
32371
|
const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
|
|
30429
32372
|
const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
|
|
32373
|
+
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
32374
|
+
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
30430
32375
|
if (showGrid) {
|
|
30431
32376
|
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
|
|
30432
32377
|
const px = mapX(x);
|
|
@@ -30437,14 +32382,99 @@ var init_MathCanvas = __esm({
|
|
|
30437
32382
|
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
|
|
30438
32383
|
}
|
|
30439
32384
|
}
|
|
32385
|
+
if (showTickLabels) {
|
|
32386
|
+
const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
|
|
32387
|
+
let kx = 0;
|
|
32388
|
+
for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
|
|
32389
|
+
if (kx % labelEveryX === 0 && x !== 0) {
|
|
32390
|
+
out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
|
|
32391
|
+
}
|
|
32392
|
+
}
|
|
32393
|
+
const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
|
|
32394
|
+
let ky = 0;
|
|
32395
|
+
for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
|
|
32396
|
+
if (ky % labelEveryY === 0 && y !== 0) {
|
|
32397
|
+
out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
|
|
32398
|
+
}
|
|
32399
|
+
}
|
|
32400
|
+
if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
|
|
32401
|
+
out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
|
|
32402
|
+
}
|
|
32403
|
+
}
|
|
32404
|
+
for (const region of regions) {
|
|
32405
|
+
if (!region.samples || region.samples.length === 0) continue;
|
|
32406
|
+
const baseline = region.baseline ?? 0;
|
|
32407
|
+
const clampedPoint = (p) => ({
|
|
32408
|
+
x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
|
|
32409
|
+
y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
|
|
32410
|
+
});
|
|
32411
|
+
const upper = region.samples.map(clampedPoint);
|
|
32412
|
+
const first = region.samples[0];
|
|
32413
|
+
const last = region.samples[region.samples.length - 1];
|
|
32414
|
+
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 })];
|
|
32415
|
+
const color = region.color ?? "#2563eb";
|
|
32416
|
+
out.push({
|
|
32417
|
+
type: "polygon",
|
|
32418
|
+
points: [...upper, ...closing],
|
|
32419
|
+
fill: color,
|
|
32420
|
+
color,
|
|
32421
|
+
opacity: region.opacity ?? 0.2,
|
|
32422
|
+
lineWidth: 1
|
|
32423
|
+
});
|
|
32424
|
+
if (region.label) {
|
|
32425
|
+
const mid = Math.floor(region.samples.length / 2);
|
|
32426
|
+
out.push({
|
|
32427
|
+
type: "text",
|
|
32428
|
+
x: mapX((first.x + last.x) / 2),
|
|
32429
|
+
y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
|
|
32430
|
+
text: region.label,
|
|
32431
|
+
color: "#111827",
|
|
32432
|
+
fontSize: 11
|
|
32433
|
+
});
|
|
32434
|
+
}
|
|
32435
|
+
}
|
|
32436
|
+
for (const bar of bars) {
|
|
32437
|
+
if (bar.x + bar.width < xMin || bar.x > xMax) continue;
|
|
32438
|
+
const y0 = bar.y0 ?? 0;
|
|
32439
|
+
const color = bar.color ?? "#93c5fd";
|
|
32440
|
+
out.push({
|
|
32441
|
+
type: "rect",
|
|
32442
|
+
x: mapX(bar.x),
|
|
32443
|
+
y: mapY(Math.max(y0, bar.y1)),
|
|
32444
|
+
width: mapX(bar.x + bar.width) - mapX(bar.x),
|
|
32445
|
+
height: Math.abs(mapY(bar.y1) - mapY(y0)),
|
|
32446
|
+
color,
|
|
32447
|
+
fill: color,
|
|
32448
|
+
opacity: bar.opacity ?? 0.5,
|
|
32449
|
+
lineWidth: 1
|
|
32450
|
+
});
|
|
32451
|
+
}
|
|
30440
32452
|
if (showAxes) {
|
|
30441
|
-
const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
|
|
30442
|
-
const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
|
|
30443
32453
|
out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
|
|
30444
32454
|
out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
|
|
30445
32455
|
}
|
|
32456
|
+
for (const guide of guides) {
|
|
32457
|
+
const color = guide.color ?? "#9ca3af";
|
|
32458
|
+
const dash = guide.dash ?? "dashed";
|
|
32459
|
+
if (guide.kind === "vline") {
|
|
32460
|
+
if (guide.at < xMin || guide.at > xMax) continue;
|
|
32461
|
+
const px = mapX(guide.at);
|
|
32462
|
+
out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
|
|
32463
|
+
if (guide.label) {
|
|
32464
|
+
out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
|
|
32465
|
+
}
|
|
32466
|
+
} else {
|
|
32467
|
+
if (guide.at < yMin || guide.at > yMax) continue;
|
|
32468
|
+
const py = mapY(guide.at);
|
|
32469
|
+
out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
|
|
32470
|
+
if (guide.label) {
|
|
32471
|
+
out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
|
|
32472
|
+
}
|
|
32473
|
+
}
|
|
32474
|
+
}
|
|
30446
32475
|
for (const curve of curves) {
|
|
30447
32476
|
if (!curve.samples || curve.samples.length < 2) continue;
|
|
32477
|
+
let lastInRange;
|
|
30448
32478
|
for (let i = 1; i < curve.samples.length; i++) {
|
|
30449
32479
|
const a = curve.samples[i - 1];
|
|
30450
32480
|
const b = curve.samples[i];
|
|
@@ -30456,19 +32486,97 @@ var init_MathCanvas = __esm({
|
|
|
30456
32486
|
x2: mapX(b.x),
|
|
30457
32487
|
y2: mapY(b.y),
|
|
30458
32488
|
color: curve.color ?? "#2563eb",
|
|
30459
|
-
lineWidth: 2
|
|
32489
|
+
lineWidth: 2,
|
|
32490
|
+
dash: curve.dash
|
|
32491
|
+
});
|
|
32492
|
+
lastInRange = b;
|
|
32493
|
+
}
|
|
32494
|
+
if (showCurveLabels && curve.label && lastInRange) {
|
|
32495
|
+
out.push({
|
|
32496
|
+
type: "text",
|
|
32497
|
+
x: mapX(lastInRange.x) + 6,
|
|
32498
|
+
y: mapY(lastInRange.y) - 6,
|
|
32499
|
+
text: curve.label,
|
|
32500
|
+
color: curve.color ?? "#2563eb",
|
|
32501
|
+
fontSize: 11
|
|
32502
|
+
});
|
|
32503
|
+
}
|
|
32504
|
+
}
|
|
32505
|
+
for (const hop of hops) {
|
|
32506
|
+
const x1 = mapX(hop.from);
|
|
32507
|
+
const x2 = mapX(hop.to);
|
|
32508
|
+
const peak = Math.min(36, plotH * 0.3);
|
|
32509
|
+
const color = hop.color ?? "#7c3aed";
|
|
32510
|
+
out.push({
|
|
32511
|
+
type: "ellipse",
|
|
32512
|
+
x: (x1 + x2) / 2,
|
|
32513
|
+
y: xAxisY,
|
|
32514
|
+
width: Math.abs(x2 - x1),
|
|
32515
|
+
height: 2 * peak,
|
|
32516
|
+
startAngle: 180,
|
|
32517
|
+
endAngle: 360,
|
|
32518
|
+
color
|
|
32519
|
+
});
|
|
32520
|
+
const s = Math.sign(hop.to - hop.from);
|
|
32521
|
+
out.push({
|
|
32522
|
+
type: "polygon",
|
|
32523
|
+
points: [
|
|
32524
|
+
{ x: x2, y: xAxisY },
|
|
32525
|
+
{ x: x2 - 4 * s, y: xAxisY - 7 },
|
|
32526
|
+
{ x: x2 + 2 * s, y: xAxisY - 7 }
|
|
32527
|
+
],
|
|
32528
|
+
fill: color,
|
|
32529
|
+
color
|
|
32530
|
+
});
|
|
32531
|
+
if (hop.label) {
|
|
32532
|
+
out.push({
|
|
32533
|
+
type: "text",
|
|
32534
|
+
x: (x1 + x2) / 2,
|
|
32535
|
+
y: xAxisY - peak - 8,
|
|
32536
|
+
text: hop.label,
|
|
32537
|
+
color: "#111827",
|
|
32538
|
+
fontSize: 10,
|
|
32539
|
+
align: "center"
|
|
32540
|
+
});
|
|
32541
|
+
}
|
|
32542
|
+
}
|
|
32543
|
+
for (const angle of angles) {
|
|
32544
|
+
const radius = angle.radius ?? 0.8;
|
|
32545
|
+
const color = angle.color ?? "#0ea5e9";
|
|
32546
|
+
out.push({
|
|
32547
|
+
type: "ellipse",
|
|
32548
|
+
x: mapX(angle.x),
|
|
32549
|
+
y: mapY(angle.y),
|
|
32550
|
+
width: 2 * radius * plotW / (xMax - xMin),
|
|
32551
|
+
height: 2 * radius * plotH / (yMax - yMin),
|
|
32552
|
+
startAngle: -angle.to,
|
|
32553
|
+
endAngle: -angle.from,
|
|
32554
|
+
color
|
|
32555
|
+
});
|
|
32556
|
+
if (angle.label) {
|
|
32557
|
+
const mid = (angle.from + angle.to) / 2;
|
|
32558
|
+
const rad = mid * Math.PI / 180;
|
|
32559
|
+
out.push({
|
|
32560
|
+
type: "text",
|
|
32561
|
+
x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
|
|
32562
|
+
y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
|
|
32563
|
+
text: angle.label,
|
|
32564
|
+
color: "#111827",
|
|
32565
|
+
fontSize: 11,
|
|
32566
|
+
align: "center"
|
|
30460
32567
|
});
|
|
30461
32568
|
}
|
|
30462
32569
|
}
|
|
30463
32570
|
for (const p of points) {
|
|
30464
32571
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
32572
|
+
const isOpen = p.style === "open";
|
|
30465
32573
|
out.push({
|
|
30466
32574
|
type: "circle",
|
|
30467
32575
|
x: mapX(p.x),
|
|
30468
32576
|
y: mapY(p.y),
|
|
30469
32577
|
radius: p.radius ?? 4,
|
|
30470
32578
|
color: p.color ?? "#dc2626",
|
|
30471
|
-
fill: p.color ?? "#dc2626"
|
|
32579
|
+
fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
|
|
30472
32580
|
});
|
|
30473
32581
|
if (p.label) {
|
|
30474
32582
|
out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
|
|
@@ -30487,7 +32595,28 @@ var init_MathCanvas = __esm({
|
|
|
30487
32595
|
}
|
|
30488
32596
|
out.push(...shapes);
|
|
30489
32597
|
return out;
|
|
30490
|
-
}, [
|
|
32598
|
+
}, [
|
|
32599
|
+
width,
|
|
32600
|
+
height,
|
|
32601
|
+
xMin,
|
|
32602
|
+
xMax,
|
|
32603
|
+
yMin,
|
|
32604
|
+
yMax,
|
|
32605
|
+
showAxes,
|
|
32606
|
+
showGrid,
|
|
32607
|
+
gridStep,
|
|
32608
|
+
showTickLabels,
|
|
32609
|
+
showCurveLabels,
|
|
32610
|
+
curves,
|
|
32611
|
+
points,
|
|
32612
|
+
vectors,
|
|
32613
|
+
regions,
|
|
32614
|
+
bars,
|
|
32615
|
+
guides,
|
|
32616
|
+
angles,
|
|
32617
|
+
hops,
|
|
32618
|
+
shapes
|
|
32619
|
+
]);
|
|
30491
32620
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
30492
32621
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
30493
32622
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -30496,6 +32625,8 @@ var init_MathCanvas = __esm({
|
|
|
30496
32625
|
width,
|
|
30497
32626
|
height,
|
|
30498
32627
|
shapes: derivedShapes,
|
|
32628
|
+
readouts,
|
|
32629
|
+
traces,
|
|
30499
32630
|
interactive,
|
|
30500
32631
|
animate,
|
|
30501
32632
|
onShapeClick,
|
|
@@ -30507,27 +32638,355 @@ var init_MathCanvas = __esm({
|
|
|
30507
32638
|
};
|
|
30508
32639
|
}
|
|
30509
32640
|
});
|
|
30510
|
-
|
|
32641
|
+
function formatMeterValue(v) {
|
|
32642
|
+
return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
|
|
32643
|
+
}
|
|
32644
|
+
function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
|
|
32645
|
+
const out = [];
|
|
32646
|
+
const color = obj.color ?? "#334155";
|
|
32647
|
+
switch (obj.kind) {
|
|
32648
|
+
case "ground": {
|
|
32649
|
+
const xStart = obj.x1 ?? 0;
|
|
32650
|
+
const xEnd = obj.x2 ?? canvasWidth;
|
|
32651
|
+
const y = obj.y ?? 0;
|
|
32652
|
+
out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
|
|
32653
|
+
for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
|
|
32654
|
+
out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
|
|
32655
|
+
}
|
|
32656
|
+
if (obj.label) {
|
|
32657
|
+
out.push({
|
|
32658
|
+
type: "text",
|
|
32659
|
+
x: (xStart + xEnd) / 2,
|
|
32660
|
+
y: y - 10,
|
|
32661
|
+
text: obj.label,
|
|
32662
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32663
|
+
fontSize: 11,
|
|
32664
|
+
align: "center"
|
|
32665
|
+
});
|
|
32666
|
+
}
|
|
32667
|
+
break;
|
|
32668
|
+
}
|
|
32669
|
+
case "wall": {
|
|
32670
|
+
const yStart = obj.y1 ?? 0;
|
|
32671
|
+
const yEnd = obj.y2 ?? canvasHeight;
|
|
32672
|
+
const x = obj.x ?? 0;
|
|
32673
|
+
out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
|
|
32674
|
+
for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
|
|
32675
|
+
out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
|
|
32676
|
+
}
|
|
32677
|
+
if (obj.label) {
|
|
32678
|
+
out.push({
|
|
32679
|
+
type: "text",
|
|
32680
|
+
x: x + 12,
|
|
32681
|
+
y: (yStart + yEnd) / 2,
|
|
32682
|
+
text: obj.label,
|
|
32683
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32684
|
+
fontSize: 11,
|
|
32685
|
+
align: "left"
|
|
32686
|
+
});
|
|
32687
|
+
}
|
|
32688
|
+
break;
|
|
32689
|
+
}
|
|
32690
|
+
case "ramp": {
|
|
32691
|
+
const x1 = obj.x1 ?? 0;
|
|
32692
|
+
const y1 = obj.y1 ?? 0;
|
|
32693
|
+
const x2 = obj.x2 ?? canvasWidth;
|
|
32694
|
+
const y2 = obj.y2 ?? canvasHeight;
|
|
32695
|
+
out.push({
|
|
32696
|
+
type: "polygon",
|
|
32697
|
+
points: [
|
|
32698
|
+
{ x: x1, y: y1 },
|
|
32699
|
+
{ x: x2, y: y2 },
|
|
32700
|
+
{ x: x1, y: y2 }
|
|
32701
|
+
],
|
|
32702
|
+
color,
|
|
32703
|
+
fill: obj.fill ?? "#e2e8f0",
|
|
32704
|
+
lineWidth: 2
|
|
32705
|
+
});
|
|
32706
|
+
if (obj.label) {
|
|
32707
|
+
out.push({
|
|
32708
|
+
type: "text",
|
|
32709
|
+
x: (2 * x1 + x2) / 3,
|
|
32710
|
+
y: (y1 + 2 * y2) / 3,
|
|
32711
|
+
text: obj.label,
|
|
32712
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32713
|
+
fontSize: 11,
|
|
32714
|
+
align: "center"
|
|
32715
|
+
});
|
|
32716
|
+
}
|
|
32717
|
+
break;
|
|
32718
|
+
}
|
|
32719
|
+
case "box": {
|
|
32720
|
+
const x = obj.x ?? 0;
|
|
32721
|
+
const y = obj.y ?? 0;
|
|
32722
|
+
const w = obj.width ?? 40;
|
|
32723
|
+
const h = obj.height ?? 40;
|
|
32724
|
+
out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
|
|
32725
|
+
if (obj.label) {
|
|
32726
|
+
out.push({
|
|
32727
|
+
type: "text",
|
|
32728
|
+
x: x + w / 2,
|
|
32729
|
+
y: y + h / 2,
|
|
32730
|
+
text: obj.label,
|
|
32731
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32732
|
+
fontSize: 11,
|
|
32733
|
+
align: "center"
|
|
32734
|
+
});
|
|
32735
|
+
}
|
|
32736
|
+
break;
|
|
32737
|
+
}
|
|
32738
|
+
case "pivot": {
|
|
32739
|
+
const x = obj.x ?? 0;
|
|
32740
|
+
const y = obj.y ?? 0;
|
|
32741
|
+
out.push({ type: "circle", x, y, radius: 5, color, fill: color });
|
|
32742
|
+
out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
|
|
32743
|
+
for (let k = 0; k < 5; k++) {
|
|
32744
|
+
const hx = x - 14 + 7 * k;
|
|
32745
|
+
out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
|
|
32746
|
+
}
|
|
32747
|
+
if (obj.label) {
|
|
32748
|
+
out.push({
|
|
32749
|
+
type: "text",
|
|
32750
|
+
x,
|
|
32751
|
+
y: y - 20,
|
|
32752
|
+
text: obj.label,
|
|
32753
|
+
color: PHYSICS_LABEL_COLOR,
|
|
32754
|
+
fontSize: 11,
|
|
32755
|
+
align: "center"
|
|
32756
|
+
});
|
|
32757
|
+
}
|
|
32758
|
+
break;
|
|
32759
|
+
}
|
|
32760
|
+
}
|
|
32761
|
+
return out;
|
|
32762
|
+
}
|
|
32763
|
+
function trailShapes(trail) {
|
|
32764
|
+
const n = trail.points.length;
|
|
32765
|
+
if (n < 2) return [];
|
|
32766
|
+
const color = trail.color ?? "#94a3b8";
|
|
32767
|
+
const lineWidth = trail.width ?? 2;
|
|
32768
|
+
const fade = trail.fade ?? true;
|
|
32769
|
+
const globalOpacity = trail.opacity ?? 1;
|
|
32770
|
+
const out = [];
|
|
32771
|
+
for (let i = 0; i < n - 1; i++) {
|
|
32772
|
+
const a = trail.points[i];
|
|
32773
|
+
const b = trail.points[i + 1];
|
|
32774
|
+
const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
|
|
32775
|
+
out.push({
|
|
32776
|
+
type: "line",
|
|
32777
|
+
x1: a.x,
|
|
32778
|
+
y1: a.y,
|
|
32779
|
+
x2: b.x,
|
|
32780
|
+
y2: b.y,
|
|
32781
|
+
color,
|
|
32782
|
+
lineWidth,
|
|
32783
|
+
opacity: segmentOpacity * globalOpacity
|
|
32784
|
+
});
|
|
32785
|
+
}
|
|
32786
|
+
return out;
|
|
32787
|
+
}
|
|
32788
|
+
function constraintShapes(c, a, b) {
|
|
32789
|
+
const color = c.color ?? "#9ca3af";
|
|
32790
|
+
const kind = c.kind ?? "rod";
|
|
32791
|
+
if (kind === "rod") {
|
|
32792
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
|
|
32793
|
+
}
|
|
32794
|
+
if (kind === "string") {
|
|
32795
|
+
return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
|
|
32796
|
+
}
|
|
32797
|
+
const COILS = 8;
|
|
32798
|
+
const AMP = 7;
|
|
32799
|
+
const LEAD = 10;
|
|
32800
|
+
const dx = b.x - a.x;
|
|
32801
|
+
const dy = b.y - a.y;
|
|
32802
|
+
const dist = Math.max(1e-6, Math.hypot(dx, dy));
|
|
32803
|
+
const ux = dx / dist;
|
|
32804
|
+
const uy = dy / dist;
|
|
32805
|
+
const perpX = -uy;
|
|
32806
|
+
const perpY = ux;
|
|
32807
|
+
const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
|
|
32808
|
+
const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
|
|
32809
|
+
const m = 2 * COILS;
|
|
32810
|
+
const polyline = [{ x: a.x, y: a.y }, aPrime];
|
|
32811
|
+
for (let j = 1; j <= m; j++) {
|
|
32812
|
+
const t = j / (m + 1);
|
|
32813
|
+
const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
|
|
32814
|
+
const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
|
|
32815
|
+
const sign = j % 2 === 0 ? 1 : -1;
|
|
32816
|
+
polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
|
|
32817
|
+
}
|
|
32818
|
+
polyline.push(bPrime, { x: b.x, y: b.y });
|
|
32819
|
+
const out = [];
|
|
32820
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
32821
|
+
out.push({
|
|
32822
|
+
type: "line",
|
|
32823
|
+
x1: polyline[i - 1].x,
|
|
32824
|
+
y1: polyline[i - 1].y,
|
|
32825
|
+
x2: polyline[i].x,
|
|
32826
|
+
y2: polyline[i].y,
|
|
32827
|
+
color,
|
|
32828
|
+
lineWidth: 2
|
|
32829
|
+
});
|
|
32830
|
+
}
|
|
32831
|
+
return out;
|
|
32832
|
+
}
|
|
32833
|
+
function vectorShapes(v, bodyById) {
|
|
32834
|
+
let ax;
|
|
32835
|
+
let ay;
|
|
32836
|
+
if (v.body) {
|
|
32837
|
+
const anchor = bodyById.get(v.body);
|
|
32838
|
+
if (!anchor) return [];
|
|
32839
|
+
ax = anchor.x;
|
|
32840
|
+
ay = anchor.y;
|
|
32841
|
+
} else {
|
|
32842
|
+
ax = v.x ?? 0;
|
|
32843
|
+
ay = v.y ?? 0;
|
|
32844
|
+
}
|
|
32845
|
+
const scale = v.scale ?? 1;
|
|
32846
|
+
const color = v.color ?? "#dc2626";
|
|
32847
|
+
const tx = ax + v.dx * scale;
|
|
32848
|
+
const ty = ay + v.dy * scale;
|
|
32849
|
+
const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
|
|
32850
|
+
if (v.label) {
|
|
32851
|
+
const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
|
|
32852
|
+
const ux = (tx - ax) / dist;
|
|
32853
|
+
const uy = (ty - ay) / dist;
|
|
32854
|
+
out.push({
|
|
32855
|
+
type: "text",
|
|
32856
|
+
x: tx + 8 * ux,
|
|
32857
|
+
y: ty + 8 * uy,
|
|
32858
|
+
text: v.label,
|
|
32859
|
+
color,
|
|
32860
|
+
fontSize: 11,
|
|
32861
|
+
align: "center"
|
|
32862
|
+
});
|
|
32863
|
+
}
|
|
32864
|
+
return out;
|
|
32865
|
+
}
|
|
32866
|
+
function angleMarkerShapes(a) {
|
|
32867
|
+
const radius = a.radius ?? 26;
|
|
32868
|
+
const color = a.color ?? "#0ea5e9";
|
|
32869
|
+
const out = [
|
|
32870
|
+
{
|
|
32871
|
+
type: "ellipse",
|
|
32872
|
+
x: a.x,
|
|
32873
|
+
y: a.y,
|
|
32874
|
+
width: radius * 2,
|
|
32875
|
+
height: radius * 2,
|
|
32876
|
+
startAngle: a.from,
|
|
32877
|
+
endAngle: a.to,
|
|
32878
|
+
color,
|
|
32879
|
+
lineWidth: 2
|
|
32880
|
+
}
|
|
32881
|
+
];
|
|
32882
|
+
if (a.label) {
|
|
32883
|
+
const mid = (a.from + a.to) / 2 * (Math.PI / 180);
|
|
32884
|
+
out.push({
|
|
32885
|
+
type: "text",
|
|
32886
|
+
x: a.x + (radius + 13) * Math.cos(mid),
|
|
32887
|
+
y: a.y + (radius + 13) * Math.sin(mid),
|
|
32888
|
+
text: a.label,
|
|
32889
|
+
color,
|
|
32890
|
+
fontSize: 11,
|
|
32891
|
+
align: "center"
|
|
32892
|
+
});
|
|
32893
|
+
}
|
|
32894
|
+
return out;
|
|
32895
|
+
}
|
|
32896
|
+
function fieldShapes(field, canvasWidth, canvasHeight) {
|
|
32897
|
+
const spacing = field.spacing ?? 48;
|
|
32898
|
+
const size = field.size ?? 14;
|
|
32899
|
+
const color = field.color ?? "#94a3b8";
|
|
32900
|
+
const regionX = field.x ?? 0;
|
|
32901
|
+
const regionY = field.y ?? 0;
|
|
32902
|
+
const regionW = field.width ?? canvasWidth;
|
|
32903
|
+
const regionH = field.height ?? canvasHeight;
|
|
32904
|
+
const out = [];
|
|
32905
|
+
for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
|
|
32906
|
+
for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
|
|
32907
|
+
if (field.kind === "arrows") {
|
|
32908
|
+
const rad = (field.angle ?? 0) * Math.PI / 180;
|
|
32909
|
+
const hx = Math.cos(rad) * size / 2;
|
|
32910
|
+
const hy = Math.sin(rad) * size / 2;
|
|
32911
|
+
out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
|
|
32912
|
+
} else if (field.kind === "into") {
|
|
32913
|
+
const r2 = size / 3;
|
|
32914
|
+
const d = 0.6 * r2 * Math.SQRT1_2;
|
|
32915
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r2, color });
|
|
32916
|
+
out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
|
|
32917
|
+
out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
|
|
32918
|
+
} else {
|
|
32919
|
+
const r2 = size / 3;
|
|
32920
|
+
out.push({ type: "circle", x: gx, y: gy, radius: r2, color });
|
|
32921
|
+
out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
|
|
32922
|
+
}
|
|
32923
|
+
}
|
|
32924
|
+
}
|
|
32925
|
+
return out;
|
|
32926
|
+
}
|
|
32927
|
+
function meterShapes(meters, canvasHeight) {
|
|
32928
|
+
const n = meters.length;
|
|
32929
|
+
const out = [];
|
|
32930
|
+
const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
|
|
32931
|
+
meters.forEach((meter, i) => {
|
|
32932
|
+
const rowY = canvasHeight - 10 - 16 * (n - i);
|
|
32933
|
+
const color = meter.color ?? "#3b82f6";
|
|
32934
|
+
const M = meter.max ?? sharedMax;
|
|
32935
|
+
const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
|
|
32936
|
+
out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
|
|
32937
|
+
out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
|
|
32938
|
+
out.push({
|
|
32939
|
+
type: "text",
|
|
32940
|
+
x: 166,
|
|
32941
|
+
y: rowY + 8,
|
|
32942
|
+
text: formatMeterValue(meter.value),
|
|
32943
|
+
color: "#6b7280",
|
|
32944
|
+
fontSize: 9
|
|
32945
|
+
});
|
|
32946
|
+
});
|
|
32947
|
+
return out;
|
|
32948
|
+
}
|
|
32949
|
+
var physicsLog2, PHYSICS_LABEL_COLOR, PhysicsCanvas;
|
|
30511
32950
|
var init_PhysicsCanvas = __esm({
|
|
30512
32951
|
"components/learning/molecules/PhysicsCanvas.tsx"() {
|
|
30513
32952
|
"use client";
|
|
30514
32953
|
init_atoms();
|
|
30515
32954
|
init_Stack();
|
|
30516
32955
|
init_LearningCanvas();
|
|
32956
|
+
init_learningScene3D();
|
|
32957
|
+
physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
|
|
32958
|
+
PHYSICS_LABEL_COLOR = "#374151";
|
|
30517
32959
|
PhysicsCanvas = ({
|
|
30518
32960
|
className,
|
|
30519
32961
|
width = 600,
|
|
30520
32962
|
height = 400,
|
|
30521
32963
|
title,
|
|
30522
32964
|
backgroundColor,
|
|
32965
|
+
mode = "2d",
|
|
32966
|
+
camera,
|
|
32967
|
+
lighting,
|
|
32968
|
+
post,
|
|
30523
32969
|
bodies = [],
|
|
30524
32970
|
constraints = [],
|
|
30525
32971
|
showVelocity = true,
|
|
30526
32972
|
showForces = false,
|
|
30527
32973
|
velocityScale = 20,
|
|
30528
32974
|
forceScale = 20,
|
|
32975
|
+
sceneObjects = [],
|
|
32976
|
+
trails = [],
|
|
32977
|
+
vectors = [],
|
|
32978
|
+
surface3d,
|
|
32979
|
+
vectors3d = [],
|
|
32980
|
+
vectorScale = 1,
|
|
32981
|
+
angles = [],
|
|
32982
|
+
field,
|
|
32983
|
+
meters = [],
|
|
30529
32984
|
shapes = [],
|
|
30530
|
-
|
|
32985
|
+
readouts,
|
|
32986
|
+
traces,
|
|
32987
|
+
showGrid,
|
|
32988
|
+
shadows,
|
|
32989
|
+
interactive,
|
|
30531
32990
|
animate = false,
|
|
30532
32991
|
onShapeClick,
|
|
30533
32992
|
isLoading,
|
|
@@ -30539,19 +32998,14 @@ var init_PhysicsCanvas = __esm({
|
|
|
30539
32998
|
for (const b of bodies) {
|
|
30540
32999
|
if (b.id) bodyById.set(b.id, b);
|
|
30541
33000
|
}
|
|
33001
|
+
if (field) out.push(...fieldShapes(field, width, height));
|
|
33002
|
+
for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
|
|
33003
|
+
for (const trail of trails) out.push(...trailShapes(trail));
|
|
30542
33004
|
for (const c of constraints) {
|
|
30543
33005
|
const a = bodyById.get(c.from);
|
|
30544
33006
|
const b = bodyById.get(c.to);
|
|
30545
33007
|
if (!a || !b) continue;
|
|
30546
|
-
out.push(
|
|
30547
|
-
type: "line",
|
|
30548
|
-
x1: a.x,
|
|
30549
|
-
y1: a.y,
|
|
30550
|
-
x2: b.x,
|
|
30551
|
-
y2: b.y,
|
|
30552
|
-
color: c.color ?? "#9ca3af",
|
|
30553
|
-
lineWidth: 2
|
|
30554
|
-
});
|
|
33008
|
+
out.push(...constraintShapes(c, a, b));
|
|
30555
33009
|
}
|
|
30556
33010
|
for (const b of bodies) {
|
|
30557
33011
|
out.push({
|
|
@@ -30596,9 +33050,181 @@ var init_PhysicsCanvas = __esm({
|
|
|
30596
33050
|
});
|
|
30597
33051
|
}
|
|
30598
33052
|
}
|
|
33053
|
+
for (const v of vectors) out.push(...vectorShapes(v, bodyById));
|
|
33054
|
+
for (const a of angles) out.push(...angleMarkerShapes(a));
|
|
33055
|
+
if (meters.length > 0) out.push(...meterShapes(meters, height));
|
|
30599
33056
|
out.push(...shapes);
|
|
30600
33057
|
return out;
|
|
30601
|
-
}, [
|
|
33058
|
+
}, [
|
|
33059
|
+
bodies,
|
|
33060
|
+
constraints,
|
|
33061
|
+
showVelocity,
|
|
33062
|
+
showForces,
|
|
33063
|
+
velocityScale,
|
|
33064
|
+
forceScale,
|
|
33065
|
+
sceneObjects,
|
|
33066
|
+
trails,
|
|
33067
|
+
vectors,
|
|
33068
|
+
angles,
|
|
33069
|
+
field,
|
|
33070
|
+
meters,
|
|
33071
|
+
shapes,
|
|
33072
|
+
width,
|
|
33073
|
+
height
|
|
33074
|
+
]);
|
|
33075
|
+
const drawables3D = React94.useMemo(() => {
|
|
33076
|
+
if (mode !== "3d") return [];
|
|
33077
|
+
if (shapes.length > 0) {
|
|
33078
|
+
physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
|
|
33079
|
+
}
|
|
33080
|
+
if (sceneObjects.length > 0) {
|
|
33081
|
+
physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
|
|
33082
|
+
}
|
|
33083
|
+
if (vectors.length > 0) {
|
|
33084
|
+
physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
|
|
33085
|
+
}
|
|
33086
|
+
if (angles.length > 0) {
|
|
33087
|
+
physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
|
|
33088
|
+
}
|
|
33089
|
+
if (field) {
|
|
33090
|
+
physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
|
|
33091
|
+
}
|
|
33092
|
+
if (meters.length > 0) {
|
|
33093
|
+
physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
|
|
33094
|
+
}
|
|
33095
|
+
if (animate) {
|
|
33096
|
+
physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
|
|
33097
|
+
}
|
|
33098
|
+
const out = [];
|
|
33099
|
+
const labelColor = labelColorForBackground(backgroundColor);
|
|
33100
|
+
const bodyById = /* @__PURE__ */ new Map();
|
|
33101
|
+
for (const b of bodies) {
|
|
33102
|
+
if (b.id) bodyById.set(b.id, b);
|
|
33103
|
+
}
|
|
33104
|
+
for (const c of constraints) {
|
|
33105
|
+
const a = bodyById.get(c.from);
|
|
33106
|
+
const b = bodyById.get(c.to);
|
|
33107
|
+
if (!a || !b) continue;
|
|
33108
|
+
const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
|
|
33109
|
+
const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
|
|
33110
|
+
if (rod) out.push(rod);
|
|
33111
|
+
}
|
|
33112
|
+
for (const b of bodies) {
|
|
33113
|
+
const radius = b.radius ?? 0.5;
|
|
33114
|
+
const bz = b.z ?? 0;
|
|
33115
|
+
out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
|
|
33116
|
+
if (b.label) {
|
|
33117
|
+
out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
|
|
33118
|
+
}
|
|
33119
|
+
const vx = b.vx ?? 0;
|
|
33120
|
+
const vy = b.vy ?? 0;
|
|
33121
|
+
const vz = b.vz ?? 0;
|
|
33122
|
+
const arrowRadius = Math.max(0.05, radius * 0.15);
|
|
33123
|
+
if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
|
|
33124
|
+
const arrow = arrowBetween(
|
|
33125
|
+
[b.x, b.y, bz],
|
|
33126
|
+
[b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
|
|
33127
|
+
"#16a34a",
|
|
33128
|
+
arrowRadius
|
|
33129
|
+
);
|
|
33130
|
+
if (arrow) out.push(arrow);
|
|
33131
|
+
}
|
|
33132
|
+
const fx = b.fx ?? 0;
|
|
33133
|
+
const fy = b.fy ?? 0;
|
|
33134
|
+
const fz = b.fz ?? 0;
|
|
33135
|
+
if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
|
|
33136
|
+
const arrow = arrowBetween(
|
|
33137
|
+
[b.x, b.y, bz],
|
|
33138
|
+
[b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
|
|
33139
|
+
"#dc2626",
|
|
33140
|
+
arrowRadius
|
|
33141
|
+
);
|
|
33142
|
+
if (arrow) out.push(arrow);
|
|
33143
|
+
}
|
|
33144
|
+
}
|
|
33145
|
+
for (const trail of trails) {
|
|
33146
|
+
if (trail.fade !== void 0) {
|
|
33147
|
+
physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
|
|
33148
|
+
}
|
|
33149
|
+
const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
|
|
33150
|
+
out.push(
|
|
33151
|
+
...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
|
|
33152
|
+
...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
|
|
33153
|
+
})
|
|
33154
|
+
);
|
|
33155
|
+
}
|
|
33156
|
+
if (surface3d) {
|
|
33157
|
+
out.push(...heightFieldMesh(surface3d));
|
|
33158
|
+
}
|
|
33159
|
+
if (vectors3d.length > 0) {
|
|
33160
|
+
out.push(
|
|
33161
|
+
...arrowField(
|
|
33162
|
+
vectors3d.map((v) => ({
|
|
33163
|
+
id: v.id,
|
|
33164
|
+
from: [v.x, v.y, v.z ?? 0],
|
|
33165
|
+
delta: [v.dx, v.dy, v.dz ?? 0],
|
|
33166
|
+
color: v.color,
|
|
33167
|
+
label: v.label,
|
|
33168
|
+
width: v.width
|
|
33169
|
+
})),
|
|
33170
|
+
{ scale: vectorScale, labelColor }
|
|
33171
|
+
)
|
|
33172
|
+
);
|
|
33173
|
+
}
|
|
33174
|
+
return out;
|
|
33175
|
+
}, [
|
|
33176
|
+
mode,
|
|
33177
|
+
bodies,
|
|
33178
|
+
constraints,
|
|
33179
|
+
showVelocity,
|
|
33180
|
+
showForces,
|
|
33181
|
+
velocityScale,
|
|
33182
|
+
forceScale,
|
|
33183
|
+
shapes,
|
|
33184
|
+
sceneObjects,
|
|
33185
|
+
trails,
|
|
33186
|
+
vectors,
|
|
33187
|
+
surface3d,
|
|
33188
|
+
vectors3d,
|
|
33189
|
+
vectorScale,
|
|
33190
|
+
angles,
|
|
33191
|
+
field,
|
|
33192
|
+
meters,
|
|
33193
|
+
animate,
|
|
33194
|
+
backgroundColor
|
|
33195
|
+
]);
|
|
33196
|
+
const bodyIndexById = React94.useMemo(() => {
|
|
33197
|
+
const m = /* @__PURE__ */ new Map();
|
|
33198
|
+
vectors3d.forEach((v, i) => {
|
|
33199
|
+
if (v.id) m.set(v.id, i);
|
|
33200
|
+
});
|
|
33201
|
+
bodies.forEach((b, i) => {
|
|
33202
|
+
if (b.id) m.set(b.id, i);
|
|
33203
|
+
});
|
|
33204
|
+
return m;
|
|
33205
|
+
}, [bodies, vectors3d]);
|
|
33206
|
+
if (mode === "3d") {
|
|
33207
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
33208
|
+
LearningScene3D,
|
|
33209
|
+
{
|
|
33210
|
+
className,
|
|
33211
|
+
width,
|
|
33212
|
+
height,
|
|
33213
|
+
title,
|
|
33214
|
+
backgroundColor,
|
|
33215
|
+
drawables: drawables3D,
|
|
33216
|
+
camera,
|
|
33217
|
+
lighting,
|
|
33218
|
+
post,
|
|
33219
|
+
showGrid,
|
|
33220
|
+
shadows,
|
|
33221
|
+
interactive,
|
|
33222
|
+
isLoading,
|
|
33223
|
+
error,
|
|
33224
|
+
onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
|
|
33225
|
+
}
|
|
33226
|
+
);
|
|
33227
|
+
}
|
|
30602
33228
|
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
|
|
30603
33229
|
title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
|
|
30604
33230
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -30608,7 +33234,9 @@ var init_PhysicsCanvas = __esm({
|
|
|
30608
33234
|
height,
|
|
30609
33235
|
backgroundColor,
|
|
30610
33236
|
shapes: derivedShapes,
|
|
30611
|
-
|
|
33237
|
+
readouts,
|
|
33238
|
+
traces,
|
|
33239
|
+
interactive: interactive ?? false,
|
|
30612
33240
|
animate,
|
|
30613
33241
|
onShapeClick,
|
|
30614
33242
|
isLoading,
|
|
@@ -30758,7 +33386,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
|
|
|
30758
33386
|
}
|
|
30759
33387
|
return nodeIds.map((id) => positions.get(id));
|
|
30760
33388
|
}
|
|
30761
|
-
function
|
|
33389
|
+
function layoutTree3(nodeIds, adjacency, roots, width, height, margin) {
|
|
30762
33390
|
const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
|
|
30763
33391
|
const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
|
|
30764
33392
|
const maxLayer = Math.max(...Array.from(layers.values()));
|
|
@@ -30814,7 +33442,7 @@ function computeStaticLayout(mode, input) {
|
|
|
30814
33442
|
const adjacency = buildAdjacency(nodeIds, edges);
|
|
30815
33443
|
const roots = findRoots(nodeIds, adjacency);
|
|
30816
33444
|
if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
|
|
30817
|
-
if (mode === "tree") return
|
|
33445
|
+
if (mode === "tree") return layoutTree3(nodeIds, adjacency, roots, width, height, margin);
|
|
30818
33446
|
return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
|
|
30819
33447
|
}
|
|
30820
33448
|
var init_graphViewLayouts = __esm({
|
|
@@ -31226,12 +33854,12 @@ var init_MapView = __esm({
|
|
|
31226
33854
|
shadowSize: [41, 41]
|
|
31227
33855
|
});
|
|
31228
33856
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
31229
|
-
const { useEffect: useEffect64, useRef:
|
|
33857
|
+
const { useEffect: useEffect64, useRef: useRef65, useCallback: useCallback96, useState: useState100 } = React94__namespace.default;
|
|
31230
33858
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
31231
33859
|
const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
31232
33860
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
31233
33861
|
const map = useMap();
|
|
31234
|
-
const prevRef =
|
|
33862
|
+
const prevRef = useRef65({ centerLat, centerLng, zoom });
|
|
31235
33863
|
useEffect64(() => {
|
|
31236
33864
|
const prev = prevRef.current;
|
|
31237
33865
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
@@ -32057,26 +34685,6 @@ var init_Lightbox = __esm({
|
|
|
32057
34685
|
Lightbox.displayName = "Lightbox";
|
|
32058
34686
|
}
|
|
32059
34687
|
});
|
|
32060
|
-
function useMediaQuery(query) {
|
|
32061
|
-
const subscribe = React94.useCallback(
|
|
32062
|
-
(onChange) => {
|
|
32063
|
-
const mql = window.matchMedia(query);
|
|
32064
|
-
mql.addEventListener("change", onChange);
|
|
32065
|
-
return () => mql.removeEventListener("change", onChange);
|
|
32066
|
-
},
|
|
32067
|
-
[query]
|
|
32068
|
-
);
|
|
32069
|
-
return React94.useSyncExternalStore(
|
|
32070
|
-
subscribe,
|
|
32071
|
-
() => window.matchMedia(query).matches,
|
|
32072
|
-
() => false
|
|
32073
|
-
);
|
|
32074
|
-
}
|
|
32075
|
-
var init_useMediaQuery = __esm({
|
|
32076
|
-
"hooks/useMediaQuery.ts"() {
|
|
32077
|
-
"use client";
|
|
32078
|
-
}
|
|
32079
|
-
});
|
|
32080
34688
|
function renderIconInput3(icon, props) {
|
|
32081
34689
|
return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
|
|
32082
34690
|
}
|
|
@@ -32115,8 +34723,8 @@ function TableView({
|
|
|
32115
34723
|
columns,
|
|
32116
34724
|
fields,
|
|
32117
34725
|
itemActions,
|
|
32118
|
-
maxInlineActions,
|
|
32119
|
-
itemClickEvent,
|
|
34726
|
+
maxInlineActions: _maxInlineActions,
|
|
34727
|
+
itemClickEvent = "",
|
|
32120
34728
|
selectable = false,
|
|
32121
34729
|
selectEvent,
|
|
32122
34730
|
selectedIds,
|
|
@@ -32166,7 +34774,6 @@ function TableView({
|
|
|
32166
34774
|
const hasMore = pageSize > 0 && visibleCount < ordered2.length;
|
|
32167
34775
|
const hasRenderProp = typeof children === "function";
|
|
32168
34776
|
const idField = dndItemIdField ?? "id";
|
|
32169
|
-
const isCoarsePointer = useMediaQuery("(pointer: coarse)");
|
|
32170
34777
|
React94__namespace.default.useEffect(() => {
|
|
32171
34778
|
tableViewLog.debug("render", {
|
|
32172
34779
|
rowCount: data.length,
|
|
@@ -32208,21 +34815,14 @@ function TableView({
|
|
|
32208
34815
|
const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
|
|
32209
34816
|
eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
|
|
32210
34817
|
};
|
|
32211
|
-
const
|
|
32212
|
-
e.stopPropagation();
|
|
32213
|
-
const payload = {
|
|
32214
|
-
id: row.id,
|
|
32215
|
-
row
|
|
32216
|
-
};
|
|
32217
|
-
eventBus.emit(`UI:${action.event}`, payload);
|
|
32218
|
-
};
|
|
34818
|
+
const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
|
|
32219
34819
|
const handleRowClick = (row) => () => {
|
|
32220
|
-
if (!
|
|
34820
|
+
if (!rowClickEvent) return;
|
|
32221
34821
|
const payload = {
|
|
32222
34822
|
id: row.id,
|
|
32223
34823
|
row
|
|
32224
34824
|
};
|
|
32225
|
-
eventBus.emit(`UI:${
|
|
34825
|
+
eventBus.emit(`UI:${rowClickEvent}`, payload);
|
|
32226
34826
|
};
|
|
32227
34827
|
const colFloors = React94__namespace.default.useMemo(
|
|
32228
34828
|
() => colDefs.map((col) => {
|
|
@@ -32238,10 +34838,7 @@ function TableView({
|
|
|
32238
34838
|
const statusNode = isLoading ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) }) : error ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "error", children: error.message }) }) : data.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) }) : null;
|
|
32239
34839
|
const lk = LOOKS[look];
|
|
32240
34840
|
const hasActions = actionDefs.length > 0;
|
|
32241
|
-
const
|
|
32242
|
-
const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
|
|
32243
|
-
const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
|
|
32244
|
-
const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
|
|
34841
|
+
const actionsTrack = hasActions ? "3rem" : null;
|
|
32245
34842
|
const gridTemplateColumns = [
|
|
32246
34843
|
selectable ? "auto" : null,
|
|
32247
34844
|
...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
|
|
@@ -32288,7 +34885,7 @@ function TableView({
|
|
|
32288
34885
|
col.key
|
|
32289
34886
|
);
|
|
32290
34887
|
}),
|
|
32291
|
-
hasActions && /* @__PURE__ */ jsxRuntime.jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
|
|
34888
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)] border-l border-[var(--color-border)] h-full" })
|
|
32292
34889
|
]
|
|
32293
34890
|
}
|
|
32294
34891
|
);
|
|
@@ -32300,12 +34897,12 @@ function TableView({
|
|
|
32300
34897
|
role: "row",
|
|
32301
34898
|
"data-entity-row": true,
|
|
32302
34899
|
"data-entity-id": id,
|
|
32303
|
-
onClick:
|
|
34900
|
+
onClick: rowClickEvent ? handleRowClick(row) : void 0,
|
|
32304
34901
|
style: !hasRenderProp ? { gridTemplateColumns } : void 0,
|
|
32305
34902
|
className: cn(
|
|
32306
34903
|
"group items-center gap-3 transition-colors duration-fast",
|
|
32307
34904
|
hasRenderProp ? "flex" : "grid",
|
|
32308
|
-
|
|
34905
|
+
rowClickEvent && "cursor-pointer",
|
|
32309
34906
|
lk.rowPad,
|
|
32310
34907
|
lk.divider && "border-b border-[var(--color-border)]",
|
|
32311
34908
|
lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
|
|
@@ -32313,7 +34910,7 @@ function TableView({
|
|
|
32313
34910
|
look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
|
|
32314
34911
|
),
|
|
32315
34912
|
children: [
|
|
32316
|
-
selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick:
|
|
34913
|
+
selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
32317
34914
|
Checkbox,
|
|
32318
34915
|
{
|
|
32319
34916
|
checked: selected.has(id),
|
|
@@ -32334,53 +34931,37 @@ function TableView({
|
|
|
32334
34931
|
}
|
|
32335
34932
|
return /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
|
|
32336
34933
|
}),
|
|
32337
|
-
hasActions && /* @__PURE__ */ jsxRuntime.
|
|
34934
|
+
hasActions && /* @__PURE__ */ jsxRuntime.jsx(
|
|
32338
34935
|
HStack,
|
|
32339
34936
|
{
|
|
32340
34937
|
gap: "xs",
|
|
32341
|
-
onClick:
|
|
34938
|
+
onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
|
|
32342
34939
|
className: cn(
|
|
32343
34940
|
// Pinned: the fixed column tracks routinely overflow the caller's
|
|
32344
|
-
// scroll container, which
|
|
32345
|
-
// Opaque so
|
|
34941
|
+
// scroll container, which would leave the kebab off-screen.
|
|
34942
|
+
// Opaque + hairline edge so it reads as a pinned column, not a
|
|
34943
|
+
// floating control, while scrolled cells pass underneath.
|
|
32346
34944
|
"justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
|
|
34945
|
+
"border-l border-[var(--color-border)]",
|
|
32347
34946
|
lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
|
|
32348
34947
|
),
|
|
32349
|
-
children:
|
|
32350
|
-
|
|
32351
|
-
|
|
32352
|
-
|
|
32353
|
-
|
|
32354
|
-
|
|
32355
|
-
|
|
32356
|
-
|
|
32357
|
-
|
|
32358
|
-
|
|
32359
|
-
|
|
32360
|
-
|
|
32361
|
-
|
|
32362
|
-
|
|
32363
|
-
}
|
|
32364
|
-
|
|
32365
|
-
|
|
32366
|
-
effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
|
|
32367
|
-
Menu,
|
|
32368
|
-
{
|
|
32369
|
-
position: "bottom-end",
|
|
32370
|
-
trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
32371
|
-
items: actionDefs.slice(effectiveMaxInline).map((action) => ({
|
|
32372
|
-
label: action.label,
|
|
32373
|
-
icon: action.icon,
|
|
32374
|
-
event: action.event,
|
|
32375
|
-
variant: action.variant === "danger" ? "danger" : "default",
|
|
32376
|
-
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
32377
|
-
id: row.id,
|
|
32378
|
-
row
|
|
32379
|
-
})
|
|
32380
|
-
}))
|
|
32381
|
-
}
|
|
32382
|
-
)
|
|
32383
|
-
]
|
|
34948
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
34949
|
+
Menu,
|
|
34950
|
+
{
|
|
34951
|
+
position: "bottom-end",
|
|
34952
|
+
trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", "data-row-id": String(row.id), children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
|
|
34953
|
+
items: actionDefs.map((action) => ({
|
|
34954
|
+
label: action.label,
|
|
34955
|
+
icon: action.icon,
|
|
34956
|
+
event: action.event,
|
|
34957
|
+
variant: action.variant === "danger" ? "danger" : "default",
|
|
34958
|
+
onClick: () => eventBus.emit(`UI:${action.event}`, {
|
|
34959
|
+
id: row.id,
|
|
34960
|
+
row
|
|
34961
|
+
})
|
|
34962
|
+
}))
|
|
34963
|
+
}
|
|
34964
|
+
)
|
|
32384
34965
|
}
|
|
32385
34966
|
)
|
|
32386
34967
|
]
|
|
@@ -32423,7 +35004,6 @@ var init_TableView = __esm({
|
|
|
32423
35004
|
init_format();
|
|
32424
35005
|
init_getNestedValue();
|
|
32425
35006
|
init_useEventBus();
|
|
32426
|
-
init_useMediaQuery();
|
|
32427
35007
|
init_Box();
|
|
32428
35008
|
init_Stack();
|
|
32429
35009
|
init_Typography();
|
|
@@ -41645,7 +44225,7 @@ function getEnumOptions(field) {
|
|
|
41645
44225
|
}));
|
|
41646
44226
|
}
|
|
41647
44227
|
const validation = field.validation;
|
|
41648
|
-
if (validation?.enum &&
|
|
44228
|
+
if (validation?.enum && validation.enum.length > 0) {
|
|
41649
44229
|
return validation.enum.map((v) => ({
|
|
41650
44230
|
value: v,
|
|
41651
44231
|
label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
|
|
@@ -47092,6 +49672,7 @@ var init_component_registry_generated = __esm({
|
|
|
47092
49672
|
init_ActionTile();
|
|
47093
49673
|
init_ActivationBlock();
|
|
47094
49674
|
init_ComponentPatterns();
|
|
49675
|
+
init_AlgoGraphCanvas();
|
|
47095
49676
|
init_AlgorithmCanvas();
|
|
47096
49677
|
init_AnimatedCounter();
|
|
47097
49678
|
init_AnimatedGraphic();
|
|
@@ -47355,6 +49936,7 @@ var init_component_registry_generated = __esm({
|
|
|
47355
49936
|
"ActivationBlock": ActivationBlock,
|
|
47356
49937
|
"Alert": AlertPattern,
|
|
47357
49938
|
"AlertPattern": AlertPattern,
|
|
49939
|
+
"AlgoGraphCanvas": AlgoGraphCanvas,
|
|
47358
49940
|
"AlgorithmCanvas": AlgorithmCanvas,
|
|
47359
49941
|
"AnimatedCounter": AnimatedCounter,
|
|
47360
49942
|
"AnimatedGraphic": AnimatedGraphic,
|
|
@@ -52227,9 +54809,9 @@ var log8 = logger.createLogger("almadar:ui:effects:client-handlers");
|
|
|
52227
54809
|
function createClientEffectHandlers(options) {
|
|
52228
54810
|
const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
|
|
52229
54811
|
return {
|
|
52230
|
-
emit: (event, payload) => {
|
|
54812
|
+
emit: (event, payload, source) => {
|
|
52231
54813
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
52232
|
-
eventBus.emit(prefixedEvent, payload);
|
|
54814
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
52233
54815
|
},
|
|
52234
54816
|
persist: async () => {
|
|
52235
54817
|
log8.warn("persist is server-side only, ignored on client");
|
|
@@ -52394,7 +54976,7 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
|
|
|
52394
54976
|
}
|
|
52395
54977
|
};
|
|
52396
54978
|
ctx.emit = (event, payload) => {
|
|
52397
|
-
emit(event, payload);
|
|
54979
|
+
emit(event, payload, { trait: traitName, tick: tick.name });
|
|
52398
54980
|
};
|
|
52399
54981
|
if (tick.guard !== void 0 && !evaluator.evaluateGuard(tick.guard, ctx)) {
|
|
52400
54982
|
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
@@ -52846,6 +55428,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
52846
55428
|
}
|
|
52847
55429
|
const effectContext = {
|
|
52848
55430
|
traitName,
|
|
55431
|
+
orbitalName: orbitalsByTrait?.[traitName],
|
|
52849
55432
|
state: previousState,
|
|
52850
55433
|
transition: `${previousState}->${newState}`,
|
|
52851
55434
|
linkedEntity,
|
|
@@ -52922,7 +55505,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
52922
55505
|
});
|
|
52923
55506
|
}
|
|
52924
55507
|
return emittedDuringExec;
|
|
52925
|
-
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot]);
|
|
55508
|
+
}, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot, orbitalsByTrait]);
|
|
52926
55509
|
const runTickEffects = React94.useCallback((tick, binding) => {
|
|
52927
55510
|
const traitName = binding.trait.name;
|
|
52928
55511
|
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
@@ -52958,9 +55541,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
52958
55541
|
log: tickLog
|
|
52959
55542
|
});
|
|
52960
55543
|
}, [executeTransitionEffects, sharedEntityStore]);
|
|
52961
|
-
const emitFromSharedWriter = React94.useCallback((event, payload) => {
|
|
55544
|
+
const emitFromSharedWriter = React94.useCallback((event, payload, source) => {
|
|
52962
55545
|
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
52963
|
-
eventBus.emit(prefixedEvent, payload);
|
|
55546
|
+
eventBus.emit(prefixedEvent, payload, source);
|
|
52964
55547
|
}, [eventBus]);
|
|
52965
55548
|
React94.useEffect(() => {
|
|
52966
55549
|
const scheduler = runtime.createTickScheduler();
|