@xom11/whiteboard 0.25.0 → 0.27.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/{ExcalidrawWithMenus-WENZRYYE.mjs → ExcalidrawWithMenus-2QPPTXJM.mjs} +3 -2
- package/dist/ExcalidrawWithMenus-2QPPTXJM.mjs.map +1 -0
- package/dist/ai.d.mts +3035 -108
- package/dist/ai.d.ts +3035 -108
- package/dist/ai.js +6736 -775
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +5110 -578
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +5 -5
- package/dist/{chunk-ESVPQWHX.mjs → chunk-4ETJ4CDY.mjs} +5 -5
- package/dist/{chunk-ESVPQWHX.mjs.map → chunk-4ETJ4CDY.mjs.map} +1 -1
- package/dist/{chunk-VNCCIV6O.mjs → chunk-AJAHD35N.mjs} +779 -9
- package/dist/chunk-AJAHD35N.mjs.map +1 -0
- package/dist/chunk-AYJPOHCI.mjs +265 -0
- package/dist/chunk-AYJPOHCI.mjs.map +1 -0
- package/dist/{chunk-M42TGYT6.mjs → chunk-BNBOIDO5.mjs} +3 -3
- package/dist/{chunk-M42TGYT6.mjs.map → chunk-BNBOIDO5.mjs.map} +1 -1
- package/dist/{chunk-CJBLJUWG.mjs → chunk-CXHNVYMD.mjs} +4 -4
- package/dist/{chunk-CJBLJUWG.mjs.map → chunk-CXHNVYMD.mjs.map} +1 -1
- package/dist/{chunk-NDEZJKNY.mjs → chunk-D5JLJ3PT.mjs} +4 -4
- package/dist/{chunk-NDEZJKNY.mjs.map → chunk-D5JLJ3PT.mjs.map} +1 -1
- package/dist/{chunk-REIJZDVZ.mjs → chunk-D5LWSN2Y.mjs} +942 -195
- package/dist/chunk-D5LWSN2Y.mjs.map +1 -0
- package/dist/{chunk-I24QOHPU.mjs → chunk-HLAOGXEK.mjs} +3 -3
- package/dist/{chunk-I24QOHPU.mjs.map → chunk-HLAOGXEK.mjs.map} +1 -1
- package/dist/{chunk-TB4CL25L.mjs → chunk-I3L56GVH.mjs} +206 -66
- package/dist/chunk-I3L56GVH.mjs.map +1 -0
- package/dist/chunk-J5LGTIGS.mjs +10 -0
- package/dist/chunk-J5LGTIGS.mjs.map +1 -0
- package/dist/{chunk-ONBCUWVI.mjs → chunk-KYMBUTPO.mjs} +3 -3
- package/dist/{chunk-ONBCUWVI.mjs.map → chunk-KYMBUTPO.mjs.map} +1 -1
- package/dist/{chunk-YSJOVBCD.mjs → chunk-KZGPSTZI.mjs} +4 -4
- package/dist/{chunk-YSJOVBCD.mjs.map → chunk-KZGPSTZI.mjs.map} +1 -1
- package/dist/{chunk-SGFJLHHG.mjs → chunk-PPKHCRRE.mjs} +3 -3
- package/dist/{chunk-SGFJLHHG.mjs.map → chunk-PPKHCRRE.mjs.map} +1 -1
- package/dist/{chunk-AYSFWUPK.mjs → chunk-SZDAS7LK.mjs} +79 -2
- package/dist/chunk-SZDAS7LK.mjs.map +1 -0
- package/dist/chunk-T3SOHYB2.mjs +851 -0
- package/dist/chunk-T3SOHYB2.mjs.map +1 -0
- package/dist/geometry-2d.d.mts +1 -1
- package/dist/geometry-2d.d.ts +1 -1
- package/dist/geometry-2d.js +5389 -1362
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +5 -4
- package/dist/geometry-3d.d.mts +1 -1
- package/dist/geometry-3d.d.ts +1 -1
- package/dist/geometry-3d.js +1333 -251
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +4 -3
- package/dist/graph-2d.d.mts +1 -1
- package/dist/graph-2d.d.ts +1 -1
- package/dist/graph-2d.js +1499 -340
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +7 -6
- package/dist/handleExtractProblem-C-U5KluK.d.mts +158 -0
- package/dist/handleExtractProblem-C-U5KluK.d.ts +158 -0
- package/dist/{host-L7FMFZUW.mjs → host-HAYCJJ2T.mjs} +1258 -441
- package/dist/host-HAYCJJ2T.mjs.map +1 -0
- package/dist/{host-QK53UYMD.mjs → host-LTJHAY5A.mjs} +10 -9
- package/dist/host-LTJHAY5A.mjs.map +1 -0
- package/dist/{host-A64ITWVX.mjs → host-M26FS244.mjs} +6 -5
- package/dist/host-M26FS244.mjs.map +1 -0
- package/dist/{host-QS2EOTRJ.mjs → host-ZQCDAT6O.mjs} +3 -2
- package/dist/host-ZQCDAT6O.mjs.map +1 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +5621 -1590
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +22 -20
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +1 -1
- package/dist/latex.d.ts +1 -1
- package/dist/latex.mjs +2 -1
- package/dist/render-ZX2O2IK7.mjs +10 -0
- package/dist/{render-3WTY7NZB.mjs.map → render-ZX2O2IK7.mjs.map} +1 -1
- package/dist/serialize-C3LSUMSA.mjs +9 -0
- package/dist/{serialize-SRJVKYUG.mjs.map → serialize-C3LSUMSA.mjs.map} +1 -1
- package/dist/{types-DWRyCa2m.d.ts → types-zc_Pa0mp.d.mts} +105 -0
- package/dist/{types-DWRyCa2m.d.mts → types-zc_Pa0mp.d.ts} +105 -0
- package/package.json +10 -1
- package/dist/ExcalidrawWithMenus-WENZRYYE.mjs.map +0 -1
- package/dist/chunk-AYSFWUPK.mjs.map +0 -1
- package/dist/chunk-REIJZDVZ.mjs.map +0 -1
- package/dist/chunk-TB4CL25L.mjs.map +0 -1
- package/dist/chunk-VNCCIV6O.mjs.map +0 -1
- package/dist/chunk-VRHWDZ66.mjs +0 -96
- package/dist/chunk-VRHWDZ66.mjs.map +0 -1
- package/dist/host-A64ITWVX.mjs.map +0 -1
- package/dist/host-L7FMFZUW.mjs.map +0 -1
- package/dist/host-QK53UYMD.mjs.map +0 -1
- package/dist/host-QS2EOTRJ.mjs.map +0 -1
- package/dist/render-3WTY7NZB.mjs +0 -9
- package/dist/serialize-SRJVKYUG.mjs +0 -8
package/dist/graph-2d.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var immer = require('immer');
|
|
5
|
-
var
|
|
5
|
+
var React5 = require('react');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
var reactDom = require('react-dom');
|
|
8
8
|
|
|
@@ -24,7 +24,7 @@ function _interopNamespace(e) {
|
|
|
24
24
|
return Object.freeze(n);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
var
|
|
27
|
+
var React5__namespace = /*#__PURE__*/_interopNamespace(React5);
|
|
28
28
|
|
|
29
29
|
var __defProp = Object.defineProperty;
|
|
30
30
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -466,10 +466,87 @@ var init_JxgRenderer = __esm({
|
|
|
466
466
|
const el = def22.render(obj, this.ctx());
|
|
467
467
|
this.elements.set(obj.id, el);
|
|
468
468
|
this.attachFreePointDragSync(obj, el);
|
|
469
|
+
this.attachGliderDragSync(obj, el);
|
|
469
470
|
} catch (err) {
|
|
470
471
|
console.warn(`[scene/render/2d] kh\xF4ng render \u0111\u01B0\u1EE3c ${obj.kind} id="${obj.id}":`, err);
|
|
471
472
|
}
|
|
472
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Sync vị trí glider của 3 constraint mới (onPerpendicular / onPerpBisector /
|
|
476
|
+
* onCircleAroundPoint) — kéo → recompute t/theta dựa trên ref points + vị trí
|
|
477
|
+
* glider hiện tại → UPDATE_ATTRS. Không đụng onLine/onSegment/onCircle hiện
|
|
478
|
+
* tại (giữ behavior cũ).
|
|
479
|
+
*/
|
|
480
|
+
attachGliderDragSync(obj, el) {
|
|
481
|
+
if (obj.kind !== "point") return;
|
|
482
|
+
const c = obj.attrs.constraint;
|
|
483
|
+
if (!c) return;
|
|
484
|
+
if (c.kind !== "onPerpendicular" && c.kind !== "onPerpBisector" && c.kind !== "onCircleAroundPoint") {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const point = el;
|
|
488
|
+
if (typeof point.on !== "function") return;
|
|
489
|
+
const sceneId = obj.id;
|
|
490
|
+
point.on("up", () => {
|
|
491
|
+
if (this.disposed) return;
|
|
492
|
+
const cur = this.store.getState().objects[sceneId];
|
|
493
|
+
if (!cur) return;
|
|
494
|
+
const curC = cur.attrs.constraint;
|
|
495
|
+
if (!curC) return;
|
|
496
|
+
if (typeof point.X !== "function" || typeof point.Y !== "function") return;
|
|
497
|
+
const x = point.X();
|
|
498
|
+
const y = point.Y();
|
|
499
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return;
|
|
500
|
+
if (curC.kind === "onPerpendicular") {
|
|
501
|
+
const T = this.elements.get(curC.through);
|
|
502
|
+
const A = this.elements.get(curC.perpToA);
|
|
503
|
+
const B = this.elements.get(curC.perpToB);
|
|
504
|
+
if (!T || !A || !B) return;
|
|
505
|
+
const dx = B.X() - A.X();
|
|
506
|
+
const dy = B.Y() - A.Y();
|
|
507
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
508
|
+
const ux = -dy / len;
|
|
509
|
+
const uy = dx / len;
|
|
510
|
+
const newT = (x - T.X()) * ux + (y - T.Y()) * uy;
|
|
511
|
+
if (Math.abs(newT - curC.t) < 1e-9) return;
|
|
512
|
+
this.store.dispatch({
|
|
513
|
+
type: "UPDATE_ATTRS",
|
|
514
|
+
payload: { id: sceneId, patch: { constraint: { ...curC, t: newT } } }
|
|
515
|
+
});
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (curC.kind === "onPerpBisector") {
|
|
519
|
+
const A = this.elements.get(curC.p1);
|
|
520
|
+
const B = this.elements.get(curC.p2);
|
|
521
|
+
if (!A || !B) return;
|
|
522
|
+
const Mx = (A.X() + B.X()) / 2;
|
|
523
|
+
const My = (A.Y() + B.Y()) / 2;
|
|
524
|
+
const dx = B.X() - A.X();
|
|
525
|
+
const dy = B.Y() - A.Y();
|
|
526
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
527
|
+
const ux = -dy / len;
|
|
528
|
+
const uy = dx / len;
|
|
529
|
+
const newT = (x - Mx) * ux + (y - My) * uy;
|
|
530
|
+
if (Math.abs(newT - curC.t) < 1e-9) return;
|
|
531
|
+
this.store.dispatch({
|
|
532
|
+
type: "UPDATE_ATTRS",
|
|
533
|
+
payload: { id: sceneId, patch: { constraint: { ...curC, t: newT } } }
|
|
534
|
+
});
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (curC.kind === "onCircleAroundPoint") {
|
|
538
|
+
const C = this.elements.get(curC.center);
|
|
539
|
+
if (!C) return;
|
|
540
|
+
const newTheta = Math.atan2(y - C.Y(), x - C.X());
|
|
541
|
+
if (Math.abs(newTheta - curC.theta) < 1e-9) return;
|
|
542
|
+
this.store.dispatch({
|
|
543
|
+
type: "UPDATE_ATTRS",
|
|
544
|
+
payload: { id: sceneId, patch: { constraint: { ...curC, theta: newTheta } } }
|
|
545
|
+
});
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
}
|
|
473
550
|
/**
|
|
474
551
|
* Đồng bộ toạ độ live của free point về scene.constraint khi user kéo bằng
|
|
475
552
|
* tay (Move tool / mobile drag). JSXGraph mutate obj.X()/Y() ngay nhưng
|
|
@@ -951,9 +1028,9 @@ var init_selectors = __esm({
|
|
|
951
1028
|
});
|
|
952
1029
|
function useEditorState(opts) {
|
|
953
1030
|
const { store, initialState, onHistoryChange, bindKeyboardShortcuts = true } = opts;
|
|
954
|
-
const onHistoryChangeRef =
|
|
1031
|
+
const onHistoryChangeRef = React5__namespace.useRef(onHistoryChange);
|
|
955
1032
|
onHistoryChangeRef.current = onHistoryChange;
|
|
956
|
-
|
|
1033
|
+
React5__namespace.useEffect(() => {
|
|
957
1034
|
if (initialState?.state) {
|
|
958
1035
|
const loaded = initialState.state;
|
|
959
1036
|
store.withoutHistory(() => {
|
|
@@ -961,14 +1038,14 @@ function useEditorState(opts) {
|
|
|
961
1038
|
});
|
|
962
1039
|
}
|
|
963
1040
|
}, []);
|
|
964
|
-
|
|
1041
|
+
React5__namespace.useEffect(() => {
|
|
965
1042
|
onHistoryChangeRef.current?.(store.canUndo(), store.canRedo());
|
|
966
1043
|
const unsub = store.subscribe(() => {
|
|
967
1044
|
onHistoryChangeRef.current?.(store.canUndo(), store.canRedo());
|
|
968
1045
|
});
|
|
969
1046
|
return unsub;
|
|
970
1047
|
}, [store]);
|
|
971
|
-
|
|
1048
|
+
React5__namespace.useEffect(() => {
|
|
972
1049
|
if (!bindKeyboardShortcuts) return;
|
|
973
1050
|
const onKey = (e) => {
|
|
974
1051
|
const ae = document.activeElement;
|
|
@@ -1554,6 +1631,31 @@ function constraintRefs2D(c) {
|
|
|
1554
1631
|
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
1555
1632
|
case "orthocenter":
|
|
1556
1633
|
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
1634
|
+
case "onPerpendicular":
|
|
1635
|
+
return [c.through, c.perpToA, c.perpToB];
|
|
1636
|
+
case "onPerpBisector":
|
|
1637
|
+
return [c.p1, c.p2];
|
|
1638
|
+
case "onCircleAroundPoint":
|
|
1639
|
+
return [c.center, c.radiusPoint];
|
|
1640
|
+
case "tangentPointExt":
|
|
1641
|
+
return [c.from, c.circle];
|
|
1642
|
+
case "circleIntersection":
|
|
1643
|
+
return [c.c1, c.c2];
|
|
1644
|
+
case "circleSecondIntersection":
|
|
1645
|
+
return [c.c1, c.c2, c.exclude];
|
|
1646
|
+
case "secondIntersection":
|
|
1647
|
+
return [c.line, c.circle, c.other];
|
|
1648
|
+
case "tangencyPoint":
|
|
1649
|
+
return [c.circle, c.onLine];
|
|
1650
|
+
case "arcMidpoint":
|
|
1651
|
+
return [c.circle, c.a, c.b, c.notContaining];
|
|
1652
|
+
case "pointAtDistance": {
|
|
1653
|
+
const d = c.distance;
|
|
1654
|
+
const extra = d.kind === "circleRadius" ? [d.circle] : d.kind === "segmentLength" ? [d.p1, d.p2] : [];
|
|
1655
|
+
return [c.from, c.through, ...extra];
|
|
1656
|
+
}
|
|
1657
|
+
case "excenter":
|
|
1658
|
+
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
1557
1659
|
default:
|
|
1558
1660
|
return [];
|
|
1559
1661
|
}
|
|
@@ -1563,7 +1665,412 @@ var init_d_constraint2 = __esm({
|
|
|
1563
1665
|
}
|
|
1564
1666
|
});
|
|
1565
1667
|
|
|
1566
|
-
// src/core/scene/kinds/point.ts
|
|
1668
|
+
// src/core/scene/kinds/point-constraints/_types.ts
|
|
1669
|
+
function definePointConstraint(m) {
|
|
1670
|
+
return m;
|
|
1671
|
+
}
|
|
1672
|
+
var init_types3 = __esm({
|
|
1673
|
+
"src/core/scene/kinds/point-constraints/_types.ts"() {
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
|
|
1677
|
+
// src/core/scene/kinds/point-constraints/free.ts
|
|
1678
|
+
var freeConstraint;
|
|
1679
|
+
var init_free = __esm({
|
|
1680
|
+
"src/core/scene/kinds/point-constraints/free.ts"() {
|
|
1681
|
+
init_types3();
|
|
1682
|
+
freeConstraint = definePointConstraint({
|
|
1683
|
+
kind: "free",
|
|
1684
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
1685
|
+
render: (obj, ctx, c, opts) => {
|
|
1686
|
+
const board = ctx.jxg;
|
|
1687
|
+
return board.create("point", [c.x, c.y], opts);
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
// src/core/scene/kinds/point-constraints/onAxis.ts
|
|
1694
|
+
var onAxisConstraint;
|
|
1695
|
+
var init_onAxis = __esm({
|
|
1696
|
+
"src/core/scene/kinds/point-constraints/onAxis.ts"() {
|
|
1697
|
+
init_types3();
|
|
1698
|
+
onAxisConstraint = definePointConstraint({
|
|
1699
|
+
kind: "onAxis",
|
|
1700
|
+
describe: (obj, _state, c) => `${obj.label} tr\xEAn tr\u1EE5c ${c.axis}`,
|
|
1701
|
+
render: (obj, ctx, c, opts) => {
|
|
1702
|
+
const board = ctx.jxg;
|
|
1703
|
+
const coords = c.axis === "x" ? [c.t, 0] : [0, c.t];
|
|
1704
|
+
return board.create("point", coords, opts);
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
// src/core/scene/kinds/point-constraints/midpoint.ts
|
|
1711
|
+
var midpointConstraint;
|
|
1712
|
+
var init_midpoint = __esm({
|
|
1713
|
+
"src/core/scene/kinds/point-constraints/midpoint.ts"() {
|
|
1714
|
+
init_types3();
|
|
1715
|
+
midpointConstraint = definePointConstraint({
|
|
1716
|
+
kind: "midpoint",
|
|
1717
|
+
describe: (obj, state, c) => {
|
|
1718
|
+
const l1 = state?.objects[c.p1]?.label ?? c.p1;
|
|
1719
|
+
const l2 = state?.objects[c.p2]?.label ?? c.p2;
|
|
1720
|
+
return `${obj.label} = trung \u0111i\u1EC3m ${l1}${l2}`;
|
|
1721
|
+
},
|
|
1722
|
+
render: (obj, ctx, c, opts) => {
|
|
1723
|
+
const board = ctx.jxg;
|
|
1724
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
1725
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
1726
|
+
return board.create("midpoint", [p1, p2], opts);
|
|
1727
|
+
}
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
// src/core/scene/kinds/point-constraints/perpFoot.ts
|
|
1733
|
+
var perpFootConstraint;
|
|
1734
|
+
var init_perpFoot = __esm({
|
|
1735
|
+
"src/core/scene/kinds/point-constraints/perpFoot.ts"() {
|
|
1736
|
+
init_types3();
|
|
1737
|
+
perpFootConstraint = definePointConstraint({
|
|
1738
|
+
kind: "perpFoot",
|
|
1739
|
+
validate: (c) => {
|
|
1740
|
+
if (!c.from || !c.onLine) {
|
|
1741
|
+
throw new Error("point.perpFoot: from v\xE0 onLine b\u1EAFt bu\u1ED9c");
|
|
1742
|
+
}
|
|
1743
|
+
},
|
|
1744
|
+
describe: (obj, state, c) => {
|
|
1745
|
+
const fromLabel = state?.objects[c.from]?.label ?? c.from;
|
|
1746
|
+
const lineLabel = state?.objects[c.onLine]?.label ?? c.onLine;
|
|
1747
|
+
return `${obj.label} = ch\xE2n \u27C2 t\u1EEB ${fromLabel} xu\u1ED1ng ${lineLabel}`;
|
|
1748
|
+
},
|
|
1749
|
+
render: (obj, ctx, c, opts) => {
|
|
1750
|
+
const board = ctx.jxg;
|
|
1751
|
+
const from = ctx.resolveRef(c.from);
|
|
1752
|
+
const onLine = ctx.resolveRef(c.onLine);
|
|
1753
|
+
return board.create("perpendicularpoint", [onLine, from], opts);
|
|
1754
|
+
}
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
|
|
1759
|
+
// src/core/scene/kinds/point-constraints/circumcenter.ts
|
|
1760
|
+
var circumcenterConstraint;
|
|
1761
|
+
var init_circumcenter = __esm({
|
|
1762
|
+
"src/core/scene/kinds/point-constraints/circumcenter.ts"() {
|
|
1763
|
+
init_types3();
|
|
1764
|
+
circumcenterConstraint = definePointConstraint({
|
|
1765
|
+
kind: "circumcenter",
|
|
1766
|
+
validate: (c) => {
|
|
1767
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1768
|
+
throw new Error("point.circumcenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1769
|
+
}
|
|
1770
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1771
|
+
throw new Error("point.circumcenter: 3 vertex id ph\u1EA3i non-empty");
|
|
1772
|
+
}
|
|
1773
|
+
},
|
|
1774
|
+
describe: (obj, state, c) => {
|
|
1775
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1776
|
+
return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp \u0394${labels}`;
|
|
1777
|
+
},
|
|
1778
|
+
render: (obj, ctx, c, opts) => {
|
|
1779
|
+
const board = ctx.jxg;
|
|
1780
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1781
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1782
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1783
|
+
return board.create("circumcenter", [a, b, c3], opts);
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
// src/core/scene/kinds/point-constraints/incenter.ts
|
|
1790
|
+
var incenterConstraint;
|
|
1791
|
+
var init_incenter = __esm({
|
|
1792
|
+
"src/core/scene/kinds/point-constraints/incenter.ts"() {
|
|
1793
|
+
init_types3();
|
|
1794
|
+
incenterConstraint = definePointConstraint({
|
|
1795
|
+
kind: "incenter",
|
|
1796
|
+
validate: (c) => {
|
|
1797
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1798
|
+
throw new Error("point.incenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1799
|
+
}
|
|
1800
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1801
|
+
throw new Error("point.incenter: 3 vertex id ph\u1EA3i non-empty");
|
|
1802
|
+
}
|
|
1803
|
+
},
|
|
1804
|
+
describe: (obj, state, c) => {
|
|
1805
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1806
|
+
return `${obj.label} = t\xE2m n\u1ED9i ti\u1EBFp \u0394${labels}`;
|
|
1807
|
+
},
|
|
1808
|
+
render: (obj, ctx, c, opts) => {
|
|
1809
|
+
const board = ctx.jxg;
|
|
1810
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1811
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1812
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1813
|
+
return board.create("incenter", [a, b, c3], opts);
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
|
|
1819
|
+
// src/core/scene/kinds/point-constraints/onLine.ts
|
|
1820
|
+
var onLineConstraint;
|
|
1821
|
+
var init_onLine = __esm({
|
|
1822
|
+
"src/core/scene/kinds/point-constraints/onLine.ts"() {
|
|
1823
|
+
init_types3();
|
|
1824
|
+
onLineConstraint = definePointConstraint({
|
|
1825
|
+
kind: "onLine",
|
|
1826
|
+
describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${state?.objects[c.lineId]?.label ?? c.lineId}`,
|
|
1827
|
+
render: (obj, ctx, c, opts) => {
|
|
1828
|
+
const board = ctx.jxg;
|
|
1829
|
+
const line = ctx.resolveRef(c.lineId);
|
|
1830
|
+
const p1 = line.point1;
|
|
1831
|
+
const p2 = line.point2;
|
|
1832
|
+
const sx = p1 && p2 ? p1.X() + c.t * (p2.X() - p1.X()) : c.t;
|
|
1833
|
+
const sy = p1 && p2 ? p1.Y() + c.t * (p2.Y() - p1.Y()) : c.t;
|
|
1834
|
+
return board.create("glider", [sx, sy, line], opts);
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
// src/core/scene/kinds/point-constraints/onSegment.ts
|
|
1841
|
+
var onSegmentConstraint;
|
|
1842
|
+
var init_onSegment = __esm({
|
|
1843
|
+
"src/core/scene/kinds/point-constraints/onSegment.ts"() {
|
|
1844
|
+
init_types3();
|
|
1845
|
+
onSegmentConstraint = definePointConstraint({
|
|
1846
|
+
kind: "onSegment",
|
|
1847
|
+
describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111o\u1EA1n ${state?.objects[c.segmentId]?.label ?? c.segmentId}`,
|
|
1848
|
+
render: (obj, ctx, c, opts) => {
|
|
1849
|
+
const board = ctx.jxg;
|
|
1850
|
+
const seg = ctx.resolveRef(c.segmentId);
|
|
1851
|
+
const p1 = seg.point1;
|
|
1852
|
+
const p2 = seg.point2;
|
|
1853
|
+
const sx = p1 && p2 ? p1.X() + c.t * (p2.X() - p1.X()) : c.t;
|
|
1854
|
+
const sy = p1 && p2 ? p1.Y() + c.t * (p2.Y() - p1.Y()) : c.t;
|
|
1855
|
+
return board.create("glider", [sx, sy, seg], opts);
|
|
1856
|
+
}
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
});
|
|
1860
|
+
|
|
1861
|
+
// src/core/scene/kinds/point-constraints/onCircle.ts
|
|
1862
|
+
var onCircleConstraint;
|
|
1863
|
+
var init_onCircle = __esm({
|
|
1864
|
+
"src/core/scene/kinds/point-constraints/onCircle.ts"() {
|
|
1865
|
+
init_types3();
|
|
1866
|
+
onCircleConstraint = definePointConstraint({
|
|
1867
|
+
kind: "onCircle",
|
|
1868
|
+
describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n ${state?.objects[c.circleId]?.label ?? c.circleId}`,
|
|
1869
|
+
render: (obj, ctx, c, opts) => {
|
|
1870
|
+
const board = ctx.jxg;
|
|
1871
|
+
const circle = ctx.resolveRef(c.circleId);
|
|
1872
|
+
const O = circle.center ?? circle.midpoint;
|
|
1873
|
+
const ox = O ? O.X() : 0;
|
|
1874
|
+
const oy = O ? O.Y() : 0;
|
|
1875
|
+
return board.create("glider", [ox + Math.cos(c.theta), oy + Math.sin(c.theta), circle], opts);
|
|
1876
|
+
}
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
});
|
|
1880
|
+
|
|
1881
|
+
// src/core/scene/kinds/point-constraints/onPolygon.ts
|
|
1882
|
+
var onPolygonConstraint;
|
|
1883
|
+
var init_onPolygon = __esm({
|
|
1884
|
+
"src/core/scene/kinds/point-constraints/onPolygon.ts"() {
|
|
1885
|
+
init_types3();
|
|
1886
|
+
onPolygonConstraint = definePointConstraint({
|
|
1887
|
+
kind: "onPolygon",
|
|
1888
|
+
describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111a gi\xE1c ${state?.objects[c.polygonId]?.label ?? c.polygonId}`,
|
|
1889
|
+
render: (obj, ctx, c, opts) => {
|
|
1890
|
+
const board = ctx.jxg;
|
|
1891
|
+
const poly = ctx.resolveRef(c.polygonId);
|
|
1892
|
+
return board.create("glider", [c.u, c.v, poly], opts);
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
});
|
|
1897
|
+
|
|
1898
|
+
// src/core/scene/kinds/point-constraints/centroid.ts
|
|
1899
|
+
var centroidConstraint;
|
|
1900
|
+
var init_centroid = __esm({
|
|
1901
|
+
"src/core/scene/kinds/point-constraints/centroid.ts"() {
|
|
1902
|
+
init_types3();
|
|
1903
|
+
centroidConstraint = definePointConstraint({
|
|
1904
|
+
kind: "centroid",
|
|
1905
|
+
validate: (c) => {
|
|
1906
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1907
|
+
throw new Error("point.centroid: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1908
|
+
}
|
|
1909
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1910
|
+
throw new Error("point.centroid: 3 vertex id ph\u1EA3i non-empty");
|
|
1911
|
+
}
|
|
1912
|
+
},
|
|
1913
|
+
describe: (obj, state, c) => {
|
|
1914
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1915
|
+
return `${obj.label} = tr\u1ECDng t\xE2m \u0394${labels}`;
|
|
1916
|
+
},
|
|
1917
|
+
render: (obj, ctx, c, opts) => {
|
|
1918
|
+
const board = ctx.jxg;
|
|
1919
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1920
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1921
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1922
|
+
return board.create("point", [
|
|
1923
|
+
() => (a.X() + b.X() + c3.X()) / 3,
|
|
1924
|
+
() => (a.Y() + b.Y() + c3.Y()) / 3
|
|
1925
|
+
], opts);
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
// src/core/scene/kinds/pointConstructions.ts
|
|
1932
|
+
function dist(p, q) {
|
|
1933
|
+
return Math.hypot(p[0] - q[0], p[1] - q[1]);
|
|
1934
|
+
}
|
|
1935
|
+
function sideOf(a, b, p) {
|
|
1936
|
+
return (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
|
|
1937
|
+
}
|
|
1938
|
+
function arcMidpoint(center, radius, a, b, notContaining) {
|
|
1939
|
+
const mcx = (a[0] + b[0]) / 2;
|
|
1940
|
+
const mcy = (a[1] + b[1]) / 2;
|
|
1941
|
+
let ux = mcx - center[0];
|
|
1942
|
+
let uy = mcy - center[1];
|
|
1943
|
+
let len = Math.hypot(ux, uy);
|
|
1944
|
+
if (len < 1e-9) {
|
|
1945
|
+
ux = -(b[1] - a[1]);
|
|
1946
|
+
uy = b[0] - a[0];
|
|
1947
|
+
len = Math.hypot(ux, uy) || 1;
|
|
1948
|
+
}
|
|
1949
|
+
ux /= len;
|
|
1950
|
+
uy /= len;
|
|
1951
|
+
const cand1 = [center[0] + radius * ux, center[1] + radius * uy];
|
|
1952
|
+
const cand2 = [center[0] - radius * ux, center[1] - radius * uy];
|
|
1953
|
+
const sN = sideOf(a, b, notContaining);
|
|
1954
|
+
if (Math.abs(sN) < 1e-9) {
|
|
1955
|
+
return dist(cand1, notContaining) >= dist(cand2, notContaining) ? cand1 : cand2;
|
|
1956
|
+
}
|
|
1957
|
+
const s1 = sideOf(a, b, cand1);
|
|
1958
|
+
return s1 * sN < 0 ? cand1 : cand2;
|
|
1959
|
+
}
|
|
1960
|
+
function excenter(vertices, oppositeIndex) {
|
|
1961
|
+
const [A, B, C] = vertices;
|
|
1962
|
+
const a = dist(B, C);
|
|
1963
|
+
const b = dist(C, A);
|
|
1964
|
+
const c = dist(A, B);
|
|
1965
|
+
const w = [a, b, c];
|
|
1966
|
+
w[oppositeIndex] = -w[oppositeIndex];
|
|
1967
|
+
const sum = w[0] + w[1] + w[2];
|
|
1968
|
+
if (Math.abs(sum) < 1e-9) return A;
|
|
1969
|
+
return [
|
|
1970
|
+
(w[0] * A[0] + w[1] * B[0] + w[2] * C[0]) / sum,
|
|
1971
|
+
(w[0] * A[1] + w[1] * B[1] + w[2] * C[1]) / sum
|
|
1972
|
+
];
|
|
1973
|
+
}
|
|
1974
|
+
function pointAtDistanceCoord(from, through, d) {
|
|
1975
|
+
const dx = through[0] - from[0];
|
|
1976
|
+
const dy = through[1] - from[1];
|
|
1977
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
1978
|
+
return [through[0] + d * dx / len, through[1] + d * dy / len];
|
|
1979
|
+
}
|
|
1980
|
+
function radicalAxisFoot(o1, r1, o2, r2) {
|
|
1981
|
+
const dx = o2[0] - o1[0], dy = o2[1] - o1[1];
|
|
1982
|
+
const d2 = dx * dx + dy * dy;
|
|
1983
|
+
if (d2 < 1e-12) return o1;
|
|
1984
|
+
const t = (d2 + r1 * r1 - r2 * r2) / (2 * d2);
|
|
1985
|
+
return [o1[0] + t * dx, o1[1] + t * dy];
|
|
1986
|
+
}
|
|
1987
|
+
var init_pointConstructions = __esm({
|
|
1988
|
+
"src/core/scene/kinds/pointConstructions.ts"() {
|
|
1989
|
+
}
|
|
1990
|
+
});
|
|
1991
|
+
|
|
1992
|
+
// src/core/scene/kinds/point-constraints/arcMidpoint.ts
|
|
1993
|
+
var arcMidpointConstraint;
|
|
1994
|
+
var init_arcMidpoint = __esm({
|
|
1995
|
+
"src/core/scene/kinds/point-constraints/arcMidpoint.ts"() {
|
|
1996
|
+
init_pointConstructions();
|
|
1997
|
+
init_types3();
|
|
1998
|
+
arcMidpointConstraint = definePointConstraint({
|
|
1999
|
+
kind: "arcMidpoint",
|
|
2000
|
+
validate: (c) => {
|
|
2001
|
+
if (!c.circle || !c.a || !c.b || !c.notContaining) {
|
|
2002
|
+
throw new Error("point.arcMidpoint: circle, a, b, notContaining b\u1EAFt bu\u1ED9c");
|
|
2003
|
+
}
|
|
2004
|
+
},
|
|
2005
|
+
describe: (obj, state, c) => {
|
|
2006
|
+
const al = state?.objects[c.a]?.label ?? c.a;
|
|
2007
|
+
const bl = state?.objects[c.b]?.label ?? c.b;
|
|
2008
|
+
const nl = state?.objects[c.notContaining]?.label ?? c.notContaining;
|
|
2009
|
+
return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl} (kh\xF4ng ch\u1EE9a ${nl})`;
|
|
2010
|
+
},
|
|
2011
|
+
render: (obj, ctx, c, opts) => {
|
|
2012
|
+
const board = ctx.jxg;
|
|
2013
|
+
const circle = ctx.resolveRef(c.circle);
|
|
2014
|
+
const A = ctx.resolveRef(c.a);
|
|
2015
|
+
const B = ctx.resolveRef(c.b);
|
|
2016
|
+
const N = ctx.resolveRef(c.notContaining);
|
|
2017
|
+
const O = circle?.center ?? circle?.midpoint ?? circle;
|
|
2018
|
+
const am = () => arcMidpoint(
|
|
2019
|
+
[O.X(), O.Y()],
|
|
2020
|
+
circle.Radius(),
|
|
2021
|
+
[A.X(), A.Y()],
|
|
2022
|
+
[B.X(), B.Y()],
|
|
2023
|
+
[N.X(), N.Y()]
|
|
2024
|
+
);
|
|
2025
|
+
return board.create("point", [() => am()[0], () => am()[1]], opts);
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
});
|
|
2030
|
+
|
|
2031
|
+
// src/core/scene/kinds/point-constraints/excenter.ts
|
|
2032
|
+
var excenterConstraint;
|
|
2033
|
+
var init_excenter = __esm({
|
|
2034
|
+
"src/core/scene/kinds/point-constraints/excenter.ts"() {
|
|
2035
|
+
init_pointConstructions();
|
|
2036
|
+
init_types3();
|
|
2037
|
+
excenterConstraint = definePointConstraint({
|
|
2038
|
+
kind: "excenter",
|
|
2039
|
+
validate: (c) => {
|
|
2040
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
2041
|
+
throw new Error("point.excenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
2042
|
+
}
|
|
2043
|
+
if (!c.opposite) throw new Error("point.excenter: opposite b\u1EAFt bu\u1ED9c");
|
|
2044
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
2045
|
+
throw new Error("point.excenter: 3 vertex id ph\u1EA3i non-empty");
|
|
2046
|
+
}
|
|
2047
|
+
if (!c.vertices.includes(c.opposite)) {
|
|
2048
|
+
throw new Error("point.excenter: opposite ph\u1EA3i l\xE0 m\u1ED9t trong vertices");
|
|
2049
|
+
}
|
|
2050
|
+
},
|
|
2051
|
+
describe: (obj, state, c) => {
|
|
2052
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
2053
|
+
const opp = state?.objects[c.opposite]?.label ?? c.opposite;
|
|
2054
|
+
return `${obj.label} = t\xE2m b\xE0ng ti\u1EBFp \u0394${labels} \u0111\u1ED1i di\u1EC7n ${opp}`;
|
|
2055
|
+
},
|
|
2056
|
+
render: (obj, ctx, c, opts) => {
|
|
2057
|
+
const board = ctx.jxg;
|
|
2058
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
2059
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
2060
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
2061
|
+
const oppIdx = c.vertices.indexOf(c.opposite);
|
|
2062
|
+
const idx = oppIdx < 0 ? 0 : oppIdx;
|
|
2063
|
+
const ex = () => excenter(
|
|
2064
|
+
[[a.X(), a.Y()], [b.X(), b.Y()], [c3.X(), c3.Y()]],
|
|
2065
|
+
idx
|
|
2066
|
+
);
|
|
2067
|
+
return board.create("point", [() => ex()[0], () => ex()[1]], opts);
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2073
|
+
// src/core/scene/kinds/point-constraints/shared.ts
|
|
1567
2074
|
function buildJxgTransforms(board, ctx, t) {
|
|
1568
2075
|
switch (t.kind) {
|
|
1569
2076
|
case "translate":
|
|
@@ -1590,11 +2097,392 @@ function buildJxgTransforms(board, ctx, t) {
|
|
|
1590
2097
|
}
|
|
1591
2098
|
}
|
|
1592
2099
|
}
|
|
2100
|
+
function makeDistanceFn(ctx, d) {
|
|
2101
|
+
const scale = d.scale ?? 1;
|
|
2102
|
+
const offset = d.offset ?? 0;
|
|
2103
|
+
if (d.kind === "literal") {
|
|
2104
|
+
const v = d.value;
|
|
2105
|
+
return () => scale * v + offset;
|
|
2106
|
+
}
|
|
2107
|
+
if (d.kind === "segmentLength") {
|
|
2108
|
+
const p = ctx.resolveRef(d.p1);
|
|
2109
|
+
const q = ctx.resolveRef(d.p2);
|
|
2110
|
+
return () => scale * Math.hypot(p.X() - q.X(), p.Y() - q.Y()) + offset;
|
|
2111
|
+
}
|
|
2112
|
+
const circle = ctx.resolveRef(d.circle);
|
|
2113
|
+
return () => scale * circle.Radius() + offset;
|
|
2114
|
+
}
|
|
2115
|
+
function buildPointOpts(obj) {
|
|
2116
|
+
return {
|
|
2117
|
+
name: obj.label,
|
|
2118
|
+
withLabel: obj.attrs.showLabel ?? true,
|
|
2119
|
+
visible: obj.visible,
|
|
2120
|
+
fixed: obj.locked,
|
|
2121
|
+
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
2122
|
+
fillColor: obj.attrs.color ?? "#1e40af",
|
|
2123
|
+
face: obj.attrs.face ?? "o",
|
|
2124
|
+
size: obj.attrs.size ?? 4
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
var init_shared = __esm({
|
|
2128
|
+
"src/core/scene/kinds/point-constraints/shared.ts"() {
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
|
|
2132
|
+
// src/core/scene/kinds/point-constraints/pointAtDistance.ts
|
|
2133
|
+
var pointAtDistanceConstraint;
|
|
2134
|
+
var init_pointAtDistance = __esm({
|
|
2135
|
+
"src/core/scene/kinds/point-constraints/pointAtDistance.ts"() {
|
|
2136
|
+
init_pointConstructions();
|
|
2137
|
+
init_types3();
|
|
2138
|
+
init_shared();
|
|
2139
|
+
pointAtDistanceConstraint = definePointConstraint({
|
|
2140
|
+
kind: "pointAtDistance",
|
|
2141
|
+
describe: (obj, state, c) => {
|
|
2142
|
+
const fromL = state?.objects[c.from]?.label ?? c.from;
|
|
2143
|
+
const thrL = state?.objects[c.through]?.label ?? c.through;
|
|
2144
|
+
const d = c.distance;
|
|
2145
|
+
const dLabel = d.kind === "literal" ? `${d.value}` : d.kind === "segmentLength" ? `${state?.objects[d.p1]?.label ?? d.p1}${state?.objects[d.p2]?.label ?? d.p2}` : `b\xE1n k\xEDnh (${state?.objects[d.circle]?.label ?? d.circle})`;
|
|
2146
|
+
return `${obj.label} = tr\xEAn tia ${fromL}${thrL} k\xE9o d\xE0i, c\xE1ch ${thrL} kho\u1EA3ng ${dLabel}`;
|
|
2147
|
+
},
|
|
2148
|
+
render: (obj, ctx, c, opts) => {
|
|
2149
|
+
const board = ctx.jxg;
|
|
2150
|
+
const A = ctx.resolveRef(c.from);
|
|
2151
|
+
const B = ctx.resolveRef(c.through);
|
|
2152
|
+
const dFn = makeDistanceFn(ctx, c.distance);
|
|
2153
|
+
const pc = () => pointAtDistanceCoord([A.X(), A.Y()], [B.X(), B.Y()], dFn());
|
|
2154
|
+
return board.create("point", [() => pc()[0], () => pc()[1]], opts);
|
|
2155
|
+
}
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
});
|
|
2159
|
+
|
|
2160
|
+
// src/core/scene/kinds/point-constraints/circleIntersection.ts
|
|
2161
|
+
var circleIntersectionConstraint;
|
|
2162
|
+
var init_circleIntersection = __esm({
|
|
2163
|
+
"src/core/scene/kinds/point-constraints/circleIntersection.ts"() {
|
|
2164
|
+
init_types3();
|
|
2165
|
+
circleIntersectionConstraint = definePointConstraint({
|
|
2166
|
+
kind: "circleIntersection",
|
|
2167
|
+
// Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
|
|
2168
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
2169
|
+
render: (obj, ctx, c, opts) => {
|
|
2170
|
+
const board = ctx.jxg;
|
|
2171
|
+
const k1 = ctx.resolveRef(c.c1);
|
|
2172
|
+
const k2 = ctx.resolveRef(c.c2);
|
|
2173
|
+
return board.create("intersection", [k1, k2, c.which], opts);
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
});
|
|
2178
|
+
|
|
2179
|
+
// src/core/scene/kinds/point-constraints/circleSecondIntersection.ts
|
|
2180
|
+
var circleSecondIntersectionConstraint;
|
|
2181
|
+
var init_circleSecondIntersection = __esm({
|
|
2182
|
+
"src/core/scene/kinds/point-constraints/circleSecondIntersection.ts"() {
|
|
2183
|
+
init_types3();
|
|
2184
|
+
circleSecondIntersectionConstraint = definePointConstraint({
|
|
2185
|
+
kind: "circleSecondIntersection",
|
|
2186
|
+
// Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
|
|
2187
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
2188
|
+
render: (obj, ctx, c, opts) => {
|
|
2189
|
+
const board = ctx.jxg;
|
|
2190
|
+
const k1 = ctx.resolveRef(c.c1);
|
|
2191
|
+
const k2 = ctx.resolveRef(c.c2);
|
|
2192
|
+
const ex = ctx.resolveRef(c.exclude);
|
|
2193
|
+
return board.create("otherintersection", [k1, k2, ex], opts);
|
|
2194
|
+
}
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
2198
|
+
|
|
2199
|
+
// src/core/scene/kinds/point-constraints/secondIntersection.ts
|
|
2200
|
+
var secondIntersectionConstraint;
|
|
2201
|
+
var init_secondIntersection = __esm({
|
|
2202
|
+
"src/core/scene/kinds/point-constraints/secondIntersection.ts"() {
|
|
2203
|
+
init_types3();
|
|
2204
|
+
secondIntersectionConstraint = definePointConstraint({
|
|
2205
|
+
kind: "secondIntersection",
|
|
2206
|
+
// Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
|
|
2207
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
2208
|
+
render: (obj, ctx, c, opts) => {
|
|
2209
|
+
const board = ctx.jxg;
|
|
2210
|
+
const line = ctx.resolveRef(c.line);
|
|
2211
|
+
const circle = ctx.resolveRef(c.circle);
|
|
2212
|
+
const other = ctx.resolveRef(c.other);
|
|
2213
|
+
return board.create("otherintersection", [circle, line, other], opts);
|
|
2214
|
+
}
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
});
|
|
2218
|
+
|
|
2219
|
+
// src/core/scene/kinds/point-constraints/tangencyPoint.ts
|
|
2220
|
+
var tangencyPointConstraint;
|
|
2221
|
+
var init_tangencyPoint = __esm({
|
|
2222
|
+
"src/core/scene/kinds/point-constraints/tangencyPoint.ts"() {
|
|
2223
|
+
init_types3();
|
|
2224
|
+
tangencyPointConstraint = definePointConstraint({
|
|
2225
|
+
kind: "tangencyPoint",
|
|
2226
|
+
// Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
|
|
2227
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
2228
|
+
render: (obj, ctx, c, opts) => {
|
|
2229
|
+
const board = ctx.jxg;
|
|
2230
|
+
const circle = ctx.resolveRef(c.circle);
|
|
2231
|
+
const line = ctx.resolveRef(c.onLine);
|
|
2232
|
+
const O = circle?.center ?? circle?.midpoint ?? circle;
|
|
2233
|
+
return board.create("perpendicularpoint", [line, O], opts);
|
|
2234
|
+
}
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
2238
|
+
|
|
2239
|
+
// src/core/scene/kinds/point-constraints/transformed.ts
|
|
2240
|
+
var transformedConstraint;
|
|
2241
|
+
var init_transformed = __esm({
|
|
2242
|
+
"src/core/scene/kinds/point-constraints/transformed.ts"() {
|
|
2243
|
+
init_types3();
|
|
2244
|
+
init_shared();
|
|
2245
|
+
transformedConstraint = definePointConstraint({
|
|
2246
|
+
kind: "transformed",
|
|
2247
|
+
describe: (obj, state, c) => {
|
|
2248
|
+
const t = c.transform;
|
|
2249
|
+
const labelRef = (id) => state?.objects[id]?.label ?? id;
|
|
2250
|
+
const op = t.kind === "translate" ? `t\u1ECBnh ti\u1EBFn (${t.dx.toFixed(2)}, ${t.dy.toFixed(2)})` : t.kind === "rotate" ? `quay ${(t.angleRad * 180 / Math.PI).toFixed(0)}\xB0 quanh ${labelRef(t.center)}` : t.kind === "reflectLine" ? `\u0111\u1ED1i x\u1EE9ng qua ${labelRef(t.line)}` : t.kind === "reflectPoint" ? `\u0111\u1ED1i x\u1EE9ng qua \u0111i\u1EC3m ${labelRef(t.center)}` : t.kind === "dilate" ? `v\u1ECB t\u1EF1 k=${t.k} quanh ${labelRef(t.center)}` : "";
|
|
2251
|
+
return `${obj.label} = \u1EA3nh c\u1EE7a ${labelRef(c.source)} (${op})`;
|
|
2252
|
+
},
|
|
2253
|
+
render: (obj, ctx, c, opts) => {
|
|
2254
|
+
const board = ctx.jxg;
|
|
2255
|
+
const src = ctx.resolveRef(c.source);
|
|
2256
|
+
const transforms = buildJxgTransforms(board, ctx, c.transform);
|
|
2257
|
+
const parent = transforms.length === 1 ? transforms[0] : transforms;
|
|
2258
|
+
const pt = board.create("point", [src, parent], opts);
|
|
2259
|
+
pt._helpers = transforms;
|
|
2260
|
+
return pt;
|
|
2261
|
+
}
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
});
|
|
2265
|
+
|
|
2266
|
+
// src/core/scene/kinds/point-constraints/orthocenter.ts
|
|
2267
|
+
var orthocenterConstraint;
|
|
2268
|
+
var init_orthocenter = __esm({
|
|
2269
|
+
"src/core/scene/kinds/point-constraints/orthocenter.ts"() {
|
|
2270
|
+
init_types3();
|
|
2271
|
+
orthocenterConstraint = definePointConstraint({
|
|
2272
|
+
kind: "orthocenter",
|
|
2273
|
+
validate: (c) => {
|
|
2274
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
2275
|
+
throw new Error("point.orthocenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
2276
|
+
}
|
|
2277
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
2278
|
+
throw new Error("point.orthocenter: 3 vertex id ph\u1EA3i non-empty");
|
|
2279
|
+
}
|
|
2280
|
+
},
|
|
2281
|
+
describe: (obj, state, c) => {
|
|
2282
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
2283
|
+
return `${obj.label} = tr\u1EF1c t\xE2m \u0394${labels}`;
|
|
2284
|
+
},
|
|
2285
|
+
render: (obj, ctx, c, opts) => {
|
|
2286
|
+
const board = ctx.jxg;
|
|
2287
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
2288
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
2289
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
2290
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
2291
|
+
const lineBC = board.create("line", [b, c3], hide);
|
|
2292
|
+
const altA = board.create("perpendicular", [lineBC, a], hide);
|
|
2293
|
+
const lineAC = board.create("line", [a, c3], hide);
|
|
2294
|
+
const altB = board.create("perpendicular", [lineAC, b], hide);
|
|
2295
|
+
const ortho = board.create("intersection", [altA, altB, 0], opts);
|
|
2296
|
+
ortho._helpers = [lineBC, altA, lineAC, altB];
|
|
2297
|
+
return ortho;
|
|
2298
|
+
}
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
|
|
2303
|
+
// src/core/scene/kinds/point-constraints/onPerpendicular.ts
|
|
2304
|
+
var onPerpendicularConstraint;
|
|
2305
|
+
var init_onPerpendicular = __esm({
|
|
2306
|
+
"src/core/scene/kinds/point-constraints/onPerpendicular.ts"() {
|
|
2307
|
+
init_types3();
|
|
2308
|
+
onPerpendicularConstraint = definePointConstraint({
|
|
2309
|
+
kind: "onPerpendicular",
|
|
2310
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
2311
|
+
render: (obj, ctx, c, opts) => {
|
|
2312
|
+
const board = ctx.jxg;
|
|
2313
|
+
const T = ctx.resolveRef(c.through);
|
|
2314
|
+
const A = ctx.resolveRef(c.perpToA);
|
|
2315
|
+
const B = ctx.resolveRef(c.perpToB);
|
|
2316
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
2317
|
+
const refLine = board.create("line", [A, B], hide);
|
|
2318
|
+
const perpLine = board.create("perpendicular", [refLine, T], hide);
|
|
2319
|
+
const dx = B.X() - A.X();
|
|
2320
|
+
const dy = B.Y() - A.Y();
|
|
2321
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
2322
|
+
const ux = -dy / len;
|
|
2323
|
+
const uy = dx / len;
|
|
2324
|
+
const x0 = T.X() + c.t * ux;
|
|
2325
|
+
const y0 = T.Y() + c.t * uy;
|
|
2326
|
+
const gl = board.create("glider", [x0, y0, perpLine], opts);
|
|
2327
|
+
gl._helpers = [refLine, perpLine];
|
|
2328
|
+
return gl;
|
|
2329
|
+
}
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
});
|
|
2333
|
+
|
|
2334
|
+
// src/core/scene/kinds/point-constraints/onPerpBisector.ts
|
|
2335
|
+
var onPerpBisectorConstraint;
|
|
2336
|
+
var init_onPerpBisector = __esm({
|
|
2337
|
+
"src/core/scene/kinds/point-constraints/onPerpBisector.ts"() {
|
|
2338
|
+
init_types3();
|
|
2339
|
+
onPerpBisectorConstraint = definePointConstraint({
|
|
2340
|
+
kind: "onPerpBisector",
|
|
2341
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
2342
|
+
render: (obj, ctx, c, opts) => {
|
|
2343
|
+
const board = ctx.jxg;
|
|
2344
|
+
const A = ctx.resolveRef(c.p1);
|
|
2345
|
+
const B = ctx.resolveRef(c.p2);
|
|
2346
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
2347
|
+
const refLine = board.create("line", [A, B], hide);
|
|
2348
|
+
const mid = board.create("midpoint", [A, B], hide);
|
|
2349
|
+
const bisLine = board.create("perpendicular", [refLine, mid], hide);
|
|
2350
|
+
const Mx = (A.X() + B.X()) / 2;
|
|
2351
|
+
const My = (A.Y() + B.Y()) / 2;
|
|
2352
|
+
const dx = B.X() - A.X();
|
|
2353
|
+
const dy = B.Y() - A.Y();
|
|
2354
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
2355
|
+
const ux = -dy / len;
|
|
2356
|
+
const uy = dx / len;
|
|
2357
|
+
const x0 = Mx + c.t * ux;
|
|
2358
|
+
const y0 = My + c.t * uy;
|
|
2359
|
+
const gl = board.create("glider", [x0, y0, bisLine], opts);
|
|
2360
|
+
gl._helpers = [refLine, mid, bisLine];
|
|
2361
|
+
return gl;
|
|
2362
|
+
}
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
});
|
|
2366
|
+
|
|
2367
|
+
// src/core/scene/kinds/point-constraints/onCircleAroundPoint.ts
|
|
2368
|
+
var onCircleAroundPointConstraint;
|
|
2369
|
+
var init_onCircleAroundPoint = __esm({
|
|
2370
|
+
"src/core/scene/kinds/point-constraints/onCircleAroundPoint.ts"() {
|
|
2371
|
+
init_types3();
|
|
2372
|
+
onCircleAroundPointConstraint = definePointConstraint({
|
|
2373
|
+
kind: "onCircleAroundPoint",
|
|
2374
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
2375
|
+
render: (obj, ctx, c, opts) => {
|
|
2376
|
+
const board = ctx.jxg;
|
|
2377
|
+
const C = ctx.resolveRef(c.center);
|
|
2378
|
+
const R = ctx.resolveRef(c.radiusPoint);
|
|
2379
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
2380
|
+
const auxCircle = board.create("circle", [C, R], hide);
|
|
2381
|
+
const r = Math.hypot(R.X() - C.X(), R.Y() - C.Y());
|
|
2382
|
+
const x0 = C.X() + r * Math.cos(c.theta);
|
|
2383
|
+
const y0 = C.Y() + r * Math.sin(c.theta);
|
|
2384
|
+
const gl = board.create("glider", [x0, y0, auxCircle], opts);
|
|
2385
|
+
gl._helpers = [auxCircle];
|
|
2386
|
+
return gl;
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
});
|
|
2391
|
+
|
|
2392
|
+
// src/core/scene/kinds/point-constraints/tangentPointExt.ts
|
|
2393
|
+
var tangentPointExtConstraint;
|
|
2394
|
+
var init_tangentPointExt = __esm({
|
|
2395
|
+
"src/core/scene/kinds/point-constraints/tangentPointExt.ts"() {
|
|
2396
|
+
init_types3();
|
|
2397
|
+
tangentPointExtConstraint = definePointConstraint({
|
|
2398
|
+
kind: "tangentPointExt",
|
|
2399
|
+
describe: (obj, state, c) => {
|
|
2400
|
+
const fromLabel = state?.objects[c.from]?.label ?? c.from;
|
|
2401
|
+
const circleLabel = state?.objects[c.circle]?.label ?? c.circle;
|
|
2402
|
+
return `${obj.label} = ti\u1EBFp \u0111i\u1EC3m c\u1EE7a (${circleLabel}) v\u1EDBi ti\u1EBFp tuy\u1EBFn t\u1EEB ${fromLabel}`;
|
|
2403
|
+
},
|
|
2404
|
+
render: (obj, ctx, c, opts) => {
|
|
2405
|
+
const board = ctx.jxg;
|
|
2406
|
+
const P = ctx.resolveRef(c.from);
|
|
2407
|
+
const K = ctx.resolveRef(c.circle);
|
|
2408
|
+
const O = K.center ?? K.midpoint;
|
|
2409
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
2410
|
+
const mid = board.create("midpoint", [P, O], hide);
|
|
2411
|
+
const thales = board.create("circle", [mid, P], hide);
|
|
2412
|
+
const inter = board.create("intersection", [thales, K, c.which], opts);
|
|
2413
|
+
inter._helpers = [mid, thales];
|
|
2414
|
+
return inter;
|
|
2415
|
+
}
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2419
|
+
|
|
2420
|
+
// src/core/scene/kinds/point-constraints/registry.ts
|
|
2421
|
+
var ALL, POINT_CONSTRAINTS;
|
|
2422
|
+
var init_registry2 = __esm({
|
|
2423
|
+
"src/core/scene/kinds/point-constraints/registry.ts"() {
|
|
2424
|
+
init_free();
|
|
2425
|
+
init_onAxis();
|
|
2426
|
+
init_midpoint();
|
|
2427
|
+
init_perpFoot();
|
|
2428
|
+
init_circumcenter();
|
|
2429
|
+
init_incenter();
|
|
2430
|
+
init_onLine();
|
|
2431
|
+
init_onSegment();
|
|
2432
|
+
init_onCircle();
|
|
2433
|
+
init_onPolygon();
|
|
2434
|
+
init_centroid();
|
|
2435
|
+
init_arcMidpoint();
|
|
2436
|
+
init_excenter();
|
|
2437
|
+
init_pointAtDistance();
|
|
2438
|
+
init_circleIntersection();
|
|
2439
|
+
init_circleSecondIntersection();
|
|
2440
|
+
init_secondIntersection();
|
|
2441
|
+
init_tangencyPoint();
|
|
2442
|
+
init_transformed();
|
|
2443
|
+
init_orthocenter();
|
|
2444
|
+
init_onPerpendicular();
|
|
2445
|
+
init_onPerpBisector();
|
|
2446
|
+
init_onCircleAroundPoint();
|
|
2447
|
+
init_tangentPointExt();
|
|
2448
|
+
ALL = [
|
|
2449
|
+
freeConstraint,
|
|
2450
|
+
onAxisConstraint,
|
|
2451
|
+
midpointConstraint,
|
|
2452
|
+
perpFootConstraint,
|
|
2453
|
+
circumcenterConstraint,
|
|
2454
|
+
incenterConstraint,
|
|
2455
|
+
onLineConstraint,
|
|
2456
|
+
onSegmentConstraint,
|
|
2457
|
+
onCircleConstraint,
|
|
2458
|
+
onPolygonConstraint,
|
|
2459
|
+
centroidConstraint,
|
|
2460
|
+
arcMidpointConstraint,
|
|
2461
|
+
excenterConstraint,
|
|
2462
|
+
pointAtDistanceConstraint,
|
|
2463
|
+
circleIntersectionConstraint,
|
|
2464
|
+
circleSecondIntersectionConstraint,
|
|
2465
|
+
secondIntersectionConstraint,
|
|
2466
|
+
tangencyPointConstraint,
|
|
2467
|
+
transformedConstraint,
|
|
2468
|
+
orthocenterConstraint,
|
|
2469
|
+
onPerpendicularConstraint,
|
|
2470
|
+
onPerpBisectorConstraint,
|
|
2471
|
+
onCircleAroundPointConstraint,
|
|
2472
|
+
tangentPointExtConstraint
|
|
2473
|
+
];
|
|
2474
|
+
POINT_CONSTRAINTS = new Map(ALL.map((m) => [m.kind, m]));
|
|
2475
|
+
}
|
|
2476
|
+
});
|
|
2477
|
+
|
|
2478
|
+
// src/core/scene/kinds/point.ts
|
|
1593
2479
|
var def3;
|
|
1594
2480
|
var init_point = __esm({
|
|
1595
2481
|
"src/core/scene/kinds/point.ts"() {
|
|
1596
2482
|
init_registry();
|
|
1597
2483
|
init_d_constraint2();
|
|
2484
|
+
init_registry2();
|
|
2485
|
+
init_shared();
|
|
1598
2486
|
def3 = {
|
|
1599
2487
|
type: "point",
|
|
1600
2488
|
schemaVersion: 1,
|
|
@@ -1604,43 +2492,7 @@ var init_point = __esm({
|
|
|
1604
2492
|
throw new Error("point: constraint required");
|
|
1605
2493
|
}
|
|
1606
2494
|
const c = a.constraint;
|
|
1607
|
-
|
|
1608
|
-
if (!c.from || !c.onLine) {
|
|
1609
|
-
throw new Error("point.perpFoot: from v\xE0 onLine b\u1EAFt bu\u1ED9c");
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
if (c.kind === "circumcenter") {
|
|
1613
|
-
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1614
|
-
throw new Error("point.circumcenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1615
|
-
}
|
|
1616
|
-
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1617
|
-
throw new Error("point.circumcenter: 3 vertex id ph\u1EA3i non-empty");
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
if (c.kind === "incenter") {
|
|
1621
|
-
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1622
|
-
throw new Error("point.incenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1623
|
-
}
|
|
1624
|
-
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1625
|
-
throw new Error("point.incenter: 3 vertex id ph\u1EA3i non-empty");
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
if (c.kind === "centroid") {
|
|
1629
|
-
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1630
|
-
throw new Error("point.centroid: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1631
|
-
}
|
|
1632
|
-
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1633
|
-
throw new Error("point.centroid: 3 vertex id ph\u1EA3i non-empty");
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
if (c.kind === "orthocenter") {
|
|
1637
|
-
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1638
|
-
throw new Error("point.orthocenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1639
|
-
}
|
|
1640
|
-
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1641
|
-
throw new Error("point.orthocenter: 3 vertex id ph\u1EA3i non-empty");
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
2495
|
+
POINT_CONSTRAINTS.get(c.kind)?.validate?.(c);
|
|
1644
2496
|
},
|
|
1645
2497
|
dependsOn: (a) => constraintRefs2D(a.constraint),
|
|
1646
2498
|
measure: (obj) => {
|
|
@@ -1655,132 +2507,16 @@ var init_point = __esm({
|
|
|
1655
2507
|
},
|
|
1656
2508
|
describe: (obj, state) => {
|
|
1657
2509
|
const c = obj.attrs.constraint;
|
|
1658
|
-
|
|
1659
|
-
if (
|
|
1660
|
-
if (c.kind === "onLine") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${state?.objects[c.lineId]?.label ?? c.lineId}`;
|
|
1661
|
-
if (c.kind === "onSegment") return `${obj.label} tr\xEAn \u0111o\u1EA1n ${state?.objects[c.segmentId]?.label ?? c.segmentId}`;
|
|
1662
|
-
if (c.kind === "onCircle") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n ${state?.objects[c.circleId]?.label ?? c.circleId}`;
|
|
1663
|
-
if (c.kind === "onPolygon") return `${obj.label} tr\xEAn \u0111a gi\xE1c ${state?.objects[c.polygonId]?.label ?? c.polygonId}`;
|
|
1664
|
-
if (c.kind === "midpoint") {
|
|
1665
|
-
const l1 = state?.objects[c.p1]?.label ?? c.p1;
|
|
1666
|
-
const l2 = state?.objects[c.p2]?.label ?? c.p2;
|
|
1667
|
-
return `${obj.label} = trung \u0111i\u1EC3m ${l1}${l2}`;
|
|
1668
|
-
}
|
|
1669
|
-
if (c.kind === "transformed") {
|
|
1670
|
-
const t = c.transform;
|
|
1671
|
-
const labelRef = (id) => state?.objects[id]?.label ?? id;
|
|
1672
|
-
const op = t.kind === "translate" ? `t\u1ECBnh ti\u1EBFn (${t.dx.toFixed(2)}, ${t.dy.toFixed(2)})` : t.kind === "rotate" ? `quay ${(t.angleRad * 180 / Math.PI).toFixed(0)}\xB0 quanh ${labelRef(t.center)}` : t.kind === "reflectLine" ? `\u0111\u1ED1i x\u1EE9ng qua ${labelRef(t.line)}` : t.kind === "reflectPoint" ? `\u0111\u1ED1i x\u1EE9ng qua \u0111i\u1EC3m ${labelRef(t.center)}` : t.kind === "dilate" ? `v\u1ECB t\u1EF1 k=${t.k} quanh ${labelRef(t.center)}` : "";
|
|
1673
|
-
return `${obj.label} = \u1EA3nh c\u1EE7a ${labelRef(c.source)} (${op})`;
|
|
1674
|
-
}
|
|
1675
|
-
if (c.kind === "perpFoot") {
|
|
1676
|
-
const fromLabel = state?.objects[c.from]?.label ?? c.from;
|
|
1677
|
-
const lineLabel = state?.objects[c.onLine]?.label ?? c.onLine;
|
|
1678
|
-
return `${obj.label} = ch\xE2n \u27C2 t\u1EEB ${fromLabel} xu\u1ED1ng ${lineLabel}`;
|
|
1679
|
-
}
|
|
1680
|
-
if (c.kind === "circumcenter") {
|
|
1681
|
-
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1682
|
-
return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp \u0394${labels}`;
|
|
1683
|
-
}
|
|
1684
|
-
if (c.kind === "incenter") {
|
|
1685
|
-
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1686
|
-
return `${obj.label} = t\xE2m n\u1ED9i ti\u1EBFp \u0394${labels}`;
|
|
1687
|
-
}
|
|
1688
|
-
if (c.kind === "centroid") {
|
|
1689
|
-
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1690
|
-
return `${obj.label} = tr\u1ECDng t\xE2m \u0394${labels}`;
|
|
1691
|
-
}
|
|
1692
|
-
if (c.kind === "orthocenter") {
|
|
1693
|
-
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1694
|
-
return `${obj.label} = tr\u1EF1c t\xE2m \u0394${labels}`;
|
|
1695
|
-
}
|
|
2510
|
+
const mod = POINT_CONSTRAINTS.get(c.kind);
|
|
2511
|
+
if (mod) return mod.describe(obj, state, c);
|
|
1696
2512
|
return `\u0110i\u1EC3m ${obj.label}`;
|
|
1697
2513
|
},
|
|
1698
2514
|
render: (obj, ctx) => {
|
|
1699
2515
|
const board = ctx.jxg;
|
|
1700
2516
|
const c = obj.attrs.constraint;
|
|
1701
|
-
const opts =
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
visible: obj.visible,
|
|
1705
|
-
fixed: obj.locked,
|
|
1706
|
-
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
1707
|
-
fillColor: obj.attrs.color ?? "#1e40af",
|
|
1708
|
-
face: obj.attrs.face ?? "o",
|
|
1709
|
-
size: obj.attrs.size ?? 4
|
|
1710
|
-
};
|
|
1711
|
-
if (c.kind === "free") return board.create("point", [c.x, c.y], opts);
|
|
1712
|
-
if (c.kind === "onAxis") {
|
|
1713
|
-
const coords = c.axis === "x" ? [c.t, 0] : [0, c.t];
|
|
1714
|
-
return board.create("point", coords, opts);
|
|
1715
|
-
}
|
|
1716
|
-
if (c.kind === "onLine") {
|
|
1717
|
-
const line = ctx.resolveRef(c.lineId);
|
|
1718
|
-
return board.create("glider", [c.t, c.t, line], opts);
|
|
1719
|
-
}
|
|
1720
|
-
if (c.kind === "onSegment") {
|
|
1721
|
-
const seg = ctx.resolveRef(c.segmentId);
|
|
1722
|
-
return board.create("glider", [c.t, c.t, seg], opts);
|
|
1723
|
-
}
|
|
1724
|
-
if (c.kind === "onCircle") {
|
|
1725
|
-
const circle = ctx.resolveRef(c.circleId);
|
|
1726
|
-
return board.create("glider", [Math.cos(c.theta), Math.sin(c.theta), circle], opts);
|
|
1727
|
-
}
|
|
1728
|
-
if (c.kind === "onPolygon") {
|
|
1729
|
-
const poly = ctx.resolveRef(c.polygonId);
|
|
1730
|
-
return board.create("glider", [c.u, c.v, poly], opts);
|
|
1731
|
-
}
|
|
1732
|
-
if (c.kind === "midpoint") {
|
|
1733
|
-
const p1 = ctx.resolveRef(c.p1);
|
|
1734
|
-
const p2 = ctx.resolveRef(c.p2);
|
|
1735
|
-
return board.create("midpoint", [p1, p2], opts);
|
|
1736
|
-
}
|
|
1737
|
-
if (c.kind === "transformed") {
|
|
1738
|
-
const src = ctx.resolveRef(c.source);
|
|
1739
|
-
const transforms = buildJxgTransforms(board, ctx, c.transform);
|
|
1740
|
-
const parent = transforms.length === 1 ? transforms[0] : transforms;
|
|
1741
|
-
const pt = board.create("point", [src, parent], opts);
|
|
1742
|
-
pt._helpers = transforms;
|
|
1743
|
-
return pt;
|
|
1744
|
-
}
|
|
1745
|
-
if (c.kind === "perpFoot") {
|
|
1746
|
-
const from = ctx.resolveRef(c.from);
|
|
1747
|
-
const onLine = ctx.resolveRef(c.onLine);
|
|
1748
|
-
return board.create("perpendicularpoint", [onLine, from], opts);
|
|
1749
|
-
}
|
|
1750
|
-
if (c.kind === "circumcenter") {
|
|
1751
|
-
const a = ctx.resolveRef(c.vertices[0]);
|
|
1752
|
-
const b = ctx.resolveRef(c.vertices[1]);
|
|
1753
|
-
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1754
|
-
return board.create("circumcenter", [a, b, c3], opts);
|
|
1755
|
-
}
|
|
1756
|
-
if (c.kind === "incenter") {
|
|
1757
|
-
const a = ctx.resolveRef(c.vertices[0]);
|
|
1758
|
-
const b = ctx.resolveRef(c.vertices[1]);
|
|
1759
|
-
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1760
|
-
return board.create("incenter", [a, b, c3], opts);
|
|
1761
|
-
}
|
|
1762
|
-
if (c.kind === "centroid") {
|
|
1763
|
-
const a = ctx.resolveRef(c.vertices[0]);
|
|
1764
|
-
const b = ctx.resolveRef(c.vertices[1]);
|
|
1765
|
-
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1766
|
-
return board.create("point", [
|
|
1767
|
-
() => (a.X() + b.X() + c3.X()) / 3,
|
|
1768
|
-
() => (a.Y() + b.Y() + c3.Y()) / 3
|
|
1769
|
-
], opts);
|
|
1770
|
-
}
|
|
1771
|
-
if (c.kind === "orthocenter") {
|
|
1772
|
-
const a = ctx.resolveRef(c.vertices[0]);
|
|
1773
|
-
const b = ctx.resolveRef(c.vertices[1]);
|
|
1774
|
-
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1775
|
-
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
1776
|
-
const lineBC = board.create("line", [b, c3], hide);
|
|
1777
|
-
const altA = board.create("perpendicular", [lineBC, a], hide);
|
|
1778
|
-
const lineAC = board.create("line", [a, c3], hide);
|
|
1779
|
-
const altB = board.create("perpendicular", [lineAC, b], hide);
|
|
1780
|
-
const ortho = board.create("intersection", [altA, altB, 0], opts);
|
|
1781
|
-
ortho._helpers = [lineBC, altA, lineAC, altB];
|
|
1782
|
-
return ortho;
|
|
1783
|
-
}
|
|
2517
|
+
const opts = buildPointOpts(obj);
|
|
2518
|
+
const mod = POINT_CONSTRAINTS.get(c.kind);
|
|
2519
|
+
if (mod) return mod.render(obj, ctx, c, opts);
|
|
1784
2520
|
return board.create("point", [0, 0], opts);
|
|
1785
2521
|
},
|
|
1786
2522
|
/**
|
|
@@ -1889,6 +2625,10 @@ function constructionRefs(c) {
|
|
|
1889
2625
|
return [c.p1, c.vertex, c.p2];
|
|
1890
2626
|
case "angleBisectorLines":
|
|
1891
2627
|
return [stripBorderSuffix(c.line1), stripBorderSuffix(c.line2)];
|
|
2628
|
+
case "lineThrough":
|
|
2629
|
+
return [...c.points];
|
|
2630
|
+
case "radicalAxis":
|
|
2631
|
+
return [c.circle1, c.circle2];
|
|
1892
2632
|
case "tangent":
|
|
1893
2633
|
return [c.throughPoint, c.toCircle];
|
|
1894
2634
|
}
|
|
@@ -1898,6 +2638,7 @@ var init_line = __esm({
|
|
|
1898
2638
|
"src/core/scene/kinds/line.ts"() {
|
|
1899
2639
|
init_registry();
|
|
1900
2640
|
init_labelOf();
|
|
2641
|
+
init_pointConstructions();
|
|
1901
2642
|
def5 = {
|
|
1902
2643
|
type: "line",
|
|
1903
2644
|
schemaVersion: 1,
|
|
@@ -1922,6 +2663,10 @@ var init_line = __esm({
|
|
|
1922
2663
|
return `${obj.label}: ph\xE2n gi\xE1c g\xF3c ${L(c.p1)}${L(c.vertex)}${L(c.p2)}`;
|
|
1923
2664
|
case "angleBisectorLines":
|
|
1924
2665
|
return `${obj.label}: ph\xE2n gi\xE1c ${L(c.line1)} & ${L(c.line2)} (${c.branch === 0 ? "1" : "2"})`;
|
|
2666
|
+
case "lineThrough":
|
|
2667
|
+
return `${obj.label}: \u0111\u01B0\u1EDDng qua ${c.points.map(L).join("")}`;
|
|
2668
|
+
case "radicalAxis":
|
|
2669
|
+
return `${obj.label}: tr\u1EE5c \u0111\u1EB3ng ph\u01B0\u01A1ng ${L(c.circle1)} & ${L(c.circle2)}`;
|
|
1925
2670
|
case "tangent":
|
|
1926
2671
|
return `${obj.label}: ti\u1EBFp tuy\u1EBFn ${L(c.toCircle)} qua ${L(c.throughPoint)}`;
|
|
1927
2672
|
}
|
|
@@ -2002,6 +2747,47 @@ var init_line = __esm({
|
|
|
2002
2747
|
selected._helpers = [other];
|
|
2003
2748
|
return selected;
|
|
2004
2749
|
}
|
|
2750
|
+
case "lineThrough": {
|
|
2751
|
+
const pts = c.points.map((id) => ctx.resolveRef(id));
|
|
2752
|
+
let bi = 0, bj = 1, best = -1;
|
|
2753
|
+
for (let i = 0; i < pts.length; i++) {
|
|
2754
|
+
for (let j = i + 1; j < pts.length; j++) {
|
|
2755
|
+
const dx = pts[i].X() - pts[j].X();
|
|
2756
|
+
const dy = pts[i].Y() - pts[j].Y();
|
|
2757
|
+
const d = dx * dx + dy * dy;
|
|
2758
|
+
if (d > best) {
|
|
2759
|
+
best = d;
|
|
2760
|
+
bi = i;
|
|
2761
|
+
bj = j;
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
return board.create("line", [pts[bi], pts[bj]], {
|
|
2766
|
+
...baseOpts,
|
|
2767
|
+
straightFirst: true,
|
|
2768
|
+
straightLast: true
|
|
2769
|
+
});
|
|
2770
|
+
}
|
|
2771
|
+
case "radicalAxis": {
|
|
2772
|
+
const k1 = ctx.resolveRef(c.circle1);
|
|
2773
|
+
const k2 = ctx.resolveRef(c.circle2);
|
|
2774
|
+
const o1 = () => [k1.center.X(), k1.center.Y()];
|
|
2775
|
+
const o2 = () => [k2.center.X(), k2.center.Y()];
|
|
2776
|
+
const foot = () => radicalAxisFoot(o1(), k1.Radius(), o2(), k2.Radius());
|
|
2777
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
2778
|
+
const f1 = board.create("point", [() => foot()[0], () => foot()[1]], hide);
|
|
2779
|
+
const f2 = board.create("point", [
|
|
2780
|
+
() => foot()[0] - (o2()[1] - o1()[1]),
|
|
2781
|
+
() => foot()[1] + (o2()[0] - o1()[0])
|
|
2782
|
+
], hide);
|
|
2783
|
+
const line = board.create("line", [f1, f2], {
|
|
2784
|
+
...baseOpts,
|
|
2785
|
+
straightFirst: true,
|
|
2786
|
+
straightLast: true
|
|
2787
|
+
});
|
|
2788
|
+
line._helpers = [f1, f2];
|
|
2789
|
+
return line;
|
|
2790
|
+
}
|
|
2005
2791
|
case "tangent": {
|
|
2006
2792
|
const through = ctx.resolveRef(c.throughPoint);
|
|
2007
2793
|
const toCircle = ctx.resolveRef(c.toCircle);
|
|
@@ -2120,10 +2906,29 @@ var init_vector = __esm({
|
|
|
2120
2906
|
});
|
|
2121
2907
|
|
|
2122
2908
|
// src/core/scene/kinds/circle.ts
|
|
2909
|
+
function asConstruction(a) {
|
|
2910
|
+
if (a.construction) return a.construction;
|
|
2911
|
+
const raw = a;
|
|
2912
|
+
if (raw.kind === "incircle" && raw.vertices) {
|
|
2913
|
+
return {
|
|
2914
|
+
kind: "incircle",
|
|
2915
|
+
p1: raw.vertices[0],
|
|
2916
|
+
p2: raw.vertices[1],
|
|
2917
|
+
p3: raw.vertices[2]
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
return void 0;
|
|
2921
|
+
}
|
|
2123
2922
|
function constructionRefs2(c) {
|
|
2124
2923
|
switch (c.kind) {
|
|
2125
2924
|
case "circumscribed":
|
|
2126
2925
|
return [c.p1, c.p2, c.p3];
|
|
2926
|
+
case "incircle":
|
|
2927
|
+
return [c.p1, c.p2, c.p3];
|
|
2928
|
+
case "excircle":
|
|
2929
|
+
return [c.p1, c.p2, c.p3];
|
|
2930
|
+
case "diameter":
|
|
2931
|
+
return [c.p1, c.p2];
|
|
2127
2932
|
}
|
|
2128
2933
|
}
|
|
2129
2934
|
var def8;
|
|
@@ -2131,19 +2936,33 @@ var init_circle = __esm({
|
|
|
2131
2936
|
"src/core/scene/kinds/circle.ts"() {
|
|
2132
2937
|
init_registry();
|
|
2133
2938
|
init_labelOf();
|
|
2939
|
+
init_pointConstructions();
|
|
2134
2940
|
def8 = {
|
|
2135
2941
|
type: "circle",
|
|
2136
2942
|
schemaVersion: 1,
|
|
2137
2943
|
migrate: {},
|
|
2138
2944
|
validate: (a) => {
|
|
2139
|
-
if (a
|
|
2945
|
+
if (asConstruction(a)) return;
|
|
2946
|
+
if (typeof a?.radius === "number") {
|
|
2947
|
+
if (!a.center) throw new Error("circle: center b\u1EAFt bu\u1ED9c khi d\xF9ng radius");
|
|
2948
|
+
if (!(a.radius > 0)) throw new Error("circle: radius ph\u1EA3i > 0");
|
|
2949
|
+
return;
|
|
2950
|
+
}
|
|
2140
2951
|
if (!a?.center || !a?.surfacePoint) {
|
|
2141
|
-
throw new Error("circle: center v\xE0 surfacePoint b\u1EAFt bu\u1ED9c (ho\u1EB7c construction)");
|
|
2952
|
+
throw new Error("circle: center v\xE0 surfacePoint b\u1EAFt bu\u1ED9c (ho\u1EB7c construction / radius)");
|
|
2142
2953
|
}
|
|
2143
2954
|
},
|
|
2144
|
-
dependsOn: (a) =>
|
|
2955
|
+
dependsOn: (a) => {
|
|
2956
|
+
const c = asConstruction(a);
|
|
2957
|
+
if (c) return constructionRefs2(c);
|
|
2958
|
+
if (typeof a.radius === "number") return [a.center];
|
|
2959
|
+
return [a.center, a.surfacePoint];
|
|
2960
|
+
},
|
|
2145
2961
|
measure: (obj, state) => {
|
|
2146
|
-
if (obj.attrs
|
|
2962
|
+
if (asConstruction(obj.attrs)) return null;
|
|
2963
|
+
if (typeof obj.attrs.radius === "number") {
|
|
2964
|
+
return [{ label: "r", value: obj.attrs.radius }];
|
|
2965
|
+
}
|
|
2147
2966
|
const center = obj.attrs.center ? state.objects[obj.attrs.center] : void 0;
|
|
2148
2967
|
const surface = obj.attrs.surfacePoint ? state.objects[obj.attrs.surfacePoint] : void 0;
|
|
2149
2968
|
if (!center || !surface) return null;
|
|
@@ -2156,10 +2975,22 @@ var init_circle = __esm({
|
|
|
2156
2975
|
},
|
|
2157
2976
|
describe: (obj, state) => {
|
|
2158
2977
|
const L = (id) => labelOf(id, state);
|
|
2159
|
-
const c = obj.attrs
|
|
2978
|
+
const c = asConstruction(obj.attrs);
|
|
2160
2979
|
if (c?.kind === "circumscribed") {
|
|
2161
2980
|
return `\u0110\u01B0\u1EDDng tr\xF2n \u0111i qua ${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
|
|
2162
2981
|
}
|
|
2982
|
+
if (c?.kind === "incircle") {
|
|
2983
|
+
return `\u0110\u01B0\u1EDDng tr\xF2n n\u1ED9i ti\u1EBFp \u0394${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
|
|
2984
|
+
}
|
|
2985
|
+
if (c?.kind === "excircle") {
|
|
2986
|
+
return `\u0110\u01B0\u1EDDng tr\xF2n b\xE0ng ti\u1EBFp \u0394${L(c.p1)}${L(c.p2)}${L(c.p3)} \u0111\u1ED1i di\u1EC7n ${L(c.opposite)}`;
|
|
2987
|
+
}
|
|
2988
|
+
if (c?.kind === "diameter") {
|
|
2989
|
+
return `\u0110\u01B0\u1EDDng tr\xF2n \u0111\u01B0\u1EDDng k\xEDnh ${L(c.p1)}${L(c.p2)}`;
|
|
2990
|
+
}
|
|
2991
|
+
if (typeof obj.attrs.radius === "number") {
|
|
2992
|
+
return `\u0110\u01B0\u1EDDng tr\xF2n t\xE2m ${L(obj.attrs.center)} b\xE1n k\xEDnh ${obj.attrs.radius}`;
|
|
2993
|
+
}
|
|
2163
2994
|
return `\u0110\u01B0\u1EDDng tr\xF2n t\xE2m ${L(obj.attrs.center)} b\xE1n k\xEDnh ${L(obj.attrs.center)}${L(obj.attrs.surfacePoint)}`;
|
|
2164
2995
|
},
|
|
2165
2996
|
render: (obj, ctx) => {
|
|
@@ -2174,13 +3005,66 @@ var init_circle = __esm({
|
|
|
2174
3005
|
visible: obj.visible,
|
|
2175
3006
|
fixed: obj.locked
|
|
2176
3007
|
};
|
|
2177
|
-
const c = obj.attrs
|
|
3008
|
+
const c = asConstruction(obj.attrs);
|
|
2178
3009
|
if (c?.kind === "circumscribed") {
|
|
2179
3010
|
const p1 = ctx.resolveRef(c.p1);
|
|
2180
3011
|
const p2 = ctx.resolveRef(c.p2);
|
|
2181
3012
|
const p3 = ctx.resolveRef(c.p3);
|
|
2182
3013
|
return board.create("circumcircle", [p1, p2, p3], baseOpts);
|
|
2183
3014
|
}
|
|
3015
|
+
if (c?.kind === "incircle") {
|
|
3016
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
3017
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
3018
|
+
const p3 = ctx.resolveRef(c.p3);
|
|
3019
|
+
const center2 = board.create("incenter", [p1, p2, p3], {
|
|
3020
|
+
visible: obj.visible,
|
|
3021
|
+
withLabel: true,
|
|
3022
|
+
fixed: true,
|
|
3023
|
+
name: obj.label
|
|
3024
|
+
});
|
|
3025
|
+
const circ = board.create("incircle", [p1, p2, p3], baseOpts);
|
|
3026
|
+
circ.center = circ.center ?? center2;
|
|
3027
|
+
circ._helpers = [center2];
|
|
3028
|
+
return circ;
|
|
3029
|
+
}
|
|
3030
|
+
if (c?.kind === "excircle") {
|
|
3031
|
+
const P = [ctx.resolveRef(c.p1), ctx.resolveRef(c.p2), ctx.resolveRef(c.p3)];
|
|
3032
|
+
const ids = [c.p1, c.p2, c.p3];
|
|
3033
|
+
const oppIdx = Math.max(0, ids.indexOf(c.opposite));
|
|
3034
|
+
const verts = () => [
|
|
3035
|
+
[P[0].X(), P[0].Y()],
|
|
3036
|
+
[P[1].X(), P[1].Y()],
|
|
3037
|
+
[P[2].X(), P[2].Y()]
|
|
3038
|
+
];
|
|
3039
|
+
const ctr = () => excenter(verts(), oppIdx);
|
|
3040
|
+
const radius = () => {
|
|
3041
|
+
const I = ctr();
|
|
3042
|
+
const others = [0, 1, 2].filter((i) => i !== oppIdx);
|
|
3043
|
+
const v = verts();
|
|
3044
|
+
const a = v[others[0]];
|
|
3045
|
+
const b = v[others[1]];
|
|
3046
|
+
const dx = b[0] - a[0];
|
|
3047
|
+
const dy = b[1] - a[1];
|
|
3048
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
3049
|
+
return Math.abs((I[0] - a[0]) * dy - (I[1] - a[1]) * dx) / len;
|
|
3050
|
+
};
|
|
3051
|
+
const center2 = board.create("point", [() => ctr()[0], () => ctr()[1]], { visible: false, withLabel: false, fixed: true, name: "" });
|
|
3052
|
+
const circ = board.create("circle", [center2, () => radius()], baseOpts);
|
|
3053
|
+
circ._helpers = [center2];
|
|
3054
|
+
return circ;
|
|
3055
|
+
}
|
|
3056
|
+
if (c?.kind === "diameter") {
|
|
3057
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
3058
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
3059
|
+
const center2 = board.create("midpoint", [p1, p2], { visible: false, withLabel: false, fixed: true, name: "" });
|
|
3060
|
+
const circ = board.create("circle", [center2, p2], baseOpts);
|
|
3061
|
+
circ._helpers = [center2];
|
|
3062
|
+
return circ;
|
|
3063
|
+
}
|
|
3064
|
+
if (typeof obj.attrs.radius === "number") {
|
|
3065
|
+
const center2 = ctx.resolveRef(obj.attrs.center);
|
|
3066
|
+
return board.create("circle", [center2, obj.attrs.radius], baseOpts);
|
|
3067
|
+
}
|
|
2184
3068
|
const center = ctx.resolveRef(obj.attrs.center);
|
|
2185
3069
|
const surface = ctx.resolveRef(obj.attrs.surfacePoint);
|
|
2186
3070
|
return board.create("circle", [center, surface], baseOpts);
|
|
@@ -2340,6 +3224,26 @@ function regularVertexLabels(p1Label, p2Label, n) {
|
|
|
2340
3224
|
}
|
|
2341
3225
|
return `${p1Label}${p2Label}\u2026`;
|
|
2342
3226
|
}
|
|
3227
|
+
function specialShapeName(kind) {
|
|
3228
|
+
switch (kind) {
|
|
3229
|
+
case "square":
|
|
3230
|
+
return "H\xECnh vu\xF4ng";
|
|
3231
|
+
case "rectangle":
|
|
3232
|
+
return "H\xECnh ch\u1EEF nh\u1EADt";
|
|
3233
|
+
case "rhombus":
|
|
3234
|
+
return "H\xECnh thoi";
|
|
3235
|
+
case "parallelogram":
|
|
3236
|
+
return "H\xECnh b\xECnh h\xE0nh";
|
|
3237
|
+
case "isoTrapezoid":
|
|
3238
|
+
return "H\xECnh thang c\xE2n";
|
|
3239
|
+
case "isoTriangle":
|
|
3240
|
+
return "Tam gi\xE1c c\xE2n";
|
|
3241
|
+
case "rightTriangle":
|
|
3242
|
+
return "Tam gi\xE1c vu\xF4ng";
|
|
3243
|
+
case "regular":
|
|
3244
|
+
return "";
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
2343
3247
|
var def11;
|
|
2344
3248
|
var init_polygon = __esm({
|
|
2345
3249
|
"src/core/scene/kinds/polygon.ts"() {
|
|
@@ -2351,13 +3255,27 @@ var init_polygon = __esm({
|
|
|
2351
3255
|
migrate: {},
|
|
2352
3256
|
validate: (a) => {
|
|
2353
3257
|
if (a?.construction) {
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
3258
|
+
const c = a.construction;
|
|
3259
|
+
if (c.kind === "regular") {
|
|
3260
|
+
if (!c.p1 || !c.p2) throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
3261
|
+
if (!Number.isFinite(c.n) || c.n < 3) throw new Error("polygon (regular): n \u2265 3");
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
if (c.kind === "square") {
|
|
3265
|
+
if (!c.p1 || !c.p2) throw new Error("polygon (square): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
if (c.kind === "rectangle" || c.kind === "rhombus" || c.kind === "parallelogram" || c.kind === "isoTrapezoid") {
|
|
3269
|
+
if (!c.p1 || !c.p2 || !c.p3) throw new Error(`polygon (${c.kind}): p1, p2, p3 b\u1EAFt bu\u1ED9c`);
|
|
3270
|
+
return;
|
|
3271
|
+
}
|
|
3272
|
+
if (c.kind === "isoTriangle") {
|
|
3273
|
+
if (!c.base1 || !c.base2 || !c.apex) throw new Error("polygon (isoTriangle): base1, base2, apex b\u1EAFt bu\u1ED9c");
|
|
3274
|
+
return;
|
|
3275
|
+
}
|
|
3276
|
+
if (c.kind === "rightTriangle") {
|
|
3277
|
+
if (!c.rightAngle || !c.leg1End || !c.leg2End) throw new Error("polygon (rightTriangle): rightAngle, leg1End, leg2End b\u1EAFt bu\u1ED9c");
|
|
3278
|
+
return;
|
|
2361
3279
|
}
|
|
2362
3280
|
return;
|
|
2363
3281
|
}
|
|
@@ -2366,14 +3284,51 @@ var init_polygon = __esm({
|
|
|
2366
3284
|
}
|
|
2367
3285
|
},
|
|
2368
3286
|
dependsOn: (a) => {
|
|
2369
|
-
|
|
2370
|
-
return [...a.vertices ?? []];
|
|
3287
|
+
const c = a.construction;
|
|
3288
|
+
if (!c) return [...a.vertices ?? []];
|
|
3289
|
+
switch (c.kind) {
|
|
3290
|
+
case "regular":
|
|
3291
|
+
return [c.p1, c.p2];
|
|
3292
|
+
case "square":
|
|
3293
|
+
return [c.p1, c.p2];
|
|
3294
|
+
case "rectangle":
|
|
3295
|
+
case "rhombus":
|
|
3296
|
+
case "parallelogram":
|
|
3297
|
+
case "isoTrapezoid":
|
|
3298
|
+
return [c.p1, c.p2, c.p3];
|
|
3299
|
+
case "isoTriangle":
|
|
3300
|
+
return [c.base1, c.base2, c.apex];
|
|
3301
|
+
case "rightTriangle":
|
|
3302
|
+
return [c.rightAngle, c.leg1End, c.leg2End];
|
|
3303
|
+
}
|
|
2371
3304
|
},
|
|
2372
3305
|
describe: (obj, state) => {
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
3306
|
+
const c = obj.attrs.construction;
|
|
3307
|
+
if (c) {
|
|
3308
|
+
if (c.kind === "regular") {
|
|
3309
|
+
const labels2 = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
|
|
3310
|
+
return `${regularPolygonName(c.n)} ${labels2}`;
|
|
3311
|
+
}
|
|
3312
|
+
const name = specialShapeName(c.kind);
|
|
3313
|
+
let labels = [];
|
|
3314
|
+
switch (c.kind) {
|
|
3315
|
+
case "square":
|
|
3316
|
+
labels = [labelOf(c.p1, state), labelOf(c.p2, state)];
|
|
3317
|
+
break;
|
|
3318
|
+
case "rectangle":
|
|
3319
|
+
case "rhombus":
|
|
3320
|
+
case "parallelogram":
|
|
3321
|
+
case "isoTrapezoid":
|
|
3322
|
+
labels = [labelOf(c.p1, state), labelOf(c.p2, state), labelOf(c.p3, state)];
|
|
3323
|
+
break;
|
|
3324
|
+
case "isoTriangle":
|
|
3325
|
+
labels = [labelOf(c.apex, state), labelOf(c.base1, state), labelOf(c.base2, state)];
|
|
3326
|
+
break;
|
|
3327
|
+
case "rightTriangle":
|
|
3328
|
+
labels = [labelOf(c.rightAngle, state), labelOf(c.leg1End, state), labelOf(c.leg2End, state)];
|
|
3329
|
+
break;
|
|
3330
|
+
}
|
|
3331
|
+
return `${name} ${labels.join("")}`;
|
|
2377
3332
|
}
|
|
2378
3333
|
return `\u0110a gi\xE1c ${(obj.attrs.vertices ?? []).map((id) => labelOf(id, state)).join("")}`;
|
|
2379
3334
|
},
|
|
@@ -2381,22 +3336,85 @@ var init_polygon = __esm({
|
|
|
2381
3336
|
const board = ctx.jxg;
|
|
2382
3337
|
const label = obj.label;
|
|
2383
3338
|
const showValue = obj.attrs.showValue ?? false;
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
3339
|
+
const cons = obj.attrs.construction;
|
|
3340
|
+
const commonAttrs = {
|
|
3341
|
+
name: label,
|
|
3342
|
+
withLabel: obj.attrs.showLabel ?? false,
|
|
3343
|
+
borders: {
|
|
3344
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
3345
|
+
strokeWidth: obj.attrs.width ?? 2
|
|
3346
|
+
},
|
|
3347
|
+
fillColor: obj.attrs.color ?? "#60a5fa",
|
|
3348
|
+
fillOpacity: obj.attrs.fillOpacity ?? 0.15,
|
|
3349
|
+
visible: obj.visible,
|
|
3350
|
+
fixed: obj.locked
|
|
3351
|
+
};
|
|
3352
|
+
if (cons?.kind === "regular") {
|
|
3353
|
+
const p1 = ctx.resolveRef(cons.p1);
|
|
3354
|
+
const p2 = ctx.resolveRef(cons.p2);
|
|
3355
|
+
return board.create("regularpolygon", [p1, p2, cons.n], commonAttrs);
|
|
3356
|
+
}
|
|
3357
|
+
if (cons?.kind === "square") {
|
|
3358
|
+
const p1 = ctx.resolveRef(cons.p1);
|
|
3359
|
+
const p2 = ctx.resolveRef(cons.p2);
|
|
3360
|
+
return board.create("regularpolygon", [p1, p2, 4], commonAttrs);
|
|
3361
|
+
}
|
|
3362
|
+
if (cons?.kind === "rectangle" || cons?.kind === "rhombus" || cons?.kind === "parallelogram") {
|
|
3363
|
+
const A = ctx.resolveRef(cons.p1);
|
|
3364
|
+
const B = ctx.resolveRef(cons.p2);
|
|
3365
|
+
const C = ctx.resolveRef(cons.p3);
|
|
3366
|
+
const D = board.create(
|
|
3367
|
+
"point",
|
|
3368
|
+
[() => A.X() + C.X() - B.X(), () => A.Y() + C.Y() - B.Y()],
|
|
3369
|
+
{ visible: false, withLabel: false, fixed: true, name: "" }
|
|
3370
|
+
);
|
|
3371
|
+
const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
|
|
3372
|
+
poly2._helpers = [D];
|
|
3373
|
+
return poly2;
|
|
3374
|
+
}
|
|
3375
|
+
if (cons?.kind === "isoTrapezoid") {
|
|
3376
|
+
const A = ctx.resolveRef(cons.p1);
|
|
3377
|
+
const B = ctx.resolveRef(cons.p2);
|
|
3378
|
+
const C = ctx.resolveRef(cons.p3);
|
|
3379
|
+
const Dx = () => {
|
|
3380
|
+
const Mx = (A.X() + B.X()) / 2;
|
|
3381
|
+
const My = (A.Y() + B.Y()) / 2;
|
|
3382
|
+
const ux = B.X() - A.X();
|
|
3383
|
+
const uy = B.Y() - A.Y();
|
|
3384
|
+
const len2 = ux * ux + uy * uy || 1;
|
|
3385
|
+
const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
|
|
3386
|
+
return C.X() - 2 * proj * ux;
|
|
3387
|
+
};
|
|
3388
|
+
const Dy = () => {
|
|
3389
|
+
const Mx = (A.X() + B.X()) / 2;
|
|
3390
|
+
const My = (A.Y() + B.Y()) / 2;
|
|
3391
|
+
const ux = B.X() - A.X();
|
|
3392
|
+
const uy = B.Y() - A.Y();
|
|
3393
|
+
const len2 = ux * ux + uy * uy || 1;
|
|
3394
|
+
const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
|
|
3395
|
+
return C.Y() - 2 * proj * uy;
|
|
3396
|
+
};
|
|
3397
|
+
const D = board.create("point", [Dx, Dy], {
|
|
3398
|
+
visible: false,
|
|
3399
|
+
withLabel: false,
|
|
3400
|
+
fixed: true,
|
|
3401
|
+
name: ""
|
|
2399
3402
|
});
|
|
3403
|
+
const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
|
|
3404
|
+
poly2._helpers = [D];
|
|
3405
|
+
return poly2;
|
|
3406
|
+
}
|
|
3407
|
+
if (cons?.kind === "isoTriangle") {
|
|
3408
|
+
const Apex = ctx.resolveRef(cons.apex);
|
|
3409
|
+
const B1 = ctx.resolveRef(cons.base1);
|
|
3410
|
+
const B2 = ctx.resolveRef(cons.base2);
|
|
3411
|
+
return board.create("polygon", [Apex, B1, B2], commonAttrs);
|
|
3412
|
+
}
|
|
3413
|
+
if (cons?.kind === "rightTriangle") {
|
|
3414
|
+
const R = ctx.resolveRef(cons.rightAngle);
|
|
3415
|
+
const P = ctx.resolveRef(cons.leg1End);
|
|
3416
|
+
const Q = ctx.resolveRef(cons.leg2End);
|
|
3417
|
+
return board.create("polygon", [R, P, Q], commonAttrs);
|
|
2400
3418
|
}
|
|
2401
3419
|
const verts = (obj.attrs.vertices ?? []).map((id) => ctx.resolveRef(id));
|
|
2402
3420
|
const poly = board.create("polygon", verts, {
|
|
@@ -3324,21 +4342,21 @@ var init_handlers = __esm({
|
|
|
3324
4342
|
}
|
|
3325
4343
|
});
|
|
3326
4344
|
function useToolStateMachine(initial) {
|
|
3327
|
-
const [tool, setToolState] =
|
|
3328
|
-
const [pendingIds, setPendingIds] =
|
|
3329
|
-
const toolRef =
|
|
3330
|
-
const pendingIdsRef =
|
|
3331
|
-
const setTool =
|
|
4345
|
+
const [tool, setToolState] = React5.useState(initial);
|
|
4346
|
+
const [pendingIds, setPendingIds] = React5.useState([]);
|
|
4347
|
+
const toolRef = React5.useRef(initial);
|
|
4348
|
+
const pendingIdsRef = React5.useRef([]);
|
|
4349
|
+
const setTool = React5.useCallback((t) => {
|
|
3332
4350
|
toolRef.current = t;
|
|
3333
4351
|
pendingIdsRef.current = [];
|
|
3334
4352
|
setToolState(t);
|
|
3335
4353
|
setPendingIds([]);
|
|
3336
4354
|
}, []);
|
|
3337
|
-
const pushPending =
|
|
4355
|
+
const pushPending = React5.useCallback((id) => {
|
|
3338
4356
|
pendingIdsRef.current = [...pendingIdsRef.current, id];
|
|
3339
4357
|
setPendingIds(pendingIdsRef.current);
|
|
3340
4358
|
}, []);
|
|
3341
|
-
const clearPending =
|
|
4359
|
+
const clearPending = React5.useCallback(() => {
|
|
3342
4360
|
pendingIdsRef.current = [];
|
|
3343
4361
|
setPendingIds([]);
|
|
3344
4362
|
}, []);
|
|
@@ -3507,24 +4525,24 @@ var init_MiniBoard = __esm({
|
|
|
3507
4525
|
init_safeJsx();
|
|
3508
4526
|
init_attachJxgWheelZoom();
|
|
3509
4527
|
init_initJxgBoard();
|
|
3510
|
-
MiniBoard =
|
|
4528
|
+
MiniBoard = React5__namespace.default.forwardRef(
|
|
3511
4529
|
function MiniBoard2({ store, selectedTool, showAxis, showGrid, isDark, onReady, onSelectionChange: _onSelectionChange }, ref) {
|
|
3512
|
-
const isDarkRef =
|
|
4530
|
+
const isDarkRef = React5.useRef(!!isDark);
|
|
3513
4531
|
isDarkRef.current = !!isDark;
|
|
3514
|
-
const containerId =
|
|
3515
|
-
const containerRef =
|
|
3516
|
-
const boardRef =
|
|
3517
|
-
const jxgRef =
|
|
3518
|
-
const rendererRef =
|
|
4532
|
+
const containerId = React5.useId().replace(/:/g, "_") + "_graph_jxg";
|
|
4533
|
+
const containerRef = React5.useRef(null);
|
|
4534
|
+
const boardRef = React5.useRef(null);
|
|
4535
|
+
const jxgRef = React5.useRef(null);
|
|
4536
|
+
const rendererRef = React5.useRef(null);
|
|
3519
4537
|
const toolSM = useToolStateMachine(selectedTool);
|
|
3520
|
-
const showAxisRef =
|
|
4538
|
+
const showAxisRef = React5.useRef(showAxis);
|
|
3521
4539
|
showAxisRef.current = showAxis;
|
|
3522
|
-
const showGridRef =
|
|
4540
|
+
const showGridRef = React5.useRef(showGrid);
|
|
3523
4541
|
showGridRef.current = showGrid;
|
|
3524
|
-
|
|
4542
|
+
React5.useEffect(() => {
|
|
3525
4543
|
if (toolSM.toolRef.current !== selectedTool) toolSM.setTool(selectedTool);
|
|
3526
4544
|
}, [selectedTool]);
|
|
3527
|
-
|
|
4545
|
+
React5.useEffect(() => {
|
|
3528
4546
|
if (typeof window === "undefined" || !containerRef.current) return;
|
|
3529
4547
|
let cancelled = false;
|
|
3530
4548
|
let wheelCleanup = null;
|
|
@@ -3610,7 +4628,7 @@ var init_MiniBoard = __esm({
|
|
|
3610
4628
|
boardRef.current = null;
|
|
3611
4629
|
};
|
|
3612
4630
|
}, [containerId]);
|
|
3613
|
-
|
|
4631
|
+
React5.useImperativeHandle(
|
|
3614
4632
|
ref,
|
|
3615
4633
|
() => ({
|
|
3616
4634
|
getState: () => store.getState(),
|
|
@@ -3656,27 +4674,27 @@ function reducer(state, action) {
|
|
|
3656
4674
|
}
|
|
3657
4675
|
}
|
|
3658
4676
|
function ToastProvider({ children, maxVisible = 3 }) {
|
|
3659
|
-
const [items, dispatch] =
|
|
3660
|
-
const timersRef =
|
|
3661
|
-
const itemsRef =
|
|
4677
|
+
const [items, dispatch] = React5.useReducer(reducer, []);
|
|
4678
|
+
const timersRef = React5.useRef(/* @__PURE__ */ new Map());
|
|
4679
|
+
const itemsRef = React5.useRef(items);
|
|
3662
4680
|
itemsRef.current = items;
|
|
3663
|
-
const clearTimer =
|
|
4681
|
+
const clearTimer = React5.useCallback((id) => {
|
|
3664
4682
|
const t = timersRef.current.get(id);
|
|
3665
4683
|
if (t) {
|
|
3666
4684
|
clearTimeout(t);
|
|
3667
4685
|
timersRef.current.delete(id);
|
|
3668
4686
|
}
|
|
3669
4687
|
}, []);
|
|
3670
|
-
const dismiss =
|
|
4688
|
+
const dismiss = React5.useCallback((id) => {
|
|
3671
4689
|
clearTimer(id);
|
|
3672
4690
|
dispatch({ type: "DISMISS", id });
|
|
3673
4691
|
}, [clearTimer]);
|
|
3674
|
-
const scheduleAutoDismiss =
|
|
4692
|
+
const scheduleAutoDismiss = React5.useCallback((id, duration) => {
|
|
3675
4693
|
if (duration <= 0) return;
|
|
3676
4694
|
const t = setTimeout(() => dismiss(id), duration);
|
|
3677
4695
|
timersRef.current.set(id, t);
|
|
3678
4696
|
}, [dismiss]);
|
|
3679
|
-
const showToast =
|
|
4697
|
+
const showToast = React5.useCallback((message, opts = {}) => {
|
|
3680
4698
|
const variant = opts.variant ?? "info";
|
|
3681
4699
|
const duration = opts.duration ?? 3e3;
|
|
3682
4700
|
const id = opts.id ?? `toast-${++autoIdCounter}`;
|
|
@@ -3690,17 +4708,17 @@ function ToastProvider({ children, maxVisible = 3 }) {
|
|
|
3690
4708
|
}
|
|
3691
4709
|
scheduleAutoDismiss(id, duration);
|
|
3692
4710
|
}, [clearTimer, maxVisible, scheduleAutoDismiss]);
|
|
3693
|
-
|
|
4711
|
+
React5.useEffect(() => () => {
|
|
3694
4712
|
timersRef.current.forEach((t) => clearTimeout(t));
|
|
3695
4713
|
timersRef.current.clear();
|
|
3696
4714
|
}, []);
|
|
3697
|
-
const value =
|
|
4715
|
+
const value = React5.useMemo(() => ({ items, showToast, dismiss }), [items, showToast, dismiss]);
|
|
3698
4716
|
return /* @__PURE__ */ jsxRuntime.jsx(ToastContext.Provider, { value, children });
|
|
3699
4717
|
}
|
|
3700
4718
|
var ToastContext, autoIdCounter;
|
|
3701
4719
|
var init_ToastProvider = __esm({
|
|
3702
4720
|
"src/stamps/shared/Toast/ToastProvider.tsx"() {
|
|
3703
|
-
ToastContext =
|
|
4721
|
+
ToastContext = React5.createContext(null);
|
|
3704
4722
|
autoIdCounter = 0;
|
|
3705
4723
|
}
|
|
3706
4724
|
});
|
|
@@ -3758,7 +4776,7 @@ var init_Toast = __esm({
|
|
|
3758
4776
|
}
|
|
3759
4777
|
});
|
|
3760
4778
|
function useToast() {
|
|
3761
|
-
const ctx =
|
|
4779
|
+
const ctx = React5.useContext(ToastContext);
|
|
3762
4780
|
if (!ctx) {
|
|
3763
4781
|
throw new Error("useToast must be used inside <ToastProvider>");
|
|
3764
4782
|
}
|
|
@@ -3803,7 +4821,7 @@ var init_EditorPanel = __esm({
|
|
|
3803
4821
|
init_scene();
|
|
3804
4822
|
init_constants();
|
|
3805
4823
|
init_Toast2();
|
|
3806
|
-
GraphEditorPanelInner =
|
|
4824
|
+
GraphEditorPanelInner = React5.forwardRef(
|
|
3807
4825
|
function GraphEditorPanel({
|
|
3808
4826
|
store,
|
|
3809
4827
|
onInsert,
|
|
@@ -3822,23 +4840,23 @@ var init_EditorPanel = __esm({
|
|
|
3822
4840
|
canUndo,
|
|
3823
4841
|
canRedo
|
|
3824
4842
|
}, ref) {
|
|
3825
|
-
const miniRef =
|
|
3826
|
-
const [ready, setReady] =
|
|
3827
|
-
const [hasContent, setHasContent] =
|
|
3828
|
-
const onSelectionChangeRef =
|
|
3829
|
-
|
|
4843
|
+
const miniRef = React5.useRef(null);
|
|
4844
|
+
const [ready, setReady] = React5.useState(false);
|
|
4845
|
+
const [hasContent, setHasContent] = React5.useState(false);
|
|
4846
|
+
const onSelectionChangeRef = React5.useRef(onSelectionChange);
|
|
4847
|
+
React5.useEffect(() => {
|
|
3830
4848
|
onSelectionChangeRef.current = onSelectionChange;
|
|
3831
4849
|
}, [onSelectionChange]);
|
|
3832
4850
|
useEditorState({ store, onHistoryChange });
|
|
3833
|
-
|
|
4851
|
+
React5.useEffect(() => {
|
|
3834
4852
|
const sync = () => setHasContent(Object.keys(store.getState().objects).length > 0);
|
|
3835
4853
|
sync();
|
|
3836
4854
|
return store.subscribe(sync);
|
|
3837
4855
|
}, [store]);
|
|
3838
|
-
const handleReady =
|
|
4856
|
+
const handleReady = React5.useCallback(() => {
|
|
3839
4857
|
setReady(true);
|
|
3840
4858
|
}, []);
|
|
3841
|
-
const performInsert =
|
|
4859
|
+
const performInsert = React5.useCallback(() => {
|
|
3842
4860
|
const h = miniRef.current;
|
|
3843
4861
|
if (!h) return false;
|
|
3844
4862
|
const state = h.getState();
|
|
@@ -3856,7 +4874,7 @@ var init_EditorPanel = __esm({
|
|
|
3856
4874
|
})();
|
|
3857
4875
|
return true;
|
|
3858
4876
|
}, [isDark, onInsert]);
|
|
3859
|
-
|
|
4877
|
+
React5.useImperativeHandle(ref, () => ({
|
|
3860
4878
|
insert: performInsert,
|
|
3861
4879
|
hasContent: () => Object.keys(miniRef.current?.getState().objects ?? {}).length > 0,
|
|
3862
4880
|
getStore: () => miniRef.current?.getStore() ?? null,
|
|
@@ -4014,13 +5032,27 @@ var init_EditorPanel = __esm({
|
|
|
4014
5032
|
);
|
|
4015
5033
|
}
|
|
4016
5034
|
);
|
|
4017
|
-
GraphEditorPanel2 =
|
|
5035
|
+
GraphEditorPanel2 = React5.forwardRef(
|
|
4018
5036
|
function GraphEditorPanel3(props, ref) {
|
|
4019
5037
|
return /* @__PURE__ */ jsxRuntime.jsx(ToastProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(GraphEditorPanelInner, { ...props, ref }) });
|
|
4020
5038
|
}
|
|
4021
5039
|
);
|
|
4022
5040
|
}
|
|
4023
5041
|
});
|
|
5042
|
+
function clamp(n, min, max) {
|
|
5043
|
+
return Math.max(min, Math.min(max, n));
|
|
5044
|
+
}
|
|
5045
|
+
function readStoredWidth(key, fallback, min, max) {
|
|
5046
|
+
if (!key || typeof window === "undefined") return fallback;
|
|
5047
|
+
try {
|
|
5048
|
+
const raw = window.localStorage.getItem(key);
|
|
5049
|
+
if (!raw) return fallback;
|
|
5050
|
+
const n = parseInt(raw, 10);
|
|
5051
|
+
if (Number.isFinite(n)) return clamp(n, min, max);
|
|
5052
|
+
} catch {
|
|
5053
|
+
}
|
|
5054
|
+
return fallback;
|
|
5055
|
+
}
|
|
4024
5056
|
function CloseIcon() {
|
|
4025
5057
|
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
4026
5058
|
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
@@ -4028,8 +5060,64 @@ function CloseIcon() {
|
|
|
4028
5060
|
] });
|
|
4029
5061
|
}
|
|
4030
5062
|
function LeftPanelShell(props) {
|
|
4031
|
-
const {
|
|
5063
|
+
const {
|
|
5064
|
+
title,
|
|
5065
|
+
icon,
|
|
5066
|
+
onClose,
|
|
5067
|
+
isDark,
|
|
5068
|
+
tabs,
|
|
5069
|
+
activeTab,
|
|
5070
|
+
onTabChange,
|
|
5071
|
+
testId,
|
|
5072
|
+
resizable,
|
|
5073
|
+
widthStorageKey,
|
|
5074
|
+
defaultWidth,
|
|
5075
|
+
minWidth,
|
|
5076
|
+
maxWidth,
|
|
5077
|
+
children
|
|
5078
|
+
} = props;
|
|
4032
5079
|
const showTabs = !!tabs && tabs.length >= 2;
|
|
5080
|
+
const min = minWidth ?? FALLBACK_MIN_WIDTH;
|
|
5081
|
+
const max = maxWidth ?? FALLBACK_MAX_WIDTH;
|
|
5082
|
+
const initial = clamp(defaultWidth ?? FALLBACK_DEFAULT_WIDTH, min, max);
|
|
5083
|
+
const [width, setWidth] = React5__namespace.useState(
|
|
5084
|
+
() => resizable ? readStoredWidth(widthStorageKey, initial, min, max) : initial
|
|
5085
|
+
);
|
|
5086
|
+
const widthRef = React5__namespace.useRef(width);
|
|
5087
|
+
widthRef.current = width;
|
|
5088
|
+
React5__namespace.useEffect(() => {
|
|
5089
|
+
if (!resizable || !widthStorageKey || typeof window === "undefined") return;
|
|
5090
|
+
try {
|
|
5091
|
+
window.localStorage.setItem(widthStorageKey, String(width));
|
|
5092
|
+
} catch {
|
|
5093
|
+
}
|
|
5094
|
+
}, [resizable, widthStorageKey, width]);
|
|
5095
|
+
const onResizeStart = React5__namespace.useCallback(
|
|
5096
|
+
(e) => {
|
|
5097
|
+
if (!resizable) return;
|
|
5098
|
+
e.preventDefault();
|
|
5099
|
+
const startX = e.clientX;
|
|
5100
|
+
const startW = widthRef.current;
|
|
5101
|
+
const onMove = (ev) => {
|
|
5102
|
+
setWidth(clamp(startW + (ev.clientX - startX), min, max));
|
|
5103
|
+
};
|
|
5104
|
+
const onUp = () => {
|
|
5105
|
+
window.removeEventListener("mousemove", onMove);
|
|
5106
|
+
window.removeEventListener("mouseup", onUp);
|
|
5107
|
+
document.body.style.cursor = "";
|
|
5108
|
+
document.body.style.userSelect = "";
|
|
5109
|
+
};
|
|
5110
|
+
window.addEventListener("mousemove", onMove);
|
|
5111
|
+
window.addEventListener("mouseup", onUp);
|
|
5112
|
+
document.body.style.cursor = "ew-resize";
|
|
5113
|
+
document.body.style.userSelect = "none";
|
|
5114
|
+
},
|
|
5115
|
+
[resizable, min, max]
|
|
5116
|
+
);
|
|
5117
|
+
const onResizeDoubleClick = React5__namespace.useCallback(() => {
|
|
5118
|
+
if (!resizable) return;
|
|
5119
|
+
setWidth(initial);
|
|
5120
|
+
}, [resizable, initial]);
|
|
4033
5121
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4034
5122
|
"aside",
|
|
4035
5123
|
{
|
|
@@ -4037,10 +5125,12 @@ function LeftPanelShell(props) {
|
|
|
4037
5125
|
"aria-label": title,
|
|
4038
5126
|
"data-testid": testId ?? "left-panel",
|
|
4039
5127
|
"data-stamp-area": "true",
|
|
5128
|
+
style: resizable ? { width: `${width}px` } : void 0,
|
|
4040
5129
|
className: [
|
|
4041
5130
|
isDark ? "theme--dark " : "",
|
|
4042
|
-
"absolute left-0 top-0 z-30 flex h-full
|
|
4043
|
-
|
|
5131
|
+
"absolute left-0 top-0 z-30 flex h-full flex-col border-r border-slate-200 bg-white shadow-md animate-in slide-in-from-left duration-200",
|
|
5132
|
+
resizable ? "" : "w-60"
|
|
5133
|
+
].join(" "),
|
|
4044
5134
|
children: [
|
|
4045
5135
|
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex items-center justify-between border-b border-slate-200 bg-gradient-to-r from-slate-50 to-white px-3 py-2", children: [
|
|
4046
5136
|
/* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "flex items-center gap-2 text-sm font-semibold text-slate-800", children: [
|
|
@@ -4075,6 +5165,20 @@ function LeftPanelShell(props) {
|
|
|
4075
5165
|
className: "min-h-0 flex-1 overflow-y-auto p-3 space-y-3",
|
|
4076
5166
|
children
|
|
4077
5167
|
}
|
|
5168
|
+
),
|
|
5169
|
+
resizable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5170
|
+
"div",
|
|
5171
|
+
{
|
|
5172
|
+
role: "separator",
|
|
5173
|
+
"aria-orientation": "vertical",
|
|
5174
|
+
"aria-label": "K\xE9o \u0111\u1EC3 \u0111\u1ED5i r\u1ED9ng panel",
|
|
5175
|
+
"data-testid": "left-panel-resizer",
|
|
5176
|
+
onMouseDown: onResizeStart,
|
|
5177
|
+
onDoubleClick: onResizeDoubleClick,
|
|
5178
|
+
className: "group absolute right-0 top-0 z-40 h-full w-1.5 -mr-0.5 cursor-ew-resize select-none",
|
|
5179
|
+
title: "K\xE9o \u0111\u1EC3 \u0111\u1ED5i r\u1ED9ng (double-click \u0111\u1EC3 reset)",
|
|
5180
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-y-0 right-0 w-px bg-slate-200 transition group-hover:bg-emerald-400 group-hover:w-0.5 group-active:bg-emerald-500 group-active:w-0.5" })
|
|
5181
|
+
}
|
|
4078
5182
|
)
|
|
4079
5183
|
]
|
|
4080
5184
|
}
|
|
@@ -4104,9 +5208,13 @@ function Section(props) {
|
|
|
4104
5208
|
props.children
|
|
4105
5209
|
] });
|
|
4106
5210
|
}
|
|
5211
|
+
var FALLBACK_DEFAULT_WIDTH, FALLBACK_MIN_WIDTH, FALLBACK_MAX_WIDTH;
|
|
4107
5212
|
var init_LeftPanelShell = __esm({
|
|
4108
5213
|
"src/core/scene/ui/LeftPanelShell.tsx"() {
|
|
4109
5214
|
"use client";
|
|
5215
|
+
FALLBACK_DEFAULT_WIDTH = 240;
|
|
5216
|
+
FALLBACK_MIN_WIDTH = 220;
|
|
5217
|
+
FALLBACK_MAX_WIDTH = 480;
|
|
4110
5218
|
}
|
|
4111
5219
|
});
|
|
4112
5220
|
|
|
@@ -4157,7 +5265,7 @@ var init_kindMeta = __esm({
|
|
|
4157
5265
|
});
|
|
4158
5266
|
function ObjectRowMenu(props) {
|
|
4159
5267
|
const { locked, onToggleLocked, onRename, onChangeColor, onDelete } = props;
|
|
4160
|
-
const [open, setOpen] =
|
|
5268
|
+
const [open, setOpen] = React5__namespace.useState(false);
|
|
4161
5269
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative inline-block", children: [
|
|
4162
5270
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4163
5271
|
"button",
|
|
@@ -4310,11 +5418,11 @@ var init_ObjectRow = __esm({
|
|
|
4310
5418
|
});
|
|
4311
5419
|
function ObjectListPanel(props) {
|
|
4312
5420
|
const { store, selectedId, onSelect, renderRow } = props;
|
|
4313
|
-
const subscribe =
|
|
5421
|
+
const subscribe = React5__namespace.useCallback(
|
|
4314
5422
|
(cb) => store.subscribe(() => cb()),
|
|
4315
5423
|
[store]
|
|
4316
5424
|
);
|
|
4317
|
-
const state =
|
|
5425
|
+
const state = React5__namespace.useSyncExternalStore(subscribe, store.getState, store.getState);
|
|
4318
5426
|
const objects = listObjects(state);
|
|
4319
5427
|
function handleSelect(id) {
|
|
4320
5428
|
onSelect?.(id === selectedId ? null : id);
|
|
@@ -4345,7 +5453,7 @@ function ObjectListPanel(props) {
|
|
|
4345
5453
|
if (renderRow) {
|
|
4346
5454
|
const custom = renderRow(obj, { selected, onClick });
|
|
4347
5455
|
if (custom != null) {
|
|
4348
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5456
|
+
return /* @__PURE__ */ jsxRuntime.jsx(React5__namespace.Fragment, { children: custom }, obj.id);
|
|
4349
5457
|
}
|
|
4350
5458
|
}
|
|
4351
5459
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4458,29 +5566,29 @@ var init_AxisGridSection = __esm({
|
|
|
4458
5566
|
|
|
4459
5567
|
// src/stamps/shared/StampLeftPanel/types.ts
|
|
4460
5568
|
var TOOLTIP_DELAY_MS;
|
|
4461
|
-
var
|
|
5569
|
+
var init_types4 = __esm({
|
|
4462
5570
|
"src/stamps/shared/StampLeftPanel/types.ts"() {
|
|
4463
5571
|
TOOLTIP_DELAY_MS = 400;
|
|
4464
5572
|
}
|
|
4465
5573
|
});
|
|
4466
5574
|
function useToolHoverTooltip() {
|
|
4467
|
-
const [hover, setHover] =
|
|
4468
|
-
const [portalReady, setPortalReady] =
|
|
4469
|
-
const hoverTimerRef =
|
|
4470
|
-
|
|
5575
|
+
const [hover, setHover] = React5.useState(null);
|
|
5576
|
+
const [portalReady, setPortalReady] = React5.useState(false);
|
|
5577
|
+
const hoverTimerRef = React5.useRef(null);
|
|
5578
|
+
React5.useEffect(() => {
|
|
4471
5579
|
setPortalReady(true);
|
|
4472
5580
|
return () => {
|
|
4473
5581
|
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
|
|
4474
5582
|
};
|
|
4475
5583
|
}, []);
|
|
4476
|
-
const showHover =
|
|
5584
|
+
const showHover = React5.useCallback((el, t) => {
|
|
4477
5585
|
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
|
|
4478
5586
|
hoverTimerRef.current = setTimeout(() => {
|
|
4479
5587
|
const r = el.getBoundingClientRect();
|
|
4480
5588
|
setHover({ label: t.label, hint: t.hint, x: r.right, y: r.top + r.height / 2 });
|
|
4481
5589
|
}, TOOLTIP_DELAY_MS);
|
|
4482
5590
|
}, []);
|
|
4483
|
-
const hideHover =
|
|
5591
|
+
const hideHover = React5.useCallback(() => {
|
|
4484
5592
|
if (hoverTimerRef.current) {
|
|
4485
5593
|
clearTimeout(hoverTimerRef.current);
|
|
4486
5594
|
hoverTimerRef.current = null;
|
|
@@ -4491,27 +5599,118 @@ function useToolHoverTooltip() {
|
|
|
4491
5599
|
}
|
|
4492
5600
|
var init_useToolHoverTooltip = __esm({
|
|
4493
5601
|
"src/stamps/shared/StampLeftPanel/useToolHoverTooltip.ts"() {
|
|
4494
|
-
|
|
5602
|
+
init_types4();
|
|
4495
5603
|
}
|
|
4496
5604
|
});
|
|
5605
|
+
function normalize(s) {
|
|
5606
|
+
return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/đ/g, "d").replace(/Đ/g, "d");
|
|
5607
|
+
}
|
|
5608
|
+
function SearchIcon() {
|
|
5609
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
5610
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
5611
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "20", y1: "20", x2: "16.5", y2: "16.5" })
|
|
5612
|
+
] });
|
|
5613
|
+
}
|
|
5614
|
+
function ClearIcon() {
|
|
5615
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
5616
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
5617
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
|
|
5618
|
+
] });
|
|
5619
|
+
}
|
|
5620
|
+
function ToolResultList(props) {
|
|
5621
|
+
const { tools, activeTool, onToolChange } = props;
|
|
5622
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5", "data-testid": "tool-result-list", children: tools.map((t) => {
|
|
5623
|
+
const active = activeTool === t.key;
|
|
5624
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5625
|
+
"button",
|
|
5626
|
+
{
|
|
5627
|
+
type: "button",
|
|
5628
|
+
"data-tool": t.key,
|
|
5629
|
+
"aria-label": t.label,
|
|
5630
|
+
"aria-pressed": active,
|
|
5631
|
+
onClick: () => onToolChange(t.key),
|
|
5632
|
+
className: [
|
|
5633
|
+
"flex items-center gap-2 rounded-md px-2 py-1.5 text-left transition",
|
|
5634
|
+
active ? "bg-emerald-600 text-white" : "text-slate-700 hover:bg-slate-100"
|
|
5635
|
+
].join(" "),
|
|
5636
|
+
children: [
|
|
5637
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center", children: t.icon }),
|
|
5638
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0", children: [
|
|
5639
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-[12px] font-medium leading-tight", children: t.label }),
|
|
5640
|
+
t.hint && /* @__PURE__ */ jsxRuntime.jsx("span", { className: ["block truncate text-[10px] leading-tight", active ? "text-emerald-50" : "text-slate-400"].join(" "), children: t.hint })
|
|
5641
|
+
] })
|
|
5642
|
+
]
|
|
5643
|
+
},
|
|
5644
|
+
t.key
|
|
5645
|
+
);
|
|
5646
|
+
}) });
|
|
5647
|
+
}
|
|
4497
5648
|
function ToolGrid(props) {
|
|
4498
5649
|
const { tools, groupOrder, groupLabels, activeTool, onToolChange, chord } = props;
|
|
4499
5650
|
const { hover, portalReady, showHover, hideHover } = useToolHoverTooltip();
|
|
4500
|
-
const
|
|
5651
|
+
const [query, setQuery] = React5.useState("");
|
|
5652
|
+
const normalizedQuery = React5.useMemo(() => normalize(query.trim()), [query]);
|
|
5653
|
+
const filteredTools = React5.useMemo(() => {
|
|
5654
|
+
if (!normalizedQuery) return tools;
|
|
5655
|
+
return tools.filter((t) => {
|
|
5656
|
+
if (normalize(t.label).includes(normalizedQuery)) return true;
|
|
5657
|
+
if (t.hint && normalize(t.hint).includes(normalizedQuery)) return true;
|
|
5658
|
+
return false;
|
|
5659
|
+
});
|
|
5660
|
+
}, [tools, normalizedQuery]);
|
|
5661
|
+
const grouped = React5.useMemo(() => {
|
|
4501
5662
|
var _a;
|
|
4502
5663
|
const acc = {};
|
|
4503
|
-
for (const t of
|
|
5664
|
+
for (const t of filteredTools) {
|
|
4504
5665
|
(acc[_a = t.group] ?? (acc[_a] = [])).push(t);
|
|
4505
5666
|
}
|
|
4506
5667
|
return acc;
|
|
4507
|
-
}, [
|
|
4508
|
-
const groupKeys =
|
|
4509
|
-
() => groupOrder.filter((g) => grouped[g]),
|
|
5668
|
+
}, [filteredTools]);
|
|
5669
|
+
const groupKeys = React5.useMemo(
|
|
5670
|
+
() => groupOrder.filter((g) => grouped[g] && grouped[g].length > 0),
|
|
4510
5671
|
[grouped, groupOrder]
|
|
4511
5672
|
);
|
|
4512
|
-
const
|
|
5673
|
+
const noMatch = normalizedQuery !== "" && groupKeys.length === 0;
|
|
4513
5674
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4514
|
-
|
|
5675
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
5676
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-slate-400", children: /* @__PURE__ */ jsxRuntime.jsx(SearchIcon, {}) }),
|
|
5677
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5678
|
+
"input",
|
|
5679
|
+
{
|
|
5680
|
+
type: "search",
|
|
5681
|
+
value: query,
|
|
5682
|
+
onChange: (e) => setQuery(e.target.value),
|
|
5683
|
+
placeholder: "T\xECm c\xF4ng c\u1EE5\u2026",
|
|
5684
|
+
"aria-label": "T\xECm c\xF4ng c\u1EE5",
|
|
5685
|
+
"data-testid": "tool-search-input",
|
|
5686
|
+
className: "w-full rounded-md border border-slate-200 bg-slate-50 py-1.5 pl-7 pr-7 text-[12px] text-slate-800 placeholder:text-slate-400 focus:border-emerald-400 focus:bg-white focus:outline-none focus:ring-1 focus:ring-emerald-300"
|
|
5687
|
+
}
|
|
5688
|
+
),
|
|
5689
|
+
query && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5690
|
+
"button",
|
|
5691
|
+
{
|
|
5692
|
+
type: "button",
|
|
5693
|
+
onClick: () => setQuery(""),
|
|
5694
|
+
"aria-label": "Xo\xE1 t\xECm ki\u1EBFm",
|
|
5695
|
+
"data-testid": "tool-search-clear",
|
|
5696
|
+
className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-slate-400 transition hover:bg-slate-200 hover:text-slate-700",
|
|
5697
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ClearIcon, {})
|
|
5698
|
+
}
|
|
5699
|
+
)
|
|
5700
|
+
] }),
|
|
5701
|
+
noMatch && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5702
|
+
"div",
|
|
5703
|
+
{
|
|
5704
|
+
"data-testid": "tool-search-empty",
|
|
5705
|
+
className: "rounded-md border border-dashed border-slate-200 bg-slate-50 px-3 py-4 text-center text-[11px] text-slate-500",
|
|
5706
|
+
children: [
|
|
5707
|
+
"Kh\xF4ng c\xF3 c\xF4ng c\u1EE5 n\xE0o kh\u1EDBp \u201C",
|
|
5708
|
+
query.trim(),
|
|
5709
|
+
"\u201D."
|
|
5710
|
+
]
|
|
5711
|
+
}
|
|
5712
|
+
),
|
|
5713
|
+
normalizedQuery !== "" && !noMatch ? /* @__PURE__ */ jsxRuntime.jsx(ToolResultList, { tools: filteredTools, activeTool, onToolChange }) : groupKeys.map((group) => {
|
|
4515
5714
|
const isChordActive = chord?.activeGroup === group;
|
|
4516
5715
|
const dimmed = chord?.activeGroup != null && !isChordActive;
|
|
4517
5716
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -4525,23 +5724,10 @@ function ToolGrid(props) {
|
|
|
4525
5724
|
dimmed ? "opacity-55" : "opacity-100"
|
|
4526
5725
|
].join(" "),
|
|
4527
5726
|
children: [
|
|
4528
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4529
|
-
|
|
4530
|
-
chord && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4531
|
-
"span",
|
|
4532
|
-
{
|
|
4533
|
-
"data-testid": `chord-letter-${group}`,
|
|
4534
|
-
className: [
|
|
4535
|
-
"font-mono text-[10px] leading-none transition",
|
|
4536
|
-
isChordActive ? "text-emerald-700 font-bold" : "text-slate-400"
|
|
4537
|
-
].join(" "),
|
|
4538
|
-
children: chord.letterForGroup(group)
|
|
4539
|
-
}
|
|
4540
|
-
)
|
|
4541
|
-
] }),
|
|
4542
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 gap-1", children: grouped[group].map((t, i) => {
|
|
5727
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-slate-500", children: groupLabels[group] }),
|
|
5728
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 gap-1", children: grouped[group].map((t) => {
|
|
4543
5729
|
const active = activeTool === t.key;
|
|
4544
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
5730
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4545
5731
|
"button",
|
|
4546
5732
|
{
|
|
4547
5733
|
type: "button",
|
|
@@ -4558,20 +5744,7 @@ function ToolGrid(props) {
|
|
|
4558
5744
|
"relative flex h-10 items-center justify-center rounded-md transition",
|
|
4559
5745
|
active ? "bg-emerald-600 text-white shadow-sm" : "text-slate-700 hover:bg-slate-100 hover:text-slate-900"
|
|
4560
5746
|
].join(" "),
|
|
4561
|
-
children:
|
|
4562
|
-
t.icon,
|
|
4563
|
-
chord && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4564
|
-
"span",
|
|
4565
|
-
{
|
|
4566
|
-
"data-testid": `chord-num-${t.key}`,
|
|
4567
|
-
className: [
|
|
4568
|
-
"pointer-events-none absolute bottom-0 right-0.5 font-mono text-[9px] leading-none transition",
|
|
4569
|
-
active ? "text-white/70" : isChordActive ? "text-emerald-700 font-bold" : "text-slate-400"
|
|
4570
|
-
].join(" "),
|
|
4571
|
-
children: i + 1
|
|
4572
|
-
}
|
|
4573
|
-
)
|
|
4574
|
-
]
|
|
5747
|
+
children: t.icon
|
|
4575
5748
|
},
|
|
4576
5749
|
t.key
|
|
4577
5750
|
);
|
|
@@ -4581,22 +5754,6 @@ function ToolGrid(props) {
|
|
|
4581
5754
|
group
|
|
4582
5755
|
);
|
|
4583
5756
|
}),
|
|
4584
|
-
chord?.activeGroup && activeGroupTools && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4585
|
-
"div",
|
|
4586
|
-
{
|
|
4587
|
-
"data-testid": "chord-hint",
|
|
4588
|
-
className: "mt-1 rounded border border-emerald-200 bg-emerald-50/60 px-2 py-1 text-[11px] leading-snug text-slate-600",
|
|
4589
|
-
children: [
|
|
4590
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-semibold text-emerald-700", children: chord.letterForGroup(chord.activeGroup) }),
|
|
4591
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mx-1 text-slate-400", children: "\u2192" }),
|
|
4592
|
-
activeGroupTools.map((t, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mr-2 inline-block", children: [
|
|
4593
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-semibold text-emerald-700", children: i + 1 }),
|
|
4594
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1", children: t.label })
|
|
4595
|
-
] }, t.key)),
|
|
4596
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-400", children: "Esc hu\u1EF7" })
|
|
4597
|
-
]
|
|
4598
|
-
}
|
|
4599
|
-
),
|
|
4600
5757
|
portalReady && hover && typeof document !== "undefined" ? reactDom.createPortal(
|
|
4601
5758
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4602
5759
|
"div",
|
|
@@ -4643,9 +5800,9 @@ function StampLeftPanelDesktop(props) {
|
|
|
4643
5800
|
objects,
|
|
4644
5801
|
tabs
|
|
4645
5802
|
} = props;
|
|
4646
|
-
const [tab, setTab] =
|
|
5803
|
+
const [tab, setTab] = React5.useState("tools");
|
|
4647
5804
|
const hasObjects = !!objects;
|
|
4648
|
-
|
|
5805
|
+
React5.useEffect(() => {
|
|
4649
5806
|
if (!hasObjects && tab === "objects") setTab("tools");
|
|
4650
5807
|
}, [hasObjects, tab]);
|
|
4651
5808
|
const tabSpecs = hasObjects ? [
|
|
@@ -4663,6 +5820,8 @@ function StampLeftPanelDesktop(props) {
|
|
|
4663
5820
|
tabs: tabSpecs,
|
|
4664
5821
|
activeTab: hasObjects ? tab : void 0,
|
|
4665
5822
|
onTabChange: hasObjects ? setTab : void 0,
|
|
5823
|
+
resizable: true,
|
|
5824
|
+
widthStorageKey: "xom11.stamp-left-panel.width",
|
|
4666
5825
|
children: !hasObjects || tab === "tools" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4667
5826
|
/* @__PURE__ */ jsxRuntime.jsx(AxisGridSection, { view, history }),
|
|
4668
5827
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4724,9 +5883,9 @@ function MobileToolDrawer({
|
|
|
4724
5883
|
testId,
|
|
4725
5884
|
objectsTab
|
|
4726
5885
|
}) {
|
|
4727
|
-
const [mobileTab, setMobileTab] =
|
|
4728
|
-
const prevOpenRef =
|
|
4729
|
-
|
|
5886
|
+
const [mobileTab, setMobileTab] = React5__namespace.default.useState("tools");
|
|
5887
|
+
const prevOpenRef = React5__namespace.default.useRef(drawerOpen);
|
|
5888
|
+
React5__namespace.default.useEffect(() => {
|
|
4730
5889
|
if (!prevOpenRef.current && drawerOpen) setMobileTab("tools");
|
|
4731
5890
|
prevOpenRef.current = drawerOpen;
|
|
4732
5891
|
}, [drawerOpen]);
|
|
@@ -4932,7 +6091,7 @@ function StampLeftPanelMobile(props) {
|
|
|
4932
6091
|
drawerOpen,
|
|
4933
6092
|
onDrawerClose
|
|
4934
6093
|
} = props;
|
|
4935
|
-
const groups =
|
|
6094
|
+
const groups = React5.useMemo(() => {
|
|
4936
6095
|
const acc = /* @__PURE__ */ new Map();
|
|
4937
6096
|
for (const t of tools) {
|
|
4938
6097
|
if (!acc.has(t.group)) acc.set(t.group, []);
|
|
@@ -5122,11 +6281,11 @@ function readMatch(query) {
|
|
|
5122
6281
|
}
|
|
5123
6282
|
}
|
|
5124
6283
|
function useIsMobile() {
|
|
5125
|
-
const [state, setState] =
|
|
6284
|
+
const [state, setState] = React5.useState(() => ({
|
|
5126
6285
|
isMobile: readMatch(MOBILE_QUERY),
|
|
5127
6286
|
isTouchOnly: readMatch(NO_HOVER_QUERY)
|
|
5128
6287
|
}));
|
|
5129
|
-
|
|
6288
|
+
React5.useEffect(() => {
|
|
5130
6289
|
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
5131
6290
|
const mql = window.matchMedia(MOBILE_QUERY);
|
|
5132
6291
|
const tql = window.matchMedia(NO_HOVER_QUERY);
|
|
@@ -5152,7 +6311,7 @@ var init_useIsMobile = __esm({
|
|
|
5152
6311
|
}
|
|
5153
6312
|
});
|
|
5154
6313
|
function useStampStore(domain, editingElement, parseInitial) {
|
|
5155
|
-
const ref =
|
|
6314
|
+
const ref = React5.useRef(null);
|
|
5156
6315
|
if (!ref.current) {
|
|
5157
6316
|
const initial = editingElement?.customData ? parseInitial(editingElement.customData) ?? createEmptyState(domain) : createEmptyState(domain);
|
|
5158
6317
|
ref.current = createStore(initial);
|
|
@@ -5251,9 +6410,9 @@ var init_tools = __esm({
|
|
|
5251
6410
|
}
|
|
5252
6411
|
});
|
|
5253
6412
|
function FunctionRow({ obj, store, selected, onClick }) {
|
|
5254
|
-
const [local, setLocal] =
|
|
5255
|
-
const [error, setError] =
|
|
5256
|
-
|
|
6413
|
+
const [local, setLocal] = React5.useState(obj.attrs.expression);
|
|
6414
|
+
const [error, setError] = React5.useState(null);
|
|
6415
|
+
React5.useEffect(() => {
|
|
5257
6416
|
setLocal(obj.attrs.expression);
|
|
5258
6417
|
setError(null);
|
|
5259
6418
|
}, [obj.attrs.expression]);
|
|
@@ -5474,35 +6633,35 @@ var init_host = __esm({
|
|
|
5474
6633
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 3 L3 21 L21 21" }),
|
|
5475
6634
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 14 Q9 8 12 10 Q15 12 18 6" })
|
|
5476
6635
|
] });
|
|
5477
|
-
Graph2DStampHost =
|
|
6636
|
+
Graph2DStampHost = React5.forwardRef(
|
|
5478
6637
|
function Graph2DStampHost2({ api, editingElement, onClose, isDark }, ref) {
|
|
5479
|
-
const panelRef =
|
|
6638
|
+
const panelRef = React5.useRef(null);
|
|
5480
6639
|
const { isMobile } = useIsMobile();
|
|
5481
|
-
const [drawerOpen, setDrawerOpen] =
|
|
6640
|
+
const [drawerOpen, setDrawerOpen] = React5.useState(false);
|
|
5482
6641
|
const sceneStore = useStampStore("graph2d", editingElement, parseInitialState);
|
|
5483
|
-
const [selectedObjectId, setSelectedObjectId] =
|
|
6642
|
+
const [selectedObjectId, setSelectedObjectId] = React5.useState(void 0);
|
|
5484
6643
|
const initialMeta = sceneStore.getState().meta;
|
|
5485
6644
|
const initialView = initialMeta.domain === "graph2d" ? initialMeta.view : null;
|
|
5486
|
-
const [selectedTool, setSelectedTool] =
|
|
5487
|
-
const [showAxis, setShowAxisState] =
|
|
5488
|
-
const [showGrid, setShowGridState] =
|
|
5489
|
-
const [canUndo, setCanUndo] =
|
|
5490
|
-
const [canRedo, setCanRedo] =
|
|
5491
|
-
const handleHistoryChange =
|
|
6645
|
+
const [selectedTool, setSelectedTool] = React5.useState("move");
|
|
6646
|
+
const [showAxis, setShowAxisState] = React5.useState(initialView?.showAxis ?? true);
|
|
6647
|
+
const [showGrid, setShowGridState] = React5.useState(initialView?.showGrid ?? true);
|
|
6648
|
+
const [canUndo, setCanUndo] = React5.useState(false);
|
|
6649
|
+
const [canRedo, setCanRedo] = React5.useState(false);
|
|
6650
|
+
const handleHistoryChange = React5.useCallback((u, r) => {
|
|
5492
6651
|
setCanUndo(u);
|
|
5493
6652
|
setCanRedo(r);
|
|
5494
6653
|
}, []);
|
|
5495
|
-
const handleUndo =
|
|
5496
|
-
const handleRedo =
|
|
5497
|
-
const handleShowAxisChange =
|
|
6654
|
+
const handleUndo = React5.useCallback(() => sceneStore.undo(), [sceneStore]);
|
|
6655
|
+
const handleRedo = React5.useCallback(() => sceneStore.redo(), [sceneStore]);
|
|
6656
|
+
const handleShowAxisChange = React5.useCallback((b) => {
|
|
5498
6657
|
setShowAxisState(b);
|
|
5499
6658
|
sceneStore.dispatch({ type: "UPDATE_VIEW", payload: { patch: { showAxis: b } } });
|
|
5500
6659
|
}, [sceneStore]);
|
|
5501
|
-
const handleShowGridChange =
|
|
6660
|
+
const handleShowGridChange = React5.useCallback((b) => {
|
|
5502
6661
|
setShowGridState(b);
|
|
5503
6662
|
sceneStore.dispatch({ type: "UPDATE_VIEW", payload: { patch: { showGrid: b } } });
|
|
5504
6663
|
}, [sceneStore]);
|
|
5505
|
-
const handleAddFunction =
|
|
6664
|
+
const handleAddFunction = React5.useCallback(() => {
|
|
5506
6665
|
const existing = Object.values(sceneStore.getState().objects).filter((o) => o.kind === "function2d");
|
|
5507
6666
|
const id = `f${existing.length + 1}`;
|
|
5508
6667
|
sceneStore.dispatch({
|
|
@@ -5521,7 +6680,7 @@ var init_host = __esm({
|
|
|
5521
6680
|
}
|
|
5522
6681
|
});
|
|
5523
6682
|
}, [sceneStore]);
|
|
5524
|
-
const handleAddParameter =
|
|
6683
|
+
const handleAddParameter = React5.useCallback(() => {
|
|
5525
6684
|
const existing = Object.values(sceneStore.getState().objects).filter((o) => o.kind === "parameter");
|
|
5526
6685
|
const labels = "abcdefghijklmnopqrstuvwxyz";
|
|
5527
6686
|
const usedLabels = new Set(existing.map((o) => o.label));
|
|
@@ -5549,7 +6708,7 @@ var init_host = __esm({
|
|
|
5549
6708
|
}
|
|
5550
6709
|
});
|
|
5551
6710
|
}, [sceneStore]);
|
|
5552
|
-
const handleInsert =
|
|
6711
|
+
const handleInsert = React5.useCallback(
|
|
5553
6712
|
async (jsonState, svgString) => {
|
|
5554
6713
|
if (!api) return;
|
|
5555
6714
|
try {
|
|
@@ -5569,7 +6728,7 @@ var init_host = __esm({
|
|
|
5569
6728
|
},
|
|
5570
6729
|
[api, editingElement?.id, onClose]
|
|
5571
6730
|
);
|
|
5572
|
-
|
|
6731
|
+
React5.useImperativeHandle(
|
|
5573
6732
|
ref,
|
|
5574
6733
|
() => ({
|
|
5575
6734
|
tryInsert: () => panelRef.current?.insert() ?? false,
|
|
@@ -5577,7 +6736,7 @@ var init_host = __esm({
|
|
|
5577
6736
|
}),
|
|
5578
6737
|
[]
|
|
5579
6738
|
);
|
|
5580
|
-
const renderRow =
|
|
6739
|
+
const renderRow = React5.useMemo(() => makeRenderRow(sceneStore), [sceneStore]);
|
|
5581
6740
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5582
6741
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5583
6742
|
StampLeftPanel,
|
|
@@ -5655,7 +6814,7 @@ init_render();
|
|
|
5655
6814
|
init_types();
|
|
5656
6815
|
init_serialize();
|
|
5657
6816
|
init_svgToStampFile();
|
|
5658
|
-
var Graph2DStampHost3 =
|
|
6817
|
+
var Graph2DStampHost3 = React5.lazy(
|
|
5659
6818
|
() => Promise.resolve().then(() => (init_host(), host_exports)).then((m) => ({ default: m.Graph2DStampHost }))
|
|
5660
6819
|
);
|
|
5661
6820
|
var Graph2DIcon = /* @__PURE__ */ jsxRuntime.jsxs(
|