@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/geometry-3d.js
CHANGED
|
@@ -923,6 +923,31 @@ function constraintRefs2D(c) {
|
|
|
923
923
|
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
924
924
|
case "orthocenter":
|
|
925
925
|
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
926
|
+
case "onPerpendicular":
|
|
927
|
+
return [c.through, c.perpToA, c.perpToB];
|
|
928
|
+
case "onPerpBisector":
|
|
929
|
+
return [c.p1, c.p2];
|
|
930
|
+
case "onCircleAroundPoint":
|
|
931
|
+
return [c.center, c.radiusPoint];
|
|
932
|
+
case "tangentPointExt":
|
|
933
|
+
return [c.from, c.circle];
|
|
934
|
+
case "circleIntersection":
|
|
935
|
+
return [c.c1, c.c2];
|
|
936
|
+
case "circleSecondIntersection":
|
|
937
|
+
return [c.c1, c.c2, c.exclude];
|
|
938
|
+
case "secondIntersection":
|
|
939
|
+
return [c.line, c.circle, c.other];
|
|
940
|
+
case "tangencyPoint":
|
|
941
|
+
return [c.circle, c.onLine];
|
|
942
|
+
case "arcMidpoint":
|
|
943
|
+
return [c.circle, c.a, c.b, c.notContaining];
|
|
944
|
+
case "pointAtDistance": {
|
|
945
|
+
const d = c.distance;
|
|
946
|
+
const extra = d.kind === "circleRadius" ? [d.circle] : d.kind === "segmentLength" ? [d.p1, d.p2] : [];
|
|
947
|
+
return [c.from, c.through, ...extra];
|
|
948
|
+
}
|
|
949
|
+
case "excenter":
|
|
950
|
+
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
926
951
|
default:
|
|
927
952
|
return [];
|
|
928
953
|
}
|
|
@@ -932,7 +957,412 @@ var init_d_constraint2 = __esm({
|
|
|
932
957
|
}
|
|
933
958
|
});
|
|
934
959
|
|
|
935
|
-
// src/core/scene/kinds/point.ts
|
|
960
|
+
// src/core/scene/kinds/point-constraints/_types.ts
|
|
961
|
+
function definePointConstraint(m) {
|
|
962
|
+
return m;
|
|
963
|
+
}
|
|
964
|
+
var init_types2 = __esm({
|
|
965
|
+
"src/core/scene/kinds/point-constraints/_types.ts"() {
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
// src/core/scene/kinds/point-constraints/free.ts
|
|
970
|
+
var freeConstraint;
|
|
971
|
+
var init_free = __esm({
|
|
972
|
+
"src/core/scene/kinds/point-constraints/free.ts"() {
|
|
973
|
+
init_types2();
|
|
974
|
+
freeConstraint = definePointConstraint({
|
|
975
|
+
kind: "free",
|
|
976
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
977
|
+
render: (obj, ctx, c, opts) => {
|
|
978
|
+
const board = ctx.jxg;
|
|
979
|
+
return board.create("point", [c.x, c.y], opts);
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
// src/core/scene/kinds/point-constraints/onAxis.ts
|
|
986
|
+
var onAxisConstraint;
|
|
987
|
+
var init_onAxis = __esm({
|
|
988
|
+
"src/core/scene/kinds/point-constraints/onAxis.ts"() {
|
|
989
|
+
init_types2();
|
|
990
|
+
onAxisConstraint = definePointConstraint({
|
|
991
|
+
kind: "onAxis",
|
|
992
|
+
describe: (obj, _state, c) => `${obj.label} tr\xEAn tr\u1EE5c ${c.axis}`,
|
|
993
|
+
render: (obj, ctx, c, opts) => {
|
|
994
|
+
const board = ctx.jxg;
|
|
995
|
+
const coords = c.axis === "x" ? [c.t, 0] : [0, c.t];
|
|
996
|
+
return board.create("point", coords, opts);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
// src/core/scene/kinds/point-constraints/midpoint.ts
|
|
1003
|
+
var midpointConstraint;
|
|
1004
|
+
var init_midpoint = __esm({
|
|
1005
|
+
"src/core/scene/kinds/point-constraints/midpoint.ts"() {
|
|
1006
|
+
init_types2();
|
|
1007
|
+
midpointConstraint = definePointConstraint({
|
|
1008
|
+
kind: "midpoint",
|
|
1009
|
+
describe: (obj, state, c) => {
|
|
1010
|
+
const l1 = state?.objects[c.p1]?.label ?? c.p1;
|
|
1011
|
+
const l2 = state?.objects[c.p2]?.label ?? c.p2;
|
|
1012
|
+
return `${obj.label} = trung \u0111i\u1EC3m ${l1}${l2}`;
|
|
1013
|
+
},
|
|
1014
|
+
render: (obj, ctx, c, opts) => {
|
|
1015
|
+
const board = ctx.jxg;
|
|
1016
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
1017
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
1018
|
+
return board.create("midpoint", [p1, p2], opts);
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
// src/core/scene/kinds/point-constraints/perpFoot.ts
|
|
1025
|
+
var perpFootConstraint;
|
|
1026
|
+
var init_perpFoot = __esm({
|
|
1027
|
+
"src/core/scene/kinds/point-constraints/perpFoot.ts"() {
|
|
1028
|
+
init_types2();
|
|
1029
|
+
perpFootConstraint = definePointConstraint({
|
|
1030
|
+
kind: "perpFoot",
|
|
1031
|
+
validate: (c) => {
|
|
1032
|
+
if (!c.from || !c.onLine) {
|
|
1033
|
+
throw new Error("point.perpFoot: from v\xE0 onLine b\u1EAFt bu\u1ED9c");
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
describe: (obj, state, c) => {
|
|
1037
|
+
const fromLabel = state?.objects[c.from]?.label ?? c.from;
|
|
1038
|
+
const lineLabel = state?.objects[c.onLine]?.label ?? c.onLine;
|
|
1039
|
+
return `${obj.label} = ch\xE2n \u27C2 t\u1EEB ${fromLabel} xu\u1ED1ng ${lineLabel}`;
|
|
1040
|
+
},
|
|
1041
|
+
render: (obj, ctx, c, opts) => {
|
|
1042
|
+
const board = ctx.jxg;
|
|
1043
|
+
const from = ctx.resolveRef(c.from);
|
|
1044
|
+
const onLine = ctx.resolveRef(c.onLine);
|
|
1045
|
+
return board.create("perpendicularpoint", [onLine, from], opts);
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
// src/core/scene/kinds/point-constraints/circumcenter.ts
|
|
1052
|
+
var circumcenterConstraint;
|
|
1053
|
+
var init_circumcenter = __esm({
|
|
1054
|
+
"src/core/scene/kinds/point-constraints/circumcenter.ts"() {
|
|
1055
|
+
init_types2();
|
|
1056
|
+
circumcenterConstraint = definePointConstraint({
|
|
1057
|
+
kind: "circumcenter",
|
|
1058
|
+
validate: (c) => {
|
|
1059
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1060
|
+
throw new Error("point.circumcenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1061
|
+
}
|
|
1062
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1063
|
+
throw new Error("point.circumcenter: 3 vertex id ph\u1EA3i non-empty");
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
describe: (obj, state, c) => {
|
|
1067
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1068
|
+
return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp \u0394${labels}`;
|
|
1069
|
+
},
|
|
1070
|
+
render: (obj, ctx, c, opts) => {
|
|
1071
|
+
const board = ctx.jxg;
|
|
1072
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1073
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1074
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1075
|
+
return board.create("circumcenter", [a, b, c3], opts);
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
// src/core/scene/kinds/point-constraints/incenter.ts
|
|
1082
|
+
var incenterConstraint;
|
|
1083
|
+
var init_incenter = __esm({
|
|
1084
|
+
"src/core/scene/kinds/point-constraints/incenter.ts"() {
|
|
1085
|
+
init_types2();
|
|
1086
|
+
incenterConstraint = definePointConstraint({
|
|
1087
|
+
kind: "incenter",
|
|
1088
|
+
validate: (c) => {
|
|
1089
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1090
|
+
throw new Error("point.incenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1091
|
+
}
|
|
1092
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1093
|
+
throw new Error("point.incenter: 3 vertex id ph\u1EA3i non-empty");
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
describe: (obj, state, c) => {
|
|
1097
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1098
|
+
return `${obj.label} = t\xE2m n\u1ED9i ti\u1EBFp \u0394${labels}`;
|
|
1099
|
+
},
|
|
1100
|
+
render: (obj, ctx, c, opts) => {
|
|
1101
|
+
const board = ctx.jxg;
|
|
1102
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1103
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1104
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1105
|
+
return board.create("incenter", [a, b, c3], opts);
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
// src/core/scene/kinds/point-constraints/onLine.ts
|
|
1112
|
+
var onLineConstraint;
|
|
1113
|
+
var init_onLine = __esm({
|
|
1114
|
+
"src/core/scene/kinds/point-constraints/onLine.ts"() {
|
|
1115
|
+
init_types2();
|
|
1116
|
+
onLineConstraint = definePointConstraint({
|
|
1117
|
+
kind: "onLine",
|
|
1118
|
+
describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${state?.objects[c.lineId]?.label ?? c.lineId}`,
|
|
1119
|
+
render: (obj, ctx, c, opts) => {
|
|
1120
|
+
const board = ctx.jxg;
|
|
1121
|
+
const line = ctx.resolveRef(c.lineId);
|
|
1122
|
+
const p1 = line.point1;
|
|
1123
|
+
const p2 = line.point2;
|
|
1124
|
+
const sx = p1 && p2 ? p1.X() + c.t * (p2.X() - p1.X()) : c.t;
|
|
1125
|
+
const sy = p1 && p2 ? p1.Y() + c.t * (p2.Y() - p1.Y()) : c.t;
|
|
1126
|
+
return board.create("glider", [sx, sy, line], opts);
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// src/core/scene/kinds/point-constraints/onSegment.ts
|
|
1133
|
+
var onSegmentConstraint;
|
|
1134
|
+
var init_onSegment = __esm({
|
|
1135
|
+
"src/core/scene/kinds/point-constraints/onSegment.ts"() {
|
|
1136
|
+
init_types2();
|
|
1137
|
+
onSegmentConstraint = definePointConstraint({
|
|
1138
|
+
kind: "onSegment",
|
|
1139
|
+
describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111o\u1EA1n ${state?.objects[c.segmentId]?.label ?? c.segmentId}`,
|
|
1140
|
+
render: (obj, ctx, c, opts) => {
|
|
1141
|
+
const board = ctx.jxg;
|
|
1142
|
+
const seg = ctx.resolveRef(c.segmentId);
|
|
1143
|
+
const p1 = seg.point1;
|
|
1144
|
+
const p2 = seg.point2;
|
|
1145
|
+
const sx = p1 && p2 ? p1.X() + c.t * (p2.X() - p1.X()) : c.t;
|
|
1146
|
+
const sy = p1 && p2 ? p1.Y() + c.t * (p2.Y() - p1.Y()) : c.t;
|
|
1147
|
+
return board.create("glider", [sx, sy, seg], opts);
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
// src/core/scene/kinds/point-constraints/onCircle.ts
|
|
1154
|
+
var onCircleConstraint;
|
|
1155
|
+
var init_onCircle = __esm({
|
|
1156
|
+
"src/core/scene/kinds/point-constraints/onCircle.ts"() {
|
|
1157
|
+
init_types2();
|
|
1158
|
+
onCircleConstraint = definePointConstraint({
|
|
1159
|
+
kind: "onCircle",
|
|
1160
|
+
describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n ${state?.objects[c.circleId]?.label ?? c.circleId}`,
|
|
1161
|
+
render: (obj, ctx, c, opts) => {
|
|
1162
|
+
const board = ctx.jxg;
|
|
1163
|
+
const circle = ctx.resolveRef(c.circleId);
|
|
1164
|
+
const O = circle.center ?? circle.midpoint;
|
|
1165
|
+
const ox = O ? O.X() : 0;
|
|
1166
|
+
const oy = O ? O.Y() : 0;
|
|
1167
|
+
return board.create("glider", [ox + Math.cos(c.theta), oy + Math.sin(c.theta), circle], opts);
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
// src/core/scene/kinds/point-constraints/onPolygon.ts
|
|
1174
|
+
var onPolygonConstraint;
|
|
1175
|
+
var init_onPolygon = __esm({
|
|
1176
|
+
"src/core/scene/kinds/point-constraints/onPolygon.ts"() {
|
|
1177
|
+
init_types2();
|
|
1178
|
+
onPolygonConstraint = definePointConstraint({
|
|
1179
|
+
kind: "onPolygon",
|
|
1180
|
+
describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111a gi\xE1c ${state?.objects[c.polygonId]?.label ?? c.polygonId}`,
|
|
1181
|
+
render: (obj, ctx, c, opts) => {
|
|
1182
|
+
const board = ctx.jxg;
|
|
1183
|
+
const poly = ctx.resolveRef(c.polygonId);
|
|
1184
|
+
return board.create("glider", [c.u, c.v, poly], opts);
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
// src/core/scene/kinds/point-constraints/centroid.ts
|
|
1191
|
+
var centroidConstraint;
|
|
1192
|
+
var init_centroid = __esm({
|
|
1193
|
+
"src/core/scene/kinds/point-constraints/centroid.ts"() {
|
|
1194
|
+
init_types2();
|
|
1195
|
+
centroidConstraint = definePointConstraint({
|
|
1196
|
+
kind: "centroid",
|
|
1197
|
+
validate: (c) => {
|
|
1198
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1199
|
+
throw new Error("point.centroid: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1200
|
+
}
|
|
1201
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1202
|
+
throw new Error("point.centroid: 3 vertex id ph\u1EA3i non-empty");
|
|
1203
|
+
}
|
|
1204
|
+
},
|
|
1205
|
+
describe: (obj, state, c) => {
|
|
1206
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1207
|
+
return `${obj.label} = tr\u1ECDng t\xE2m \u0394${labels}`;
|
|
1208
|
+
},
|
|
1209
|
+
render: (obj, ctx, c, opts) => {
|
|
1210
|
+
const board = ctx.jxg;
|
|
1211
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1212
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1213
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1214
|
+
return board.create("point", [
|
|
1215
|
+
() => (a.X() + b.X() + c3.X()) / 3,
|
|
1216
|
+
() => (a.Y() + b.Y() + c3.Y()) / 3
|
|
1217
|
+
], opts);
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
// src/core/scene/kinds/pointConstructions.ts
|
|
1224
|
+
function dist(p, q) {
|
|
1225
|
+
return Math.hypot(p[0] - q[0], p[1] - q[1]);
|
|
1226
|
+
}
|
|
1227
|
+
function sideOf(a, b, p) {
|
|
1228
|
+
return (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
|
|
1229
|
+
}
|
|
1230
|
+
function arcMidpoint(center, radius, a, b, notContaining) {
|
|
1231
|
+
const mcx = (a[0] + b[0]) / 2;
|
|
1232
|
+
const mcy = (a[1] + b[1]) / 2;
|
|
1233
|
+
let ux = mcx - center[0];
|
|
1234
|
+
let uy = mcy - center[1];
|
|
1235
|
+
let len = Math.hypot(ux, uy);
|
|
1236
|
+
if (len < 1e-9) {
|
|
1237
|
+
ux = -(b[1] - a[1]);
|
|
1238
|
+
uy = b[0] - a[0];
|
|
1239
|
+
len = Math.hypot(ux, uy) || 1;
|
|
1240
|
+
}
|
|
1241
|
+
ux /= len;
|
|
1242
|
+
uy /= len;
|
|
1243
|
+
const cand1 = [center[0] + radius * ux, center[1] + radius * uy];
|
|
1244
|
+
const cand2 = [center[0] - radius * ux, center[1] - radius * uy];
|
|
1245
|
+
const sN = sideOf(a, b, notContaining);
|
|
1246
|
+
if (Math.abs(sN) < 1e-9) {
|
|
1247
|
+
return dist(cand1, notContaining) >= dist(cand2, notContaining) ? cand1 : cand2;
|
|
1248
|
+
}
|
|
1249
|
+
const s1 = sideOf(a, b, cand1);
|
|
1250
|
+
return s1 * sN < 0 ? cand1 : cand2;
|
|
1251
|
+
}
|
|
1252
|
+
function excenter(vertices, oppositeIndex) {
|
|
1253
|
+
const [A, B, C] = vertices;
|
|
1254
|
+
const a = dist(B, C);
|
|
1255
|
+
const b = dist(C, A);
|
|
1256
|
+
const c = dist(A, B);
|
|
1257
|
+
const w = [a, b, c];
|
|
1258
|
+
w[oppositeIndex] = -w[oppositeIndex];
|
|
1259
|
+
const sum = w[0] + w[1] + w[2];
|
|
1260
|
+
if (Math.abs(sum) < 1e-9) return A;
|
|
1261
|
+
return [
|
|
1262
|
+
(w[0] * A[0] + w[1] * B[0] + w[2] * C[0]) / sum,
|
|
1263
|
+
(w[0] * A[1] + w[1] * B[1] + w[2] * C[1]) / sum
|
|
1264
|
+
];
|
|
1265
|
+
}
|
|
1266
|
+
function pointAtDistanceCoord(from, through, d) {
|
|
1267
|
+
const dx = through[0] - from[0];
|
|
1268
|
+
const dy = through[1] - from[1];
|
|
1269
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
1270
|
+
return [through[0] + d * dx / len, through[1] + d * dy / len];
|
|
1271
|
+
}
|
|
1272
|
+
function radicalAxisFoot(o1, r1, o2, r2) {
|
|
1273
|
+
const dx = o2[0] - o1[0], dy = o2[1] - o1[1];
|
|
1274
|
+
const d2 = dx * dx + dy * dy;
|
|
1275
|
+
if (d2 < 1e-12) return o1;
|
|
1276
|
+
const t = (d2 + r1 * r1 - r2 * r2) / (2 * d2);
|
|
1277
|
+
return [o1[0] + t * dx, o1[1] + t * dy];
|
|
1278
|
+
}
|
|
1279
|
+
var init_pointConstructions = __esm({
|
|
1280
|
+
"src/core/scene/kinds/pointConstructions.ts"() {
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
// src/core/scene/kinds/point-constraints/arcMidpoint.ts
|
|
1285
|
+
var arcMidpointConstraint;
|
|
1286
|
+
var init_arcMidpoint = __esm({
|
|
1287
|
+
"src/core/scene/kinds/point-constraints/arcMidpoint.ts"() {
|
|
1288
|
+
init_pointConstructions();
|
|
1289
|
+
init_types2();
|
|
1290
|
+
arcMidpointConstraint = definePointConstraint({
|
|
1291
|
+
kind: "arcMidpoint",
|
|
1292
|
+
validate: (c) => {
|
|
1293
|
+
if (!c.circle || !c.a || !c.b || !c.notContaining) {
|
|
1294
|
+
throw new Error("point.arcMidpoint: circle, a, b, notContaining b\u1EAFt bu\u1ED9c");
|
|
1295
|
+
}
|
|
1296
|
+
},
|
|
1297
|
+
describe: (obj, state, c) => {
|
|
1298
|
+
const al = state?.objects[c.a]?.label ?? c.a;
|
|
1299
|
+
const bl = state?.objects[c.b]?.label ?? c.b;
|
|
1300
|
+
const nl = state?.objects[c.notContaining]?.label ?? c.notContaining;
|
|
1301
|
+
return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl} (kh\xF4ng ch\u1EE9a ${nl})`;
|
|
1302
|
+
},
|
|
1303
|
+
render: (obj, ctx, c, opts) => {
|
|
1304
|
+
const board = ctx.jxg;
|
|
1305
|
+
const circle = ctx.resolveRef(c.circle);
|
|
1306
|
+
const A = ctx.resolveRef(c.a);
|
|
1307
|
+
const B = ctx.resolveRef(c.b);
|
|
1308
|
+
const N = ctx.resolveRef(c.notContaining);
|
|
1309
|
+
const O = circle?.center ?? circle?.midpoint ?? circle;
|
|
1310
|
+
const am = () => arcMidpoint(
|
|
1311
|
+
[O.X(), O.Y()],
|
|
1312
|
+
circle.Radius(),
|
|
1313
|
+
[A.X(), A.Y()],
|
|
1314
|
+
[B.X(), B.Y()],
|
|
1315
|
+
[N.X(), N.Y()]
|
|
1316
|
+
);
|
|
1317
|
+
return board.create("point", [() => am()[0], () => am()[1]], opts);
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
// src/core/scene/kinds/point-constraints/excenter.ts
|
|
1324
|
+
var excenterConstraint;
|
|
1325
|
+
var init_excenter = __esm({
|
|
1326
|
+
"src/core/scene/kinds/point-constraints/excenter.ts"() {
|
|
1327
|
+
init_pointConstructions();
|
|
1328
|
+
init_types2();
|
|
1329
|
+
excenterConstraint = definePointConstraint({
|
|
1330
|
+
kind: "excenter",
|
|
1331
|
+
validate: (c) => {
|
|
1332
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1333
|
+
throw new Error("point.excenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1334
|
+
}
|
|
1335
|
+
if (!c.opposite) throw new Error("point.excenter: opposite b\u1EAFt bu\u1ED9c");
|
|
1336
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1337
|
+
throw new Error("point.excenter: 3 vertex id ph\u1EA3i non-empty");
|
|
1338
|
+
}
|
|
1339
|
+
if (!c.vertices.includes(c.opposite)) {
|
|
1340
|
+
throw new Error("point.excenter: opposite ph\u1EA3i l\xE0 m\u1ED9t trong vertices");
|
|
1341
|
+
}
|
|
1342
|
+
},
|
|
1343
|
+
describe: (obj, state, c) => {
|
|
1344
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1345
|
+
const opp = state?.objects[c.opposite]?.label ?? c.opposite;
|
|
1346
|
+
return `${obj.label} = t\xE2m b\xE0ng ti\u1EBFp \u0394${labels} \u0111\u1ED1i di\u1EC7n ${opp}`;
|
|
1347
|
+
},
|
|
1348
|
+
render: (obj, ctx, c, opts) => {
|
|
1349
|
+
const board = ctx.jxg;
|
|
1350
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1351
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1352
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1353
|
+
const oppIdx = c.vertices.indexOf(c.opposite);
|
|
1354
|
+
const idx = oppIdx < 0 ? 0 : oppIdx;
|
|
1355
|
+
const ex = () => excenter(
|
|
1356
|
+
[[a.X(), a.Y()], [b.X(), b.Y()], [c3.X(), c3.Y()]],
|
|
1357
|
+
idx
|
|
1358
|
+
);
|
|
1359
|
+
return board.create("point", [() => ex()[0], () => ex()[1]], opts);
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
// src/core/scene/kinds/point-constraints/shared.ts
|
|
936
1366
|
function buildJxgTransforms(board, ctx, t) {
|
|
937
1367
|
switch (t.kind) {
|
|
938
1368
|
case "translate":
|
|
@@ -959,11 +1389,392 @@ function buildJxgTransforms(board, ctx, t) {
|
|
|
959
1389
|
}
|
|
960
1390
|
}
|
|
961
1391
|
}
|
|
1392
|
+
function makeDistanceFn(ctx, d) {
|
|
1393
|
+
const scale3 = d.scale ?? 1;
|
|
1394
|
+
const offset = d.offset ?? 0;
|
|
1395
|
+
if (d.kind === "literal") {
|
|
1396
|
+
const v = d.value;
|
|
1397
|
+
return () => scale3 * v + offset;
|
|
1398
|
+
}
|
|
1399
|
+
if (d.kind === "segmentLength") {
|
|
1400
|
+
const p = ctx.resolveRef(d.p1);
|
|
1401
|
+
const q = ctx.resolveRef(d.p2);
|
|
1402
|
+
return () => scale3 * Math.hypot(p.X() - q.X(), p.Y() - q.Y()) + offset;
|
|
1403
|
+
}
|
|
1404
|
+
const circle = ctx.resolveRef(d.circle);
|
|
1405
|
+
return () => scale3 * circle.Radius() + offset;
|
|
1406
|
+
}
|
|
1407
|
+
function buildPointOpts(obj) {
|
|
1408
|
+
return {
|
|
1409
|
+
name: obj.label,
|
|
1410
|
+
withLabel: obj.attrs.showLabel ?? true,
|
|
1411
|
+
visible: obj.visible,
|
|
1412
|
+
fixed: obj.locked,
|
|
1413
|
+
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
1414
|
+
fillColor: obj.attrs.color ?? "#1e40af",
|
|
1415
|
+
face: obj.attrs.face ?? "o",
|
|
1416
|
+
size: obj.attrs.size ?? 4
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
var init_shared = __esm({
|
|
1420
|
+
"src/core/scene/kinds/point-constraints/shared.ts"() {
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
|
|
1424
|
+
// src/core/scene/kinds/point-constraints/pointAtDistance.ts
|
|
1425
|
+
var pointAtDistanceConstraint;
|
|
1426
|
+
var init_pointAtDistance = __esm({
|
|
1427
|
+
"src/core/scene/kinds/point-constraints/pointAtDistance.ts"() {
|
|
1428
|
+
init_pointConstructions();
|
|
1429
|
+
init_types2();
|
|
1430
|
+
init_shared();
|
|
1431
|
+
pointAtDistanceConstraint = definePointConstraint({
|
|
1432
|
+
kind: "pointAtDistance",
|
|
1433
|
+
describe: (obj, state, c) => {
|
|
1434
|
+
const fromL = state?.objects[c.from]?.label ?? c.from;
|
|
1435
|
+
const thrL = state?.objects[c.through]?.label ?? c.through;
|
|
1436
|
+
const d = c.distance;
|
|
1437
|
+
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})`;
|
|
1438
|
+
return `${obj.label} = tr\xEAn tia ${fromL}${thrL} k\xE9o d\xE0i, c\xE1ch ${thrL} kho\u1EA3ng ${dLabel}`;
|
|
1439
|
+
},
|
|
1440
|
+
render: (obj, ctx, c, opts) => {
|
|
1441
|
+
const board = ctx.jxg;
|
|
1442
|
+
const A = ctx.resolveRef(c.from);
|
|
1443
|
+
const B = ctx.resolveRef(c.through);
|
|
1444
|
+
const dFn = makeDistanceFn(ctx, c.distance);
|
|
1445
|
+
const pc = () => pointAtDistanceCoord([A.X(), A.Y()], [B.X(), B.Y()], dFn());
|
|
1446
|
+
return board.create("point", [() => pc()[0], () => pc()[1]], opts);
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
// src/core/scene/kinds/point-constraints/circleIntersection.ts
|
|
1453
|
+
var circleIntersectionConstraint;
|
|
1454
|
+
var init_circleIntersection = __esm({
|
|
1455
|
+
"src/core/scene/kinds/point-constraints/circleIntersection.ts"() {
|
|
1456
|
+
init_types2();
|
|
1457
|
+
circleIntersectionConstraint = definePointConstraint({
|
|
1458
|
+
kind: "circleIntersection",
|
|
1459
|
+
// Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
|
|
1460
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
1461
|
+
render: (obj, ctx, c, opts) => {
|
|
1462
|
+
const board = ctx.jxg;
|
|
1463
|
+
const k1 = ctx.resolveRef(c.c1);
|
|
1464
|
+
const k2 = ctx.resolveRef(c.c2);
|
|
1465
|
+
return board.create("intersection", [k1, k2, c.which], opts);
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
// src/core/scene/kinds/point-constraints/circleSecondIntersection.ts
|
|
1472
|
+
var circleSecondIntersectionConstraint;
|
|
1473
|
+
var init_circleSecondIntersection = __esm({
|
|
1474
|
+
"src/core/scene/kinds/point-constraints/circleSecondIntersection.ts"() {
|
|
1475
|
+
init_types2();
|
|
1476
|
+
circleSecondIntersectionConstraint = definePointConstraint({
|
|
1477
|
+
kind: "circleSecondIntersection",
|
|
1478
|
+
// Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
|
|
1479
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
1480
|
+
render: (obj, ctx, c, opts) => {
|
|
1481
|
+
const board = ctx.jxg;
|
|
1482
|
+
const k1 = ctx.resolveRef(c.c1);
|
|
1483
|
+
const k2 = ctx.resolveRef(c.c2);
|
|
1484
|
+
const ex = ctx.resolveRef(c.exclude);
|
|
1485
|
+
return board.create("otherintersection", [k1, k2, ex], opts);
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
// src/core/scene/kinds/point-constraints/secondIntersection.ts
|
|
1492
|
+
var secondIntersectionConstraint;
|
|
1493
|
+
var init_secondIntersection = __esm({
|
|
1494
|
+
"src/core/scene/kinds/point-constraints/secondIntersection.ts"() {
|
|
1495
|
+
init_types2();
|
|
1496
|
+
secondIntersectionConstraint = definePointConstraint({
|
|
1497
|
+
kind: "secondIntersection",
|
|
1498
|
+
// Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
|
|
1499
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
1500
|
+
render: (obj, ctx, c, opts) => {
|
|
1501
|
+
const board = ctx.jxg;
|
|
1502
|
+
const line = ctx.resolveRef(c.line);
|
|
1503
|
+
const circle = ctx.resolveRef(c.circle);
|
|
1504
|
+
const other = ctx.resolveRef(c.other);
|
|
1505
|
+
return board.create("otherintersection", [circle, line, other], opts);
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
// src/core/scene/kinds/point-constraints/tangencyPoint.ts
|
|
1512
|
+
var tangencyPointConstraint;
|
|
1513
|
+
var init_tangencyPoint = __esm({
|
|
1514
|
+
"src/core/scene/kinds/point-constraints/tangencyPoint.ts"() {
|
|
1515
|
+
init_types2();
|
|
1516
|
+
tangencyPointConstraint = definePointConstraint({
|
|
1517
|
+
kind: "tangencyPoint",
|
|
1518
|
+
// Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
|
|
1519
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
1520
|
+
render: (obj, ctx, c, opts) => {
|
|
1521
|
+
const board = ctx.jxg;
|
|
1522
|
+
const circle = ctx.resolveRef(c.circle);
|
|
1523
|
+
const line = ctx.resolveRef(c.onLine);
|
|
1524
|
+
const O = circle?.center ?? circle?.midpoint ?? circle;
|
|
1525
|
+
return board.create("perpendicularpoint", [line, O], opts);
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1531
|
+
// src/core/scene/kinds/point-constraints/transformed.ts
|
|
1532
|
+
var transformedConstraint;
|
|
1533
|
+
var init_transformed = __esm({
|
|
1534
|
+
"src/core/scene/kinds/point-constraints/transformed.ts"() {
|
|
1535
|
+
init_types2();
|
|
1536
|
+
init_shared();
|
|
1537
|
+
transformedConstraint = definePointConstraint({
|
|
1538
|
+
kind: "transformed",
|
|
1539
|
+
describe: (obj, state, c) => {
|
|
1540
|
+
const t = c.transform;
|
|
1541
|
+
const labelRef = (id) => state?.objects[id]?.label ?? id;
|
|
1542
|
+
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)}` : "";
|
|
1543
|
+
return `${obj.label} = \u1EA3nh c\u1EE7a ${labelRef(c.source)} (${op})`;
|
|
1544
|
+
},
|
|
1545
|
+
render: (obj, ctx, c, opts) => {
|
|
1546
|
+
const board = ctx.jxg;
|
|
1547
|
+
const src = ctx.resolveRef(c.source);
|
|
1548
|
+
const transforms = buildJxgTransforms(board, ctx, c.transform);
|
|
1549
|
+
const parent = transforms.length === 1 ? transforms[0] : transforms;
|
|
1550
|
+
const pt = board.create("point", [src, parent], opts);
|
|
1551
|
+
pt._helpers = transforms;
|
|
1552
|
+
return pt;
|
|
1553
|
+
}
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
// src/core/scene/kinds/point-constraints/orthocenter.ts
|
|
1559
|
+
var orthocenterConstraint;
|
|
1560
|
+
var init_orthocenter = __esm({
|
|
1561
|
+
"src/core/scene/kinds/point-constraints/orthocenter.ts"() {
|
|
1562
|
+
init_types2();
|
|
1563
|
+
orthocenterConstraint = definePointConstraint({
|
|
1564
|
+
kind: "orthocenter",
|
|
1565
|
+
validate: (c) => {
|
|
1566
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1567
|
+
throw new Error("point.orthocenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1568
|
+
}
|
|
1569
|
+
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1570
|
+
throw new Error("point.orthocenter: 3 vertex id ph\u1EA3i non-empty");
|
|
1571
|
+
}
|
|
1572
|
+
},
|
|
1573
|
+
describe: (obj, state, c) => {
|
|
1574
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1575
|
+
return `${obj.label} = tr\u1EF1c t\xE2m \u0394${labels}`;
|
|
1576
|
+
},
|
|
1577
|
+
render: (obj, ctx, c, opts) => {
|
|
1578
|
+
const board = ctx.jxg;
|
|
1579
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1580
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1581
|
+
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1582
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
1583
|
+
const lineBC = board.create("line", [b, c3], hide);
|
|
1584
|
+
const altA = board.create("perpendicular", [lineBC, a], hide);
|
|
1585
|
+
const lineAC = board.create("line", [a, c3], hide);
|
|
1586
|
+
const altB = board.create("perpendicular", [lineAC, b], hide);
|
|
1587
|
+
const ortho = board.create("intersection", [altA, altB, 0], opts);
|
|
1588
|
+
ortho._helpers = [lineBC, altA, lineAC, altB];
|
|
1589
|
+
return ortho;
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
});
|
|
1594
|
+
|
|
1595
|
+
// src/core/scene/kinds/point-constraints/onPerpendicular.ts
|
|
1596
|
+
var onPerpendicularConstraint;
|
|
1597
|
+
var init_onPerpendicular = __esm({
|
|
1598
|
+
"src/core/scene/kinds/point-constraints/onPerpendicular.ts"() {
|
|
1599
|
+
init_types2();
|
|
1600
|
+
onPerpendicularConstraint = definePointConstraint({
|
|
1601
|
+
kind: "onPerpendicular",
|
|
1602
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
1603
|
+
render: (obj, ctx, c, opts) => {
|
|
1604
|
+
const board = ctx.jxg;
|
|
1605
|
+
const T = ctx.resolveRef(c.through);
|
|
1606
|
+
const A = ctx.resolveRef(c.perpToA);
|
|
1607
|
+
const B = ctx.resolveRef(c.perpToB);
|
|
1608
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
1609
|
+
const refLine = board.create("line", [A, B], hide);
|
|
1610
|
+
const perpLine = board.create("perpendicular", [refLine, T], hide);
|
|
1611
|
+
const dx = B.X() - A.X();
|
|
1612
|
+
const dy = B.Y() - A.Y();
|
|
1613
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
1614
|
+
const ux = -dy / len;
|
|
1615
|
+
const uy = dx / len;
|
|
1616
|
+
const x0 = T.X() + c.t * ux;
|
|
1617
|
+
const y0 = T.Y() + c.t * uy;
|
|
1618
|
+
const gl = board.create("glider", [x0, y0, perpLine], opts);
|
|
1619
|
+
gl._helpers = [refLine, perpLine];
|
|
1620
|
+
return gl;
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1625
|
+
|
|
1626
|
+
// src/core/scene/kinds/point-constraints/onPerpBisector.ts
|
|
1627
|
+
var onPerpBisectorConstraint;
|
|
1628
|
+
var init_onPerpBisector = __esm({
|
|
1629
|
+
"src/core/scene/kinds/point-constraints/onPerpBisector.ts"() {
|
|
1630
|
+
init_types2();
|
|
1631
|
+
onPerpBisectorConstraint = definePointConstraint({
|
|
1632
|
+
kind: "onPerpBisector",
|
|
1633
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
1634
|
+
render: (obj, ctx, c, opts) => {
|
|
1635
|
+
const board = ctx.jxg;
|
|
1636
|
+
const A = ctx.resolveRef(c.p1);
|
|
1637
|
+
const B = ctx.resolveRef(c.p2);
|
|
1638
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
1639
|
+
const refLine = board.create("line", [A, B], hide);
|
|
1640
|
+
const mid = board.create("midpoint", [A, B], hide);
|
|
1641
|
+
const bisLine = board.create("perpendicular", [refLine, mid], hide);
|
|
1642
|
+
const Mx = (A.X() + B.X()) / 2;
|
|
1643
|
+
const My = (A.Y() + B.Y()) / 2;
|
|
1644
|
+
const dx = B.X() - A.X();
|
|
1645
|
+
const dy = B.Y() - A.Y();
|
|
1646
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
1647
|
+
const ux = -dy / len;
|
|
1648
|
+
const uy = dx / len;
|
|
1649
|
+
const x0 = Mx + c.t * ux;
|
|
1650
|
+
const y0 = My + c.t * uy;
|
|
1651
|
+
const gl = board.create("glider", [x0, y0, bisLine], opts);
|
|
1652
|
+
gl._helpers = [refLine, mid, bisLine];
|
|
1653
|
+
return gl;
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
// src/core/scene/kinds/point-constraints/onCircleAroundPoint.ts
|
|
1660
|
+
var onCircleAroundPointConstraint;
|
|
1661
|
+
var init_onCircleAroundPoint = __esm({
|
|
1662
|
+
"src/core/scene/kinds/point-constraints/onCircleAroundPoint.ts"() {
|
|
1663
|
+
init_types2();
|
|
1664
|
+
onCircleAroundPointConstraint = definePointConstraint({
|
|
1665
|
+
kind: "onCircleAroundPoint",
|
|
1666
|
+
describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
|
|
1667
|
+
render: (obj, ctx, c, opts) => {
|
|
1668
|
+
const board = ctx.jxg;
|
|
1669
|
+
const C = ctx.resolveRef(c.center);
|
|
1670
|
+
const R = ctx.resolveRef(c.radiusPoint);
|
|
1671
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
1672
|
+
const auxCircle = board.create("circle", [C, R], hide);
|
|
1673
|
+
const r = Math.hypot(R.X() - C.X(), R.Y() - C.Y());
|
|
1674
|
+
const x0 = C.X() + r * Math.cos(c.theta);
|
|
1675
|
+
const y0 = C.Y() + r * Math.sin(c.theta);
|
|
1676
|
+
const gl = board.create("glider", [x0, y0, auxCircle], opts);
|
|
1677
|
+
gl._helpers = [auxCircle];
|
|
1678
|
+
return gl;
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
|
|
1684
|
+
// src/core/scene/kinds/point-constraints/tangentPointExt.ts
|
|
1685
|
+
var tangentPointExtConstraint;
|
|
1686
|
+
var init_tangentPointExt = __esm({
|
|
1687
|
+
"src/core/scene/kinds/point-constraints/tangentPointExt.ts"() {
|
|
1688
|
+
init_types2();
|
|
1689
|
+
tangentPointExtConstraint = definePointConstraint({
|
|
1690
|
+
kind: "tangentPointExt",
|
|
1691
|
+
describe: (obj, state, c) => {
|
|
1692
|
+
const fromLabel = state?.objects[c.from]?.label ?? c.from;
|
|
1693
|
+
const circleLabel = state?.objects[c.circle]?.label ?? c.circle;
|
|
1694
|
+
return `${obj.label} = ti\u1EBFp \u0111i\u1EC3m c\u1EE7a (${circleLabel}) v\u1EDBi ti\u1EBFp tuy\u1EBFn t\u1EEB ${fromLabel}`;
|
|
1695
|
+
},
|
|
1696
|
+
render: (obj, ctx, c, opts) => {
|
|
1697
|
+
const board = ctx.jxg;
|
|
1698
|
+
const P = ctx.resolveRef(c.from);
|
|
1699
|
+
const K = ctx.resolveRef(c.circle);
|
|
1700
|
+
const O = K.center ?? K.midpoint;
|
|
1701
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
1702
|
+
const mid = board.create("midpoint", [P, O], hide);
|
|
1703
|
+
const thales = board.create("circle", [mid, P], hide);
|
|
1704
|
+
const inter = board.create("intersection", [thales, K, c.which], opts);
|
|
1705
|
+
inter._helpers = [mid, thales];
|
|
1706
|
+
return inter;
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
});
|
|
1711
|
+
|
|
1712
|
+
// src/core/scene/kinds/point-constraints/registry.ts
|
|
1713
|
+
var ALL, POINT_CONSTRAINTS;
|
|
1714
|
+
var init_registry2 = __esm({
|
|
1715
|
+
"src/core/scene/kinds/point-constraints/registry.ts"() {
|
|
1716
|
+
init_free();
|
|
1717
|
+
init_onAxis();
|
|
1718
|
+
init_midpoint();
|
|
1719
|
+
init_perpFoot();
|
|
1720
|
+
init_circumcenter();
|
|
1721
|
+
init_incenter();
|
|
1722
|
+
init_onLine();
|
|
1723
|
+
init_onSegment();
|
|
1724
|
+
init_onCircle();
|
|
1725
|
+
init_onPolygon();
|
|
1726
|
+
init_centroid();
|
|
1727
|
+
init_arcMidpoint();
|
|
1728
|
+
init_excenter();
|
|
1729
|
+
init_pointAtDistance();
|
|
1730
|
+
init_circleIntersection();
|
|
1731
|
+
init_circleSecondIntersection();
|
|
1732
|
+
init_secondIntersection();
|
|
1733
|
+
init_tangencyPoint();
|
|
1734
|
+
init_transformed();
|
|
1735
|
+
init_orthocenter();
|
|
1736
|
+
init_onPerpendicular();
|
|
1737
|
+
init_onPerpBisector();
|
|
1738
|
+
init_onCircleAroundPoint();
|
|
1739
|
+
init_tangentPointExt();
|
|
1740
|
+
ALL = [
|
|
1741
|
+
freeConstraint,
|
|
1742
|
+
onAxisConstraint,
|
|
1743
|
+
midpointConstraint,
|
|
1744
|
+
perpFootConstraint,
|
|
1745
|
+
circumcenterConstraint,
|
|
1746
|
+
incenterConstraint,
|
|
1747
|
+
onLineConstraint,
|
|
1748
|
+
onSegmentConstraint,
|
|
1749
|
+
onCircleConstraint,
|
|
1750
|
+
onPolygonConstraint,
|
|
1751
|
+
centroidConstraint,
|
|
1752
|
+
arcMidpointConstraint,
|
|
1753
|
+
excenterConstraint,
|
|
1754
|
+
pointAtDistanceConstraint,
|
|
1755
|
+
circleIntersectionConstraint,
|
|
1756
|
+
circleSecondIntersectionConstraint,
|
|
1757
|
+
secondIntersectionConstraint,
|
|
1758
|
+
tangencyPointConstraint,
|
|
1759
|
+
transformedConstraint,
|
|
1760
|
+
orthocenterConstraint,
|
|
1761
|
+
onPerpendicularConstraint,
|
|
1762
|
+
onPerpBisectorConstraint,
|
|
1763
|
+
onCircleAroundPointConstraint,
|
|
1764
|
+
tangentPointExtConstraint
|
|
1765
|
+
];
|
|
1766
|
+
POINT_CONSTRAINTS = new Map(ALL.map((m) => [m.kind, m]));
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
|
|
1770
|
+
// src/core/scene/kinds/point.ts
|
|
962
1771
|
var def3;
|
|
963
1772
|
var init_point = __esm({
|
|
964
1773
|
"src/core/scene/kinds/point.ts"() {
|
|
965
1774
|
init_registry();
|
|
966
1775
|
init_d_constraint2();
|
|
1776
|
+
init_registry2();
|
|
1777
|
+
init_shared();
|
|
967
1778
|
def3 = {
|
|
968
1779
|
type: "point",
|
|
969
1780
|
schemaVersion: 1,
|
|
@@ -973,43 +1784,7 @@ var init_point = __esm({
|
|
|
973
1784
|
throw new Error("point: constraint required");
|
|
974
1785
|
}
|
|
975
1786
|
const c = a.constraint;
|
|
976
|
-
|
|
977
|
-
if (!c.from || !c.onLine) {
|
|
978
|
-
throw new Error("point.perpFoot: from v\xE0 onLine b\u1EAFt bu\u1ED9c");
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
if (c.kind === "circumcenter") {
|
|
982
|
-
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
983
|
-
throw new Error("point.circumcenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
984
|
-
}
|
|
985
|
-
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
986
|
-
throw new Error("point.circumcenter: 3 vertex id ph\u1EA3i non-empty");
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
if (c.kind === "incenter") {
|
|
990
|
-
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
991
|
-
throw new Error("point.incenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
992
|
-
}
|
|
993
|
-
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
994
|
-
throw new Error("point.incenter: 3 vertex id ph\u1EA3i non-empty");
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
if (c.kind === "centroid") {
|
|
998
|
-
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
999
|
-
throw new Error("point.centroid: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1000
|
-
}
|
|
1001
|
-
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1002
|
-
throw new Error("point.centroid: 3 vertex id ph\u1EA3i non-empty");
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
if (c.kind === "orthocenter") {
|
|
1006
|
-
if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
|
|
1007
|
-
throw new Error("point.orthocenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
|
|
1008
|
-
}
|
|
1009
|
-
if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
|
|
1010
|
-
throw new Error("point.orthocenter: 3 vertex id ph\u1EA3i non-empty");
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1787
|
+
POINT_CONSTRAINTS.get(c.kind)?.validate?.(c);
|
|
1013
1788
|
},
|
|
1014
1789
|
dependsOn: (a) => constraintRefs2D(a.constraint),
|
|
1015
1790
|
measure: (obj) => {
|
|
@@ -1024,132 +1799,16 @@ var init_point = __esm({
|
|
|
1024
1799
|
},
|
|
1025
1800
|
describe: (obj, state) => {
|
|
1026
1801
|
const c = obj.attrs.constraint;
|
|
1027
|
-
|
|
1028
|
-
if (
|
|
1029
|
-
if (c.kind === "onLine") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${state?.objects[c.lineId]?.label ?? c.lineId}`;
|
|
1030
|
-
if (c.kind === "onSegment") return `${obj.label} tr\xEAn \u0111o\u1EA1n ${state?.objects[c.segmentId]?.label ?? c.segmentId}`;
|
|
1031
|
-
if (c.kind === "onCircle") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n ${state?.objects[c.circleId]?.label ?? c.circleId}`;
|
|
1032
|
-
if (c.kind === "onPolygon") return `${obj.label} tr\xEAn \u0111a gi\xE1c ${state?.objects[c.polygonId]?.label ?? c.polygonId}`;
|
|
1033
|
-
if (c.kind === "midpoint") {
|
|
1034
|
-
const l1 = state?.objects[c.p1]?.label ?? c.p1;
|
|
1035
|
-
const l2 = state?.objects[c.p2]?.label ?? c.p2;
|
|
1036
|
-
return `${obj.label} = trung \u0111i\u1EC3m ${l1}${l2}`;
|
|
1037
|
-
}
|
|
1038
|
-
if (c.kind === "transformed") {
|
|
1039
|
-
const t = c.transform;
|
|
1040
|
-
const labelRef = (id) => state?.objects[id]?.label ?? id;
|
|
1041
|
-
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)}` : "";
|
|
1042
|
-
return `${obj.label} = \u1EA3nh c\u1EE7a ${labelRef(c.source)} (${op})`;
|
|
1043
|
-
}
|
|
1044
|
-
if (c.kind === "perpFoot") {
|
|
1045
|
-
const fromLabel = state?.objects[c.from]?.label ?? c.from;
|
|
1046
|
-
const lineLabel = state?.objects[c.onLine]?.label ?? c.onLine;
|
|
1047
|
-
return `${obj.label} = ch\xE2n \u27C2 t\u1EEB ${fromLabel} xu\u1ED1ng ${lineLabel}`;
|
|
1048
|
-
}
|
|
1049
|
-
if (c.kind === "circumcenter") {
|
|
1050
|
-
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1051
|
-
return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp \u0394${labels}`;
|
|
1052
|
-
}
|
|
1053
|
-
if (c.kind === "incenter") {
|
|
1054
|
-
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1055
|
-
return `${obj.label} = t\xE2m n\u1ED9i ti\u1EBFp \u0394${labels}`;
|
|
1056
|
-
}
|
|
1057
|
-
if (c.kind === "centroid") {
|
|
1058
|
-
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1059
|
-
return `${obj.label} = tr\u1ECDng t\xE2m \u0394${labels}`;
|
|
1060
|
-
}
|
|
1061
|
-
if (c.kind === "orthocenter") {
|
|
1062
|
-
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1063
|
-
return `${obj.label} = tr\u1EF1c t\xE2m \u0394${labels}`;
|
|
1064
|
-
}
|
|
1802
|
+
const mod = POINT_CONSTRAINTS.get(c.kind);
|
|
1803
|
+
if (mod) return mod.describe(obj, state, c);
|
|
1065
1804
|
return `\u0110i\u1EC3m ${obj.label}`;
|
|
1066
1805
|
},
|
|
1067
1806
|
render: (obj, ctx) => {
|
|
1068
1807
|
const board = ctx.jxg;
|
|
1069
1808
|
const c = obj.attrs.constraint;
|
|
1070
|
-
const opts =
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
visible: obj.visible,
|
|
1074
|
-
fixed: obj.locked,
|
|
1075
|
-
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
1076
|
-
fillColor: obj.attrs.color ?? "#1e40af",
|
|
1077
|
-
face: obj.attrs.face ?? "o",
|
|
1078
|
-
size: obj.attrs.size ?? 4
|
|
1079
|
-
};
|
|
1080
|
-
if (c.kind === "free") return board.create("point", [c.x, c.y], opts);
|
|
1081
|
-
if (c.kind === "onAxis") {
|
|
1082
|
-
const coords = c.axis === "x" ? [c.t, 0] : [0, c.t];
|
|
1083
|
-
return board.create("point", coords, opts);
|
|
1084
|
-
}
|
|
1085
|
-
if (c.kind === "onLine") {
|
|
1086
|
-
const line = ctx.resolveRef(c.lineId);
|
|
1087
|
-
return board.create("glider", [c.t, c.t, line], opts);
|
|
1088
|
-
}
|
|
1089
|
-
if (c.kind === "onSegment") {
|
|
1090
|
-
const seg = ctx.resolveRef(c.segmentId);
|
|
1091
|
-
return board.create("glider", [c.t, c.t, seg], opts);
|
|
1092
|
-
}
|
|
1093
|
-
if (c.kind === "onCircle") {
|
|
1094
|
-
const circle = ctx.resolveRef(c.circleId);
|
|
1095
|
-
return board.create("glider", [Math.cos(c.theta), Math.sin(c.theta), circle], opts);
|
|
1096
|
-
}
|
|
1097
|
-
if (c.kind === "onPolygon") {
|
|
1098
|
-
const poly = ctx.resolveRef(c.polygonId);
|
|
1099
|
-
return board.create("glider", [c.u, c.v, poly], opts);
|
|
1100
|
-
}
|
|
1101
|
-
if (c.kind === "midpoint") {
|
|
1102
|
-
const p1 = ctx.resolveRef(c.p1);
|
|
1103
|
-
const p2 = ctx.resolveRef(c.p2);
|
|
1104
|
-
return board.create("midpoint", [p1, p2], opts);
|
|
1105
|
-
}
|
|
1106
|
-
if (c.kind === "transformed") {
|
|
1107
|
-
const src = ctx.resolveRef(c.source);
|
|
1108
|
-
const transforms = buildJxgTransforms(board, ctx, c.transform);
|
|
1109
|
-
const parent = transforms.length === 1 ? transforms[0] : transforms;
|
|
1110
|
-
const pt = board.create("point", [src, parent], opts);
|
|
1111
|
-
pt._helpers = transforms;
|
|
1112
|
-
return pt;
|
|
1113
|
-
}
|
|
1114
|
-
if (c.kind === "perpFoot") {
|
|
1115
|
-
const from = ctx.resolveRef(c.from);
|
|
1116
|
-
const onLine = ctx.resolveRef(c.onLine);
|
|
1117
|
-
return board.create("perpendicularpoint", [onLine, from], opts);
|
|
1118
|
-
}
|
|
1119
|
-
if (c.kind === "circumcenter") {
|
|
1120
|
-
const a = ctx.resolveRef(c.vertices[0]);
|
|
1121
|
-
const b = ctx.resolveRef(c.vertices[1]);
|
|
1122
|
-
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1123
|
-
return board.create("circumcenter", [a, b, c3], opts);
|
|
1124
|
-
}
|
|
1125
|
-
if (c.kind === "incenter") {
|
|
1126
|
-
const a = ctx.resolveRef(c.vertices[0]);
|
|
1127
|
-
const b = ctx.resolveRef(c.vertices[1]);
|
|
1128
|
-
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1129
|
-
return board.create("incenter", [a, b, c3], opts);
|
|
1130
|
-
}
|
|
1131
|
-
if (c.kind === "centroid") {
|
|
1132
|
-
const a = ctx.resolveRef(c.vertices[0]);
|
|
1133
|
-
const b = ctx.resolveRef(c.vertices[1]);
|
|
1134
|
-
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1135
|
-
return board.create("point", [
|
|
1136
|
-
() => (a.X() + b.X() + c3.X()) / 3,
|
|
1137
|
-
() => (a.Y() + b.Y() + c3.Y()) / 3
|
|
1138
|
-
], opts);
|
|
1139
|
-
}
|
|
1140
|
-
if (c.kind === "orthocenter") {
|
|
1141
|
-
const a = ctx.resolveRef(c.vertices[0]);
|
|
1142
|
-
const b = ctx.resolveRef(c.vertices[1]);
|
|
1143
|
-
const c3 = ctx.resolveRef(c.vertices[2]);
|
|
1144
|
-
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
1145
|
-
const lineBC = board.create("line", [b, c3], hide);
|
|
1146
|
-
const altA = board.create("perpendicular", [lineBC, a], hide);
|
|
1147
|
-
const lineAC = board.create("line", [a, c3], hide);
|
|
1148
|
-
const altB = board.create("perpendicular", [lineAC, b], hide);
|
|
1149
|
-
const ortho = board.create("intersection", [altA, altB, 0], opts);
|
|
1150
|
-
ortho._helpers = [lineBC, altA, lineAC, altB];
|
|
1151
|
-
return ortho;
|
|
1152
|
-
}
|
|
1809
|
+
const opts = buildPointOpts(obj);
|
|
1810
|
+
const mod = POINT_CONSTRAINTS.get(c.kind);
|
|
1811
|
+
if (mod) return mod.render(obj, ctx, c, opts);
|
|
1153
1812
|
return board.create("point", [0, 0], opts);
|
|
1154
1813
|
},
|
|
1155
1814
|
/**
|
|
@@ -1258,6 +1917,10 @@ function constructionRefs(c) {
|
|
|
1258
1917
|
return [c.p1, c.vertex, c.p2];
|
|
1259
1918
|
case "angleBisectorLines":
|
|
1260
1919
|
return [stripBorderSuffix(c.line1), stripBorderSuffix(c.line2)];
|
|
1920
|
+
case "lineThrough":
|
|
1921
|
+
return [...c.points];
|
|
1922
|
+
case "radicalAxis":
|
|
1923
|
+
return [c.circle1, c.circle2];
|
|
1261
1924
|
case "tangent":
|
|
1262
1925
|
return [c.throughPoint, c.toCircle];
|
|
1263
1926
|
}
|
|
@@ -1267,6 +1930,7 @@ var init_line = __esm({
|
|
|
1267
1930
|
"src/core/scene/kinds/line.ts"() {
|
|
1268
1931
|
init_registry();
|
|
1269
1932
|
init_labelOf();
|
|
1933
|
+
init_pointConstructions();
|
|
1270
1934
|
def5 = {
|
|
1271
1935
|
type: "line",
|
|
1272
1936
|
schemaVersion: 1,
|
|
@@ -1291,6 +1955,10 @@ var init_line = __esm({
|
|
|
1291
1955
|
return `${obj.label}: ph\xE2n gi\xE1c g\xF3c ${L(c.p1)}${L(c.vertex)}${L(c.p2)}`;
|
|
1292
1956
|
case "angleBisectorLines":
|
|
1293
1957
|
return `${obj.label}: ph\xE2n gi\xE1c ${L(c.line1)} & ${L(c.line2)} (${c.branch === 0 ? "1" : "2"})`;
|
|
1958
|
+
case "lineThrough":
|
|
1959
|
+
return `${obj.label}: \u0111\u01B0\u1EDDng qua ${c.points.map(L).join("")}`;
|
|
1960
|
+
case "radicalAxis":
|
|
1961
|
+
return `${obj.label}: tr\u1EE5c \u0111\u1EB3ng ph\u01B0\u01A1ng ${L(c.circle1)} & ${L(c.circle2)}`;
|
|
1294
1962
|
case "tangent":
|
|
1295
1963
|
return `${obj.label}: ti\u1EBFp tuy\u1EBFn ${L(c.toCircle)} qua ${L(c.throughPoint)}`;
|
|
1296
1964
|
}
|
|
@@ -1371,6 +2039,47 @@ var init_line = __esm({
|
|
|
1371
2039
|
selected._helpers = [other];
|
|
1372
2040
|
return selected;
|
|
1373
2041
|
}
|
|
2042
|
+
case "lineThrough": {
|
|
2043
|
+
const pts = c.points.map((id) => ctx.resolveRef(id));
|
|
2044
|
+
let bi = 0, bj = 1, best = -1;
|
|
2045
|
+
for (let i = 0; i < pts.length; i++) {
|
|
2046
|
+
for (let j = i + 1; j < pts.length; j++) {
|
|
2047
|
+
const dx = pts[i].X() - pts[j].X();
|
|
2048
|
+
const dy = pts[i].Y() - pts[j].Y();
|
|
2049
|
+
const d = dx * dx + dy * dy;
|
|
2050
|
+
if (d > best) {
|
|
2051
|
+
best = d;
|
|
2052
|
+
bi = i;
|
|
2053
|
+
bj = j;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
return board.create("line", [pts[bi], pts[bj]], {
|
|
2058
|
+
...baseOpts,
|
|
2059
|
+
straightFirst: true,
|
|
2060
|
+
straightLast: true
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
case "radicalAxis": {
|
|
2064
|
+
const k1 = ctx.resolveRef(c.circle1);
|
|
2065
|
+
const k2 = ctx.resolveRef(c.circle2);
|
|
2066
|
+
const o1 = () => [k1.center.X(), k1.center.Y()];
|
|
2067
|
+
const o2 = () => [k2.center.X(), k2.center.Y()];
|
|
2068
|
+
const foot = () => radicalAxisFoot(o1(), k1.Radius(), o2(), k2.Radius());
|
|
2069
|
+
const hide = { visible: false, withLabel: false, fixed: true, name: "" };
|
|
2070
|
+
const f1 = board.create("point", [() => foot()[0], () => foot()[1]], hide);
|
|
2071
|
+
const f2 = board.create("point", [
|
|
2072
|
+
() => foot()[0] - (o2()[1] - o1()[1]),
|
|
2073
|
+
() => foot()[1] + (o2()[0] - o1()[0])
|
|
2074
|
+
], hide);
|
|
2075
|
+
const line = board.create("line", [f1, f2], {
|
|
2076
|
+
...baseOpts,
|
|
2077
|
+
straightFirst: true,
|
|
2078
|
+
straightLast: true
|
|
2079
|
+
});
|
|
2080
|
+
line._helpers = [f1, f2];
|
|
2081
|
+
return line;
|
|
2082
|
+
}
|
|
1374
2083
|
case "tangent": {
|
|
1375
2084
|
const through = ctx.resolveRef(c.throughPoint);
|
|
1376
2085
|
const toCircle = ctx.resolveRef(c.toCircle);
|
|
@@ -1489,10 +2198,29 @@ var init_vector = __esm({
|
|
|
1489
2198
|
});
|
|
1490
2199
|
|
|
1491
2200
|
// src/core/scene/kinds/circle.ts
|
|
2201
|
+
function asConstruction(a) {
|
|
2202
|
+
if (a.construction) return a.construction;
|
|
2203
|
+
const raw = a;
|
|
2204
|
+
if (raw.kind === "incircle" && raw.vertices) {
|
|
2205
|
+
return {
|
|
2206
|
+
kind: "incircle",
|
|
2207
|
+
p1: raw.vertices[0],
|
|
2208
|
+
p2: raw.vertices[1],
|
|
2209
|
+
p3: raw.vertices[2]
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
return void 0;
|
|
2213
|
+
}
|
|
1492
2214
|
function constructionRefs2(c) {
|
|
1493
2215
|
switch (c.kind) {
|
|
1494
2216
|
case "circumscribed":
|
|
1495
2217
|
return [c.p1, c.p2, c.p3];
|
|
2218
|
+
case "incircle":
|
|
2219
|
+
return [c.p1, c.p2, c.p3];
|
|
2220
|
+
case "excircle":
|
|
2221
|
+
return [c.p1, c.p2, c.p3];
|
|
2222
|
+
case "diameter":
|
|
2223
|
+
return [c.p1, c.p2];
|
|
1496
2224
|
}
|
|
1497
2225
|
}
|
|
1498
2226
|
var def8;
|
|
@@ -1500,19 +2228,33 @@ var init_circle = __esm({
|
|
|
1500
2228
|
"src/core/scene/kinds/circle.ts"() {
|
|
1501
2229
|
init_registry();
|
|
1502
2230
|
init_labelOf();
|
|
2231
|
+
init_pointConstructions();
|
|
1503
2232
|
def8 = {
|
|
1504
2233
|
type: "circle",
|
|
1505
2234
|
schemaVersion: 1,
|
|
1506
2235
|
migrate: {},
|
|
1507
2236
|
validate: (a) => {
|
|
1508
|
-
if (a
|
|
2237
|
+
if (asConstruction(a)) return;
|
|
2238
|
+
if (typeof a?.radius === "number") {
|
|
2239
|
+
if (!a.center) throw new Error("circle: center b\u1EAFt bu\u1ED9c khi d\xF9ng radius");
|
|
2240
|
+
if (!(a.radius > 0)) throw new Error("circle: radius ph\u1EA3i > 0");
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
1509
2243
|
if (!a?.center || !a?.surfacePoint) {
|
|
1510
|
-
throw new Error("circle: center v\xE0 surfacePoint b\u1EAFt bu\u1ED9c (ho\u1EB7c construction)");
|
|
2244
|
+
throw new Error("circle: center v\xE0 surfacePoint b\u1EAFt bu\u1ED9c (ho\u1EB7c construction / radius)");
|
|
1511
2245
|
}
|
|
1512
2246
|
},
|
|
1513
|
-
dependsOn: (a) =>
|
|
2247
|
+
dependsOn: (a) => {
|
|
2248
|
+
const c = asConstruction(a);
|
|
2249
|
+
if (c) return constructionRefs2(c);
|
|
2250
|
+
if (typeof a.radius === "number") return [a.center];
|
|
2251
|
+
return [a.center, a.surfacePoint];
|
|
2252
|
+
},
|
|
1514
2253
|
measure: (obj, state) => {
|
|
1515
|
-
if (obj.attrs
|
|
2254
|
+
if (asConstruction(obj.attrs)) return null;
|
|
2255
|
+
if (typeof obj.attrs.radius === "number") {
|
|
2256
|
+
return [{ label: "r", value: obj.attrs.radius }];
|
|
2257
|
+
}
|
|
1516
2258
|
const center = obj.attrs.center ? state.objects[obj.attrs.center] : void 0;
|
|
1517
2259
|
const surface = obj.attrs.surfacePoint ? state.objects[obj.attrs.surfacePoint] : void 0;
|
|
1518
2260
|
if (!center || !surface) return null;
|
|
@@ -1525,17 +2267,31 @@ var init_circle = __esm({
|
|
|
1525
2267
|
},
|
|
1526
2268
|
describe: (obj, state) => {
|
|
1527
2269
|
const L = (id) => labelOf(id, state);
|
|
1528
|
-
const c = obj.attrs
|
|
2270
|
+
const c = asConstruction(obj.attrs);
|
|
1529
2271
|
if (c?.kind === "circumscribed") {
|
|
1530
2272
|
return `\u0110\u01B0\u1EDDng tr\xF2n \u0111i qua ${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
|
|
1531
2273
|
}
|
|
2274
|
+
if (c?.kind === "incircle") {
|
|
2275
|
+
return `\u0110\u01B0\u1EDDng tr\xF2n n\u1ED9i ti\u1EBFp \u0394${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
|
|
2276
|
+
}
|
|
2277
|
+
if (c?.kind === "excircle") {
|
|
2278
|
+
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)}`;
|
|
2279
|
+
}
|
|
2280
|
+
if (c?.kind === "diameter") {
|
|
2281
|
+
return `\u0110\u01B0\u1EDDng tr\xF2n \u0111\u01B0\u1EDDng k\xEDnh ${L(c.p1)}${L(c.p2)}`;
|
|
2282
|
+
}
|
|
2283
|
+
if (typeof obj.attrs.radius === "number") {
|
|
2284
|
+
return `\u0110\u01B0\u1EDDng tr\xF2n t\xE2m ${L(obj.attrs.center)} b\xE1n k\xEDnh ${obj.attrs.radius}`;
|
|
2285
|
+
}
|
|
1532
2286
|
return `\u0110\u01B0\u1EDDng tr\xF2n t\xE2m ${L(obj.attrs.center)} b\xE1n k\xEDnh ${L(obj.attrs.center)}${L(obj.attrs.surfacePoint)}`;
|
|
1533
2287
|
},
|
|
1534
2288
|
render: (obj, ctx) => {
|
|
1535
2289
|
const board = ctx.jxg;
|
|
2290
|
+
const isCenterLabel = (l) => /^[A-Z]['′]?\d*$/u.test(l);
|
|
2291
|
+
const isCenter = isCenterLabel(obj.label);
|
|
1536
2292
|
const baseOpts = {
|
|
1537
2293
|
name: obj.label,
|
|
1538
|
-
withLabel: obj.attrs.showLabel ?? false,
|
|
2294
|
+
withLabel: isCenter ? obj.attrs.showLabel ?? false : true,
|
|
1539
2295
|
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
1540
2296
|
strokeWidth: obj.attrs.width ?? 2,
|
|
1541
2297
|
dash: obj.attrs.dash ?? 0,
|
|
@@ -1543,13 +2299,81 @@ var init_circle = __esm({
|
|
|
1543
2299
|
visible: obj.visible,
|
|
1544
2300
|
fixed: obj.locked
|
|
1545
2301
|
};
|
|
1546
|
-
const c = obj.attrs
|
|
2302
|
+
const c = asConstruction(obj.attrs);
|
|
1547
2303
|
if (c?.kind === "circumscribed") {
|
|
1548
2304
|
const p1 = ctx.resolveRef(c.p1);
|
|
1549
2305
|
const p2 = ctx.resolveRef(c.p2);
|
|
1550
2306
|
const p3 = ctx.resolveRef(c.p3);
|
|
2307
|
+
if (isCenter) {
|
|
2308
|
+
const center2 = board.create("circumcenter", [p1, p2, p3], {
|
|
2309
|
+
visible: obj.visible,
|
|
2310
|
+
withLabel: true,
|
|
2311
|
+
fixed: true,
|
|
2312
|
+
name: obj.label
|
|
2313
|
+
});
|
|
2314
|
+
const circ = board.create("circumcircle", [p1, p2, p3], { ...baseOpts, withLabel: false });
|
|
2315
|
+
circ.center = circ.center ?? center2;
|
|
2316
|
+
circ._helpers = [center2];
|
|
2317
|
+
return circ;
|
|
2318
|
+
}
|
|
1551
2319
|
return board.create("circumcircle", [p1, p2, p3], baseOpts);
|
|
1552
2320
|
}
|
|
2321
|
+
if (c?.kind === "incircle") {
|
|
2322
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
2323
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
2324
|
+
const p3 = ctx.resolveRef(c.p3);
|
|
2325
|
+
if (isCenter) {
|
|
2326
|
+
const center2 = board.create("incenter", [p1, p2, p3], {
|
|
2327
|
+
visible: obj.visible,
|
|
2328
|
+
withLabel: true,
|
|
2329
|
+
fixed: true,
|
|
2330
|
+
name: obj.label
|
|
2331
|
+
});
|
|
2332
|
+
const circ = board.create("incircle", [p1, p2, p3], { ...baseOpts, withLabel: false });
|
|
2333
|
+
circ.center = circ.center ?? center2;
|
|
2334
|
+
circ._helpers = [center2];
|
|
2335
|
+
return circ;
|
|
2336
|
+
}
|
|
2337
|
+
return board.create("incircle", [p1, p2, p3], baseOpts);
|
|
2338
|
+
}
|
|
2339
|
+
if (c?.kind === "excircle") {
|
|
2340
|
+
const P = [ctx.resolveRef(c.p1), ctx.resolveRef(c.p2), ctx.resolveRef(c.p3)];
|
|
2341
|
+
const ids = [c.p1, c.p2, c.p3];
|
|
2342
|
+
const oppIdx = Math.max(0, ids.indexOf(c.opposite));
|
|
2343
|
+
const verts = () => [
|
|
2344
|
+
[P[0].X(), P[0].Y()],
|
|
2345
|
+
[P[1].X(), P[1].Y()],
|
|
2346
|
+
[P[2].X(), P[2].Y()]
|
|
2347
|
+
];
|
|
2348
|
+
const ctr = () => excenter(verts(), oppIdx);
|
|
2349
|
+
const radius = () => {
|
|
2350
|
+
const I = ctr();
|
|
2351
|
+
const others = [0, 1, 2].filter((i) => i !== oppIdx);
|
|
2352
|
+
const v = verts();
|
|
2353
|
+
const a = v[others[0]];
|
|
2354
|
+
const b = v[others[1]];
|
|
2355
|
+
const dx = b[0] - a[0];
|
|
2356
|
+
const dy = b[1] - a[1];
|
|
2357
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
2358
|
+
return Math.abs((I[0] - a[0]) * dy - (I[1] - a[1]) * dx) / len;
|
|
2359
|
+
};
|
|
2360
|
+
const center2 = board.create("point", [() => ctr()[0], () => ctr()[1]], { visible: false, withLabel: false, fixed: true, name: "" });
|
|
2361
|
+
const circ = board.create("circle", [center2, () => radius()], baseOpts);
|
|
2362
|
+
circ._helpers = [center2];
|
|
2363
|
+
return circ;
|
|
2364
|
+
}
|
|
2365
|
+
if (c?.kind === "diameter") {
|
|
2366
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
2367
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
2368
|
+
const center2 = board.create("midpoint", [p1, p2], { visible: false, withLabel: false, fixed: true, name: "" });
|
|
2369
|
+
const circ = board.create("circle", [center2, p2], baseOpts);
|
|
2370
|
+
circ._helpers = [center2];
|
|
2371
|
+
return circ;
|
|
2372
|
+
}
|
|
2373
|
+
if (typeof obj.attrs.radius === "number") {
|
|
2374
|
+
const center2 = ctx.resolveRef(obj.attrs.center);
|
|
2375
|
+
return board.create("circle", [center2, obj.attrs.radius], baseOpts);
|
|
2376
|
+
}
|
|
1553
2377
|
const center = ctx.resolveRef(obj.attrs.center);
|
|
1554
2378
|
const surface = ctx.resolveRef(obj.attrs.surfacePoint);
|
|
1555
2379
|
return board.create("circle", [center, surface], baseOpts);
|
|
@@ -1709,6 +2533,26 @@ function regularVertexLabels(p1Label, p2Label, n) {
|
|
|
1709
2533
|
}
|
|
1710
2534
|
return `${p1Label}${p2Label}\u2026`;
|
|
1711
2535
|
}
|
|
2536
|
+
function specialShapeName(kind) {
|
|
2537
|
+
switch (kind) {
|
|
2538
|
+
case "square":
|
|
2539
|
+
return "H\xECnh vu\xF4ng";
|
|
2540
|
+
case "rectangle":
|
|
2541
|
+
return "H\xECnh ch\u1EEF nh\u1EADt";
|
|
2542
|
+
case "rhombus":
|
|
2543
|
+
return "H\xECnh thoi";
|
|
2544
|
+
case "parallelogram":
|
|
2545
|
+
return "H\xECnh b\xECnh h\xE0nh";
|
|
2546
|
+
case "isoTrapezoid":
|
|
2547
|
+
return "H\xECnh thang c\xE2n";
|
|
2548
|
+
case "isoTriangle":
|
|
2549
|
+
return "Tam gi\xE1c c\xE2n";
|
|
2550
|
+
case "rightTriangle":
|
|
2551
|
+
return "Tam gi\xE1c vu\xF4ng";
|
|
2552
|
+
case "regular":
|
|
2553
|
+
return "";
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
1712
2556
|
var def11;
|
|
1713
2557
|
var init_polygon = __esm({
|
|
1714
2558
|
"src/core/scene/kinds/polygon.ts"() {
|
|
@@ -1720,13 +2564,27 @@ var init_polygon = __esm({
|
|
|
1720
2564
|
migrate: {},
|
|
1721
2565
|
validate: (a) => {
|
|
1722
2566
|
if (a?.construction) {
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
2567
|
+
const c = a.construction;
|
|
2568
|
+
if (c.kind === "regular") {
|
|
2569
|
+
if (!c.p1 || !c.p2) throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
2570
|
+
if (!Number.isFinite(c.n) || c.n < 3) throw new Error("polygon (regular): n \u2265 3");
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
if (c.kind === "square") {
|
|
2574
|
+
if (!c.p1 || !c.p2) throw new Error("polygon (square): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
if (c.kind === "rectangle" || c.kind === "rhombus" || c.kind === "parallelogram" || c.kind === "isoTrapezoid") {
|
|
2578
|
+
if (!c.p1 || !c.p2 || !c.p3) throw new Error(`polygon (${c.kind}): p1, p2, p3 b\u1EAFt bu\u1ED9c`);
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
if (c.kind === "isoTriangle") {
|
|
2582
|
+
if (!c.base1 || !c.base2 || !c.apex) throw new Error("polygon (isoTriangle): base1, base2, apex b\u1EAFt bu\u1ED9c");
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
if (c.kind === "rightTriangle") {
|
|
2586
|
+
if (!c.rightAngle || !c.leg1End || !c.leg2End) throw new Error("polygon (rightTriangle): rightAngle, leg1End, leg2End b\u1EAFt bu\u1ED9c");
|
|
2587
|
+
return;
|
|
1730
2588
|
}
|
|
1731
2589
|
return;
|
|
1732
2590
|
}
|
|
@@ -1735,14 +2593,51 @@ var init_polygon = __esm({
|
|
|
1735
2593
|
}
|
|
1736
2594
|
},
|
|
1737
2595
|
dependsOn: (a) => {
|
|
1738
|
-
|
|
1739
|
-
return [...a.vertices ?? []];
|
|
2596
|
+
const c = a.construction;
|
|
2597
|
+
if (!c) return [...a.vertices ?? []];
|
|
2598
|
+
switch (c.kind) {
|
|
2599
|
+
case "regular":
|
|
2600
|
+
return [c.p1, c.p2];
|
|
2601
|
+
case "square":
|
|
2602
|
+
return [c.p1, c.p2];
|
|
2603
|
+
case "rectangle":
|
|
2604
|
+
case "rhombus":
|
|
2605
|
+
case "parallelogram":
|
|
2606
|
+
case "isoTrapezoid":
|
|
2607
|
+
return [c.p1, c.p2, c.p3];
|
|
2608
|
+
case "isoTriangle":
|
|
2609
|
+
return [c.base1, c.base2, c.apex];
|
|
2610
|
+
case "rightTriangle":
|
|
2611
|
+
return [c.rightAngle, c.leg1End, c.leg2End];
|
|
2612
|
+
}
|
|
1740
2613
|
},
|
|
1741
2614
|
describe: (obj, state) => {
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
2615
|
+
const c = obj.attrs.construction;
|
|
2616
|
+
if (c) {
|
|
2617
|
+
if (c.kind === "regular") {
|
|
2618
|
+
const labels2 = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
|
|
2619
|
+
return `${regularPolygonName(c.n)} ${labels2}`;
|
|
2620
|
+
}
|
|
2621
|
+
const name = specialShapeName(c.kind);
|
|
2622
|
+
let labels = [];
|
|
2623
|
+
switch (c.kind) {
|
|
2624
|
+
case "square":
|
|
2625
|
+
labels = [labelOf(c.p1, state), labelOf(c.p2, state)];
|
|
2626
|
+
break;
|
|
2627
|
+
case "rectangle":
|
|
2628
|
+
case "rhombus":
|
|
2629
|
+
case "parallelogram":
|
|
2630
|
+
case "isoTrapezoid":
|
|
2631
|
+
labels = [labelOf(c.p1, state), labelOf(c.p2, state), labelOf(c.p3, state)];
|
|
2632
|
+
break;
|
|
2633
|
+
case "isoTriangle":
|
|
2634
|
+
labels = [labelOf(c.apex, state), labelOf(c.base1, state), labelOf(c.base2, state)];
|
|
2635
|
+
break;
|
|
2636
|
+
case "rightTriangle":
|
|
2637
|
+
labels = [labelOf(c.rightAngle, state), labelOf(c.leg1End, state), labelOf(c.leg2End, state)];
|
|
2638
|
+
break;
|
|
2639
|
+
}
|
|
2640
|
+
return `${name} ${labels.join("")}`;
|
|
1746
2641
|
}
|
|
1747
2642
|
return `\u0110a gi\xE1c ${(obj.attrs.vertices ?? []).map((id) => labelOf(id, state)).join("")}`;
|
|
1748
2643
|
},
|
|
@@ -1750,22 +2645,85 @@ var init_polygon = __esm({
|
|
|
1750
2645
|
const board = ctx.jxg;
|
|
1751
2646
|
const label = obj.label;
|
|
1752
2647
|
const showValue = obj.attrs.showValue ?? false;
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
2648
|
+
const cons = obj.attrs.construction;
|
|
2649
|
+
const commonAttrs = {
|
|
2650
|
+
name: label,
|
|
2651
|
+
withLabel: obj.attrs.showLabel ?? false,
|
|
2652
|
+
borders: {
|
|
2653
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
2654
|
+
strokeWidth: obj.attrs.width ?? 2
|
|
2655
|
+
},
|
|
2656
|
+
fillColor: obj.attrs.color ?? "#60a5fa",
|
|
2657
|
+
fillOpacity: obj.attrs.fillOpacity ?? 0.15,
|
|
2658
|
+
visible: obj.visible,
|
|
2659
|
+
fixed: obj.locked
|
|
2660
|
+
};
|
|
2661
|
+
if (cons?.kind === "regular") {
|
|
2662
|
+
const p1 = ctx.resolveRef(cons.p1);
|
|
2663
|
+
const p2 = ctx.resolveRef(cons.p2);
|
|
2664
|
+
return board.create("regularpolygon", [p1, p2, cons.n], commonAttrs);
|
|
2665
|
+
}
|
|
2666
|
+
if (cons?.kind === "square") {
|
|
2667
|
+
const p1 = ctx.resolveRef(cons.p1);
|
|
2668
|
+
const p2 = ctx.resolveRef(cons.p2);
|
|
2669
|
+
return board.create("regularpolygon", [p1, p2, 4], commonAttrs);
|
|
2670
|
+
}
|
|
2671
|
+
if (cons?.kind === "rectangle" || cons?.kind === "rhombus" || cons?.kind === "parallelogram") {
|
|
2672
|
+
const A = ctx.resolveRef(cons.p1);
|
|
2673
|
+
const B = ctx.resolveRef(cons.p2);
|
|
2674
|
+
const C = ctx.resolveRef(cons.p3);
|
|
2675
|
+
const D = board.create(
|
|
2676
|
+
"point",
|
|
2677
|
+
[() => A.X() + C.X() - B.X(), () => A.Y() + C.Y() - B.Y()],
|
|
2678
|
+
{ visible: false, withLabel: false, fixed: true, name: "" }
|
|
2679
|
+
);
|
|
2680
|
+
const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
|
|
2681
|
+
poly2._helpers = [D];
|
|
2682
|
+
return poly2;
|
|
2683
|
+
}
|
|
2684
|
+
if (cons?.kind === "isoTrapezoid") {
|
|
2685
|
+
const A = ctx.resolveRef(cons.p1);
|
|
2686
|
+
const B = ctx.resolveRef(cons.p2);
|
|
2687
|
+
const C = ctx.resolveRef(cons.p3);
|
|
2688
|
+
const Dx = () => {
|
|
2689
|
+
const Mx = (A.X() + B.X()) / 2;
|
|
2690
|
+
const My = (A.Y() + B.Y()) / 2;
|
|
2691
|
+
const ux = B.X() - A.X();
|
|
2692
|
+
const uy = B.Y() - A.Y();
|
|
2693
|
+
const len2 = ux * ux + uy * uy || 1;
|
|
2694
|
+
const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
|
|
2695
|
+
return C.X() - 2 * proj * ux;
|
|
2696
|
+
};
|
|
2697
|
+
const Dy = () => {
|
|
2698
|
+
const Mx = (A.X() + B.X()) / 2;
|
|
2699
|
+
const My = (A.Y() + B.Y()) / 2;
|
|
2700
|
+
const ux = B.X() - A.X();
|
|
2701
|
+
const uy = B.Y() - A.Y();
|
|
2702
|
+
const len2 = ux * ux + uy * uy || 1;
|
|
2703
|
+
const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
|
|
2704
|
+
return C.Y() - 2 * proj * uy;
|
|
2705
|
+
};
|
|
2706
|
+
const D = board.create("point", [Dx, Dy], {
|
|
2707
|
+
visible: false,
|
|
2708
|
+
withLabel: false,
|
|
2709
|
+
fixed: true,
|
|
2710
|
+
name: ""
|
|
1768
2711
|
});
|
|
2712
|
+
const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
|
|
2713
|
+
poly2._helpers = [D];
|
|
2714
|
+
return poly2;
|
|
2715
|
+
}
|
|
2716
|
+
if (cons?.kind === "isoTriangle") {
|
|
2717
|
+
const Apex = ctx.resolveRef(cons.apex);
|
|
2718
|
+
const B1 = ctx.resolveRef(cons.base1);
|
|
2719
|
+
const B2 = ctx.resolveRef(cons.base2);
|
|
2720
|
+
return board.create("polygon", [Apex, B1, B2], commonAttrs);
|
|
2721
|
+
}
|
|
2722
|
+
if (cons?.kind === "rightTriangle") {
|
|
2723
|
+
const R = ctx.resolveRef(cons.rightAngle);
|
|
2724
|
+
const P = ctx.resolveRef(cons.leg1End);
|
|
2725
|
+
const Q = ctx.resolveRef(cons.leg2End);
|
|
2726
|
+
return board.create("polygon", [R, P, Q], commonAttrs);
|
|
1769
2727
|
}
|
|
1770
2728
|
const verts = (obj.attrs.vertices ?? []).map((id) => ctx.resolveRef(id));
|
|
1771
2729
|
const poly = board.create("polygon", verts, {
|
|
@@ -2464,7 +3422,7 @@ var init_serialize = __esm({
|
|
|
2464
3422
|
|
|
2465
3423
|
// src/core/scene/render/types.ts
|
|
2466
3424
|
var DEFAULT_THEME_3D;
|
|
2467
|
-
var
|
|
3425
|
+
var init_types3 = __esm({
|
|
2468
3426
|
"src/core/scene/render/types.ts"() {
|
|
2469
3427
|
DEFAULT_THEME_3D = {
|
|
2470
3428
|
point: { size: 4, color: "#1e40af" },
|
|
@@ -2479,7 +3437,7 @@ var JxgRenderer3D;
|
|
|
2479
3437
|
var init_JxgRenderer3D = __esm({
|
|
2480
3438
|
"src/core/scene/render/JxgRenderer3D.ts"() {
|
|
2481
3439
|
init_registry();
|
|
2482
|
-
|
|
3440
|
+
init_types3();
|
|
2483
3441
|
JxgRenderer3D = class {
|
|
2484
3442
|
constructor(store, view, options = {}) {
|
|
2485
3443
|
this.elements = /* @__PURE__ */ new Map();
|
|
@@ -5499,6 +6457,20 @@ var init_EditorPanel = __esm({
|
|
|
5499
6457
|
);
|
|
5500
6458
|
}
|
|
5501
6459
|
});
|
|
6460
|
+
function clamp(n, min, max) {
|
|
6461
|
+
return Math.max(min, Math.min(max, n));
|
|
6462
|
+
}
|
|
6463
|
+
function readStoredWidth(key, fallback, min, max) {
|
|
6464
|
+
if (!key || typeof window === "undefined") return fallback;
|
|
6465
|
+
try {
|
|
6466
|
+
const raw = window.localStorage.getItem(key);
|
|
6467
|
+
if (!raw) return fallback;
|
|
6468
|
+
const n = parseInt(raw, 10);
|
|
6469
|
+
if (Number.isFinite(n)) return clamp(n, min, max);
|
|
6470
|
+
} catch {
|
|
6471
|
+
}
|
|
6472
|
+
return fallback;
|
|
6473
|
+
}
|
|
5502
6474
|
function CloseIcon() {
|
|
5503
6475
|
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: [
|
|
5504
6476
|
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
@@ -5506,8 +6478,64 @@ function CloseIcon() {
|
|
|
5506
6478
|
] });
|
|
5507
6479
|
}
|
|
5508
6480
|
function LeftPanelShell(props) {
|
|
5509
|
-
const {
|
|
6481
|
+
const {
|
|
6482
|
+
title,
|
|
6483
|
+
icon,
|
|
6484
|
+
onClose,
|
|
6485
|
+
isDark,
|
|
6486
|
+
tabs,
|
|
6487
|
+
activeTab,
|
|
6488
|
+
onTabChange,
|
|
6489
|
+
testId,
|
|
6490
|
+
resizable,
|
|
6491
|
+
widthStorageKey,
|
|
6492
|
+
defaultWidth,
|
|
6493
|
+
minWidth,
|
|
6494
|
+
maxWidth,
|
|
6495
|
+
children
|
|
6496
|
+
} = props;
|
|
5510
6497
|
const showTabs = !!tabs && tabs.length >= 2;
|
|
6498
|
+
const min = minWidth ?? FALLBACK_MIN_WIDTH;
|
|
6499
|
+
const max = maxWidth ?? FALLBACK_MAX_WIDTH;
|
|
6500
|
+
const initial = clamp(defaultWidth ?? FALLBACK_DEFAULT_WIDTH, min, max);
|
|
6501
|
+
const [width, setWidth] = React5__namespace.useState(
|
|
6502
|
+
() => resizable ? readStoredWidth(widthStorageKey, initial, min, max) : initial
|
|
6503
|
+
);
|
|
6504
|
+
const widthRef = React5__namespace.useRef(width);
|
|
6505
|
+
widthRef.current = width;
|
|
6506
|
+
React5__namespace.useEffect(() => {
|
|
6507
|
+
if (!resizable || !widthStorageKey || typeof window === "undefined") return;
|
|
6508
|
+
try {
|
|
6509
|
+
window.localStorage.setItem(widthStorageKey, String(width));
|
|
6510
|
+
} catch {
|
|
6511
|
+
}
|
|
6512
|
+
}, [resizable, widthStorageKey, width]);
|
|
6513
|
+
const onResizeStart = React5__namespace.useCallback(
|
|
6514
|
+
(e) => {
|
|
6515
|
+
if (!resizable) return;
|
|
6516
|
+
e.preventDefault();
|
|
6517
|
+
const startX = e.clientX;
|
|
6518
|
+
const startW = widthRef.current;
|
|
6519
|
+
const onMove = (ev) => {
|
|
6520
|
+
setWidth(clamp(startW + (ev.clientX - startX), min, max));
|
|
6521
|
+
};
|
|
6522
|
+
const onUp = () => {
|
|
6523
|
+
window.removeEventListener("mousemove", onMove);
|
|
6524
|
+
window.removeEventListener("mouseup", onUp);
|
|
6525
|
+
document.body.style.cursor = "";
|
|
6526
|
+
document.body.style.userSelect = "";
|
|
6527
|
+
};
|
|
6528
|
+
window.addEventListener("mousemove", onMove);
|
|
6529
|
+
window.addEventListener("mouseup", onUp);
|
|
6530
|
+
document.body.style.cursor = "ew-resize";
|
|
6531
|
+
document.body.style.userSelect = "none";
|
|
6532
|
+
},
|
|
6533
|
+
[resizable, min, max]
|
|
6534
|
+
);
|
|
6535
|
+
const onResizeDoubleClick = React5__namespace.useCallback(() => {
|
|
6536
|
+
if (!resizable) return;
|
|
6537
|
+
setWidth(initial);
|
|
6538
|
+
}, [resizable, initial]);
|
|
5511
6539
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5512
6540
|
"aside",
|
|
5513
6541
|
{
|
|
@@ -5515,10 +6543,12 @@ function LeftPanelShell(props) {
|
|
|
5515
6543
|
"aria-label": title,
|
|
5516
6544
|
"data-testid": testId ?? "left-panel",
|
|
5517
6545
|
"data-stamp-area": "true",
|
|
6546
|
+
style: resizable ? { width: `${width}px` } : void 0,
|
|
5518
6547
|
className: [
|
|
5519
6548
|
isDark ? "theme--dark " : "",
|
|
5520
|
-
"absolute left-0 top-0 z-30 flex h-full
|
|
5521
|
-
|
|
6549
|
+
"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",
|
|
6550
|
+
resizable ? "" : "w-60"
|
|
6551
|
+
].join(" "),
|
|
5522
6552
|
children: [
|
|
5523
6553
|
/* @__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: [
|
|
5524
6554
|
/* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "flex items-center gap-2 text-sm font-semibold text-slate-800", children: [
|
|
@@ -5553,6 +6583,20 @@ function LeftPanelShell(props) {
|
|
|
5553
6583
|
className: "min-h-0 flex-1 overflow-y-auto p-3 space-y-3",
|
|
5554
6584
|
children
|
|
5555
6585
|
}
|
|
6586
|
+
),
|
|
6587
|
+
resizable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6588
|
+
"div",
|
|
6589
|
+
{
|
|
6590
|
+
role: "separator",
|
|
6591
|
+
"aria-orientation": "vertical",
|
|
6592
|
+
"aria-label": "K\xE9o \u0111\u1EC3 \u0111\u1ED5i r\u1ED9ng panel",
|
|
6593
|
+
"data-testid": "left-panel-resizer",
|
|
6594
|
+
onMouseDown: onResizeStart,
|
|
6595
|
+
onDoubleClick: onResizeDoubleClick,
|
|
6596
|
+
className: "group absolute right-0 top-0 z-40 h-full w-1.5 -mr-0.5 cursor-ew-resize select-none",
|
|
6597
|
+
title: "K\xE9o \u0111\u1EC3 \u0111\u1ED5i r\u1ED9ng (double-click \u0111\u1EC3 reset)",
|
|
6598
|
+
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" })
|
|
6599
|
+
}
|
|
5556
6600
|
)
|
|
5557
6601
|
]
|
|
5558
6602
|
}
|
|
@@ -5582,9 +6626,13 @@ function Section(props) {
|
|
|
5582
6626
|
props.children
|
|
5583
6627
|
] });
|
|
5584
6628
|
}
|
|
6629
|
+
var FALLBACK_DEFAULT_WIDTH, FALLBACK_MIN_WIDTH, FALLBACK_MAX_WIDTH;
|
|
5585
6630
|
var init_LeftPanelShell = __esm({
|
|
5586
6631
|
"src/core/scene/ui/LeftPanelShell.tsx"() {
|
|
5587
6632
|
"use client";
|
|
6633
|
+
FALLBACK_DEFAULT_WIDTH = 240;
|
|
6634
|
+
FALLBACK_MIN_WIDTH = 220;
|
|
6635
|
+
FALLBACK_MAX_WIDTH = 480;
|
|
5588
6636
|
}
|
|
5589
6637
|
});
|
|
5590
6638
|
|
|
@@ -5936,7 +6984,7 @@ var init_AxisGridSection = __esm({
|
|
|
5936
6984
|
|
|
5937
6985
|
// src/stamps/shared/StampLeftPanel/types.ts
|
|
5938
6986
|
var TOOLTIP_DELAY_MS;
|
|
5939
|
-
var
|
|
6987
|
+
var init_types4 = __esm({
|
|
5940
6988
|
"src/stamps/shared/StampLeftPanel/types.ts"() {
|
|
5941
6989
|
TOOLTIP_DELAY_MS = 400;
|
|
5942
6990
|
}
|
|
@@ -5969,27 +7017,118 @@ function useToolHoverTooltip() {
|
|
|
5969
7017
|
}
|
|
5970
7018
|
var init_useToolHoverTooltip = __esm({
|
|
5971
7019
|
"src/stamps/shared/StampLeftPanel/useToolHoverTooltip.ts"() {
|
|
5972
|
-
|
|
7020
|
+
init_types4();
|
|
5973
7021
|
}
|
|
5974
7022
|
});
|
|
7023
|
+
function normalize2(s) {
|
|
7024
|
+
return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/đ/g, "d").replace(/Đ/g, "d");
|
|
7025
|
+
}
|
|
7026
|
+
function SearchIcon() {
|
|
7027
|
+
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: [
|
|
7028
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
7029
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "20", y1: "20", x2: "16.5", y2: "16.5" })
|
|
7030
|
+
] });
|
|
7031
|
+
}
|
|
7032
|
+
function ClearIcon() {
|
|
7033
|
+
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: [
|
|
7034
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
7035
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
|
|
7036
|
+
] });
|
|
7037
|
+
}
|
|
7038
|
+
function ToolResultList(props) {
|
|
7039
|
+
const { tools, activeTool, onToolChange } = props;
|
|
7040
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5", "data-testid": "tool-result-list", children: tools.map((t) => {
|
|
7041
|
+
const active = activeTool === t.key;
|
|
7042
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
7043
|
+
"button",
|
|
7044
|
+
{
|
|
7045
|
+
type: "button",
|
|
7046
|
+
"data-tool": t.key,
|
|
7047
|
+
"aria-label": t.label,
|
|
7048
|
+
"aria-pressed": active,
|
|
7049
|
+
onClick: () => onToolChange(t.key),
|
|
7050
|
+
className: [
|
|
7051
|
+
"flex items-center gap-2 rounded-md px-2 py-1.5 text-left transition",
|
|
7052
|
+
active ? "bg-emerald-600 text-white" : "text-slate-700 hover:bg-slate-100"
|
|
7053
|
+
].join(" "),
|
|
7054
|
+
children: [
|
|
7055
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center", children: t.icon }),
|
|
7056
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0", children: [
|
|
7057
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-[12px] font-medium leading-tight", children: t.label }),
|
|
7058
|
+
t.hint && /* @__PURE__ */ jsxRuntime.jsx("span", { className: ["block truncate text-[10px] leading-tight", active ? "text-emerald-50" : "text-slate-400"].join(" "), children: t.hint })
|
|
7059
|
+
] })
|
|
7060
|
+
]
|
|
7061
|
+
},
|
|
7062
|
+
t.key
|
|
7063
|
+
);
|
|
7064
|
+
}) });
|
|
7065
|
+
}
|
|
5975
7066
|
function ToolGrid(props) {
|
|
5976
7067
|
const { tools, groupOrder, groupLabels, activeTool, onToolChange, chord } = props;
|
|
5977
7068
|
const { hover, portalReady, showHover, hideHover } = useToolHoverTooltip();
|
|
7069
|
+
const [query, setQuery] = React5.useState("");
|
|
7070
|
+
const normalizedQuery = React5.useMemo(() => normalize2(query.trim()), [query]);
|
|
7071
|
+
const filteredTools = React5.useMemo(() => {
|
|
7072
|
+
if (!normalizedQuery) return tools;
|
|
7073
|
+
return tools.filter((t) => {
|
|
7074
|
+
if (normalize2(t.label).includes(normalizedQuery)) return true;
|
|
7075
|
+
if (t.hint && normalize2(t.hint).includes(normalizedQuery)) return true;
|
|
7076
|
+
return false;
|
|
7077
|
+
});
|
|
7078
|
+
}, [tools, normalizedQuery]);
|
|
5978
7079
|
const grouped = React5.useMemo(() => {
|
|
5979
7080
|
var _a;
|
|
5980
7081
|
const acc = {};
|
|
5981
|
-
for (const t of
|
|
7082
|
+
for (const t of filteredTools) {
|
|
5982
7083
|
(acc[_a = t.group] ?? (acc[_a] = [])).push(t);
|
|
5983
7084
|
}
|
|
5984
7085
|
return acc;
|
|
5985
|
-
}, [
|
|
7086
|
+
}, [filteredTools]);
|
|
5986
7087
|
const groupKeys = React5.useMemo(
|
|
5987
|
-
() => groupOrder.filter((g) => grouped[g]),
|
|
7088
|
+
() => groupOrder.filter((g) => grouped[g] && grouped[g].length > 0),
|
|
5988
7089
|
[grouped, groupOrder]
|
|
5989
7090
|
);
|
|
5990
|
-
const
|
|
7091
|
+
const noMatch = normalizedQuery !== "" && groupKeys.length === 0;
|
|
5991
7092
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5992
|
-
|
|
7093
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
7094
|
+
/* @__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, {}) }),
|
|
7095
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
7096
|
+
"input",
|
|
7097
|
+
{
|
|
7098
|
+
type: "search",
|
|
7099
|
+
value: query,
|
|
7100
|
+
onChange: (e) => setQuery(e.target.value),
|
|
7101
|
+
placeholder: "T\xECm c\xF4ng c\u1EE5\u2026",
|
|
7102
|
+
"aria-label": "T\xECm c\xF4ng c\u1EE5",
|
|
7103
|
+
"data-testid": "tool-search-input",
|
|
7104
|
+
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"
|
|
7105
|
+
}
|
|
7106
|
+
),
|
|
7107
|
+
query && /* @__PURE__ */ jsxRuntime.jsx(
|
|
7108
|
+
"button",
|
|
7109
|
+
{
|
|
7110
|
+
type: "button",
|
|
7111
|
+
onClick: () => setQuery(""),
|
|
7112
|
+
"aria-label": "Xo\xE1 t\xECm ki\u1EBFm",
|
|
7113
|
+
"data-testid": "tool-search-clear",
|
|
7114
|
+
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",
|
|
7115
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ClearIcon, {})
|
|
7116
|
+
}
|
|
7117
|
+
)
|
|
7118
|
+
] }),
|
|
7119
|
+
noMatch && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
7120
|
+
"div",
|
|
7121
|
+
{
|
|
7122
|
+
"data-testid": "tool-search-empty",
|
|
7123
|
+
className: "rounded-md border border-dashed border-slate-200 bg-slate-50 px-3 py-4 text-center text-[11px] text-slate-500",
|
|
7124
|
+
children: [
|
|
7125
|
+
"Kh\xF4ng c\xF3 c\xF4ng c\u1EE5 n\xE0o kh\u1EDBp \u201C",
|
|
7126
|
+
query.trim(),
|
|
7127
|
+
"\u201D."
|
|
7128
|
+
]
|
|
7129
|
+
}
|
|
7130
|
+
),
|
|
7131
|
+
normalizedQuery !== "" && !noMatch ? /* @__PURE__ */ jsxRuntime.jsx(ToolResultList, { tools: filteredTools, activeTool, onToolChange }) : groupKeys.map((group) => {
|
|
5993
7132
|
const isChordActive = chord?.activeGroup === group;
|
|
5994
7133
|
const dimmed = chord?.activeGroup != null && !isChordActive;
|
|
5995
7134
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -6003,23 +7142,10 @@ function ToolGrid(props) {
|
|
|
6003
7142
|
dimmed ? "opacity-55" : "opacity-100"
|
|
6004
7143
|
].join(" "),
|
|
6005
7144
|
children: [
|
|
6006
|
-
/* @__PURE__ */ jsxRuntime.
|
|
6007
|
-
|
|
6008
|
-
chord && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6009
|
-
"span",
|
|
6010
|
-
{
|
|
6011
|
-
"data-testid": `chord-letter-${group}`,
|
|
6012
|
-
className: [
|
|
6013
|
-
"font-mono text-[10px] leading-none transition",
|
|
6014
|
-
isChordActive ? "text-emerald-700 font-bold" : "text-slate-400"
|
|
6015
|
-
].join(" "),
|
|
6016
|
-
children: chord.letterForGroup(group)
|
|
6017
|
-
}
|
|
6018
|
-
)
|
|
6019
|
-
] }),
|
|
6020
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 gap-1", children: grouped[group].map((t, i) => {
|
|
7145
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-slate-500", children: groupLabels[group] }),
|
|
7146
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 gap-1", children: grouped[group].map((t) => {
|
|
6021
7147
|
const active = activeTool === t.key;
|
|
6022
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
7148
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
6023
7149
|
"button",
|
|
6024
7150
|
{
|
|
6025
7151
|
type: "button",
|
|
@@ -6036,20 +7162,7 @@ function ToolGrid(props) {
|
|
|
6036
7162
|
"relative flex h-10 items-center justify-center rounded-md transition",
|
|
6037
7163
|
active ? "bg-emerald-600 text-white shadow-sm" : "text-slate-700 hover:bg-slate-100 hover:text-slate-900"
|
|
6038
7164
|
].join(" "),
|
|
6039
|
-
children:
|
|
6040
|
-
t.icon,
|
|
6041
|
-
chord && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6042
|
-
"span",
|
|
6043
|
-
{
|
|
6044
|
-
"data-testid": `chord-num-${t.key}`,
|
|
6045
|
-
className: [
|
|
6046
|
-
"pointer-events-none absolute bottom-0 right-0.5 font-mono text-[9px] leading-none transition",
|
|
6047
|
-
active ? "text-white/70" : isChordActive ? "text-emerald-700 font-bold" : "text-slate-400"
|
|
6048
|
-
].join(" "),
|
|
6049
|
-
children: i + 1
|
|
6050
|
-
}
|
|
6051
|
-
)
|
|
6052
|
-
]
|
|
7165
|
+
children: t.icon
|
|
6053
7166
|
},
|
|
6054
7167
|
t.key
|
|
6055
7168
|
);
|
|
@@ -6059,22 +7172,6 @@ function ToolGrid(props) {
|
|
|
6059
7172
|
group
|
|
6060
7173
|
);
|
|
6061
7174
|
}),
|
|
6062
|
-
chord?.activeGroup && activeGroupTools && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6063
|
-
"div",
|
|
6064
|
-
{
|
|
6065
|
-
"data-testid": "chord-hint",
|
|
6066
|
-
className: "mt-1 rounded border border-emerald-200 bg-emerald-50/60 px-2 py-1 text-[11px] leading-snug text-slate-600",
|
|
6067
|
-
children: [
|
|
6068
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-semibold text-emerald-700", children: chord.letterForGroup(chord.activeGroup) }),
|
|
6069
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mx-1 text-slate-400", children: "\u2192" }),
|
|
6070
|
-
activeGroupTools.map((t, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mr-2 inline-block", children: [
|
|
6071
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-semibold text-emerald-700", children: i + 1 }),
|
|
6072
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1", children: t.label })
|
|
6073
|
-
] }, t.key)),
|
|
6074
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-400", children: "Esc hu\u1EF7" })
|
|
6075
|
-
]
|
|
6076
|
-
}
|
|
6077
|
-
),
|
|
6078
7175
|
portalReady && hover && typeof document !== "undefined" ? reactDom.createPortal(
|
|
6079
7176
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6080
7177
|
"div",
|
|
@@ -6141,6 +7238,8 @@ function StampLeftPanelDesktop(props) {
|
|
|
6141
7238
|
tabs: tabSpecs,
|
|
6142
7239
|
activeTab: hasObjects ? tab : void 0,
|
|
6143
7240
|
onTabChange: hasObjects ? setTab : void 0,
|
|
7241
|
+
resizable: true,
|
|
7242
|
+
widthStorageKey: "xom11.stamp-left-panel.width",
|
|
6144
7243
|
children: !hasObjects || tab === "tools" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
6145
7244
|
/* @__PURE__ */ jsxRuntime.jsx(AxisGridSection, { view, history }),
|
|
6146
7245
|
/* @__PURE__ */ jsxRuntime.jsx(
|