@xom11/whiteboard 0.25.0 → 0.28.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 +6780 -788
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +5140 -577
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +5 -5
- package/dist/{chunk-NDEZJKNY.mjs → chunk-5JM35CXV.mjs} +4 -4
- package/dist/{chunk-NDEZJKNY.mjs.map → chunk-5JM35CXV.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-M42TGYT6.mjs → chunk-BNBOIDO5.mjs} +3 -3
- package/dist/{chunk-M42TGYT6.mjs.map → chunk-BNBOIDO5.mjs.map} +1 -1
- package/dist/{chunk-ONBCUWVI.mjs → chunk-BU5KLO3P.mjs} +3 -3
- package/dist/{chunk-ONBCUWVI.mjs.map → chunk-BU5KLO3P.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-H22OZYTW.mjs +265 -0
- package/dist/chunk-H22OZYTW.mjs.map +1 -0
- package/dist/chunk-J5LGTIGS.mjs +10 -0
- package/dist/chunk-J5LGTIGS.mjs.map +1 -0
- package/dist/{chunk-TB4CL25L.mjs → chunk-OQIQNKPQ.mjs} +206 -66
- package/dist/chunk-OQIQNKPQ.mjs.map +1 -0
- 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-YSJOVBCD.mjs → chunk-QCZVFEN4.mjs} +4 -4
- package/dist/{chunk-YSJOVBCD.mjs.map → chunk-QCZVFEN4.mjs.map} +1 -1
- package/dist/{chunk-ESVPQWHX.mjs → chunk-QRUAEXLR.mjs} +5 -5
- package/dist/{chunk-ESVPQWHX.mjs.map → chunk-QRUAEXLR.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/{chunk-I24QOHPU.mjs → chunk-V3YJ6JFL.mjs} +3 -3
- package/dist/{chunk-I24QOHPU.mjs.map → chunk-V3YJ6JFL.mjs.map} +1 -1
- package/dist/{chunk-REIJZDVZ.mjs → chunk-ZTQBUKLJ.mjs} +960 -196
- package/dist/chunk-ZTQBUKLJ.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 +5521 -1384
- 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 +1351 -252
- 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 +1517 -341
- 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-A64ITWVX.mjs → host-2ISGVO7O.mjs} +6 -5
- package/dist/host-2ISGVO7O.mjs.map +1 -0
- package/dist/{host-L7FMFZUW.mjs → host-4P766V4J.mjs} +1363 -463
- package/dist/host-4P766V4J.mjs.map +1 -0
- package/dist/{host-QK53UYMD.mjs → host-HOSJHQ5H.mjs} +10 -9
- package/dist/host-HOSJHQ5H.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 +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +5746 -1603
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +26 -22
- 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-N4G6RFBB.mjs +9 -0
- package/dist/{serialize-SRJVKYUG.mjs.map → serialize-N4G6RFBB.mjs.map} +1 -1
- package/dist/{types-DWRyCa2m.d.ts → types-BHYC2Fiw.d.mts} +130 -1
- package/dist/{types-DWRyCa2m.d.mts → types-BHYC2Fiw.d.ts} +130 -1
- 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,17 +2975,31 @@ 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) => {
|
|
2166
2997
|
const board = ctx.jxg;
|
|
2998
|
+
const isCenterLabel = (l) => /^[A-Z]['′]?\d*$/u.test(l);
|
|
2999
|
+
const isCenter = isCenterLabel(obj.label);
|
|
2167
3000
|
const baseOpts = {
|
|
2168
3001
|
name: obj.label,
|
|
2169
|
-
withLabel: obj.attrs.showLabel ?? false,
|
|
3002
|
+
withLabel: isCenter ? obj.attrs.showLabel ?? false : true,
|
|
2170
3003
|
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
2171
3004
|
strokeWidth: obj.attrs.width ?? 2,
|
|
2172
3005
|
dash: obj.attrs.dash ?? 0,
|
|
@@ -2174,13 +3007,81 @@ var init_circle = __esm({
|
|
|
2174
3007
|
visible: obj.visible,
|
|
2175
3008
|
fixed: obj.locked
|
|
2176
3009
|
};
|
|
2177
|
-
const c = obj.attrs
|
|
3010
|
+
const c = asConstruction(obj.attrs);
|
|
2178
3011
|
if (c?.kind === "circumscribed") {
|
|
2179
3012
|
const p1 = ctx.resolveRef(c.p1);
|
|
2180
3013
|
const p2 = ctx.resolveRef(c.p2);
|
|
2181
3014
|
const p3 = ctx.resolveRef(c.p3);
|
|
3015
|
+
if (isCenter) {
|
|
3016
|
+
const center2 = board.create("circumcenter", [p1, p2, p3], {
|
|
3017
|
+
visible: obj.visible,
|
|
3018
|
+
withLabel: true,
|
|
3019
|
+
fixed: true,
|
|
3020
|
+
name: obj.label
|
|
3021
|
+
});
|
|
3022
|
+
const circ = board.create("circumcircle", [p1, p2, p3], { ...baseOpts, withLabel: false });
|
|
3023
|
+
circ.center = circ.center ?? center2;
|
|
3024
|
+
circ._helpers = [center2];
|
|
3025
|
+
return circ;
|
|
3026
|
+
}
|
|
2182
3027
|
return board.create("circumcircle", [p1, p2, p3], baseOpts);
|
|
2183
3028
|
}
|
|
3029
|
+
if (c?.kind === "incircle") {
|
|
3030
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
3031
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
3032
|
+
const p3 = ctx.resolveRef(c.p3);
|
|
3033
|
+
if (isCenter) {
|
|
3034
|
+
const center2 = board.create("incenter", [p1, p2, p3], {
|
|
3035
|
+
visible: obj.visible,
|
|
3036
|
+
withLabel: true,
|
|
3037
|
+
fixed: true,
|
|
3038
|
+
name: obj.label
|
|
3039
|
+
});
|
|
3040
|
+
const circ = board.create("incircle", [p1, p2, p3], { ...baseOpts, withLabel: false });
|
|
3041
|
+
circ.center = circ.center ?? center2;
|
|
3042
|
+
circ._helpers = [center2];
|
|
3043
|
+
return circ;
|
|
3044
|
+
}
|
|
3045
|
+
return board.create("incircle", [p1, p2, p3], baseOpts);
|
|
3046
|
+
}
|
|
3047
|
+
if (c?.kind === "excircle") {
|
|
3048
|
+
const P = [ctx.resolveRef(c.p1), ctx.resolveRef(c.p2), ctx.resolveRef(c.p3)];
|
|
3049
|
+
const ids = [c.p1, c.p2, c.p3];
|
|
3050
|
+
const oppIdx = Math.max(0, ids.indexOf(c.opposite));
|
|
3051
|
+
const verts = () => [
|
|
3052
|
+
[P[0].X(), P[0].Y()],
|
|
3053
|
+
[P[1].X(), P[1].Y()],
|
|
3054
|
+
[P[2].X(), P[2].Y()]
|
|
3055
|
+
];
|
|
3056
|
+
const ctr = () => excenter(verts(), oppIdx);
|
|
3057
|
+
const radius = () => {
|
|
3058
|
+
const I = ctr();
|
|
3059
|
+
const others = [0, 1, 2].filter((i) => i !== oppIdx);
|
|
3060
|
+
const v = verts();
|
|
3061
|
+
const a = v[others[0]];
|
|
3062
|
+
const b = v[others[1]];
|
|
3063
|
+
const dx = b[0] - a[0];
|
|
3064
|
+
const dy = b[1] - a[1];
|
|
3065
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
3066
|
+
return Math.abs((I[0] - a[0]) * dy - (I[1] - a[1]) * dx) / len;
|
|
3067
|
+
};
|
|
3068
|
+
const center2 = board.create("point", [() => ctr()[0], () => ctr()[1]], { visible: false, withLabel: false, fixed: true, name: "" });
|
|
3069
|
+
const circ = board.create("circle", [center2, () => radius()], baseOpts);
|
|
3070
|
+
circ._helpers = [center2];
|
|
3071
|
+
return circ;
|
|
3072
|
+
}
|
|
3073
|
+
if (c?.kind === "diameter") {
|
|
3074
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
3075
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
3076
|
+
const center2 = board.create("midpoint", [p1, p2], { visible: false, withLabel: false, fixed: true, name: "" });
|
|
3077
|
+
const circ = board.create("circle", [center2, p2], baseOpts);
|
|
3078
|
+
circ._helpers = [center2];
|
|
3079
|
+
return circ;
|
|
3080
|
+
}
|
|
3081
|
+
if (typeof obj.attrs.radius === "number") {
|
|
3082
|
+
const center2 = ctx.resolveRef(obj.attrs.center);
|
|
3083
|
+
return board.create("circle", [center2, obj.attrs.radius], baseOpts);
|
|
3084
|
+
}
|
|
2184
3085
|
const center = ctx.resolveRef(obj.attrs.center);
|
|
2185
3086
|
const surface = ctx.resolveRef(obj.attrs.surfacePoint);
|
|
2186
3087
|
return board.create("circle", [center, surface], baseOpts);
|
|
@@ -2340,6 +3241,26 @@ function regularVertexLabels(p1Label, p2Label, n) {
|
|
|
2340
3241
|
}
|
|
2341
3242
|
return `${p1Label}${p2Label}\u2026`;
|
|
2342
3243
|
}
|
|
3244
|
+
function specialShapeName(kind) {
|
|
3245
|
+
switch (kind) {
|
|
3246
|
+
case "square":
|
|
3247
|
+
return "H\xECnh vu\xF4ng";
|
|
3248
|
+
case "rectangle":
|
|
3249
|
+
return "H\xECnh ch\u1EEF nh\u1EADt";
|
|
3250
|
+
case "rhombus":
|
|
3251
|
+
return "H\xECnh thoi";
|
|
3252
|
+
case "parallelogram":
|
|
3253
|
+
return "H\xECnh b\xECnh h\xE0nh";
|
|
3254
|
+
case "isoTrapezoid":
|
|
3255
|
+
return "H\xECnh thang c\xE2n";
|
|
3256
|
+
case "isoTriangle":
|
|
3257
|
+
return "Tam gi\xE1c c\xE2n";
|
|
3258
|
+
case "rightTriangle":
|
|
3259
|
+
return "Tam gi\xE1c vu\xF4ng";
|
|
3260
|
+
case "regular":
|
|
3261
|
+
return "";
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
2343
3264
|
var def11;
|
|
2344
3265
|
var init_polygon = __esm({
|
|
2345
3266
|
"src/core/scene/kinds/polygon.ts"() {
|
|
@@ -2351,13 +3272,27 @@ var init_polygon = __esm({
|
|
|
2351
3272
|
migrate: {},
|
|
2352
3273
|
validate: (a) => {
|
|
2353
3274
|
if (a?.construction) {
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
3275
|
+
const c = a.construction;
|
|
3276
|
+
if (c.kind === "regular") {
|
|
3277
|
+
if (!c.p1 || !c.p2) throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
3278
|
+
if (!Number.isFinite(c.n) || c.n < 3) throw new Error("polygon (regular): n \u2265 3");
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
if (c.kind === "square") {
|
|
3282
|
+
if (!c.p1 || !c.p2) throw new Error("polygon (square): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
if (c.kind === "rectangle" || c.kind === "rhombus" || c.kind === "parallelogram" || c.kind === "isoTrapezoid") {
|
|
3286
|
+
if (!c.p1 || !c.p2 || !c.p3) throw new Error(`polygon (${c.kind}): p1, p2, p3 b\u1EAFt bu\u1ED9c`);
|
|
3287
|
+
return;
|
|
3288
|
+
}
|
|
3289
|
+
if (c.kind === "isoTriangle") {
|
|
3290
|
+
if (!c.base1 || !c.base2 || !c.apex) throw new Error("polygon (isoTriangle): base1, base2, apex b\u1EAFt bu\u1ED9c");
|
|
3291
|
+
return;
|
|
3292
|
+
}
|
|
3293
|
+
if (c.kind === "rightTriangle") {
|
|
3294
|
+
if (!c.rightAngle || !c.leg1End || !c.leg2End) throw new Error("polygon (rightTriangle): rightAngle, leg1End, leg2End b\u1EAFt bu\u1ED9c");
|
|
3295
|
+
return;
|
|
2361
3296
|
}
|
|
2362
3297
|
return;
|
|
2363
3298
|
}
|
|
@@ -2366,14 +3301,51 @@ var init_polygon = __esm({
|
|
|
2366
3301
|
}
|
|
2367
3302
|
},
|
|
2368
3303
|
dependsOn: (a) => {
|
|
2369
|
-
|
|
2370
|
-
return [...a.vertices ?? []];
|
|
3304
|
+
const c = a.construction;
|
|
3305
|
+
if (!c) return [...a.vertices ?? []];
|
|
3306
|
+
switch (c.kind) {
|
|
3307
|
+
case "regular":
|
|
3308
|
+
return [c.p1, c.p2];
|
|
3309
|
+
case "square":
|
|
3310
|
+
return [c.p1, c.p2];
|
|
3311
|
+
case "rectangle":
|
|
3312
|
+
case "rhombus":
|
|
3313
|
+
case "parallelogram":
|
|
3314
|
+
case "isoTrapezoid":
|
|
3315
|
+
return [c.p1, c.p2, c.p3];
|
|
3316
|
+
case "isoTriangle":
|
|
3317
|
+
return [c.base1, c.base2, c.apex];
|
|
3318
|
+
case "rightTriangle":
|
|
3319
|
+
return [c.rightAngle, c.leg1End, c.leg2End];
|
|
3320
|
+
}
|
|
2371
3321
|
},
|
|
2372
3322
|
describe: (obj, state) => {
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
3323
|
+
const c = obj.attrs.construction;
|
|
3324
|
+
if (c) {
|
|
3325
|
+
if (c.kind === "regular") {
|
|
3326
|
+
const labels2 = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
|
|
3327
|
+
return `${regularPolygonName(c.n)} ${labels2}`;
|
|
3328
|
+
}
|
|
3329
|
+
const name = specialShapeName(c.kind);
|
|
3330
|
+
let labels = [];
|
|
3331
|
+
switch (c.kind) {
|
|
3332
|
+
case "square":
|
|
3333
|
+
labels = [labelOf(c.p1, state), labelOf(c.p2, state)];
|
|
3334
|
+
break;
|
|
3335
|
+
case "rectangle":
|
|
3336
|
+
case "rhombus":
|
|
3337
|
+
case "parallelogram":
|
|
3338
|
+
case "isoTrapezoid":
|
|
3339
|
+
labels = [labelOf(c.p1, state), labelOf(c.p2, state), labelOf(c.p3, state)];
|
|
3340
|
+
break;
|
|
3341
|
+
case "isoTriangle":
|
|
3342
|
+
labels = [labelOf(c.apex, state), labelOf(c.base1, state), labelOf(c.base2, state)];
|
|
3343
|
+
break;
|
|
3344
|
+
case "rightTriangle":
|
|
3345
|
+
labels = [labelOf(c.rightAngle, state), labelOf(c.leg1End, state), labelOf(c.leg2End, state)];
|
|
3346
|
+
break;
|
|
3347
|
+
}
|
|
3348
|
+
return `${name} ${labels.join("")}`;
|
|
2377
3349
|
}
|
|
2378
3350
|
return `\u0110a gi\xE1c ${(obj.attrs.vertices ?? []).map((id) => labelOf(id, state)).join("")}`;
|
|
2379
3351
|
},
|
|
@@ -2381,22 +3353,85 @@ var init_polygon = __esm({
|
|
|
2381
3353
|
const board = ctx.jxg;
|
|
2382
3354
|
const label = obj.label;
|
|
2383
3355
|
const showValue = obj.attrs.showValue ?? false;
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
3356
|
+
const cons = obj.attrs.construction;
|
|
3357
|
+
const commonAttrs = {
|
|
3358
|
+
name: label,
|
|
3359
|
+
withLabel: obj.attrs.showLabel ?? false,
|
|
3360
|
+
borders: {
|
|
3361
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
3362
|
+
strokeWidth: obj.attrs.width ?? 2
|
|
3363
|
+
},
|
|
3364
|
+
fillColor: obj.attrs.color ?? "#60a5fa",
|
|
3365
|
+
fillOpacity: obj.attrs.fillOpacity ?? 0.15,
|
|
3366
|
+
visible: obj.visible,
|
|
3367
|
+
fixed: obj.locked
|
|
3368
|
+
};
|
|
3369
|
+
if (cons?.kind === "regular") {
|
|
3370
|
+
const p1 = ctx.resolveRef(cons.p1);
|
|
3371
|
+
const p2 = ctx.resolveRef(cons.p2);
|
|
3372
|
+
return board.create("regularpolygon", [p1, p2, cons.n], commonAttrs);
|
|
3373
|
+
}
|
|
3374
|
+
if (cons?.kind === "square") {
|
|
3375
|
+
const p1 = ctx.resolveRef(cons.p1);
|
|
3376
|
+
const p2 = ctx.resolveRef(cons.p2);
|
|
3377
|
+
return board.create("regularpolygon", [p1, p2, 4], commonAttrs);
|
|
3378
|
+
}
|
|
3379
|
+
if (cons?.kind === "rectangle" || cons?.kind === "rhombus" || cons?.kind === "parallelogram") {
|
|
3380
|
+
const A = ctx.resolveRef(cons.p1);
|
|
3381
|
+
const B = ctx.resolveRef(cons.p2);
|
|
3382
|
+
const C = ctx.resolveRef(cons.p3);
|
|
3383
|
+
const D = board.create(
|
|
3384
|
+
"point",
|
|
3385
|
+
[() => A.X() + C.X() - B.X(), () => A.Y() + C.Y() - B.Y()],
|
|
3386
|
+
{ visible: false, withLabel: false, fixed: true, name: "" }
|
|
3387
|
+
);
|
|
3388
|
+
const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
|
|
3389
|
+
poly2._helpers = [D];
|
|
3390
|
+
return poly2;
|
|
3391
|
+
}
|
|
3392
|
+
if (cons?.kind === "isoTrapezoid") {
|
|
3393
|
+
const A = ctx.resolveRef(cons.p1);
|
|
3394
|
+
const B = ctx.resolveRef(cons.p2);
|
|
3395
|
+
const C = ctx.resolveRef(cons.p3);
|
|
3396
|
+
const Dx = () => {
|
|
3397
|
+
const Mx = (A.X() + B.X()) / 2;
|
|
3398
|
+
const My = (A.Y() + B.Y()) / 2;
|
|
3399
|
+
const ux = B.X() - A.X();
|
|
3400
|
+
const uy = B.Y() - A.Y();
|
|
3401
|
+
const len2 = ux * ux + uy * uy || 1;
|
|
3402
|
+
const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
|
|
3403
|
+
return C.X() - 2 * proj * ux;
|
|
3404
|
+
};
|
|
3405
|
+
const Dy = () => {
|
|
3406
|
+
const Mx = (A.X() + B.X()) / 2;
|
|
3407
|
+
const My = (A.Y() + B.Y()) / 2;
|
|
3408
|
+
const ux = B.X() - A.X();
|
|
3409
|
+
const uy = B.Y() - A.Y();
|
|
3410
|
+
const len2 = ux * ux + uy * uy || 1;
|
|
3411
|
+
const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
|
|
3412
|
+
return C.Y() - 2 * proj * uy;
|
|
3413
|
+
};
|
|
3414
|
+
const D = board.create("point", [Dx, Dy], {
|
|
3415
|
+
visible: false,
|
|
3416
|
+
withLabel: false,
|
|
3417
|
+
fixed: true,
|
|
3418
|
+
name: ""
|
|
2399
3419
|
});
|
|
3420
|
+
const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
|
|
3421
|
+
poly2._helpers = [D];
|
|
3422
|
+
return poly2;
|
|
3423
|
+
}
|
|
3424
|
+
if (cons?.kind === "isoTriangle") {
|
|
3425
|
+
const Apex = ctx.resolveRef(cons.apex);
|
|
3426
|
+
const B1 = ctx.resolveRef(cons.base1);
|
|
3427
|
+
const B2 = ctx.resolveRef(cons.base2);
|
|
3428
|
+
return board.create("polygon", [Apex, B1, B2], commonAttrs);
|
|
3429
|
+
}
|
|
3430
|
+
if (cons?.kind === "rightTriangle") {
|
|
3431
|
+
const R = ctx.resolveRef(cons.rightAngle);
|
|
3432
|
+
const P = ctx.resolveRef(cons.leg1End);
|
|
3433
|
+
const Q = ctx.resolveRef(cons.leg2End);
|
|
3434
|
+
return board.create("polygon", [R, P, Q], commonAttrs);
|
|
2400
3435
|
}
|
|
2401
3436
|
const verts = (obj.attrs.vertices ?? []).map((id) => ctx.resolveRef(id));
|
|
2402
3437
|
const poly = board.create("polygon", verts, {
|
|
@@ -3324,21 +4359,21 @@ var init_handlers = __esm({
|
|
|
3324
4359
|
}
|
|
3325
4360
|
});
|
|
3326
4361
|
function useToolStateMachine(initial) {
|
|
3327
|
-
const [tool, setToolState] =
|
|
3328
|
-
const [pendingIds, setPendingIds] =
|
|
3329
|
-
const toolRef =
|
|
3330
|
-
const pendingIdsRef =
|
|
3331
|
-
const setTool =
|
|
4362
|
+
const [tool, setToolState] = React5.useState(initial);
|
|
4363
|
+
const [pendingIds, setPendingIds] = React5.useState([]);
|
|
4364
|
+
const toolRef = React5.useRef(initial);
|
|
4365
|
+
const pendingIdsRef = React5.useRef([]);
|
|
4366
|
+
const setTool = React5.useCallback((t) => {
|
|
3332
4367
|
toolRef.current = t;
|
|
3333
4368
|
pendingIdsRef.current = [];
|
|
3334
4369
|
setToolState(t);
|
|
3335
4370
|
setPendingIds([]);
|
|
3336
4371
|
}, []);
|
|
3337
|
-
const pushPending =
|
|
4372
|
+
const pushPending = React5.useCallback((id) => {
|
|
3338
4373
|
pendingIdsRef.current = [...pendingIdsRef.current, id];
|
|
3339
4374
|
setPendingIds(pendingIdsRef.current);
|
|
3340
4375
|
}, []);
|
|
3341
|
-
const clearPending =
|
|
4376
|
+
const clearPending = React5.useCallback(() => {
|
|
3342
4377
|
pendingIdsRef.current = [];
|
|
3343
4378
|
setPendingIds([]);
|
|
3344
4379
|
}, []);
|
|
@@ -3507,24 +4542,24 @@ var init_MiniBoard = __esm({
|
|
|
3507
4542
|
init_safeJsx();
|
|
3508
4543
|
init_attachJxgWheelZoom();
|
|
3509
4544
|
init_initJxgBoard();
|
|
3510
|
-
MiniBoard =
|
|
4545
|
+
MiniBoard = React5__namespace.default.forwardRef(
|
|
3511
4546
|
function MiniBoard2({ store, selectedTool, showAxis, showGrid, isDark, onReady, onSelectionChange: _onSelectionChange }, ref) {
|
|
3512
|
-
const isDarkRef =
|
|
4547
|
+
const isDarkRef = React5.useRef(!!isDark);
|
|
3513
4548
|
isDarkRef.current = !!isDark;
|
|
3514
|
-
const containerId =
|
|
3515
|
-
const containerRef =
|
|
3516
|
-
const boardRef =
|
|
3517
|
-
const jxgRef =
|
|
3518
|
-
const rendererRef =
|
|
4549
|
+
const containerId = React5.useId().replace(/:/g, "_") + "_graph_jxg";
|
|
4550
|
+
const containerRef = React5.useRef(null);
|
|
4551
|
+
const boardRef = React5.useRef(null);
|
|
4552
|
+
const jxgRef = React5.useRef(null);
|
|
4553
|
+
const rendererRef = React5.useRef(null);
|
|
3519
4554
|
const toolSM = useToolStateMachine(selectedTool);
|
|
3520
|
-
const showAxisRef =
|
|
4555
|
+
const showAxisRef = React5.useRef(showAxis);
|
|
3521
4556
|
showAxisRef.current = showAxis;
|
|
3522
|
-
const showGridRef =
|
|
4557
|
+
const showGridRef = React5.useRef(showGrid);
|
|
3523
4558
|
showGridRef.current = showGrid;
|
|
3524
|
-
|
|
4559
|
+
React5.useEffect(() => {
|
|
3525
4560
|
if (toolSM.toolRef.current !== selectedTool) toolSM.setTool(selectedTool);
|
|
3526
4561
|
}, [selectedTool]);
|
|
3527
|
-
|
|
4562
|
+
React5.useEffect(() => {
|
|
3528
4563
|
if (typeof window === "undefined" || !containerRef.current) return;
|
|
3529
4564
|
let cancelled = false;
|
|
3530
4565
|
let wheelCleanup = null;
|
|
@@ -3610,7 +4645,7 @@ var init_MiniBoard = __esm({
|
|
|
3610
4645
|
boardRef.current = null;
|
|
3611
4646
|
};
|
|
3612
4647
|
}, [containerId]);
|
|
3613
|
-
|
|
4648
|
+
React5.useImperativeHandle(
|
|
3614
4649
|
ref,
|
|
3615
4650
|
() => ({
|
|
3616
4651
|
getState: () => store.getState(),
|
|
@@ -3656,27 +4691,27 @@ function reducer(state, action) {
|
|
|
3656
4691
|
}
|
|
3657
4692
|
}
|
|
3658
4693
|
function ToastProvider({ children, maxVisible = 3 }) {
|
|
3659
|
-
const [items, dispatch] =
|
|
3660
|
-
const timersRef =
|
|
3661
|
-
const itemsRef =
|
|
4694
|
+
const [items, dispatch] = React5.useReducer(reducer, []);
|
|
4695
|
+
const timersRef = React5.useRef(/* @__PURE__ */ new Map());
|
|
4696
|
+
const itemsRef = React5.useRef(items);
|
|
3662
4697
|
itemsRef.current = items;
|
|
3663
|
-
const clearTimer =
|
|
4698
|
+
const clearTimer = React5.useCallback((id) => {
|
|
3664
4699
|
const t = timersRef.current.get(id);
|
|
3665
4700
|
if (t) {
|
|
3666
4701
|
clearTimeout(t);
|
|
3667
4702
|
timersRef.current.delete(id);
|
|
3668
4703
|
}
|
|
3669
4704
|
}, []);
|
|
3670
|
-
const dismiss =
|
|
4705
|
+
const dismiss = React5.useCallback((id) => {
|
|
3671
4706
|
clearTimer(id);
|
|
3672
4707
|
dispatch({ type: "DISMISS", id });
|
|
3673
4708
|
}, [clearTimer]);
|
|
3674
|
-
const scheduleAutoDismiss =
|
|
4709
|
+
const scheduleAutoDismiss = React5.useCallback((id, duration) => {
|
|
3675
4710
|
if (duration <= 0) return;
|
|
3676
4711
|
const t = setTimeout(() => dismiss(id), duration);
|
|
3677
4712
|
timersRef.current.set(id, t);
|
|
3678
4713
|
}, [dismiss]);
|
|
3679
|
-
const showToast =
|
|
4714
|
+
const showToast = React5.useCallback((message, opts = {}) => {
|
|
3680
4715
|
const variant = opts.variant ?? "info";
|
|
3681
4716
|
const duration = opts.duration ?? 3e3;
|
|
3682
4717
|
const id = opts.id ?? `toast-${++autoIdCounter}`;
|
|
@@ -3690,17 +4725,17 @@ function ToastProvider({ children, maxVisible = 3 }) {
|
|
|
3690
4725
|
}
|
|
3691
4726
|
scheduleAutoDismiss(id, duration);
|
|
3692
4727
|
}, [clearTimer, maxVisible, scheduleAutoDismiss]);
|
|
3693
|
-
|
|
4728
|
+
React5.useEffect(() => () => {
|
|
3694
4729
|
timersRef.current.forEach((t) => clearTimeout(t));
|
|
3695
4730
|
timersRef.current.clear();
|
|
3696
4731
|
}, []);
|
|
3697
|
-
const value =
|
|
4732
|
+
const value = React5.useMemo(() => ({ items, showToast, dismiss }), [items, showToast, dismiss]);
|
|
3698
4733
|
return /* @__PURE__ */ jsxRuntime.jsx(ToastContext.Provider, { value, children });
|
|
3699
4734
|
}
|
|
3700
4735
|
var ToastContext, autoIdCounter;
|
|
3701
4736
|
var init_ToastProvider = __esm({
|
|
3702
4737
|
"src/stamps/shared/Toast/ToastProvider.tsx"() {
|
|
3703
|
-
ToastContext =
|
|
4738
|
+
ToastContext = React5.createContext(null);
|
|
3704
4739
|
autoIdCounter = 0;
|
|
3705
4740
|
}
|
|
3706
4741
|
});
|
|
@@ -3758,7 +4793,7 @@ var init_Toast = __esm({
|
|
|
3758
4793
|
}
|
|
3759
4794
|
});
|
|
3760
4795
|
function useToast() {
|
|
3761
|
-
const ctx =
|
|
4796
|
+
const ctx = React5.useContext(ToastContext);
|
|
3762
4797
|
if (!ctx) {
|
|
3763
4798
|
throw new Error("useToast must be used inside <ToastProvider>");
|
|
3764
4799
|
}
|
|
@@ -3803,7 +4838,7 @@ var init_EditorPanel = __esm({
|
|
|
3803
4838
|
init_scene();
|
|
3804
4839
|
init_constants();
|
|
3805
4840
|
init_Toast2();
|
|
3806
|
-
GraphEditorPanelInner =
|
|
4841
|
+
GraphEditorPanelInner = React5.forwardRef(
|
|
3807
4842
|
function GraphEditorPanel({
|
|
3808
4843
|
store,
|
|
3809
4844
|
onInsert,
|
|
@@ -3822,23 +4857,23 @@ var init_EditorPanel = __esm({
|
|
|
3822
4857
|
canUndo,
|
|
3823
4858
|
canRedo
|
|
3824
4859
|
}, ref) {
|
|
3825
|
-
const miniRef =
|
|
3826
|
-
const [ready, setReady] =
|
|
3827
|
-
const [hasContent, setHasContent] =
|
|
3828
|
-
const onSelectionChangeRef =
|
|
3829
|
-
|
|
4860
|
+
const miniRef = React5.useRef(null);
|
|
4861
|
+
const [ready, setReady] = React5.useState(false);
|
|
4862
|
+
const [hasContent, setHasContent] = React5.useState(false);
|
|
4863
|
+
const onSelectionChangeRef = React5.useRef(onSelectionChange);
|
|
4864
|
+
React5.useEffect(() => {
|
|
3830
4865
|
onSelectionChangeRef.current = onSelectionChange;
|
|
3831
4866
|
}, [onSelectionChange]);
|
|
3832
4867
|
useEditorState({ store, onHistoryChange });
|
|
3833
|
-
|
|
4868
|
+
React5.useEffect(() => {
|
|
3834
4869
|
const sync = () => setHasContent(Object.keys(store.getState().objects).length > 0);
|
|
3835
4870
|
sync();
|
|
3836
4871
|
return store.subscribe(sync);
|
|
3837
4872
|
}, [store]);
|
|
3838
|
-
const handleReady =
|
|
4873
|
+
const handleReady = React5.useCallback(() => {
|
|
3839
4874
|
setReady(true);
|
|
3840
4875
|
}, []);
|
|
3841
|
-
const performInsert =
|
|
4876
|
+
const performInsert = React5.useCallback(() => {
|
|
3842
4877
|
const h = miniRef.current;
|
|
3843
4878
|
if (!h) return false;
|
|
3844
4879
|
const state = h.getState();
|
|
@@ -3856,7 +4891,7 @@ var init_EditorPanel = __esm({
|
|
|
3856
4891
|
})();
|
|
3857
4892
|
return true;
|
|
3858
4893
|
}, [isDark, onInsert]);
|
|
3859
|
-
|
|
4894
|
+
React5.useImperativeHandle(ref, () => ({
|
|
3860
4895
|
insert: performInsert,
|
|
3861
4896
|
hasContent: () => Object.keys(miniRef.current?.getState().objects ?? {}).length > 0,
|
|
3862
4897
|
getStore: () => miniRef.current?.getStore() ?? null,
|
|
@@ -4014,13 +5049,27 @@ var init_EditorPanel = __esm({
|
|
|
4014
5049
|
);
|
|
4015
5050
|
}
|
|
4016
5051
|
);
|
|
4017
|
-
GraphEditorPanel2 =
|
|
5052
|
+
GraphEditorPanel2 = React5.forwardRef(
|
|
4018
5053
|
function GraphEditorPanel3(props, ref) {
|
|
4019
5054
|
return /* @__PURE__ */ jsxRuntime.jsx(ToastProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(GraphEditorPanelInner, { ...props, ref }) });
|
|
4020
5055
|
}
|
|
4021
5056
|
);
|
|
4022
5057
|
}
|
|
4023
5058
|
});
|
|
5059
|
+
function clamp(n, min, max) {
|
|
5060
|
+
return Math.max(min, Math.min(max, n));
|
|
5061
|
+
}
|
|
5062
|
+
function readStoredWidth(key, fallback, min, max) {
|
|
5063
|
+
if (!key || typeof window === "undefined") return fallback;
|
|
5064
|
+
try {
|
|
5065
|
+
const raw = window.localStorage.getItem(key);
|
|
5066
|
+
if (!raw) return fallback;
|
|
5067
|
+
const n = parseInt(raw, 10);
|
|
5068
|
+
if (Number.isFinite(n)) return clamp(n, min, max);
|
|
5069
|
+
} catch {
|
|
5070
|
+
}
|
|
5071
|
+
return fallback;
|
|
5072
|
+
}
|
|
4024
5073
|
function CloseIcon() {
|
|
4025
5074
|
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
5075
|
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
@@ -4028,8 +5077,64 @@ function CloseIcon() {
|
|
|
4028
5077
|
] });
|
|
4029
5078
|
}
|
|
4030
5079
|
function LeftPanelShell(props) {
|
|
4031
|
-
const {
|
|
5080
|
+
const {
|
|
5081
|
+
title,
|
|
5082
|
+
icon,
|
|
5083
|
+
onClose,
|
|
5084
|
+
isDark,
|
|
5085
|
+
tabs,
|
|
5086
|
+
activeTab,
|
|
5087
|
+
onTabChange,
|
|
5088
|
+
testId,
|
|
5089
|
+
resizable,
|
|
5090
|
+
widthStorageKey,
|
|
5091
|
+
defaultWidth,
|
|
5092
|
+
minWidth,
|
|
5093
|
+
maxWidth,
|
|
5094
|
+
children
|
|
5095
|
+
} = props;
|
|
4032
5096
|
const showTabs = !!tabs && tabs.length >= 2;
|
|
5097
|
+
const min = minWidth ?? FALLBACK_MIN_WIDTH;
|
|
5098
|
+
const max = maxWidth ?? FALLBACK_MAX_WIDTH;
|
|
5099
|
+
const initial = clamp(defaultWidth ?? FALLBACK_DEFAULT_WIDTH, min, max);
|
|
5100
|
+
const [width, setWidth] = React5__namespace.useState(
|
|
5101
|
+
() => resizable ? readStoredWidth(widthStorageKey, initial, min, max) : initial
|
|
5102
|
+
);
|
|
5103
|
+
const widthRef = React5__namespace.useRef(width);
|
|
5104
|
+
widthRef.current = width;
|
|
5105
|
+
React5__namespace.useEffect(() => {
|
|
5106
|
+
if (!resizable || !widthStorageKey || typeof window === "undefined") return;
|
|
5107
|
+
try {
|
|
5108
|
+
window.localStorage.setItem(widthStorageKey, String(width));
|
|
5109
|
+
} catch {
|
|
5110
|
+
}
|
|
5111
|
+
}, [resizable, widthStorageKey, width]);
|
|
5112
|
+
const onResizeStart = React5__namespace.useCallback(
|
|
5113
|
+
(e) => {
|
|
5114
|
+
if (!resizable) return;
|
|
5115
|
+
e.preventDefault();
|
|
5116
|
+
const startX = e.clientX;
|
|
5117
|
+
const startW = widthRef.current;
|
|
5118
|
+
const onMove = (ev) => {
|
|
5119
|
+
setWidth(clamp(startW + (ev.clientX - startX), min, max));
|
|
5120
|
+
};
|
|
5121
|
+
const onUp = () => {
|
|
5122
|
+
window.removeEventListener("mousemove", onMove);
|
|
5123
|
+
window.removeEventListener("mouseup", onUp);
|
|
5124
|
+
document.body.style.cursor = "";
|
|
5125
|
+
document.body.style.userSelect = "";
|
|
5126
|
+
};
|
|
5127
|
+
window.addEventListener("mousemove", onMove);
|
|
5128
|
+
window.addEventListener("mouseup", onUp);
|
|
5129
|
+
document.body.style.cursor = "ew-resize";
|
|
5130
|
+
document.body.style.userSelect = "none";
|
|
5131
|
+
},
|
|
5132
|
+
[resizable, min, max]
|
|
5133
|
+
);
|
|
5134
|
+
const onResizeDoubleClick = React5__namespace.useCallback(() => {
|
|
5135
|
+
if (!resizable) return;
|
|
5136
|
+
setWidth(initial);
|
|
5137
|
+
}, [resizable, initial]);
|
|
4033
5138
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4034
5139
|
"aside",
|
|
4035
5140
|
{
|
|
@@ -4037,10 +5142,12 @@ function LeftPanelShell(props) {
|
|
|
4037
5142
|
"aria-label": title,
|
|
4038
5143
|
"data-testid": testId ?? "left-panel",
|
|
4039
5144
|
"data-stamp-area": "true",
|
|
5145
|
+
style: resizable ? { width: `${width}px` } : void 0,
|
|
4040
5146
|
className: [
|
|
4041
5147
|
isDark ? "theme--dark " : "",
|
|
4042
|
-
"absolute left-0 top-0 z-30 flex h-full
|
|
4043
|
-
|
|
5148
|
+
"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",
|
|
5149
|
+
resizable ? "" : "w-60"
|
|
5150
|
+
].join(" "),
|
|
4044
5151
|
children: [
|
|
4045
5152
|
/* @__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
5153
|
/* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "flex items-center gap-2 text-sm font-semibold text-slate-800", children: [
|
|
@@ -4075,6 +5182,20 @@ function LeftPanelShell(props) {
|
|
|
4075
5182
|
className: "min-h-0 flex-1 overflow-y-auto p-3 space-y-3",
|
|
4076
5183
|
children
|
|
4077
5184
|
}
|
|
5185
|
+
),
|
|
5186
|
+
resizable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5187
|
+
"div",
|
|
5188
|
+
{
|
|
5189
|
+
role: "separator",
|
|
5190
|
+
"aria-orientation": "vertical",
|
|
5191
|
+
"aria-label": "K\xE9o \u0111\u1EC3 \u0111\u1ED5i r\u1ED9ng panel",
|
|
5192
|
+
"data-testid": "left-panel-resizer",
|
|
5193
|
+
onMouseDown: onResizeStart,
|
|
5194
|
+
onDoubleClick: onResizeDoubleClick,
|
|
5195
|
+
className: "group absolute right-0 top-0 z-40 h-full w-1.5 -mr-0.5 cursor-ew-resize select-none",
|
|
5196
|
+
title: "K\xE9o \u0111\u1EC3 \u0111\u1ED5i r\u1ED9ng (double-click \u0111\u1EC3 reset)",
|
|
5197
|
+
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" })
|
|
5198
|
+
}
|
|
4078
5199
|
)
|
|
4079
5200
|
]
|
|
4080
5201
|
}
|
|
@@ -4104,9 +5225,13 @@ function Section(props) {
|
|
|
4104
5225
|
props.children
|
|
4105
5226
|
] });
|
|
4106
5227
|
}
|
|
5228
|
+
var FALLBACK_DEFAULT_WIDTH, FALLBACK_MIN_WIDTH, FALLBACK_MAX_WIDTH;
|
|
4107
5229
|
var init_LeftPanelShell = __esm({
|
|
4108
5230
|
"src/core/scene/ui/LeftPanelShell.tsx"() {
|
|
4109
5231
|
"use client";
|
|
5232
|
+
FALLBACK_DEFAULT_WIDTH = 240;
|
|
5233
|
+
FALLBACK_MIN_WIDTH = 220;
|
|
5234
|
+
FALLBACK_MAX_WIDTH = 480;
|
|
4110
5235
|
}
|
|
4111
5236
|
});
|
|
4112
5237
|
|
|
@@ -4157,7 +5282,7 @@ var init_kindMeta = __esm({
|
|
|
4157
5282
|
});
|
|
4158
5283
|
function ObjectRowMenu(props) {
|
|
4159
5284
|
const { locked, onToggleLocked, onRename, onChangeColor, onDelete } = props;
|
|
4160
|
-
const [open, setOpen] =
|
|
5285
|
+
const [open, setOpen] = React5__namespace.useState(false);
|
|
4161
5286
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative inline-block", children: [
|
|
4162
5287
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4163
5288
|
"button",
|
|
@@ -4310,11 +5435,11 @@ var init_ObjectRow = __esm({
|
|
|
4310
5435
|
});
|
|
4311
5436
|
function ObjectListPanel(props) {
|
|
4312
5437
|
const { store, selectedId, onSelect, renderRow } = props;
|
|
4313
|
-
const subscribe =
|
|
5438
|
+
const subscribe = React5__namespace.useCallback(
|
|
4314
5439
|
(cb) => store.subscribe(() => cb()),
|
|
4315
5440
|
[store]
|
|
4316
5441
|
);
|
|
4317
|
-
const state =
|
|
5442
|
+
const state = React5__namespace.useSyncExternalStore(subscribe, store.getState, store.getState);
|
|
4318
5443
|
const objects = listObjects(state);
|
|
4319
5444
|
function handleSelect(id) {
|
|
4320
5445
|
onSelect?.(id === selectedId ? null : id);
|
|
@@ -4345,7 +5470,7 @@ function ObjectListPanel(props) {
|
|
|
4345
5470
|
if (renderRow) {
|
|
4346
5471
|
const custom = renderRow(obj, { selected, onClick });
|
|
4347
5472
|
if (custom != null) {
|
|
4348
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5473
|
+
return /* @__PURE__ */ jsxRuntime.jsx(React5__namespace.Fragment, { children: custom }, obj.id);
|
|
4349
5474
|
}
|
|
4350
5475
|
}
|
|
4351
5476
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4458,29 +5583,29 @@ var init_AxisGridSection = __esm({
|
|
|
4458
5583
|
|
|
4459
5584
|
// src/stamps/shared/StampLeftPanel/types.ts
|
|
4460
5585
|
var TOOLTIP_DELAY_MS;
|
|
4461
|
-
var
|
|
5586
|
+
var init_types4 = __esm({
|
|
4462
5587
|
"src/stamps/shared/StampLeftPanel/types.ts"() {
|
|
4463
5588
|
TOOLTIP_DELAY_MS = 400;
|
|
4464
5589
|
}
|
|
4465
5590
|
});
|
|
4466
5591
|
function useToolHoverTooltip() {
|
|
4467
|
-
const [hover, setHover] =
|
|
4468
|
-
const [portalReady, setPortalReady] =
|
|
4469
|
-
const hoverTimerRef =
|
|
4470
|
-
|
|
5592
|
+
const [hover, setHover] = React5.useState(null);
|
|
5593
|
+
const [portalReady, setPortalReady] = React5.useState(false);
|
|
5594
|
+
const hoverTimerRef = React5.useRef(null);
|
|
5595
|
+
React5.useEffect(() => {
|
|
4471
5596
|
setPortalReady(true);
|
|
4472
5597
|
return () => {
|
|
4473
5598
|
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
|
|
4474
5599
|
};
|
|
4475
5600
|
}, []);
|
|
4476
|
-
const showHover =
|
|
5601
|
+
const showHover = React5.useCallback((el, t) => {
|
|
4477
5602
|
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
|
|
4478
5603
|
hoverTimerRef.current = setTimeout(() => {
|
|
4479
5604
|
const r = el.getBoundingClientRect();
|
|
4480
5605
|
setHover({ label: t.label, hint: t.hint, x: r.right, y: r.top + r.height / 2 });
|
|
4481
5606
|
}, TOOLTIP_DELAY_MS);
|
|
4482
5607
|
}, []);
|
|
4483
|
-
const hideHover =
|
|
5608
|
+
const hideHover = React5.useCallback(() => {
|
|
4484
5609
|
if (hoverTimerRef.current) {
|
|
4485
5610
|
clearTimeout(hoverTimerRef.current);
|
|
4486
5611
|
hoverTimerRef.current = null;
|
|
@@ -4491,27 +5616,118 @@ function useToolHoverTooltip() {
|
|
|
4491
5616
|
}
|
|
4492
5617
|
var init_useToolHoverTooltip = __esm({
|
|
4493
5618
|
"src/stamps/shared/StampLeftPanel/useToolHoverTooltip.ts"() {
|
|
4494
|
-
|
|
5619
|
+
init_types4();
|
|
4495
5620
|
}
|
|
4496
5621
|
});
|
|
5622
|
+
function normalize(s) {
|
|
5623
|
+
return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/đ/g, "d").replace(/Đ/g, "d");
|
|
5624
|
+
}
|
|
5625
|
+
function SearchIcon() {
|
|
5626
|
+
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: [
|
|
5627
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
5628
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "20", y1: "20", x2: "16.5", y2: "16.5" })
|
|
5629
|
+
] });
|
|
5630
|
+
}
|
|
5631
|
+
function ClearIcon() {
|
|
5632
|
+
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: [
|
|
5633
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
5634
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
|
|
5635
|
+
] });
|
|
5636
|
+
}
|
|
5637
|
+
function ToolResultList(props) {
|
|
5638
|
+
const { tools, activeTool, onToolChange } = props;
|
|
5639
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5", "data-testid": "tool-result-list", children: tools.map((t) => {
|
|
5640
|
+
const active = activeTool === t.key;
|
|
5641
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5642
|
+
"button",
|
|
5643
|
+
{
|
|
5644
|
+
type: "button",
|
|
5645
|
+
"data-tool": t.key,
|
|
5646
|
+
"aria-label": t.label,
|
|
5647
|
+
"aria-pressed": active,
|
|
5648
|
+
onClick: () => onToolChange(t.key),
|
|
5649
|
+
className: [
|
|
5650
|
+
"flex items-center gap-2 rounded-md px-2 py-1.5 text-left transition",
|
|
5651
|
+
active ? "bg-emerald-600 text-white" : "text-slate-700 hover:bg-slate-100"
|
|
5652
|
+
].join(" "),
|
|
5653
|
+
children: [
|
|
5654
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center", children: t.icon }),
|
|
5655
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0", children: [
|
|
5656
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-[12px] font-medium leading-tight", children: t.label }),
|
|
5657
|
+
t.hint && /* @__PURE__ */ jsxRuntime.jsx("span", { className: ["block truncate text-[10px] leading-tight", active ? "text-emerald-50" : "text-slate-400"].join(" "), children: t.hint })
|
|
5658
|
+
] })
|
|
5659
|
+
]
|
|
5660
|
+
},
|
|
5661
|
+
t.key
|
|
5662
|
+
);
|
|
5663
|
+
}) });
|
|
5664
|
+
}
|
|
4497
5665
|
function ToolGrid(props) {
|
|
4498
5666
|
const { tools, groupOrder, groupLabels, activeTool, onToolChange, chord } = props;
|
|
4499
5667
|
const { hover, portalReady, showHover, hideHover } = useToolHoverTooltip();
|
|
4500
|
-
const
|
|
5668
|
+
const [query, setQuery] = React5.useState("");
|
|
5669
|
+
const normalizedQuery = React5.useMemo(() => normalize(query.trim()), [query]);
|
|
5670
|
+
const filteredTools = React5.useMemo(() => {
|
|
5671
|
+
if (!normalizedQuery) return tools;
|
|
5672
|
+
return tools.filter((t) => {
|
|
5673
|
+
if (normalize(t.label).includes(normalizedQuery)) return true;
|
|
5674
|
+
if (t.hint && normalize(t.hint).includes(normalizedQuery)) return true;
|
|
5675
|
+
return false;
|
|
5676
|
+
});
|
|
5677
|
+
}, [tools, normalizedQuery]);
|
|
5678
|
+
const grouped = React5.useMemo(() => {
|
|
4501
5679
|
var _a;
|
|
4502
5680
|
const acc = {};
|
|
4503
|
-
for (const t of
|
|
5681
|
+
for (const t of filteredTools) {
|
|
4504
5682
|
(acc[_a = t.group] ?? (acc[_a] = [])).push(t);
|
|
4505
5683
|
}
|
|
4506
5684
|
return acc;
|
|
4507
|
-
}, [
|
|
4508
|
-
const groupKeys =
|
|
4509
|
-
() => groupOrder.filter((g) => grouped[g]),
|
|
5685
|
+
}, [filteredTools]);
|
|
5686
|
+
const groupKeys = React5.useMemo(
|
|
5687
|
+
() => groupOrder.filter((g) => grouped[g] && grouped[g].length > 0),
|
|
4510
5688
|
[grouped, groupOrder]
|
|
4511
5689
|
);
|
|
4512
|
-
const
|
|
5690
|
+
const noMatch = normalizedQuery !== "" && groupKeys.length === 0;
|
|
4513
5691
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4514
|
-
|
|
5692
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
5693
|
+
/* @__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, {}) }),
|
|
5694
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5695
|
+
"input",
|
|
5696
|
+
{
|
|
5697
|
+
type: "search",
|
|
5698
|
+
value: query,
|
|
5699
|
+
onChange: (e) => setQuery(e.target.value),
|
|
5700
|
+
placeholder: "T\xECm c\xF4ng c\u1EE5\u2026",
|
|
5701
|
+
"aria-label": "T\xECm c\xF4ng c\u1EE5",
|
|
5702
|
+
"data-testid": "tool-search-input",
|
|
5703
|
+
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"
|
|
5704
|
+
}
|
|
5705
|
+
),
|
|
5706
|
+
query && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5707
|
+
"button",
|
|
5708
|
+
{
|
|
5709
|
+
type: "button",
|
|
5710
|
+
onClick: () => setQuery(""),
|
|
5711
|
+
"aria-label": "Xo\xE1 t\xECm ki\u1EBFm",
|
|
5712
|
+
"data-testid": "tool-search-clear",
|
|
5713
|
+
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",
|
|
5714
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ClearIcon, {})
|
|
5715
|
+
}
|
|
5716
|
+
)
|
|
5717
|
+
] }),
|
|
5718
|
+
noMatch && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5719
|
+
"div",
|
|
5720
|
+
{
|
|
5721
|
+
"data-testid": "tool-search-empty",
|
|
5722
|
+
className: "rounded-md border border-dashed border-slate-200 bg-slate-50 px-3 py-4 text-center text-[11px] text-slate-500",
|
|
5723
|
+
children: [
|
|
5724
|
+
"Kh\xF4ng c\xF3 c\xF4ng c\u1EE5 n\xE0o kh\u1EDBp \u201C",
|
|
5725
|
+
query.trim(),
|
|
5726
|
+
"\u201D."
|
|
5727
|
+
]
|
|
5728
|
+
}
|
|
5729
|
+
),
|
|
5730
|
+
normalizedQuery !== "" && !noMatch ? /* @__PURE__ */ jsxRuntime.jsx(ToolResultList, { tools: filteredTools, activeTool, onToolChange }) : groupKeys.map((group) => {
|
|
4515
5731
|
const isChordActive = chord?.activeGroup === group;
|
|
4516
5732
|
const dimmed = chord?.activeGroup != null && !isChordActive;
|
|
4517
5733
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -4525,23 +5741,10 @@ function ToolGrid(props) {
|
|
|
4525
5741
|
dimmed ? "opacity-55" : "opacity-100"
|
|
4526
5742
|
].join(" "),
|
|
4527
5743
|
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) => {
|
|
5744
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-slate-500", children: groupLabels[group] }),
|
|
5745
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 gap-1", children: grouped[group].map((t) => {
|
|
4543
5746
|
const active = activeTool === t.key;
|
|
4544
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
5747
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4545
5748
|
"button",
|
|
4546
5749
|
{
|
|
4547
5750
|
type: "button",
|
|
@@ -4558,20 +5761,7 @@ function ToolGrid(props) {
|
|
|
4558
5761
|
"relative flex h-10 items-center justify-center rounded-md transition",
|
|
4559
5762
|
active ? "bg-emerald-600 text-white shadow-sm" : "text-slate-700 hover:bg-slate-100 hover:text-slate-900"
|
|
4560
5763
|
].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
|
-
]
|
|
5764
|
+
children: t.icon
|
|
4575
5765
|
},
|
|
4576
5766
|
t.key
|
|
4577
5767
|
);
|
|
@@ -4581,22 +5771,6 @@ function ToolGrid(props) {
|
|
|
4581
5771
|
group
|
|
4582
5772
|
);
|
|
4583
5773
|
}),
|
|
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
5774
|
portalReady && hover && typeof document !== "undefined" ? reactDom.createPortal(
|
|
4601
5775
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4602
5776
|
"div",
|
|
@@ -4643,9 +5817,9 @@ function StampLeftPanelDesktop(props) {
|
|
|
4643
5817
|
objects,
|
|
4644
5818
|
tabs
|
|
4645
5819
|
} = props;
|
|
4646
|
-
const [tab, setTab] =
|
|
5820
|
+
const [tab, setTab] = React5.useState("tools");
|
|
4647
5821
|
const hasObjects = !!objects;
|
|
4648
|
-
|
|
5822
|
+
React5.useEffect(() => {
|
|
4649
5823
|
if (!hasObjects && tab === "objects") setTab("tools");
|
|
4650
5824
|
}, [hasObjects, tab]);
|
|
4651
5825
|
const tabSpecs = hasObjects ? [
|
|
@@ -4663,6 +5837,8 @@ function StampLeftPanelDesktop(props) {
|
|
|
4663
5837
|
tabs: tabSpecs,
|
|
4664
5838
|
activeTab: hasObjects ? tab : void 0,
|
|
4665
5839
|
onTabChange: hasObjects ? setTab : void 0,
|
|
5840
|
+
resizable: true,
|
|
5841
|
+
widthStorageKey: "xom11.stamp-left-panel.width",
|
|
4666
5842
|
children: !hasObjects || tab === "tools" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4667
5843
|
/* @__PURE__ */ jsxRuntime.jsx(AxisGridSection, { view, history }),
|
|
4668
5844
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4724,9 +5900,9 @@ function MobileToolDrawer({
|
|
|
4724
5900
|
testId,
|
|
4725
5901
|
objectsTab
|
|
4726
5902
|
}) {
|
|
4727
|
-
const [mobileTab, setMobileTab] =
|
|
4728
|
-
const prevOpenRef =
|
|
4729
|
-
|
|
5903
|
+
const [mobileTab, setMobileTab] = React5__namespace.default.useState("tools");
|
|
5904
|
+
const prevOpenRef = React5__namespace.default.useRef(drawerOpen);
|
|
5905
|
+
React5__namespace.default.useEffect(() => {
|
|
4730
5906
|
if (!prevOpenRef.current && drawerOpen) setMobileTab("tools");
|
|
4731
5907
|
prevOpenRef.current = drawerOpen;
|
|
4732
5908
|
}, [drawerOpen]);
|
|
@@ -4932,7 +6108,7 @@ function StampLeftPanelMobile(props) {
|
|
|
4932
6108
|
drawerOpen,
|
|
4933
6109
|
onDrawerClose
|
|
4934
6110
|
} = props;
|
|
4935
|
-
const groups =
|
|
6111
|
+
const groups = React5.useMemo(() => {
|
|
4936
6112
|
const acc = /* @__PURE__ */ new Map();
|
|
4937
6113
|
for (const t of tools) {
|
|
4938
6114
|
if (!acc.has(t.group)) acc.set(t.group, []);
|
|
@@ -5122,11 +6298,11 @@ function readMatch(query) {
|
|
|
5122
6298
|
}
|
|
5123
6299
|
}
|
|
5124
6300
|
function useIsMobile() {
|
|
5125
|
-
const [state, setState] =
|
|
6301
|
+
const [state, setState] = React5.useState(() => ({
|
|
5126
6302
|
isMobile: readMatch(MOBILE_QUERY),
|
|
5127
6303
|
isTouchOnly: readMatch(NO_HOVER_QUERY)
|
|
5128
6304
|
}));
|
|
5129
|
-
|
|
6305
|
+
React5.useEffect(() => {
|
|
5130
6306
|
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
5131
6307
|
const mql = window.matchMedia(MOBILE_QUERY);
|
|
5132
6308
|
const tql = window.matchMedia(NO_HOVER_QUERY);
|
|
@@ -5152,7 +6328,7 @@ var init_useIsMobile = __esm({
|
|
|
5152
6328
|
}
|
|
5153
6329
|
});
|
|
5154
6330
|
function useStampStore(domain, editingElement, parseInitial) {
|
|
5155
|
-
const ref =
|
|
6331
|
+
const ref = React5.useRef(null);
|
|
5156
6332
|
if (!ref.current) {
|
|
5157
6333
|
const initial = editingElement?.customData ? parseInitial(editingElement.customData) ?? createEmptyState(domain) : createEmptyState(domain);
|
|
5158
6334
|
ref.current = createStore(initial);
|
|
@@ -5251,9 +6427,9 @@ var init_tools = __esm({
|
|
|
5251
6427
|
}
|
|
5252
6428
|
});
|
|
5253
6429
|
function FunctionRow({ obj, store, selected, onClick }) {
|
|
5254
|
-
const [local, setLocal] =
|
|
5255
|
-
const [error, setError] =
|
|
5256
|
-
|
|
6430
|
+
const [local, setLocal] = React5.useState(obj.attrs.expression);
|
|
6431
|
+
const [error, setError] = React5.useState(null);
|
|
6432
|
+
React5.useEffect(() => {
|
|
5257
6433
|
setLocal(obj.attrs.expression);
|
|
5258
6434
|
setError(null);
|
|
5259
6435
|
}, [obj.attrs.expression]);
|
|
@@ -5474,35 +6650,35 @@ var init_host = __esm({
|
|
|
5474
6650
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 3 L3 21 L21 21" }),
|
|
5475
6651
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 14 Q9 8 12 10 Q15 12 18 6" })
|
|
5476
6652
|
] });
|
|
5477
|
-
Graph2DStampHost =
|
|
6653
|
+
Graph2DStampHost = React5.forwardRef(
|
|
5478
6654
|
function Graph2DStampHost2({ api, editingElement, onClose, isDark }, ref) {
|
|
5479
|
-
const panelRef =
|
|
6655
|
+
const panelRef = React5.useRef(null);
|
|
5480
6656
|
const { isMobile } = useIsMobile();
|
|
5481
|
-
const [drawerOpen, setDrawerOpen] =
|
|
6657
|
+
const [drawerOpen, setDrawerOpen] = React5.useState(false);
|
|
5482
6658
|
const sceneStore = useStampStore("graph2d", editingElement, parseInitialState);
|
|
5483
|
-
const [selectedObjectId, setSelectedObjectId] =
|
|
6659
|
+
const [selectedObjectId, setSelectedObjectId] = React5.useState(void 0);
|
|
5484
6660
|
const initialMeta = sceneStore.getState().meta;
|
|
5485
6661
|
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 =
|
|
6662
|
+
const [selectedTool, setSelectedTool] = React5.useState("move");
|
|
6663
|
+
const [showAxis, setShowAxisState] = React5.useState(initialView?.showAxis ?? true);
|
|
6664
|
+
const [showGrid, setShowGridState] = React5.useState(initialView?.showGrid ?? true);
|
|
6665
|
+
const [canUndo, setCanUndo] = React5.useState(false);
|
|
6666
|
+
const [canRedo, setCanRedo] = React5.useState(false);
|
|
6667
|
+
const handleHistoryChange = React5.useCallback((u, r) => {
|
|
5492
6668
|
setCanUndo(u);
|
|
5493
6669
|
setCanRedo(r);
|
|
5494
6670
|
}, []);
|
|
5495
|
-
const handleUndo =
|
|
5496
|
-
const handleRedo =
|
|
5497
|
-
const handleShowAxisChange =
|
|
6671
|
+
const handleUndo = React5.useCallback(() => sceneStore.undo(), [sceneStore]);
|
|
6672
|
+
const handleRedo = React5.useCallback(() => sceneStore.redo(), [sceneStore]);
|
|
6673
|
+
const handleShowAxisChange = React5.useCallback((b) => {
|
|
5498
6674
|
setShowAxisState(b);
|
|
5499
6675
|
sceneStore.dispatch({ type: "UPDATE_VIEW", payload: { patch: { showAxis: b } } });
|
|
5500
6676
|
}, [sceneStore]);
|
|
5501
|
-
const handleShowGridChange =
|
|
6677
|
+
const handleShowGridChange = React5.useCallback((b) => {
|
|
5502
6678
|
setShowGridState(b);
|
|
5503
6679
|
sceneStore.dispatch({ type: "UPDATE_VIEW", payload: { patch: { showGrid: b } } });
|
|
5504
6680
|
}, [sceneStore]);
|
|
5505
|
-
const handleAddFunction =
|
|
6681
|
+
const handleAddFunction = React5.useCallback(() => {
|
|
5506
6682
|
const existing = Object.values(sceneStore.getState().objects).filter((o) => o.kind === "function2d");
|
|
5507
6683
|
const id = `f${existing.length + 1}`;
|
|
5508
6684
|
sceneStore.dispatch({
|
|
@@ -5521,7 +6697,7 @@ var init_host = __esm({
|
|
|
5521
6697
|
}
|
|
5522
6698
|
});
|
|
5523
6699
|
}, [sceneStore]);
|
|
5524
|
-
const handleAddParameter =
|
|
6700
|
+
const handleAddParameter = React5.useCallback(() => {
|
|
5525
6701
|
const existing = Object.values(sceneStore.getState().objects).filter((o) => o.kind === "parameter");
|
|
5526
6702
|
const labels = "abcdefghijklmnopqrstuvwxyz";
|
|
5527
6703
|
const usedLabels = new Set(existing.map((o) => o.label));
|
|
@@ -5549,7 +6725,7 @@ var init_host = __esm({
|
|
|
5549
6725
|
}
|
|
5550
6726
|
});
|
|
5551
6727
|
}, [sceneStore]);
|
|
5552
|
-
const handleInsert =
|
|
6728
|
+
const handleInsert = React5.useCallback(
|
|
5553
6729
|
async (jsonState, svgString) => {
|
|
5554
6730
|
if (!api) return;
|
|
5555
6731
|
try {
|
|
@@ -5569,7 +6745,7 @@ var init_host = __esm({
|
|
|
5569
6745
|
},
|
|
5570
6746
|
[api, editingElement?.id, onClose]
|
|
5571
6747
|
);
|
|
5572
|
-
|
|
6748
|
+
React5.useImperativeHandle(
|
|
5573
6749
|
ref,
|
|
5574
6750
|
() => ({
|
|
5575
6751
|
tryInsert: () => panelRef.current?.insert() ?? false,
|
|
@@ -5577,7 +6753,7 @@ var init_host = __esm({
|
|
|
5577
6753
|
}),
|
|
5578
6754
|
[]
|
|
5579
6755
|
);
|
|
5580
|
-
const renderRow =
|
|
6756
|
+
const renderRow = React5.useMemo(() => makeRenderRow(sceneStore), [sceneStore]);
|
|
5581
6757
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5582
6758
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5583
6759
|
StampLeftPanel,
|
|
@@ -5655,7 +6831,7 @@ init_render();
|
|
|
5655
6831
|
init_types();
|
|
5656
6832
|
init_serialize();
|
|
5657
6833
|
init_svgToStampFile();
|
|
5658
|
-
var Graph2DStampHost3 =
|
|
6834
|
+
var Graph2DStampHost3 = React5.lazy(
|
|
5659
6835
|
() => Promise.resolve().then(() => (init_host(), host_exports)).then((m) => ({ default: m.Graph2DStampHost }))
|
|
5660
6836
|
);
|
|
5661
6837
|
var Graph2DIcon = /* @__PURE__ */ jsxRuntime.jsxs(
|