@xom11/whiteboard 0.24.2 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -11
- package/dist/{ExcalidrawWithMenus-WENZRYYE.mjs → ExcalidrawWithMenus-2QPPTXJM.mjs} +3 -2
- package/dist/ExcalidrawWithMenus-2QPPTXJM.mjs.map +1 -0
- package/dist/ai.d.mts +3217 -434
- package/dist/ai.d.ts +3217 -434
- package/dist/ai.js +7679 -598
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +5707 -679
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +5 -5
- package/dist/{chunk-7WQXXEVR.mjs → chunk-4ETJ4CDY.mjs} +5 -5
- package/dist/{chunk-7WQXXEVR.mjs.map → chunk-4ETJ4CDY.mjs.map} +1 -1
- package/dist/chunk-AJAHD35N.mjs +1708 -0
- package/dist/chunk-AJAHD35N.mjs.map +1 -0
- package/dist/chunk-AYJPOHCI.mjs +265 -0
- package/dist/chunk-AYJPOHCI.mjs.map +1 -0
- package/dist/chunk-B4NJJZFR.mjs +18 -0
- package/dist/chunk-B4NJJZFR.mjs.map +1 -0
- package/dist/{chunk-AZIARTGX.mjs → chunk-BNBOIDO5.mjs} +3 -3
- package/dist/{chunk-AZIARTGX.mjs.map → chunk-BNBOIDO5.mjs.map} +1 -1
- package/dist/{chunk-LVNCYP4J.mjs → chunk-CXHNVYMD.mjs} +5 -5
- package/dist/{chunk-LVNCYP4J.mjs.map → chunk-CXHNVYMD.mjs.map} +1 -1
- package/dist/{chunk-45CGKJ7S.mjs → chunk-D5JLJ3PT.mjs} +4 -4
- package/dist/{chunk-45CGKJ7S.mjs.map → chunk-D5JLJ3PT.mjs.map} +1 -1
- package/dist/{chunk-WM2VDYQA.mjs → chunk-D5LWSN2Y.mjs} +944 -196
- package/dist/chunk-D5LWSN2Y.mjs.map +1 -0
- package/dist/{chunk-KRC2XOIG.mjs → chunk-HLAOGXEK.mjs} +3 -3
- package/dist/{chunk-KRC2XOIG.mjs.map → chunk-HLAOGXEK.mjs.map} +1 -1
- package/dist/{chunk-2WF6KIGF.mjs → chunk-I3L56GVH.mjs} +212 -71
- package/dist/chunk-I3L56GVH.mjs.map +1 -0
- package/dist/{chunk-ZBJBQKJ2.mjs → chunk-IHUFOV7L.mjs} +4 -19
- package/dist/chunk-IHUFOV7L.mjs.map +1 -0
- package/dist/chunk-J5LGTIGS.mjs +10 -0
- package/dist/chunk-J5LGTIGS.mjs.map +1 -0
- package/dist/{chunk-BEZSQKPY.mjs → chunk-KYMBUTPO.mjs} +5 -4
- package/dist/chunk-KYMBUTPO.mjs.map +1 -0
- package/dist/{chunk-4DS3MKID.mjs → chunk-KZGPSTZI.mjs} +4 -4
- package/dist/{chunk-4DS3MKID.mjs.map → chunk-KZGPSTZI.mjs.map} +1 -1
- package/dist/{chunk-SGFJLHHG.mjs → chunk-PPKHCRRE.mjs} +3 -3
- package/dist/{chunk-SGFJLHHG.mjs.map → chunk-PPKHCRRE.mjs.map} +1 -1
- package/dist/{chunk-BKSXPNPQ.mjs → chunk-SZDAS7LK.mjs} +81 -3
- package/dist/chunk-SZDAS7LK.mjs.map +1 -0
- package/dist/chunk-T3SOHYB2.mjs +851 -0
- package/dist/chunk-T3SOHYB2.mjs.map +1 -0
- package/dist/geometry-2d.d.mts +2 -2
- package/dist/geometry-2d.d.ts +2 -2
- package/dist/geometry-2d.js +6288 -901
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +7 -5
- package/dist/geometry-3d.d.mts +2 -2
- package/dist/geometry-3d.d.ts +2 -2
- package/dist/geometry-3d.js +1335 -253
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +6 -4
- package/dist/graph-2d.d.mts +2 -2
- package/dist/graph-2d.d.ts +2 -2
- package/dist/graph-2d.js +1501 -342
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +9 -7
- package/dist/handleExtractProblem-C-U5KluK.d.mts +158 -0
- package/dist/handleExtractProblem-C-U5KluK.d.ts +158 -0
- package/dist/{host-EPZCNFLH.mjs → host-HAYCJJ2T.mjs} +1390 -376
- package/dist/host-HAYCJJ2T.mjs.map +1 -0
- package/dist/{host-LKCMYEAV.mjs → host-LTJHAY5A.mjs} +12 -10
- package/dist/host-LTJHAY5A.mjs.map +1 -0
- package/dist/{host-ZIQ77W33.mjs → host-M26FS244.mjs} +8 -6
- package/dist/host-M26FS244.mjs.map +1 -0
- package/dist/{host-QS2EOTRJ.mjs → host-ZQCDAT6O.mjs} +3 -2
- package/dist/host-ZQCDAT6O.mjs.map +1 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +6493 -1102
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -21
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +2 -2
- package/dist/latex.d.ts +2 -2
- package/dist/latex.mjs +2 -1
- package/dist/render-ZX2O2IK7.mjs +10 -0
- package/dist/{render-SA4JTOW3.mjs.map → render-ZX2O2IK7.mjs.map} +1 -1
- package/dist/serialize-C3LSUMSA.mjs +9 -0
- package/dist/{serialize-JAVOU22E.mjs.map → serialize-C3LSUMSA.mjs.map} +1 -1
- package/dist/types-zc_Pa0mp.d.mts +418 -0
- package/dist/types-zc_Pa0mp.d.ts +418 -0
- package/package.json +10 -1
- package/dist/ExcalidrawWithMenus-WENZRYYE.mjs.map +0 -1
- package/dist/chunk-2WF6KIGF.mjs.map +0 -1
- package/dist/chunk-BEZSQKPY.mjs.map +0 -1
- package/dist/chunk-BKSXPNPQ.mjs.map +0 -1
- package/dist/chunk-CGZZO4BX.mjs +0 -96
- package/dist/chunk-CGZZO4BX.mjs.map +0 -1
- package/dist/chunk-WM2VDYQA.mjs.map +0 -1
- package/dist/chunk-ZBJBQKJ2.mjs.map +0 -1
- package/dist/host-EPZCNFLH.mjs.map +0 -1
- package/dist/host-LKCMYEAV.mjs.map +0 -1
- package/dist/host-QS2EOTRJ.mjs.map +0 -1
- package/dist/host-ZIQ77W33.mjs.map +0 -1
- package/dist/render-SA4JTOW3.mjs +0 -8
- package/dist/serialize-JAVOU22E.mjs +0 -7
- package/dist/types-Crbefnfe.d.ts +0 -128
- package/dist/types-DxlMPh-6.d.mts +0 -49
- package/dist/types-DxlMPh-6.d.ts +0 -49
- package/dist/types-vtvyKGAA.d.mts +0 -128
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useChordShortcut } from './chunk-HNQLZIEP.mjs';
|
|
3
3
|
import { useToolStateMachine } from './chunk-NVJ7K3DK.mjs';
|
|
4
|
-
import { safeJsx, initJxgBoard, attachJxgWheelZoom, useToast, ToastHost, STAMP_PANEL_DESKTOP, ToastProvider, useStampStore, StampLeftPanel } from './chunk-
|
|
5
|
-
import { serializeBoard, renderGeometrySvgFromState, isGeometryCustomData, deserializeBoard } from './chunk-
|
|
4
|
+
import { safeJsx, initJxgBoard, attachJxgWheelZoom, useToast, ToastHost, STAMP_PANEL_DESKTOP, ToastProvider, useStampStore, StampLeftPanel, ObjectRow } from './chunk-I3L56GVH.mjs';
|
|
5
|
+
import { serializeBoard, renderGeometrySvgFromState, isGeometryCustomData, deserializeBoard } from './chunk-AYJPOHCI.mjs';
|
|
6
6
|
import { themeLabel, paletteFor, themeAxis, themeGrid } from './chunk-R5FL6S7L.mjs';
|
|
7
|
-
import { JxgRenderer } from './chunk-
|
|
7
|
+
import { JxgRenderer } from './chunk-SZDAS7LK.mjs';
|
|
8
8
|
import './chunk-ICR4CVOE.mjs';
|
|
9
|
-
import { nextLabel, useEditorState, listObjects } from './chunk-
|
|
10
|
-
import './chunk-
|
|
9
|
+
import { nextLabel, useEditorState, listObjects } from './chunk-D5LWSN2Y.mjs';
|
|
10
|
+
import './chunk-IHUFOV7L.mjs';
|
|
11
|
+
import { validateFile, fileToImagePart, describeDsl, serializeState } from './chunk-AJAHD35N.mjs';
|
|
12
|
+
import { handleExtractProblem } from './chunk-T3SOHYB2.mjs';
|
|
11
13
|
import { DEFAULT_VIEW_2D } from './chunk-73Q7ADVL.mjs';
|
|
14
|
+
import './chunk-B4NJJZFR.mjs';
|
|
12
15
|
import { useIsMobile } from './chunk-P2AOIF7S.mjs';
|
|
13
16
|
import { insertStampImage } from './chunk-QGNU34T7.mjs';
|
|
14
17
|
import './chunk-5UTGXHLJ.mjs';
|
|
15
|
-
import {
|
|
18
|
+
import { __export } from './chunk-J5LGTIGS.mjs';
|
|
19
|
+
import { forwardRef, useRef, useId, useState, useEffect, useCallback, useImperativeHandle, useSyncExternalStore, useMemo, useLayoutEffect } from 'react';
|
|
16
20
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
17
21
|
import { createPortal } from 'react-dom';
|
|
18
22
|
|
|
@@ -281,6 +285,104 @@ var Icon = {
|
|
|
281
285
|
/* @__PURE__ */ jsx("circle", { cx: "8", cy: "15.5", r: "1.7", fill: C_POINT }),
|
|
282
286
|
/* @__PURE__ */ jsx("circle", { cx: "19", cy: "5.7", r: "1.9", fill: C_CONSTRUCT }),
|
|
283
287
|
/* @__PURE__ */ jsx("text", { x: "10.5", y: "10.5", fontSize: "8", fontFamily: "serif", fontStyle: "italic", fontWeight: "700", fill: "currentColor", children: "k" })
|
|
288
|
+
] }),
|
|
289
|
+
// ===== Special shapes =====
|
|
290
|
+
square: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
291
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "14", height: "14", fill: C_FILL, fillOpacity: "0.18", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
292
|
+
/* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "1.7", fill: C_POINT }),
|
|
293
|
+
/* @__PURE__ */ jsx("circle", { cx: "19", cy: "5", r: "1.7", fill: C_POINT })
|
|
294
|
+
] }),
|
|
295
|
+
rectangle: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
296
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "7", width: "18", height: "10", fill: C_FILL, fillOpacity: "0.18", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
297
|
+
/* @__PURE__ */ jsx("circle", { cx: "3", cy: "17", r: "1.7", fill: C_POINT }),
|
|
298
|
+
/* @__PURE__ */ jsx("circle", { cx: "21", cy: "17", r: "1.7", fill: C_POINT }),
|
|
299
|
+
/* @__PURE__ */ jsx("circle", { cx: "21", cy: "7", r: "1.7", fill: C_POINT })
|
|
300
|
+
] }),
|
|
301
|
+
rhombus: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
302
|
+
/* @__PURE__ */ jsx("path", { d: "M 12 3 L 21 12 L 12 21 L 3 12 Z", fill: C_FILL, fillOpacity: "0.18", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
303
|
+
/* @__PURE__ */ jsx("circle", { cx: "3", cy: "12", r: "1.7", fill: C_POINT }),
|
|
304
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "21", r: "1.7", fill: C_POINT }),
|
|
305
|
+
/* @__PURE__ */ jsx("circle", { cx: "21", cy: "12", r: "1.7", fill: C_POINT })
|
|
306
|
+
] }),
|
|
307
|
+
parallelogram: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
308
|
+
/* @__PURE__ */ jsx("path", { d: "M 5 19 L 21 19 L 19 5 L 3 5 Z", fill: C_FILL, fillOpacity: "0.18", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
309
|
+
/* @__PURE__ */ jsx("circle", { cx: "3", cy: "5", r: "1.7", fill: C_POINT }),
|
|
310
|
+
/* @__PURE__ */ jsx("circle", { cx: "19", cy: "5", r: "1.7", fill: C_POINT }),
|
|
311
|
+
/* @__PURE__ */ jsx("circle", { cx: "21", cy: "19", r: "1.7", fill: C_POINT })
|
|
312
|
+
] }),
|
|
313
|
+
isoTrapezoid: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
314
|
+
/* @__PURE__ */ jsx("path", { d: "M 3 19 L 21 19 L 17 5 L 7 5 Z", fill: C_FILL, fillOpacity: "0.18", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
315
|
+
/* @__PURE__ */ jsx("circle", { cx: "3", cy: "19", r: "1.7", fill: C_POINT }),
|
|
316
|
+
/* @__PURE__ */ jsx("circle", { cx: "21", cy: "19", r: "1.7", fill: C_POINT }),
|
|
317
|
+
/* @__PURE__ */ jsx("circle", { cx: "7", cy: "5", r: "1.7", fill: C_POINT })
|
|
318
|
+
] }),
|
|
319
|
+
isoTriangle: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
320
|
+
/* @__PURE__ */ jsx("path", { d: "M 12 4 L 21 20 L 3 20 Z", fill: C_FILL, fillOpacity: "0.18", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
321
|
+
/* @__PURE__ */ jsx("circle", { cx: "3", cy: "20", r: "1.7", fill: C_POINT }),
|
|
322
|
+
/* @__PURE__ */ jsx("circle", { cx: "21", cy: "20", r: "1.7", fill: C_POINT }),
|
|
323
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "4", r: "1.7", fill: C_POINT }),
|
|
324
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "4", x2: "12", y2: "20", stroke: C_CONSTRUCT, strokeWidth: "0.8", strokeDasharray: "1.5 1.5", opacity: "0.7" })
|
|
325
|
+
] }),
|
|
326
|
+
rightTriangle: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
327
|
+
/* @__PURE__ */ jsx("path", { d: "M 4 20 L 20 20 L 4 4 Z", fill: C_FILL, fillOpacity: "0.18", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
328
|
+
/* @__PURE__ */ jsx("path", { d: "M 4 17 L 7 17 L 7 20", fill: "none", stroke: "currentColor", strokeWidth: "1.2" }),
|
|
329
|
+
/* @__PURE__ */ jsx("circle", { cx: "4", cy: "20", r: "1.7", fill: C_POINT }),
|
|
330
|
+
/* @__PURE__ */ jsx("circle", { cx: "20", cy: "20", r: "1.7", fill: C_POINT }),
|
|
331
|
+
/* @__PURE__ */ jsx("circle", { cx: "4", cy: "4", r: "1.7", fill: C_POINT })
|
|
332
|
+
] }),
|
|
333
|
+
// ===== Nâng cao / kind chưa có icon =====
|
|
334
|
+
excenter: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
335
|
+
/* @__PURE__ */ jsx("path", { d: "M5 16 L12 5 L19 16 Z", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
336
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "20", r: "3", fill: "none", stroke: C_CONSTRUCT, strokeWidth: "1.3" }),
|
|
337
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "20", r: "1.4", fill: C_CONSTRUCT })
|
|
338
|
+
] }),
|
|
339
|
+
tangencyPoint: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
340
|
+
/* @__PURE__ */ jsx("circle", { cx: "10", cy: "12", r: "6.5", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
341
|
+
/* @__PURE__ */ jsx("line", { x1: "16.5", y1: "3", x2: "16.5", y2: "21", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
342
|
+
/* @__PURE__ */ jsx("circle", { cx: "16.5", cy: "12", r: "2.2", fill: C_CONSTRUCT })
|
|
343
|
+
] }),
|
|
344
|
+
secondIntersection: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
345
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "12", r: "6.5", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
346
|
+
/* @__PURE__ */ jsx("line", { x1: "2", y1: "8", x2: "22", y2: "16", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
347
|
+
/* @__PURE__ */ jsx("circle", { cx: "6.2", cy: "9.6", r: "1.6", fill: "currentColor" }),
|
|
348
|
+
/* @__PURE__ */ jsx("circle", { cx: "15.8", cy: "14.4", r: "2.4", fill: C_CONSTRUCT })
|
|
349
|
+
] }),
|
|
350
|
+
arcMidpoint: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
|
|
351
|
+
/* @__PURE__ */ jsx("path", { d: "M4 17 A 9 9 0 0 1 20 17", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
352
|
+
/* @__PURE__ */ jsx("circle", { cx: "4", cy: "17", r: "1.6", fill: "currentColor" }),
|
|
353
|
+
/* @__PURE__ */ jsx("circle", { cx: "20", cy: "17", r: "1.6", fill: "currentColor" }),
|
|
354
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "8.2", r: "2.4", fill: C_CONSTRUCT })
|
|
355
|
+
] }),
|
|
356
|
+
circleIntersection: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
357
|
+
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
358
|
+
/* @__PURE__ */ jsx("circle", { cx: "15", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
359
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "7.6", r: "2", fill: C_CONSTRUCT }),
|
|
360
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "16.4", r: "2", fill: C_CONSTRUCT })
|
|
361
|
+
] }),
|
|
362
|
+
tangentPointExt: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
|
|
363
|
+
/* @__PURE__ */ jsx("circle", { cx: "14", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
364
|
+
/* @__PURE__ */ jsx("circle", { cx: "3.5", cy: "12", r: "1.8", fill: C_POINT }),
|
|
365
|
+
/* @__PURE__ */ jsx("line", { x1: "3.5", y1: "12", x2: "17.5", y2: "7.5", stroke: "currentColor", strokeWidth: "1.2" }),
|
|
366
|
+
/* @__PURE__ */ jsx("line", { x1: "3.5", y1: "12", x2: "17.5", y2: "16.5", stroke: "currentColor", strokeWidth: "1.2" }),
|
|
367
|
+
/* @__PURE__ */ jsx("circle", { cx: "17.5", cy: "7.5", r: "1.8", fill: C_CONSTRUCT }),
|
|
368
|
+
/* @__PURE__ */ jsx("circle", { cx: "17.5", cy: "16.5", r: "1.8", fill: C_CONSTRUCT })
|
|
369
|
+
] }),
|
|
370
|
+
circleCR: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
371
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "7.5", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
372
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1.8", fill: C_POINT }),
|
|
373
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "12", x2: "19.5", y2: "12", stroke: C_CONSTRUCT, strokeWidth: "1.3" })
|
|
374
|
+
] }),
|
|
375
|
+
incircle: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
376
|
+
/* @__PURE__ */ jsx("path", { d: "M3 19 L12 4 L21 19 Z", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }),
|
|
377
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "14", r: "4.2", fill: "none", stroke: C_CONSTRUCT, strokeWidth: "1.3" })
|
|
378
|
+
] }),
|
|
379
|
+
excircle: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
380
|
+
/* @__PURE__ */ jsx("path", { d: "M6 9 L14 4 L18 13 Z", stroke: "currentColor", strokeWidth: "1.2" }),
|
|
381
|
+
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "17", r: "4.6", fill: "none", stroke: C_CONSTRUCT, strokeWidth: "1.3" })
|
|
382
|
+
] }),
|
|
383
|
+
pointOn: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
384
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "7.5", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
385
|
+
/* @__PURE__ */ jsx("circle", { cx: "17.3", cy: "6.7", r: "2.4", fill: C_POINT })
|
|
284
386
|
] })
|
|
285
387
|
};
|
|
286
388
|
var GeometryIconHeader = /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
@@ -341,7 +443,83 @@ var TOOLS = [
|
|
|
341
443
|
{ key: "rotate", label: "Quay \u0111\u1ED1i t\u01B0\u1EE3ng", hint: "Click object \u2192 t\xE2m quay \u2192 nh\u1EADp g\xF3c", icon: Icon.rotate, group: "transform", needs: 2, accepts: ["any", "point"] },
|
|
342
444
|
{ key: "reflectLine", label: "\u0110\u1ED1i x\u1EE9ng qua \u0111\u01B0\u1EDDng th\u1EB3ng", hint: "Click object \u2192 \u0111\u01B0\u1EDDng th\u1EB3ng", icon: Icon.reflectLine, group: "transform", needs: 2, accepts: ["any", "line"] },
|
|
343
445
|
{ key: "reflectPoint", label: "\u0110\u1ED1i x\u1EE9ng qua \u0111i\u1EC3m", hint: "Click object \u2192 t\xE2m \u0111\u1ED1i x\u1EE9ng", icon: Icon.reflectPoint, group: "transform", needs: 2, accepts: ["any", "point"] },
|
|
344
|
-
{ key: "dilate", label: "Ph\xE9p v\u1ECB t\u1EF1", hint: "Click object \u2192 t\xE2m \u2192 nh\u1EADp t\u1EF7 s\u1ED1 k", icon: Icon.dilate, group: "transform", needs: 2, accepts: ["any", "point"] }
|
|
446
|
+
{ key: "dilate", label: "Ph\xE9p v\u1ECB t\u1EF1", hint: "Click object \u2192 t\xE2m \u2192 nh\u1EADp t\u1EF7 s\u1ED1 k", icon: Icon.dilate, group: "transform", needs: 2, accepts: ["any", "point"] },
|
|
447
|
+
// ===== Hình đặc biệt (parametric construction) =====
|
|
448
|
+
{
|
|
449
|
+
key: "square",
|
|
450
|
+
label: "H\xECnh vu\xF4ng",
|
|
451
|
+
hint: "Click 2 \u0111i\u1EC3m \u2014 c\u1EA1nh \u0111\u1EA7u (3 \u0111\u1EC9nh c\xF2n l\u1EA1i t\u1EF1 suy, vu\xF4ng g\xF3c + b\u1EB1ng c\u1EA1nh)",
|
|
452
|
+
icon: Icon.square,
|
|
453
|
+
group: "special",
|
|
454
|
+
needs: 2,
|
|
455
|
+
accepts: ["point", "point"]
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
key: "rectangle",
|
|
459
|
+
label: "H\xECnh ch\u1EEF nh\u1EADt",
|
|
460
|
+
hint: "Click 2 \u0111i\u1EC3m \u0111\xE1y + 1 \u0111i\u1EC3m chi\u1EC1u cao (auto vu\xF4ng g\xF3c t\u1EA1i \u0111\u1EC9nh 2)",
|
|
461
|
+
icon: Icon.rectangle,
|
|
462
|
+
group: "special",
|
|
463
|
+
needs: 3,
|
|
464
|
+
accepts: ["point", "point", "point"]
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
key: "rhombus",
|
|
468
|
+
label: "H\xECnh thoi",
|
|
469
|
+
hint: "Click 2 \u0111i\u1EC3m c\u1EA1nh + 1 \u0111i\u1EC3m h\u01B0\u1EDBng (auto b\u1EB1ng \u0111\u1ED9 d\xE0i c\u1EA1nh)",
|
|
470
|
+
icon: Icon.rhombus,
|
|
471
|
+
group: "special",
|
|
472
|
+
needs: 3,
|
|
473
|
+
accepts: ["point", "point", "point"]
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
key: "parallelogram",
|
|
477
|
+
label: "H\xECnh b\xECnh h\xE0nh",
|
|
478
|
+
hint: "Click 3 \u0111i\u1EC3m li\xEAn ti\u1EBFp (\u0111\u1EC9nh 4 t\u1EF1 suy)",
|
|
479
|
+
icon: Icon.parallelogram,
|
|
480
|
+
group: "special",
|
|
481
|
+
needs: 3,
|
|
482
|
+
accepts: ["point", "point", "point"]
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
key: "isoTrapezoid",
|
|
486
|
+
label: "H\xECnh thang c\xE2n",
|
|
487
|
+
hint: "Click 2 \u0111i\u1EC3m \u0111\xE1y l\u1EDBn + 1 \u0111\u1EC9nh tr\xEAn (\u0111\u1EC9nh 4 ph\u1EA3n chi\u1EBFu qua trung tr\u1EF1c)",
|
|
488
|
+
icon: Icon.isoTrapezoid,
|
|
489
|
+
group: "special",
|
|
490
|
+
needs: 3,
|
|
491
|
+
accepts: ["point", "point", "point"]
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
key: "isoTriangle",
|
|
495
|
+
label: "Tam gi\xE1c c\xE2n",
|
|
496
|
+
hint: "Click 2 \u0111i\u1EC3m \u0111\xE1y + 1 \u0111\u1EC9nh (auto tr\xEAn trung tr\u1EF1c)",
|
|
497
|
+
icon: Icon.isoTriangle,
|
|
498
|
+
group: "special",
|
|
499
|
+
needs: 3,
|
|
500
|
+
accepts: ["point", "point", "point"]
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
key: "rightTriangle",
|
|
504
|
+
label: "Tam gi\xE1c vu\xF4ng",
|
|
505
|
+
hint: "Click \u0111\u1EC9nh vu\xF4ng + 2 \u0111\u1EA7u c\u1EA1nh (c\u1EA1nh 2 auto vu\xF4ng g\xF3c)",
|
|
506
|
+
icon: Icon.rightTriangle,
|
|
507
|
+
group: "special",
|
|
508
|
+
needs: 3,
|
|
509
|
+
accepts: ["point", "point", "point"]
|
|
510
|
+
},
|
|
511
|
+
// ===== Điểm / đường tròn thông dụng (kind chưa có icon trước v0.27) =====
|
|
512
|
+
{ key: "pointOn", label: "\u0110i\u1EC3m tr\xEAn \u0111\u1ED1i t\u01B0\u1EE3ng", hint: "Click 1 \u0111\u01B0\u1EDDng/\u0111o\u1EA1n/\u0111\u01B0\u1EDDng tr\xF2n c\xF3 s\u1EB5n", icon: Icon.pointOn, group: "point", needs: 1, accepts: ["lineOrCircle"] },
|
|
513
|
+
{ key: "circleCR", label: "\u0110\u01B0\u1EDDng tr\xF2n (t\xE2m + b\xE1n k\xEDnh)", hint: "Click t\xE2m r\u1ED3i nh\u1EADp b\xE1n k\xEDnh", icon: Icon.circleCR, group: "circle", needs: 1, accepts: ["point"] },
|
|
514
|
+
{ key: "incircle", label: "\u0110\u01B0\u1EDDng tr\xF2n n\u1ED9i ti\u1EBFp", hint: "Click 3 \u0111\u1EC9nh tam gi\xE1c", icon: Icon.incircle, group: "circle", needs: 3, accepts: ["point", "point", "point"] },
|
|
515
|
+
// ===== Nâng cao =====
|
|
516
|
+
{ key: "excenter", label: "T\xE2m \u0111\u01B0\u1EDDng tr\xF2n b\xE0ng ti\u1EBFp", hint: "Click 3 \u0111\u1EC9nh tam gi\xE1c (\u0111\u1EC9nh \u0111\u1EA7u = \u0111\u1EC9nh \u0111\u1ED1i di\u1EC7n)", icon: Icon.excenter, group: "advanced", needs: 3, accepts: ["point", "point", "point"] },
|
|
517
|
+
{ key: "excircle", label: "\u0110\u01B0\u1EDDng tr\xF2n b\xE0ng ti\u1EBFp", hint: "Click 3 \u0111\u1EC9nh tam gi\xE1c (\u0111\u1EC9nh \u0111\u1EA7u = \u0111\u1EC9nh \u0111\u1ED1i di\u1EC7n)", icon: Icon.excircle, group: "advanced", needs: 3, accepts: ["point", "point", "point"] },
|
|
518
|
+
{ key: "tangencyPoint", label: "Ti\u1EBFp \u0111i\u1EC3m (\u0111\u01B0\u1EDDng ti\u1EBFp x\xFAc)", hint: "Click 1 \u0111\u01B0\u1EDDng tr\xF2n + 1 ti\u1EBFp tuy\u1EBFn c\xF3 s\u1EB5n", icon: Icon.tangencyPoint, group: "advanced", needs: 2, accepts: ["circle", "line"] },
|
|
519
|
+
{ key: "secondIntersection", label: "Giao \u0111i\u1EC3m th\u1EE9 hai", hint: "Click 1 \u0111\u01B0\u1EDDng + 1 \u0111\u01B0\u1EDDng tr\xF2n + giao \u0111i\u1EC3m \u0111\xE3 bi\u1EBFt", icon: Icon.secondIntersection, group: "advanced", needs: 3, accepts: ["line", "circle", "point"] },
|
|
520
|
+
{ key: "arcMidpoint", label: "\u0110i\u1EC3m gi\u1EEFa cung", hint: "Click \u0111\u01B0\u1EDDng tr\xF2n \u2192 2 \u0111\u1EA7u cung A,B \u2192 1 \u0111i\u1EC3m ph\xEDa cung KH\xD4NG ch\u1EE9a", icon: Icon.arcMidpoint, group: "advanced", needs: 4, accepts: ["circle", "point", "point", "point"] },
|
|
521
|
+
{ key: "circleIntersection", label: "Giao 2 \u0111\u01B0\u1EDDng tr\xF2n", hint: "Click 2 \u0111\u01B0\u1EDDng tr\xF2n (t\u1EA1o c\u1EA3 2 giao \u0111i\u1EC3m)", icon: Icon.circleIntersection, group: "advanced", needs: 2, accepts: ["circle", "circle"] },
|
|
522
|
+
{ key: "tangentPointExt", label: "Ti\u1EBFp \u0111i\u1EC3m t\u1EEB \u0111i\u1EC3m ngo\xE0i", hint: "Click 1 \u0111i\u1EC3m ngo\xE0i + 1 \u0111\u01B0\u1EDDng tr\xF2n (t\u1EA1o c\u1EA3 2 ti\u1EBFp \u0111i\u1EC3m)", icon: Icon.tangentPointExt, group: "advanced", needs: 2, accepts: ["point", "circle"] }
|
|
345
523
|
];
|
|
346
524
|
var GROUP_LABELS = {
|
|
347
525
|
move: "C\u01A1 b\u1EA3n",
|
|
@@ -353,7 +531,9 @@ var GROUP_LABELS = {
|
|
|
353
531
|
triangle: "Tam gi\xE1c",
|
|
354
532
|
measure: "\u0110o l\u01B0\u1EDDng",
|
|
355
533
|
edit: "Ch\u1EC9nh s\u1EEDa",
|
|
356
|
-
transform: "Ph\xE9p bi\u1EBFn h\xECnh"
|
|
534
|
+
transform: "Ph\xE9p bi\u1EBFn h\xECnh",
|
|
535
|
+
special: "H\xECnh \u0111\u1EB7c bi\u1EC7t",
|
|
536
|
+
advanced: "N\xE2ng cao"
|
|
357
537
|
};
|
|
358
538
|
var GROUP_ORDER = [
|
|
359
539
|
"move",
|
|
@@ -365,7 +545,9 @@ var GROUP_ORDER = [
|
|
|
365
545
|
"triangle",
|
|
366
546
|
"measure",
|
|
367
547
|
"edit",
|
|
368
|
-
"transform"
|
|
548
|
+
"transform",
|
|
549
|
+
"special",
|
|
550
|
+
"advanced"
|
|
369
551
|
];
|
|
370
552
|
var A_CODE = "A".charCodeAt(0);
|
|
371
553
|
function letterForGroup(g) {
|
|
@@ -592,6 +774,20 @@ function handlePolygonTool(ctx, t, toolDef, e, x, y, bestHit) {
|
|
|
592
774
|
return true;
|
|
593
775
|
}
|
|
594
776
|
|
|
777
|
+
// src/stamps/geometry-2d/editor/handlers/finalize/lines.ts
|
|
778
|
+
var lines_exports = {};
|
|
779
|
+
__export(lines_exports, {
|
|
780
|
+
angleBisectorTool: () => angleBisectorTool,
|
|
781
|
+
lineTool: () => lineTool,
|
|
782
|
+
parallelTool: () => parallelTool,
|
|
783
|
+
perpBisectorTool: () => perpBisectorTool,
|
|
784
|
+
perpendicularTool: () => perpendicularTool,
|
|
785
|
+
rayTool: () => rayTool,
|
|
786
|
+
segmentTool: () => segmentTool,
|
|
787
|
+
tangentTool: () => tangentTool,
|
|
788
|
+
vectorTool: () => vectorTool
|
|
789
|
+
});
|
|
790
|
+
|
|
595
791
|
// src/stamps/geometry-2d/editor/handlers/classifyPointVsCircle.ts
|
|
596
792
|
function classifyPointVsCircle(point, circle) {
|
|
597
793
|
if (!point || !circle || !circle.center) return "inside";
|
|
@@ -605,7 +801,7 @@ function classifyPointVsCircle(point, circle) {
|
|
|
605
801
|
return d < r ? "inside" : "outside";
|
|
606
802
|
}
|
|
607
803
|
|
|
608
|
-
// src/stamps/geometry-2d/editor/handlers/
|
|
804
|
+
// src/stamps/geometry-2d/editor/handlers/finalize/shared.ts
|
|
609
805
|
function findPickIdByKind(ctx, kind) {
|
|
610
806
|
const picks = ctx.pendingRef.current;
|
|
611
807
|
const ids = ctx.pendingIdsRef.current;
|
|
@@ -614,362 +810,817 @@ function findPickIdByKind(ctx, kind) {
|
|
|
614
810
|
}
|
|
615
811
|
return null;
|
|
616
812
|
}
|
|
617
|
-
function
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
813
|
+
function readJxgPos(ctx, id) {
|
|
814
|
+
const j = ctx.jxgFromSceneId(id);
|
|
815
|
+
if (!j || typeof j.X !== "function") return { x: 0, y: 0 };
|
|
816
|
+
return { x: j.X(), y: j.Y() };
|
|
817
|
+
}
|
|
818
|
+
function computePerpendicularT(P, T, A, B) {
|
|
819
|
+
const dx = B.x - A.x, dy = B.y - A.y;
|
|
820
|
+
const len = Math.hypot(dx, dy);
|
|
821
|
+
if (len < 1e-12) return 0;
|
|
822
|
+
const ux = -dy / len, uy = dx / len;
|
|
823
|
+
return (P.x - T.x) * ux + (P.y - T.y) * uy;
|
|
824
|
+
}
|
|
825
|
+
function computePerpBisectorT(P, A, B) {
|
|
826
|
+
const Mx = (A.x + B.x) / 2, My = (A.y + B.y) / 2;
|
|
827
|
+
const dx = B.x - A.x, dy = B.y - A.y;
|
|
828
|
+
const len = Math.hypot(dx, dy);
|
|
829
|
+
if (len < 1e-12) return 0;
|
|
830
|
+
const ux = -dy / len, uy = dx / len;
|
|
831
|
+
return (P.x - Mx) * ux + (P.y - My) * uy;
|
|
832
|
+
}
|
|
833
|
+
function computeCircleTheta(P, C) {
|
|
834
|
+
return Math.atan2(P.y - C.y, P.x - C.x);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// src/stamps/geometry-2d/editor/handlers/finalize/lines.ts
|
|
838
|
+
var segmentTool = {
|
|
839
|
+
key: "segment",
|
|
840
|
+
finalize(ctx) {
|
|
841
|
+
const ids = ctx.pendingIdsRef.current;
|
|
842
|
+
const id = freshId(ctx, "s");
|
|
843
|
+
const label = ctx.nextLabel("segment");
|
|
844
|
+
ctx.store.dispatch({
|
|
845
|
+
type: "ADD",
|
|
846
|
+
payload: { obj: mkSceneObj(id, "segment", label, { p1: ids[0], p2: ids[1] }) }
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
var lineTool = {
|
|
851
|
+
key: "line",
|
|
852
|
+
finalize(ctx) {
|
|
853
|
+
const ids = ctx.pendingIdsRef.current;
|
|
854
|
+
const id = freshId(ctx, "l");
|
|
855
|
+
const label = ctx.nextLabel("line");
|
|
856
|
+
ctx.store.dispatch({
|
|
857
|
+
type: "ADD",
|
|
858
|
+
payload: { obj: mkSceneObj(id, "line", label, { p1: ids[0], p2: ids[1] }) }
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
function finalizePerpParallel(ctx, key) {
|
|
863
|
+
const throughPoint = findPickIdByKind(ctx, "point");
|
|
864
|
+
const toLine = findPickIdByKind(ctx, "line");
|
|
865
|
+
if (!throughPoint || !toLine) return;
|
|
866
|
+
const id = freshId(ctx, key === "perpendicular" ? "perp" : "par");
|
|
867
|
+
const label = ctx.nextLabel("line");
|
|
868
|
+
ctx.store.dispatch({
|
|
869
|
+
type: "ADD",
|
|
870
|
+
payload: { obj: mkSceneObj(id, "line", label, {
|
|
871
|
+
construction: { kind: key, throughPoint, toLine }
|
|
872
|
+
}) }
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
var perpendicularTool = {
|
|
876
|
+
key: "perpendicular",
|
|
877
|
+
finalize(ctx) {
|
|
878
|
+
finalizePerpParallel(ctx, "perpendicular");
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
var parallelTool = {
|
|
882
|
+
key: "parallel",
|
|
883
|
+
finalize(ctx) {
|
|
884
|
+
finalizePerpParallel(ctx, "parallel");
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
var perpBisectorTool = {
|
|
888
|
+
key: "perpBisector",
|
|
889
|
+
finalize(ctx) {
|
|
890
|
+
const ids = ctx.pendingIdsRef.current;
|
|
891
|
+
const id = freshId(ctx, "pb");
|
|
892
|
+
const label = ctx.nextLabel("line");
|
|
893
|
+
ctx.store.dispatch({
|
|
894
|
+
type: "ADD",
|
|
895
|
+
payload: { obj: mkSceneObj(id, "line", label, {
|
|
896
|
+
construction: { kind: "perpBisector", p1: ids[0], p2: ids[1] }
|
|
897
|
+
}) }
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
var angleBisectorTool = {
|
|
902
|
+
key: "angleBisector",
|
|
903
|
+
finalize(ctx) {
|
|
904
|
+
const ids = ctx.pendingIdsRef.current;
|
|
905
|
+
const picks = ctx.pendingRef.current;
|
|
906
|
+
if (picks.length === 2 && objKind(picks[0]) === "line" && objKind(picks[1]) === "line") {
|
|
907
|
+
for (const branch of [0, 1]) {
|
|
908
|
+
const id2 = freshId(ctx, "ab");
|
|
909
|
+
const label2 = ctx.nextLabel("line");
|
|
910
|
+
ctx.store.dispatch({
|
|
911
|
+
type: "ADD",
|
|
912
|
+
payload: { obj: mkSceneObj(id2, "line", label2, {
|
|
913
|
+
construction: { kind: "angleBisectorLines", line1: ids[0], line2: ids[1], branch }
|
|
914
|
+
}) }
|
|
915
|
+
});
|
|
916
|
+
}
|
|
637
917
|
return;
|
|
638
918
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
919
|
+
const id = freshId(ctx, "ab");
|
|
920
|
+
const label = ctx.nextLabel("line");
|
|
921
|
+
ctx.store.dispatch({
|
|
922
|
+
type: "ADD",
|
|
923
|
+
payload: { obj: mkSceneObj(id, "line", label, {
|
|
924
|
+
construction: { kind: "angleBisector", p1: ids[0], vertex: ids[1], p2: ids[2] }
|
|
925
|
+
}) }
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
var tangentTool = {
|
|
930
|
+
key: "tangent",
|
|
931
|
+
finalize(ctx) {
|
|
932
|
+
const throughId = findPickIdByKind(ctx, "point");
|
|
933
|
+
const circleId = findPickIdByKind(ctx, "circle");
|
|
934
|
+
if (!throughId || !circleId) return;
|
|
935
|
+
const picks = ctx.pendingRef.current;
|
|
936
|
+
const ids = ctx.pendingIdsRef.current;
|
|
937
|
+
const through = picks[ids.indexOf(throughId)];
|
|
938
|
+
const circle = picks[ids.indexOf(circleId)];
|
|
939
|
+
const pos = classifyPointVsCircle(through, circle);
|
|
940
|
+
if (pos === "inside") {
|
|
941
|
+
ctx.toast?.("\u0110i\u1EC3m n\u1EB1m trong \u0111\u01B0\u1EDDng tr\xF2n \u2014 kh\xF4ng c\xF3 ti\u1EBFp tuy\u1EBFn", {
|
|
942
|
+
variant: "warning",
|
|
943
|
+
id: "tangent-invalid-inside"
|
|
651
944
|
});
|
|
652
945
|
return;
|
|
653
946
|
}
|
|
654
|
-
|
|
655
|
-
const id = freshId(ctx, "
|
|
947
|
+
if (pos === "on") {
|
|
948
|
+
const id = freshId(ctx, "t");
|
|
656
949
|
const label = ctx.nextLabel("line");
|
|
657
950
|
ctx.store.dispatch({
|
|
658
951
|
type: "ADD",
|
|
659
952
|
payload: { obj: mkSceneObj(id, "line", label, {
|
|
660
|
-
construction: { kind: "
|
|
953
|
+
construction: { kind: "tangent", throughPoint: throughId, toCircle: circleId, branch: "on" }
|
|
661
954
|
}) }
|
|
662
955
|
});
|
|
663
956
|
return;
|
|
664
957
|
}
|
|
665
|
-
|
|
666
|
-
const
|
|
667
|
-
if (picks.length === 2 && objKind(picks[0]) === "line" && objKind(picks[1]) === "line") {
|
|
668
|
-
for (const branch of [0, 1]) {
|
|
669
|
-
const id2 = freshId(ctx, "ab");
|
|
670
|
-
const label2 = ctx.nextLabel("line");
|
|
671
|
-
ctx.store.dispatch({
|
|
672
|
-
type: "ADD",
|
|
673
|
-
payload: { obj: mkSceneObj(id2, "line", label2, {
|
|
674
|
-
construction: { kind: "angleBisectorLines", line1: ids[0], line2: ids[1], branch }
|
|
675
|
-
}) }
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
const id = freshId(ctx, "ab");
|
|
958
|
+
for (const branch of [0, 1]) {
|
|
959
|
+
const id = freshId(ctx, "t");
|
|
681
960
|
const label = ctx.nextLabel("line");
|
|
682
961
|
ctx.store.dispatch({
|
|
683
962
|
type: "ADD",
|
|
684
963
|
payload: { obj: mkSceneObj(id, "line", label, {
|
|
685
|
-
construction: { kind: "
|
|
964
|
+
construction: { kind: "tangent", throughPoint: throughId, toCircle: circleId, branch }
|
|
686
965
|
}) }
|
|
687
966
|
});
|
|
688
|
-
return;
|
|
689
967
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
var rayTool = {
|
|
971
|
+
key: "ray",
|
|
972
|
+
finalize(ctx) {
|
|
973
|
+
const ids = ctx.pendingIdsRef.current;
|
|
974
|
+
const id = freshId(ctx, "r");
|
|
975
|
+
const label = ctx.nextLabel("ray");
|
|
976
|
+
ctx.store.dispatch({
|
|
977
|
+
type: "ADD",
|
|
978
|
+
payload: { obj: mkSceneObj(id, "ray", label, { origin: ids[0], through: ids[1] }) }
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
var vectorTool = {
|
|
983
|
+
key: "vector",
|
|
984
|
+
finalize(ctx) {
|
|
985
|
+
const ids = ctx.pendingIdsRef.current;
|
|
986
|
+
const id = freshId(ctx, "v");
|
|
987
|
+
const label = ctx.nextLabel("vector");
|
|
988
|
+
ctx.store.dispatch({
|
|
989
|
+
type: "ADD",
|
|
990
|
+
payload: { obj: mkSceneObj(id, "vector", label, { from: ids[0], to: ids[1] }) }
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
// src/stamps/geometry-2d/editor/handlers/finalize/circles.ts
|
|
996
|
+
var circles_exports = {};
|
|
997
|
+
__export(circles_exports, {
|
|
998
|
+
arc3Tool: () => arc3Tool,
|
|
999
|
+
arcCenterTool: () => arcCenterTool,
|
|
1000
|
+
circle3Tool: () => circle3Tool,
|
|
1001
|
+
circleCenterTool: () => circleCenterTool,
|
|
1002
|
+
excircleTool: () => excircleTool,
|
|
1003
|
+
incircleTool: () => incircleTool,
|
|
1004
|
+
sectorCenterTool: () => sectorCenterTool,
|
|
1005
|
+
semicircleTool: () => semicircleTool
|
|
1006
|
+
});
|
|
1007
|
+
var circleCenterTool = {
|
|
1008
|
+
key: "circleCenter",
|
|
1009
|
+
finalize(ctx) {
|
|
1010
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1011
|
+
const id = freshId(ctx, "c");
|
|
1012
|
+
const label = ctx.nextLabel("circle");
|
|
1013
|
+
ctx.store.dispatch({
|
|
1014
|
+
type: "ADD",
|
|
1015
|
+
payload: {
|
|
1016
|
+
obj: mkSceneObj(id, "circle", label, {
|
|
1017
|
+
center: ids[0],
|
|
1018
|
+
surfacePoint: ids[1]
|
|
1019
|
+
})
|
|
716
1020
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
var circle3Tool = {
|
|
1025
|
+
key: "circle3",
|
|
1026
|
+
finalize(ctx) {
|
|
1027
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1028
|
+
const id = freshId(ctx, "cc");
|
|
1029
|
+
const label = ctx.nextLabel("circle");
|
|
1030
|
+
ctx.store.dispatch({
|
|
1031
|
+
type: "ADD",
|
|
1032
|
+
payload: {
|
|
1033
|
+
obj: mkSceneObj(id, "circle", label, {
|
|
1034
|
+
construction: { kind: "circumscribed", p1: ids[0], p2: ids[1], p3: ids[2] }
|
|
1035
|
+
})
|
|
726
1036
|
}
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
var semicircleTool = {
|
|
1041
|
+
key: "semicircle",
|
|
1042
|
+
finalize(ctx) {
|
|
1043
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1044
|
+
if (ids[0] === ids[1]) {
|
|
1045
|
+
ctx.toast?.("C\u1EA7n 2 \u0111i\u1EC3m ph\xE2n bi\u1EC7t", { variant: "warning", id: "semicircle-dup" });
|
|
727
1046
|
return;
|
|
728
1047
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
case "vector": {
|
|
739
|
-
const id = freshId(ctx, "v");
|
|
740
|
-
const label = ctx.nextLabel("vector");
|
|
741
|
-
ctx.store.dispatch({
|
|
742
|
-
type: "ADD",
|
|
743
|
-
payload: { obj: mkSceneObj(id, "vector", label, { from: ids[0], to: ids[1] }) }
|
|
744
|
-
});
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
case "circleCenter": {
|
|
748
|
-
const id = freshId(ctx, "c");
|
|
749
|
-
const label = ctx.nextLabel("circle");
|
|
750
|
-
ctx.store.dispatch({
|
|
751
|
-
type: "ADD",
|
|
752
|
-
payload: {
|
|
753
|
-
obj: mkSceneObj(id, "circle", label, {
|
|
754
|
-
center: ids[0],
|
|
755
|
-
surfacePoint: ids[1]
|
|
756
|
-
})
|
|
757
|
-
}
|
|
758
|
-
});
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
case "circle3": {
|
|
762
|
-
const id = freshId(ctx, "cc");
|
|
763
|
-
const label = ctx.nextLabel("circle");
|
|
764
|
-
ctx.store.dispatch({
|
|
765
|
-
type: "ADD",
|
|
766
|
-
payload: {
|
|
767
|
-
obj: mkSceneObj(id, "circle", label, {
|
|
768
|
-
construction: { kind: "circumscribed", p1: ids[0], p2: ids[1], p3: ids[2] }
|
|
769
|
-
})
|
|
770
|
-
}
|
|
771
|
-
});
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
case "semicircle": {
|
|
775
|
-
if (ids[0] === ids[1]) {
|
|
776
|
-
ctx.toast?.("C\u1EA7n 2 \u0111i\u1EC3m ph\xE2n bi\u1EC7t", { variant: "warning", id: "semicircle-dup" });
|
|
777
|
-
return;
|
|
1048
|
+
const id = freshId(ctx, "arc");
|
|
1049
|
+
const label = ctx.nextLabel("arc");
|
|
1050
|
+
ctx.store.dispatch({
|
|
1051
|
+
type: "ADD",
|
|
1052
|
+
payload: {
|
|
1053
|
+
obj: mkSceneObj(id, "arc", label, {
|
|
1054
|
+
construction: { kind: "semicircle", p1: ids[0], p2: ids[1] }
|
|
1055
|
+
})
|
|
778
1056
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
});
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
var arcCenterTool = {
|
|
1061
|
+
key: "arcCenter",
|
|
1062
|
+
finalize(ctx) {
|
|
1063
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1064
|
+
if (ids[0] === ids[1] || ids[0] === ids[2] || ids[1] === ids[2]) {
|
|
1065
|
+
ctx.toast?.("C\u1EA7n 3 \u0111i\u1EC3m ph\xE2n bi\u1EC7t", { variant: "warning", id: "arc-center-dup" });
|
|
789
1066
|
return;
|
|
790
1067
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
1068
|
+
const id = freshId(ctx, "arc");
|
|
1069
|
+
const label = ctx.nextLabel("arc");
|
|
1070
|
+
ctx.store.dispatch({
|
|
1071
|
+
type: "ADD",
|
|
1072
|
+
payload: {
|
|
1073
|
+
obj: mkSceneObj(id, "arc", label, {
|
|
1074
|
+
construction: { kind: "byCenter", center: ids[0], p1: ids[1], p2: ids[2] }
|
|
1075
|
+
})
|
|
795
1076
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
});
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
var arc3Tool = {
|
|
1081
|
+
key: "arc3",
|
|
1082
|
+
finalize(ctx) {
|
|
1083
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1084
|
+
if (ids[0] === ids[1] || ids[0] === ids[2] || ids[1] === ids[2]) {
|
|
1085
|
+
ctx.toast?.("C\u1EA7n 3 \u0111i\u1EC3m ph\xE2n bi\u1EC7t", { variant: "warning", id: "arc3-dup" });
|
|
806
1086
|
return;
|
|
807
1087
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const cross = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
|
|
818
|
-
if (Math.abs(cross) < 1e-6) {
|
|
819
|
-
ctx.toast?.("Kh\xF4ng v\u1EBD \u0111\u01B0\u1EE3c cung qua 3 \u0111i\u1EC3m th\u1EB3ng h\xE0ng", {
|
|
820
|
-
variant: "warning",
|
|
821
|
-
id: "arc3-collinear"
|
|
822
|
-
});
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
const id = freshId(ctx, "arc");
|
|
826
|
-
const label = ctx.nextLabel("arc");
|
|
827
|
-
ctx.store.dispatch({
|
|
828
|
-
type: "ADD",
|
|
829
|
-
payload: {
|
|
830
|
-
obj: mkSceneObj(id, "arc", label, {
|
|
831
|
-
construction: { kind: "by3Points", p1: ids[0], p2: ids[1], p3: ids[2] }
|
|
832
|
-
})
|
|
833
|
-
}
|
|
1088
|
+
const picks = ctx.pendingRef.current;
|
|
1089
|
+
const ax = picks[0].X(), ay = picks[0].Y();
|
|
1090
|
+
const bx = picks[1].X(), by = picks[1].Y();
|
|
1091
|
+
const cx = picks[2].X(), cy = picks[2].Y();
|
|
1092
|
+
const cross = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
|
|
1093
|
+
if (Math.abs(cross) < 1e-6) {
|
|
1094
|
+
ctx.toast?.("Kh\xF4ng v\u1EBD \u0111\u01B0\u1EE3c cung qua 3 \u0111i\u1EC3m th\u1EB3ng h\xE0ng", {
|
|
1095
|
+
variant: "warning",
|
|
1096
|
+
id: "arc3-collinear"
|
|
834
1097
|
});
|
|
835
1098
|
return;
|
|
836
1099
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1100
|
+
const id = freshId(ctx, "arc");
|
|
1101
|
+
const label = ctx.nextLabel("arc");
|
|
1102
|
+
ctx.store.dispatch({
|
|
1103
|
+
type: "ADD",
|
|
1104
|
+
payload: {
|
|
1105
|
+
obj: mkSceneObj(id, "arc", label, {
|
|
1106
|
+
construction: { kind: "by3Points", p1: ids[0], p2: ids[1], p3: ids[2] }
|
|
1107
|
+
})
|
|
841
1108
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
});
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
case "midpoint": {
|
|
855
|
-
const id = freshId(ctx, "mp");
|
|
856
|
-
const label = ctx.nextLabel("point");
|
|
857
|
-
ctx.store.dispatch({
|
|
858
|
-
type: "ADD",
|
|
859
|
-
payload: { obj: mkSceneObj(id, "point", label, {
|
|
860
|
-
constraint: { kind: "midpoint", p1: ids[0], p2: ids[1] }
|
|
861
|
-
}) }
|
|
862
|
-
});
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
865
|
-
case "perpFoot": {
|
|
866
|
-
const fromPoint = findPickIdByKind(ctx, "point");
|
|
867
|
-
const onLine = findPickIdByKind(ctx, "line");
|
|
868
|
-
if (!fromPoint || !onLine) return;
|
|
869
|
-
const id = freshId(ctx, "pf");
|
|
870
|
-
const label = ctx.nextLabel("point");
|
|
871
|
-
ctx.store.dispatch({
|
|
872
|
-
type: "ADD",
|
|
873
|
-
payload: { obj: mkSceneObj(id, "point", label, {
|
|
874
|
-
constraint: { kind: "perpFoot", from: fromPoint, onLine }
|
|
875
|
-
}) }
|
|
876
|
-
});
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
var sectorCenterTool = {
|
|
1113
|
+
key: "sectorCenter",
|
|
1114
|
+
finalize(ctx) {
|
|
1115
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1116
|
+
if (ids[0] === ids[1] || ids[0] === ids[2] || ids[1] === ids[2]) {
|
|
1117
|
+
ctx.toast?.("C\u1EA7n 3 \u0111i\u1EC3m ph\xE2n bi\u1EC7t", { variant: "warning", id: "sector-center-dup" });
|
|
877
1118
|
return;
|
|
878
1119
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
})
|
|
887
|
-
}
|
|
888
|
-
|
|
1120
|
+
const id = freshId(ctx, "sec");
|
|
1121
|
+
const label = ctx.nextLabel("sector");
|
|
1122
|
+
ctx.store.dispatch({
|
|
1123
|
+
type: "ADD",
|
|
1124
|
+
payload: {
|
|
1125
|
+
obj: mkSceneObj(id, "sector", label, {
|
|
1126
|
+
construction: { kind: "byCenter", center: ids[0], p1: ids[1], p2: ids[2] }
|
|
1127
|
+
})
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
var incircleTool = {
|
|
1133
|
+
key: "incircle",
|
|
1134
|
+
finalize(ctx) {
|
|
1135
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1136
|
+
const id = freshId(ctx, "ic");
|
|
1137
|
+
const label = ctx.nextLabel("circle");
|
|
1138
|
+
ctx.store.dispatch({
|
|
1139
|
+
type: "ADD",
|
|
1140
|
+
payload: { obj: mkSceneObj(id, "circle", label, {
|
|
1141
|
+
construction: { kind: "incircle", p1: ids[0], p2: ids[1], p3: ids[2] }
|
|
1142
|
+
}) }
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
var excircleTool = {
|
|
1147
|
+
key: "excircle",
|
|
1148
|
+
finalize(ctx) {
|
|
1149
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1150
|
+
const id = freshId(ctx, "exc");
|
|
1151
|
+
const label = ctx.nextLabel("circle");
|
|
1152
|
+
ctx.store.dispatch({
|
|
1153
|
+
type: "ADD",
|
|
1154
|
+
payload: { obj: mkSceneObj(id, "circle", label, {
|
|
1155
|
+
construction: { kind: "excircle", p1: ids[0], p2: ids[1], p3: ids[2], opposite: ids[0] }
|
|
1156
|
+
}) }
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
// src/stamps/geometry-2d/editor/handlers/finalize/points.ts
|
|
1162
|
+
var points_exports = {};
|
|
1163
|
+
__export(points_exports, {
|
|
1164
|
+
arcMidpointTool: () => arcMidpointTool,
|
|
1165
|
+
centroidTool: () => centroidTool,
|
|
1166
|
+
circleIntersectionTool: () => circleIntersectionTool,
|
|
1167
|
+
circumcenterTool: () => circumcenterTool,
|
|
1168
|
+
excenterTool: () => excenterTool,
|
|
1169
|
+
incenterTool: () => incenterTool,
|
|
1170
|
+
midpointTool: () => midpointTool,
|
|
1171
|
+
orthocenterTool: () => orthocenterTool,
|
|
1172
|
+
perpFootTool: () => perpFootTool,
|
|
1173
|
+
pointOnTool: () => pointOnTool,
|
|
1174
|
+
secondIntersectionTool: () => secondIntersectionTool,
|
|
1175
|
+
tangencyPointTool: () => tangencyPointTool,
|
|
1176
|
+
tangentPointExtTool: () => tangentPointExtTool
|
|
1177
|
+
});
|
|
1178
|
+
var midpointTool = {
|
|
1179
|
+
key: "midpoint",
|
|
1180
|
+
finalize(ctx) {
|
|
1181
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1182
|
+
const id = freshId(ctx, "mp");
|
|
1183
|
+
const label = ctx.nextLabel("point");
|
|
1184
|
+
ctx.store.dispatch({
|
|
1185
|
+
type: "ADD",
|
|
1186
|
+
payload: { obj: mkSceneObj(id, "point", label, {
|
|
1187
|
+
constraint: { kind: "midpoint", p1: ids[0], p2: ids[1] }
|
|
1188
|
+
}) }
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
var perpFootTool = {
|
|
1193
|
+
key: "perpFoot",
|
|
1194
|
+
finalize(ctx) {
|
|
1195
|
+
const fromPoint = findPickIdByKind(ctx, "point");
|
|
1196
|
+
const onLine = findPickIdByKind(ctx, "line");
|
|
1197
|
+
if (!fromPoint || !onLine) return;
|
|
1198
|
+
const id = freshId(ctx, "pf");
|
|
1199
|
+
const label = ctx.nextLabel("point");
|
|
1200
|
+
ctx.store.dispatch({
|
|
1201
|
+
type: "ADD",
|
|
1202
|
+
payload: { obj: mkSceneObj(id, "point", label, {
|
|
1203
|
+
constraint: { kind: "perpFoot", from: fromPoint, onLine }
|
|
1204
|
+
}) }
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
var centroidTool = {
|
|
1209
|
+
key: "centroid",
|
|
1210
|
+
finalize(ctx) {
|
|
1211
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1212
|
+
const id = freshId(ctx, "g");
|
|
1213
|
+
const label = ctx.nextLabel("point");
|
|
1214
|
+
ctx.store.dispatch({
|
|
1215
|
+
type: "ADD",
|
|
1216
|
+
payload: { obj: mkSceneObj(id, "point", label, {
|
|
1217
|
+
constraint: { kind: "centroid", vertices: [ids[0], ids[1], ids[2]] }
|
|
1218
|
+
}) }
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
var circumcenterTool = {
|
|
1223
|
+
key: "circumcenter",
|
|
1224
|
+
finalize(ctx) {
|
|
1225
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1226
|
+
const id = freshId(ctx, "o");
|
|
1227
|
+
const label = ctx.nextLabel("point");
|
|
1228
|
+
ctx.store.dispatch({
|
|
1229
|
+
type: "ADD",
|
|
1230
|
+
payload: { obj: mkSceneObj(id, "point", label, {
|
|
1231
|
+
constraint: { kind: "circumcenter", vertices: [ids[0], ids[1], ids[2]] }
|
|
1232
|
+
}) }
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
var incenterTool = {
|
|
1237
|
+
key: "incenter",
|
|
1238
|
+
finalize(ctx) {
|
|
1239
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1240
|
+
const id = freshId(ctx, "i");
|
|
1241
|
+
const label = ctx.nextLabel("point");
|
|
1242
|
+
ctx.store.dispatch({
|
|
1243
|
+
type: "ADD",
|
|
1244
|
+
payload: { obj: mkSceneObj(id, "point", label, {
|
|
1245
|
+
constraint: { kind: "incenter", vertices: [ids[0], ids[1], ids[2]] }
|
|
1246
|
+
}) }
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
};
|
|
1250
|
+
var orthocenterTool = {
|
|
1251
|
+
key: "orthocenter",
|
|
1252
|
+
finalize(ctx) {
|
|
1253
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1254
|
+
const id = freshId(ctx, "h");
|
|
1255
|
+
const label = ctx.nextLabel("point");
|
|
1256
|
+
ctx.store.dispatch({
|
|
1257
|
+
type: "ADD",
|
|
1258
|
+
payload: { obj: mkSceneObj(id, "point", label, {
|
|
1259
|
+
constraint: { kind: "orthocenter", vertices: [ids[0], ids[1], ids[2]] }
|
|
1260
|
+
}) }
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
var excenterTool = {
|
|
1265
|
+
key: "excenter",
|
|
1266
|
+
finalize(ctx) {
|
|
1267
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1268
|
+
const id = freshId(ctx, "ex");
|
|
1269
|
+
const label = ctx.nextLabel("point");
|
|
1270
|
+
ctx.store.dispatch({
|
|
1271
|
+
type: "ADD",
|
|
1272
|
+
payload: { obj: mkSceneObj(id, "point", label, {
|
|
1273
|
+
constraint: { kind: "excenter", vertices: [ids[0], ids[1], ids[2]], opposite: ids[0] }
|
|
1274
|
+
}) }
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
var tangencyPointTool = {
|
|
1279
|
+
key: "tangencyPoint",
|
|
1280
|
+
finalize(ctx) {
|
|
1281
|
+
const circleId = findPickIdByKind(ctx, "circle");
|
|
1282
|
+
const lineId = findPickIdByKind(ctx, "line");
|
|
1283
|
+
if (!circleId || !lineId) return;
|
|
1284
|
+
const id = freshId(ctx, "tp");
|
|
1285
|
+
const label = ctx.nextLabel("point");
|
|
1286
|
+
ctx.store.dispatch({
|
|
1287
|
+
type: "ADD",
|
|
1288
|
+
payload: { obj: mkSceneObj(id, "point", label, {
|
|
1289
|
+
constraint: { kind: "tangencyPoint", circle: circleId, onLine: lineId }
|
|
1290
|
+
}) }
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
var secondIntersectionTool = {
|
|
1295
|
+
key: "secondIntersection",
|
|
1296
|
+
finalize(ctx) {
|
|
1297
|
+
const lineId = findPickIdByKind(ctx, "line");
|
|
1298
|
+
const circleId = findPickIdByKind(ctx, "circle");
|
|
1299
|
+
const otherId = findPickIdByKind(ctx, "point");
|
|
1300
|
+
if (!lineId || !circleId || !otherId) return;
|
|
1301
|
+
const id = freshId(ctx, "X");
|
|
1302
|
+
const label = ctx.nextLabel("point");
|
|
1303
|
+
ctx.store.dispatch({
|
|
1304
|
+
type: "ADD",
|
|
1305
|
+
payload: { obj: mkSceneObj(id, "point", label, {
|
|
1306
|
+
constraint: { kind: "secondIntersection", line: lineId, circle: circleId, other: otherId }
|
|
1307
|
+
}) }
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
var arcMidpointTool = {
|
|
1312
|
+
key: "arcMidpoint",
|
|
1313
|
+
finalize(ctx) {
|
|
1314
|
+
const circleId = findPickIdByKind(ctx, "circle");
|
|
1315
|
+
const picks = ctx.pendingRef.current;
|
|
1316
|
+
const allIds = ctx.pendingIdsRef.current;
|
|
1317
|
+
const ptIds = [];
|
|
1318
|
+
for (let i = 0; i < picks.length; i += 1) {
|
|
1319
|
+
if (objKind(picks[i]) === "point" && allIds[i]) ptIds.push(allIds[i]);
|
|
889
1320
|
}
|
|
890
|
-
|
|
891
|
-
|
|
1321
|
+
if (!circleId || ptIds.length < 3) return;
|
|
1322
|
+
const id = freshId(ctx, "M");
|
|
1323
|
+
const label = ctx.nextLabel("point");
|
|
1324
|
+
ctx.store.dispatch({
|
|
1325
|
+
type: "ADD",
|
|
1326
|
+
payload: { obj: mkSceneObj(id, "point", label, {
|
|
1327
|
+
constraint: { kind: "arcMidpoint", circle: circleId, a: ptIds[0], b: ptIds[1], notContaining: ptIds[2] }
|
|
1328
|
+
}) }
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
var circleIntersectionTool = {
|
|
1333
|
+
key: "circleIntersection",
|
|
1334
|
+
finalize(ctx) {
|
|
1335
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1336
|
+
for (const which of [0, 1]) {
|
|
1337
|
+
const id = freshId(ctx, "X");
|
|
892
1338
|
const label = ctx.nextLabel("point");
|
|
893
1339
|
ctx.store.dispatch({
|
|
894
1340
|
type: "ADD",
|
|
895
1341
|
payload: { obj: mkSceneObj(id, "point", label, {
|
|
896
|
-
constraint: { kind: "
|
|
1342
|
+
constraint: { kind: "circleIntersection", c1: ids[0], c2: ids[1], which }
|
|
897
1343
|
}) }
|
|
898
1344
|
});
|
|
899
|
-
return;
|
|
900
1345
|
}
|
|
901
|
-
|
|
902
|
-
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
var tangentPointExtTool = {
|
|
1349
|
+
key: "tangentPointExt",
|
|
1350
|
+
finalize(ctx) {
|
|
1351
|
+
const fromId = findPickIdByKind(ctx, "point");
|
|
1352
|
+
const circleId = findPickIdByKind(ctx, "circle");
|
|
1353
|
+
if (!fromId || !circleId) return;
|
|
1354
|
+
for (const which of [0, 1]) {
|
|
1355
|
+
const id = freshId(ctx, "T");
|
|
903
1356
|
const label = ctx.nextLabel("point");
|
|
904
1357
|
ctx.store.dispatch({
|
|
905
1358
|
type: "ADD",
|
|
906
1359
|
payload: { obj: mkSceneObj(id, "point", label, {
|
|
907
|
-
constraint: { kind: "
|
|
1360
|
+
constraint: { kind: "tangentPointExt", from: fromId, circle: circleId, which }
|
|
908
1361
|
}) }
|
|
909
1362
|
});
|
|
910
|
-
return;
|
|
911
1363
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
var pointOnTool = {
|
|
1367
|
+
key: "pointOn",
|
|
1368
|
+
finalize(ctx, _toolDef, clickXY) {
|
|
1369
|
+
const obj = ctx.pendingRef.current[0];
|
|
1370
|
+
const objId = ctx.pendingIdsRef.current[0];
|
|
1371
|
+
if (!obj || !objId) return;
|
|
1372
|
+
const kind = objKind(obj);
|
|
1373
|
+
const id = freshId(ctx, "p");
|
|
1374
|
+
const label = ctx.nextLabel("point");
|
|
1375
|
+
const px = clickXY?.x ?? 0;
|
|
1376
|
+
const py = clickXY?.y ?? 0;
|
|
1377
|
+
let constraint = null;
|
|
1378
|
+
if (kind === "circle") {
|
|
1379
|
+
const o = obj.center ?? obj.midpoint;
|
|
1380
|
+
const ox = o ? o.X() : 0;
|
|
1381
|
+
const oy = o ? o.Y() : 0;
|
|
1382
|
+
constraint = { kind: "onCircle", circleId: objId, theta: Math.atan2(py - oy, px - ox) };
|
|
1383
|
+
} else if (kind === "line") {
|
|
1384
|
+
const elType = (obj.elType || "").toString().toLowerCase();
|
|
1385
|
+
const p1 = obj.point1;
|
|
1386
|
+
const p2 = obj.point2;
|
|
1387
|
+
let t = 0;
|
|
1388
|
+
if (p1 && p2) {
|
|
1389
|
+
const dx = p2.X() - p1.X();
|
|
1390
|
+
const dy = p2.Y() - p1.Y();
|
|
1391
|
+
const len2 = dx * dx + dy * dy || 1;
|
|
1392
|
+
t = ((px - p1.X()) * dx + (py - p1.Y()) * dy) / len2;
|
|
1393
|
+
}
|
|
1394
|
+
constraint = elType === "segment" ? { kind: "onSegment", segmentId: objId, t } : { kind: "onLine", lineId: objId, t };
|
|
922
1395
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1396
|
+
if (!constraint) return;
|
|
1397
|
+
ctx.store.dispatch({ type: "ADD", payload: { obj: mkSceneObj(id, "point", label, { constraint }) } });
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
// src/stamps/geometry-2d/editor/handlers/finalize/polygons.ts
|
|
1402
|
+
var polygons_exports = {};
|
|
1403
|
+
__export(polygons_exports, {
|
|
1404
|
+
isoTrapezoidTool: () => isoTrapezoidTool,
|
|
1405
|
+
isoTriangleTool: () => isoTriangleTool,
|
|
1406
|
+
parallelogramTool: () => parallelogramTool,
|
|
1407
|
+
rectangleTool: () => rectangleTool,
|
|
1408
|
+
rhombusTool: () => rhombusTool,
|
|
1409
|
+
rightTriangleTool: () => rightTriangleTool,
|
|
1410
|
+
squareTool: () => squareTool
|
|
1411
|
+
});
|
|
1412
|
+
var squareTool = {
|
|
1413
|
+
key: "square",
|
|
1414
|
+
finalize(ctx) {
|
|
1415
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1416
|
+
const id = freshId(ctx, "sq");
|
|
1417
|
+
const label = ctx.nextLabel("polygon");
|
|
1418
|
+
ctx.store.dispatch({
|
|
1419
|
+
type: "ADD",
|
|
1420
|
+
payload: { obj: mkSceneObj(id, "polygon", label, {
|
|
1421
|
+
construction: { kind: "square", p1: ids[0], p2: ids[1] }
|
|
1422
|
+
}) }
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1426
|
+
var rectangleTool = {
|
|
1427
|
+
key: "rectangle",
|
|
1428
|
+
finalize(ctx) {
|
|
1429
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1430
|
+
const [aId, bId, cId] = ids;
|
|
1431
|
+
const P = readJxgPos(ctx, cId);
|
|
1432
|
+
const Aj = readJxgPos(ctx, aId);
|
|
1433
|
+
const Bj = readJxgPos(ctx, bId);
|
|
1434
|
+
const t = computePerpendicularT(P, Bj, Aj, Bj);
|
|
1435
|
+
const polyId = freshId(ctx, "rect");
|
|
1436
|
+
const label = ctx.nextLabel("polygon");
|
|
1437
|
+
ctx.store.dispatch({
|
|
1438
|
+
type: "TRANSACTION",
|
|
1439
|
+
payload: { actions: [
|
|
1440
|
+
{ type: "UPDATE_ATTRS", payload: { id: cId, patch: {
|
|
1441
|
+
constraint: { kind: "onPerpendicular", through: bId, perpToA: aId, perpToB: bId, t }
|
|
1442
|
+
} } },
|
|
1443
|
+
{ type: "ADD", payload: { obj: mkSceneObj(polyId, "polygon", label, {
|
|
1444
|
+
construction: { kind: "rectangle", p1: aId, p2: bId, p3: cId }
|
|
1445
|
+
}) } }
|
|
1446
|
+
] }
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
var rhombusTool = {
|
|
1451
|
+
key: "rhombus",
|
|
1452
|
+
finalize(ctx) {
|
|
1453
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1454
|
+
const [aId, bId, cId] = ids;
|
|
1455
|
+
const P = readJxgPos(ctx, cId);
|
|
1456
|
+
const Bj = readJxgPos(ctx, bId);
|
|
1457
|
+
const theta = computeCircleTheta(P, Bj);
|
|
1458
|
+
const polyId = freshId(ctx, "rho");
|
|
1459
|
+
const label = ctx.nextLabel("polygon");
|
|
1460
|
+
ctx.store.dispatch({
|
|
1461
|
+
type: "TRANSACTION",
|
|
1462
|
+
payload: { actions: [
|
|
1463
|
+
{ type: "UPDATE_ATTRS", payload: { id: cId, patch: {
|
|
1464
|
+
constraint: { kind: "onCircleAroundPoint", center: bId, radiusPoint: aId, theta }
|
|
1465
|
+
} } },
|
|
1466
|
+
{ type: "ADD", payload: { obj: mkSceneObj(polyId, "polygon", label, {
|
|
1467
|
+
construction: { kind: "rhombus", p1: aId, p2: bId, p3: cId }
|
|
1468
|
+
}) } }
|
|
1469
|
+
] }
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
function finalizePgmTrap(ctx, key) {
|
|
1474
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1475
|
+
const [aId, bId, cId] = ids;
|
|
1476
|
+
const prefix = key === "parallelogram" ? "pgm" : "trap";
|
|
1477
|
+
const polyId = freshId(ctx, prefix);
|
|
1478
|
+
const label = ctx.nextLabel("polygon");
|
|
1479
|
+
ctx.store.dispatch({
|
|
1480
|
+
type: "ADD",
|
|
1481
|
+
payload: { obj: mkSceneObj(polyId, "polygon", label, {
|
|
1482
|
+
construction: { kind: key, p1: aId, p2: bId, p3: cId }
|
|
1483
|
+
}) }
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
var parallelogramTool = {
|
|
1487
|
+
key: "parallelogram",
|
|
1488
|
+
finalize(ctx) {
|
|
1489
|
+
finalizePgmTrap(ctx, "parallelogram");
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
var isoTrapezoidTool = {
|
|
1493
|
+
key: "isoTrapezoid",
|
|
1494
|
+
finalize(ctx) {
|
|
1495
|
+
finalizePgmTrap(ctx, "isoTrapezoid");
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
var isoTriangleTool = {
|
|
1499
|
+
key: "isoTriangle",
|
|
1500
|
+
finalize(ctx) {
|
|
1501
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1502
|
+
const [b1Id, b2Id, apexId] = ids;
|
|
1503
|
+
const P = readJxgPos(ctx, apexId);
|
|
1504
|
+
const B1 = readJxgPos(ctx, b1Id);
|
|
1505
|
+
const B2 = readJxgPos(ctx, b2Id);
|
|
1506
|
+
const t = computePerpBisectorT(P, B1, B2);
|
|
1507
|
+
const polyId = freshId(ctx, "iso");
|
|
1508
|
+
const label = ctx.nextLabel("polygon");
|
|
1509
|
+
ctx.store.dispatch({
|
|
1510
|
+
type: "TRANSACTION",
|
|
1511
|
+
payload: { actions: [
|
|
1512
|
+
{ type: "UPDATE_ATTRS", payload: { id: apexId, patch: {
|
|
1513
|
+
constraint: { kind: "onPerpBisector", p1: b1Id, p2: b2Id, t }
|
|
1514
|
+
} } },
|
|
1515
|
+
{ type: "ADD", payload: { obj: mkSceneObj(polyId, "polygon", label, {
|
|
1516
|
+
construction: { kind: "isoTriangle", base1: b1Id, base2: b2Id, apex: apexId }
|
|
1517
|
+
}) } }
|
|
1518
|
+
] }
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
var rightTriangleTool = {
|
|
1523
|
+
key: "rightTriangle",
|
|
1524
|
+
finalize(ctx) {
|
|
1525
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1526
|
+
const [rId, p1Id, p2Id] = ids;
|
|
1527
|
+
const P = readJxgPos(ctx, p2Id);
|
|
1528
|
+
const R = readJxgPos(ctx, rId);
|
|
1529
|
+
const P1 = readJxgPos(ctx, p1Id);
|
|
1530
|
+
const t = computePerpendicularT(P, R, R, P1);
|
|
1531
|
+
const polyId = freshId(ctx, "rtri");
|
|
1532
|
+
const label = ctx.nextLabel("polygon");
|
|
1533
|
+
ctx.store.dispatch({
|
|
1534
|
+
type: "TRANSACTION",
|
|
1535
|
+
payload: { actions: [
|
|
1536
|
+
{ type: "UPDATE_ATTRS", payload: { id: p2Id, patch: {
|
|
1537
|
+
constraint: { kind: "onPerpendicular", through: rId, perpToA: rId, perpToB: p1Id, t }
|
|
1538
|
+
} } },
|
|
1539
|
+
{ type: "ADD", payload: { obj: mkSceneObj(polyId, "polygon", label, {
|
|
1540
|
+
construction: { kind: "rightTriangle", rightAngle: rId, leg1End: p1Id, leg2End: p2Id }
|
|
1541
|
+
}) } }
|
|
1542
|
+
] }
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
|
|
1547
|
+
// src/stamps/geometry-2d/editor/handlers/finalize/measure.ts
|
|
1548
|
+
var measure_exports = {};
|
|
1549
|
+
__export(measure_exports, {
|
|
1550
|
+
angleTool: () => angleTool,
|
|
1551
|
+
distanceTool: () => distanceTool,
|
|
1552
|
+
intersectTool: () => intersectTool
|
|
1553
|
+
});
|
|
1554
|
+
var angleTool = {
|
|
1555
|
+
key: "angle",
|
|
1556
|
+
finalize(ctx) {
|
|
1557
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1558
|
+
const id = freshId(ctx, "ang");
|
|
1559
|
+
const label = ctx.nextLabel("angle");
|
|
1560
|
+
ctx.store.dispatch({
|
|
1561
|
+
type: "ADD",
|
|
1562
|
+
payload: { obj: mkSceneObj(id, "angle", label, {
|
|
1563
|
+
p1: ids[0],
|
|
1564
|
+
vertex: ids[1],
|
|
1565
|
+
p2: ids[2]
|
|
1566
|
+
}) }
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
var distanceTool = {
|
|
1571
|
+
key: "distance",
|
|
1572
|
+
finalize(ctx) {
|
|
1573
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1574
|
+
const id = freshId(ctx, "d");
|
|
1575
|
+
const label = ctx.nextLabel("distance");
|
|
1576
|
+
ctx.store.dispatch({
|
|
1577
|
+
type: "ADD",
|
|
1578
|
+
payload: { obj: mkSceneObj(id, "distance", label, { p1: ids[0], p2: ids[1] }) }
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
var intersectTool = {
|
|
1583
|
+
key: "intersect",
|
|
1584
|
+
finalize(ctx) {
|
|
1585
|
+
const ids = ctx.pendingIdsRef.current;
|
|
1586
|
+
const picks = ctx.pendingRef.current;
|
|
1587
|
+
const pendIds = ctx.pendingIdsRef.current;
|
|
1588
|
+
const aIdx = pendIds.indexOf(ids[0]);
|
|
1589
|
+
const bIdx = pendIds.indexOf(ids[1]);
|
|
1590
|
+
if (aIdx < 0 || bIdx < 0) return;
|
|
1591
|
+
const aKind = objKind(picks[aIdx]);
|
|
1592
|
+
const bKind = objKind(picks[bIdx]);
|
|
1593
|
+
if (aKind === "line" && bKind === "line") {
|
|
1594
|
+
dispatchAddIntersection(ctx, { kind: "lineLine", ref1: ids[0], ref2: ids[1] });
|
|
934
1595
|
return;
|
|
935
1596
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1597
|
+
const isLineCircle = aKind === "line" && bKind === "circle" || aKind === "circle" && bKind === "line";
|
|
1598
|
+
const isCircleCircle = aKind === "circle" && bKind === "circle";
|
|
1599
|
+
if (!isLineCircle && !isCircleCircle) return;
|
|
1600
|
+
for (const branch of [0, 1]) {
|
|
1601
|
+
dispatchAddIntersection(ctx, {
|
|
1602
|
+
kind: isLineCircle ? "lineCircle" : "circleCircle",
|
|
1603
|
+
ref1: ids[0],
|
|
1604
|
+
ref2: ids[1],
|
|
1605
|
+
branch
|
|
942
1606
|
});
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
case "intersect": {
|
|
946
|
-
const picks = ctx.pendingRef.current;
|
|
947
|
-
const pendIds = ctx.pendingIdsRef.current;
|
|
948
|
-
const aIdx = pendIds.indexOf(ids[0]);
|
|
949
|
-
const bIdx = pendIds.indexOf(ids[1]);
|
|
950
|
-
if (aIdx < 0 || bIdx < 0) return;
|
|
951
|
-
const aKind = objKind(picks[aIdx]);
|
|
952
|
-
const bKind = objKind(picks[bIdx]);
|
|
953
|
-
if (aKind === "line" && bKind === "line") {
|
|
954
|
-
dispatchAddIntersection(ctx, { kind: "lineLine", ref1: ids[0], ref2: ids[1] });
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
957
|
-
const isLineCircle = aKind === "line" && bKind === "circle" || aKind === "circle" && bKind === "line";
|
|
958
|
-
const isCircleCircle = aKind === "circle" && bKind === "circle";
|
|
959
|
-
if (!isLineCircle && !isCircleCircle) return;
|
|
960
|
-
for (const branch of [0, 1]) {
|
|
961
|
-
dispatchAddIntersection(ctx, {
|
|
962
|
-
kind: isLineCircle ? "lineCircle" : "circleCircle",
|
|
963
|
-
ref1: ids[0],
|
|
964
|
-
ref2: ids[1],
|
|
965
|
-
branch
|
|
966
|
-
});
|
|
967
|
-
}
|
|
968
|
-
return;
|
|
969
1607
|
}
|
|
970
|
-
default:
|
|
971
|
-
return;
|
|
972
1608
|
}
|
|
1609
|
+
};
|
|
1610
|
+
|
|
1611
|
+
// src/stamps/geometry-2d/editor/handlers/finalize/registry.ts
|
|
1612
|
+
var ALL = [
|
|
1613
|
+
...Object.values(lines_exports),
|
|
1614
|
+
...Object.values(circles_exports),
|
|
1615
|
+
...Object.values(points_exports),
|
|
1616
|
+
...Object.values(polygons_exports),
|
|
1617
|
+
...Object.values(measure_exports)
|
|
1618
|
+
].filter((m) => !!m && typeof m.finalize === "function");
|
|
1619
|
+
var TOOL_MODULES = new Map(ALL.map((m) => [m.key, m]));
|
|
1620
|
+
|
|
1621
|
+
// src/stamps/geometry-2d/editor/handlers/finalizeShape.ts
|
|
1622
|
+
function finalizeShape(ctx, toolDef, clickXY) {
|
|
1623
|
+
TOOL_MODULES.get(toolDef.key)?.finalize(ctx, toolDef, clickXY);
|
|
973
1624
|
}
|
|
974
1625
|
|
|
975
1626
|
// src/stamps/geometry-2d/editor/transforms.ts
|
|
@@ -1011,6 +1662,20 @@ function finalizeTransform(ctx, tool, pendingIds, value) {
|
|
|
1011
1662
|
});
|
|
1012
1663
|
return;
|
|
1013
1664
|
}
|
|
1665
|
+
if (tool === "circleCR") {
|
|
1666
|
+
const r = Math.abs(value);
|
|
1667
|
+
if (!(r > 0)) {
|
|
1668
|
+
ctx.flashWarn("B\xE1n k\xEDnh ph\u1EA3i > 0");
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
const id = freshId(ctx, "c");
|
|
1672
|
+
const label = ctx.nextLabel("circle");
|
|
1673
|
+
ctx.store.dispatch({
|
|
1674
|
+
type: "ADD",
|
|
1675
|
+
payload: { obj: mkSceneObj(id, "circle", label, { center: pendingIds[0], radius: r }) }
|
|
1676
|
+
});
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1014
1679
|
const sourceId = pendingIds[0];
|
|
1015
1680
|
const state = ctx.store.getState();
|
|
1016
1681
|
const source = state.objects[sourceId];
|
|
@@ -1204,7 +1869,7 @@ function handleMultiClickTool(ctx, toolDef, e, x, y, hits, bestHit) {
|
|
|
1204
1869
|
}
|
|
1205
1870
|
if (ctx.pendingIdsRef.current.length >= toolDef.needs) {
|
|
1206
1871
|
const tk = toolDef.key;
|
|
1207
|
-
if (tk === "rotate" || tk === "dilate" || tk === "regularPolygon") {
|
|
1872
|
+
if (tk === "rotate" || tk === "dilate" || tk === "regularPolygon" || tk === "circleCR") {
|
|
1208
1873
|
const cx = (e.clientX ?? 0) + 8;
|
|
1209
1874
|
const cy = (e.clientY ?? 0) + 8;
|
|
1210
1875
|
ctx.pendingTransformRef.current = {
|
|
@@ -1220,7 +1885,7 @@ function handleMultiClickTool(ctx, toolDef, e, x, y, hits, bestHit) {
|
|
|
1220
1885
|
ctx.clearPending();
|
|
1221
1886
|
return;
|
|
1222
1887
|
}
|
|
1223
|
-
finalizeShape(ctx, toolDef);
|
|
1888
|
+
finalizeShape(ctx, toolDef, { x, y });
|
|
1224
1889
|
ctx.clearPending();
|
|
1225
1890
|
} else {
|
|
1226
1891
|
ctx.refreshPreview();
|
|
@@ -1754,10 +2419,7 @@ function useAxisGridSync({
|
|
|
1754
2419
|
}
|
|
1755
2420
|
function useEditorShortcuts({
|
|
1756
2421
|
store,
|
|
1757
|
-
pendingIdsRef,
|
|
1758
2422
|
selectedSetRef,
|
|
1759
|
-
clearPending,
|
|
1760
|
-
clearSelection,
|
|
1761
2423
|
deleteSelection
|
|
1762
2424
|
}) {
|
|
1763
2425
|
useEffect(() => {
|
|
@@ -1779,18 +2441,6 @@ function useEditorShortcuts({
|
|
|
1779
2441
|
store.redo();
|
|
1780
2442
|
return;
|
|
1781
2443
|
}
|
|
1782
|
-
if (e.key === "Escape" && !inField) {
|
|
1783
|
-
if (pendingIdsRef.current.length > 0) {
|
|
1784
|
-
e.preventDefault();
|
|
1785
|
-
e.stopPropagation();
|
|
1786
|
-
clearPending();
|
|
1787
|
-
}
|
|
1788
|
-
if (selectedSetRef.current.size > 0) {
|
|
1789
|
-
e.preventDefault();
|
|
1790
|
-
e.stopPropagation();
|
|
1791
|
-
clearSelection();
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
2444
|
if ((e.key === "Delete" || e.key === "Backspace") && !inField && selectedSetRef.current.size > 0) {
|
|
1795
2445
|
e.preventDefault();
|
|
1796
2446
|
e.stopPropagation();
|
|
@@ -1799,7 +2449,7 @@ function useEditorShortcuts({
|
|
|
1799
2449
|
};
|
|
1800
2450
|
window.addEventListener("keydown", onKey, { capture: true });
|
|
1801
2451
|
return () => window.removeEventListener("keydown", onKey, { capture: true });
|
|
1802
|
-
}, [store,
|
|
2452
|
+
}, [store, selectedSetRef, deleteSelection]);
|
|
1803
2453
|
}
|
|
1804
2454
|
function useJxgSceneIdMap({ store, rendererRef }) {
|
|
1805
2455
|
const jxgIdToSceneRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -2003,10 +2653,7 @@ var MiniBoard2D = forwardRef(function MiniBoard2D2({ onReady, store, selectedToo
|
|
|
2003
2653
|
};
|
|
2004
2654
|
useEditorShortcuts({
|
|
2005
2655
|
store,
|
|
2006
|
-
pendingIdsRef: toolSM.pendingIdsRef,
|
|
2007
2656
|
selectedSetRef,
|
|
2008
|
-
clearPending,
|
|
2009
|
-
clearSelection,
|
|
2010
2657
|
deleteSelection
|
|
2011
2658
|
});
|
|
2012
2659
|
useAxisGridSync({ boardRef, axisObjsRef, isDarkRef, showAxis, showGrid });
|
|
@@ -2548,7 +3195,8 @@ var MultiPropertiesPopover = (props) => {
|
|
|
2548
3195
|
var LABELS = {
|
|
2549
3196
|
rotate: { aria: "G\xF3c quay", label: "G\xF3c (\xB0)", step: 15 },
|
|
2550
3197
|
dilate: { aria: "T\u1EF7 s\u1ED1 k", label: "T\u1EF7 s\u1ED1 k", step: 0.5 },
|
|
2551
|
-
regularPolygon: { aria: "S\u1ED1 c\u1EA1nh \u0111a gi\xE1c \u0111\u1EC1u", label: "S\u1ED1 c\u1EA1nh (n \u2265 3)", step: 1, min: 3 }
|
|
3198
|
+
regularPolygon: { aria: "S\u1ED1 c\u1EA1nh \u0111a gi\xE1c \u0111\u1EC1u", label: "S\u1ED1 c\u1EA1nh (n \u2265 3)", step: 1, min: 3 },
|
|
3199
|
+
circleCR: { aria: "B\xE1n k\xEDnh \u0111\u01B0\u1EDDng tr\xF2n", label: "B\xE1n k\xEDnh", step: 0.5, min: 0 }
|
|
2552
3200
|
};
|
|
2553
3201
|
var TransformParamPopover = ({ kind, anchor, defaultValue, onConfirm, onCancel, isDark }) => {
|
|
2554
3202
|
const [value, setValue] = useState(defaultValue);
|
|
@@ -2620,12 +3268,41 @@ var TransformParamPopover = ({ kind, anchor, defaultValue, onConfirm, onCancel,
|
|
|
2620
3268
|
);
|
|
2621
3269
|
return createPortal(node, document.body);
|
|
2622
3270
|
};
|
|
2623
|
-
function useAiFigure(generator) {
|
|
3271
|
+
function useAiFigure(generator, options = {}) {
|
|
3272
|
+
const { currentState } = options;
|
|
2624
3273
|
const [prompt, setPrompt] = useState("");
|
|
2625
3274
|
const [isLoading, setIsLoading] = useState(false);
|
|
2626
3275
|
const [error, setError] = useState(null);
|
|
3276
|
+
const [tokens, setTokens] = useState(0);
|
|
2627
3277
|
const abortRef = useRef(null);
|
|
2628
3278
|
const requestIdRef = useRef(0);
|
|
3279
|
+
const { dsl: currentDsl, unsupported, entityCount, hasContent } = useMemo(() => {
|
|
3280
|
+
if (!currentState || currentState.order.length === 0) {
|
|
3281
|
+
return {
|
|
3282
|
+
dsl: null,
|
|
3283
|
+
unsupported: [],
|
|
3284
|
+
entityCount: { points: 0, shapes: 0 },
|
|
3285
|
+
hasContent: false
|
|
3286
|
+
};
|
|
3287
|
+
}
|
|
3288
|
+
const { dsl, unsupported: unsupported2 } = serializeState(currentState);
|
|
3289
|
+
return {
|
|
3290
|
+
dsl,
|
|
3291
|
+
unsupported: unsupported2,
|
|
3292
|
+
entityCount: { points: dsl.points.length, shapes: dsl.shapes.length },
|
|
3293
|
+
hasContent: true
|
|
3294
|
+
};
|
|
3295
|
+
}, [currentState]);
|
|
3296
|
+
const hasUnsupported = unsupported.length > 0;
|
|
3297
|
+
const initialMode = hasContent && !hasUnsupported ? "refine" : "build";
|
|
3298
|
+
const [mode, setModeInternal] = useState(initialMode);
|
|
3299
|
+
useEffect(() => {
|
|
3300
|
+
if (!hasContent && mode === "refine") setModeInternal("build");
|
|
3301
|
+
if (hasUnsupported && mode === "refine") setModeInternal("build");
|
|
3302
|
+
}, [hasContent, hasUnsupported, mode]);
|
|
3303
|
+
const setMode = useCallback((next) => {
|
|
3304
|
+
setModeInternal(next);
|
|
3305
|
+
}, []);
|
|
2629
3306
|
useEffect(() => () => abortRef.current?.abort(), []);
|
|
2630
3307
|
const submit = useCallback(async () => {
|
|
2631
3308
|
const problem = prompt.trim();
|
|
@@ -2643,8 +3320,15 @@ function useAiFigure(generator) {
|
|
|
2643
3320
|
abortRef.current = controller;
|
|
2644
3321
|
setIsLoading(true);
|
|
2645
3322
|
setError(null);
|
|
3323
|
+
setTokens(0);
|
|
2646
3324
|
try {
|
|
2647
|
-
const generated = await generator(problem, {
|
|
3325
|
+
const generated = await generator(problem, {
|
|
3326
|
+
signal: controller.signal,
|
|
3327
|
+
onProgress: (info) => {
|
|
3328
|
+
if (requestId === requestIdRef.current) setTokens(info.tokens);
|
|
3329
|
+
},
|
|
3330
|
+
...mode === "refine" && currentDsl ? { currentDsl } : {}
|
|
3331
|
+
});
|
|
2648
3332
|
if (controller.signal.aborted || requestId !== requestIdRef.current) return null;
|
|
2649
3333
|
if (!generated.ok) {
|
|
2650
3334
|
setError(generated.message);
|
|
@@ -2656,7 +3340,9 @@ function useAiFigure(generator) {
|
|
|
2656
3340
|
return null;
|
|
2657
3341
|
}
|
|
2658
3342
|
if (requestId === requestIdRef.current) {
|
|
2659
|
-
setError(
|
|
3343
|
+
setError(
|
|
3344
|
+
caught instanceof Error && caught.message ? caught.message : "Kh\xF4ng th\u1EC3 d\u1EF1ng h\xECnh b\u1EB1ng AI."
|
|
3345
|
+
);
|
|
2660
3346
|
}
|
|
2661
3347
|
return null;
|
|
2662
3348
|
} finally {
|
|
@@ -2665,60 +3351,351 @@ function useAiFigure(generator) {
|
|
|
2665
3351
|
setIsLoading(false);
|
|
2666
3352
|
}
|
|
2667
3353
|
}
|
|
2668
|
-
}, [generator, prompt]);
|
|
2669
|
-
|
|
3354
|
+
}, [generator, prompt, mode, currentDsl]);
|
|
3355
|
+
const cancel = useCallback(() => {
|
|
3356
|
+
abortRef.current?.abort();
|
|
3357
|
+
}, []);
|
|
3358
|
+
return {
|
|
3359
|
+
prompt,
|
|
3360
|
+
setPrompt,
|
|
3361
|
+
isLoading,
|
|
3362
|
+
error,
|
|
3363
|
+
submit,
|
|
3364
|
+
cancel,
|
|
3365
|
+
tokens,
|
|
3366
|
+
mode,
|
|
3367
|
+
setMode,
|
|
3368
|
+
entityCount,
|
|
3369
|
+
hasUnsupported
|
|
3370
|
+
};
|
|
2670
3371
|
}
|
|
2671
|
-
|
|
3372
|
+
var PaperclipIcon = (props) => /* @__PURE__ */ jsx(
|
|
3373
|
+
"svg",
|
|
3374
|
+
{
|
|
3375
|
+
viewBox: "0 0 24 24",
|
|
3376
|
+
fill: "none",
|
|
3377
|
+
stroke: "currentColor",
|
|
3378
|
+
strokeWidth: 1.75,
|
|
3379
|
+
strokeLinecap: "round",
|
|
3380
|
+
strokeLinejoin: "round",
|
|
3381
|
+
"aria-hidden": true,
|
|
3382
|
+
...props,
|
|
3383
|
+
children: /* @__PURE__ */ jsx("path", { d: "M21.44 11.05 12.25 20.24a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66L9.41 17.41a2 2 0 1 1-2.83-2.83l8.49-8.49" })
|
|
3384
|
+
}
|
|
3385
|
+
);
|
|
3386
|
+
var ArrowUpIcon = (props) => /* @__PURE__ */ jsxs(
|
|
3387
|
+
"svg",
|
|
3388
|
+
{
|
|
3389
|
+
viewBox: "0 0 24 24",
|
|
3390
|
+
fill: "none",
|
|
3391
|
+
stroke: "currentColor",
|
|
3392
|
+
strokeWidth: 2.25,
|
|
3393
|
+
strokeLinecap: "round",
|
|
3394
|
+
strokeLinejoin: "round",
|
|
3395
|
+
"aria-hidden": true,
|
|
3396
|
+
...props,
|
|
3397
|
+
children: [
|
|
3398
|
+
/* @__PURE__ */ jsx("path", { d: "M12 19V5" }),
|
|
3399
|
+
/* @__PURE__ */ jsx("path", { d: "m5 12 7-7 7 7" })
|
|
3400
|
+
]
|
|
3401
|
+
}
|
|
3402
|
+
);
|
|
3403
|
+
var StopIcon = (props) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) });
|
|
3404
|
+
function AiFigurePrompt({
|
|
3405
|
+
generator,
|
|
3406
|
+
onGenerated,
|
|
3407
|
+
currentState,
|
|
3408
|
+
extractProblem = handleExtractProblem
|
|
3409
|
+
}) {
|
|
2672
3410
|
const {
|
|
2673
3411
|
prompt,
|
|
2674
3412
|
setPrompt,
|
|
2675
3413
|
isLoading,
|
|
2676
3414
|
error,
|
|
2677
|
-
submit
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
3415
|
+
submit,
|
|
3416
|
+
cancel,
|
|
3417
|
+
tokens,
|
|
3418
|
+
mode,
|
|
3419
|
+
setMode,
|
|
3420
|
+
entityCount,
|
|
3421
|
+
hasUnsupported
|
|
3422
|
+
} = useAiFigure(generator, { currentState });
|
|
3423
|
+
const [elapsed, setElapsed] = useState(0);
|
|
3424
|
+
useEffect(() => {
|
|
3425
|
+
if (!isLoading) {
|
|
3426
|
+
setElapsed(0);
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
const id = setInterval(() => setElapsed((s) => s + 1), 1e3);
|
|
3430
|
+
return () => clearInterval(id);
|
|
3431
|
+
}, [isLoading]);
|
|
3432
|
+
const [image, setImage] = useState(null);
|
|
3433
|
+
const [ocrLoading, setOcrLoading] = useState(false);
|
|
3434
|
+
const [ocrError, setOcrError] = useState(null);
|
|
3435
|
+
const [ocrWarning, setOcrWarning] = useState(null);
|
|
3436
|
+
const [isDragOver, setIsDragOver] = useState(false);
|
|
3437
|
+
const fileInputRef = useRef(null);
|
|
3438
|
+
const textareaRef = useRef(null);
|
|
3439
|
+
const imagePreview = image ? `data:${image.mediaType};base64,${image.base64}` : null;
|
|
3440
|
+
useEffect(() => {
|
|
3441
|
+
setOcrError(null);
|
|
3442
|
+
setOcrWarning(null);
|
|
3443
|
+
}, [image]);
|
|
3444
|
+
const handleFile = useCallback(
|
|
3445
|
+
async (file) => {
|
|
3446
|
+
if (isLoading || ocrLoading) return;
|
|
3447
|
+
const v = validateFile(file);
|
|
3448
|
+
if (!v.ok) {
|
|
3449
|
+
setOcrError(v.message);
|
|
3450
|
+
return;
|
|
3451
|
+
}
|
|
3452
|
+
try {
|
|
3453
|
+
const part = await fileToImagePart(file);
|
|
3454
|
+
setImage(part);
|
|
3455
|
+
} catch (e) {
|
|
3456
|
+
setOcrError(e instanceof Error ? e.message : "Kh\xF4ng decode \u0111\u01B0\u1EE3c \u1EA3nh");
|
|
3457
|
+
}
|
|
3458
|
+
},
|
|
3459
|
+
[isLoading, ocrLoading]
|
|
3460
|
+
);
|
|
3461
|
+
const handleFileInput = useCallback(
|
|
3462
|
+
(e) => {
|
|
3463
|
+
const file = e.target.files?.[0];
|
|
3464
|
+
if (file) void handleFile(file);
|
|
3465
|
+
e.target.value = "";
|
|
3466
|
+
},
|
|
3467
|
+
[handleFile]
|
|
3468
|
+
);
|
|
3469
|
+
const handlePaste = useCallback(
|
|
3470
|
+
(e) => {
|
|
3471
|
+
const item = Array.from(e.clipboardData.items).find(
|
|
3472
|
+
(it) => it.kind === "file" && it.type.startsWith("image/")
|
|
3473
|
+
);
|
|
3474
|
+
if (!item) return;
|
|
3475
|
+
const file = item.getAsFile();
|
|
3476
|
+
if (!file) return;
|
|
3477
|
+
e.preventDefault();
|
|
3478
|
+
void handleFile(file);
|
|
3479
|
+
},
|
|
3480
|
+
[handleFile]
|
|
3481
|
+
);
|
|
3482
|
+
const handleDrop = useCallback(
|
|
3483
|
+
(e) => {
|
|
3484
|
+
e.preventDefault();
|
|
3485
|
+
setIsDragOver(false);
|
|
3486
|
+
const file = Array.from(e.dataTransfer.files).find(
|
|
3487
|
+
(f) => f.type.startsWith("image/")
|
|
3488
|
+
);
|
|
3489
|
+
if (file) void handleFile(file);
|
|
3490
|
+
},
|
|
3491
|
+
[handleFile]
|
|
3492
|
+
);
|
|
3493
|
+
const runOcr = useCallback(async () => {
|
|
3494
|
+
if (!image) return;
|
|
3495
|
+
setOcrLoading(true);
|
|
3496
|
+
setOcrError(null);
|
|
3497
|
+
setOcrWarning(null);
|
|
3498
|
+
try {
|
|
3499
|
+
const r = await extractProblem(image);
|
|
3500
|
+
if (r.kind === "success" || r.kind === "low-confidence") {
|
|
3501
|
+
setPrompt(r.text);
|
|
3502
|
+
if (r.kind === "low-confidence") setOcrWarning(r.warning);
|
|
3503
|
+
requestAnimationFrame(() => textareaRef.current?.focus());
|
|
3504
|
+
} else {
|
|
3505
|
+
setOcrError(r.message);
|
|
3506
|
+
}
|
|
3507
|
+
} finally {
|
|
3508
|
+
setOcrLoading(false);
|
|
3509
|
+
}
|
|
3510
|
+
}, [image, setPrompt, extractProblem]);
|
|
3511
|
+
const handleSendClick = useCallback(async () => {
|
|
3512
|
+
if (image && !prompt.trim() && !ocrLoading) {
|
|
3513
|
+
await runOcr();
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
2681
3516
|
const generated = await submit();
|
|
2682
3517
|
if (generated) onGenerated(generated);
|
|
2683
|
-
}, [
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
3518
|
+
}, [image, prompt, ocrLoading, runOcr, submit, onGenerated]);
|
|
3519
|
+
const handleSwitchToBuild = useCallback(() => {
|
|
3520
|
+
if (currentState && currentState.order.length > 0) {
|
|
3521
|
+
const ok = window.confirm(
|
|
3522
|
+
"D\u1EF1ng m\u1EDBi s\u1EBD thay to\xE0n b\u1ED9 h\xECnh hi\u1EC7n t\u1EA1i b\u1EB1ng h\xECnh m\u1EDBi t\u1EEB AI. Ti\u1EBFp t\u1EE5c?"
|
|
3523
|
+
);
|
|
3524
|
+
if (!ok) return;
|
|
3525
|
+
}
|
|
3526
|
+
setMode("build");
|
|
3527
|
+
}, [currentState, setMode]);
|
|
3528
|
+
const hasContent = currentState != null && currentState.order.length > 0;
|
|
3529
|
+
const promptEmpty = !prompt.trim();
|
|
3530
|
+
const willOcr = image != null && promptEmpty;
|
|
3531
|
+
const sendDisabled = !image && promptEmpty || ocrLoading || isLoading && !willOcr;
|
|
3532
|
+
const refineChipLabel = entityCount.points + entityCount.shapes > 0 ? `Th\xEAm v\xE0o \xB7 ${entityCount.points}\u0111, ${entityCount.shapes}\u0111o\u1EA1n` : "Th\xEAm v\xE0o";
|
|
3533
|
+
const placeholder = willOcr ? "B\u1EA5m g\u1EEDi \u0111\u1EC3 \u0111\u1ECDc \u0111\u1EC1 t\u1EEB \u1EA3nh \u2014 ho\u1EB7c t\u1EF1 g\xF5 \u1EDF \u0111\xE2y\u2026" : mode === "refine" ? "M\xF4 t\u1EA3 ph\u1EA7n c\u1EA7n th\xEAm (vd: trung \u0111i\u1EC3m M c\u1EE7a BC). C\xF3 th\u1EC3 d\xE1n \u1EA3nh \u0111\u1EC1 (Ctrl+V)." : "M\xF4 t\u1EA3 \u0111\u1EC1 b\xE0i c\u1EA7n d\u1EF1ng \u2014 ho\u1EB7c d\xE1n/\u0111\xEDnh \u1EA3nh \u0111\u1EC1 (Ctrl+V).";
|
|
3534
|
+
return /* @__PURE__ */ jsxs("div", { className: "border-b border-slate-200 bg-slate-50 px-3 py-3", children: [
|
|
3535
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
|
|
3536
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium tracking-wide text-slate-600", children: "D\u1EF1ng h\xECnh b\u1EB1ng AI" }),
|
|
3537
|
+
hasContent && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", role: "tablist", "aria-label": "Ch\u1EBF \u0111\u1ED9 AI", children: [
|
|
3538
|
+
/* @__PURE__ */ jsx(
|
|
3539
|
+
"button",
|
|
3540
|
+
{
|
|
3541
|
+
type: "button",
|
|
3542
|
+
role: "tab",
|
|
3543
|
+
"aria-selected": mode === "refine",
|
|
3544
|
+
"data-testid": "geometry-ai-mode-refine",
|
|
3545
|
+
onClick: () => setMode("refine"),
|
|
3546
|
+
disabled: isLoading || hasUnsupported,
|
|
3547
|
+
title: hasUnsupported ? "H\xECnh c\xF3 \u0111\u1ED1i t\u01B0\u1EE3ng ngo\xE0i DSL \u2014 ch\u1EC9 d\u1EF1ng m\u1EDBi \u0111\u01B0\u1EE3c" : refineChipLabel,
|
|
3548
|
+
className: `rounded-full px-2.5 py-0.5 text-[11px] transition ${mode === "refine" ? "bg-emerald-600 text-white shadow-sm" : "text-slate-500 hover:text-emerald-700"} ${hasUnsupported ? "cursor-not-allowed opacity-40" : ""}`,
|
|
3549
|
+
children: refineChipLabel
|
|
3550
|
+
}
|
|
3551
|
+
),
|
|
3552
|
+
/* @__PURE__ */ jsx(
|
|
3553
|
+
"button",
|
|
3554
|
+
{
|
|
3555
|
+
type: "button",
|
|
3556
|
+
role: "tab",
|
|
3557
|
+
"aria-selected": mode === "build",
|
|
3558
|
+
"data-testid": "geometry-ai-mode-build",
|
|
3559
|
+
onClick: handleSwitchToBuild,
|
|
3560
|
+
disabled: isLoading,
|
|
3561
|
+
className: `rounded-full px-2.5 py-0.5 text-[11px] transition ${mode === "build" ? "bg-emerald-600 text-white shadow-sm" : "text-slate-500 hover:text-emerald-700"}`,
|
|
3562
|
+
children: "D\u1EF1ng m\u1EDBi"
|
|
3563
|
+
}
|
|
3564
|
+
)
|
|
3565
|
+
] })
|
|
3566
|
+
] }),
|
|
3567
|
+
hasUnsupported && /* @__PURE__ */ jsx(
|
|
3568
|
+
"p",
|
|
3569
|
+
{
|
|
3570
|
+
className: "mb-1.5 text-[10px] text-amber-700",
|
|
3571
|
+
"data-testid": "geometry-ai-unsupported-warning",
|
|
3572
|
+
children: "H\xECnh c\xF3 \u0111\u1ED1i t\u01B0\u1EE3ng ngo\xE0i DSL \u2014 ch\u1EC9 d\u1EF1ng m\u1EDBi \u0111\u01B0\u1EE3c"
|
|
3573
|
+
}
|
|
3574
|
+
),
|
|
3575
|
+
/* @__PURE__ */ jsxs(
|
|
3576
|
+
"div",
|
|
3577
|
+
{
|
|
3578
|
+
onDragOver: (e) => {
|
|
3579
|
+
e.preventDefault();
|
|
3580
|
+
setIsDragOver(true);
|
|
3581
|
+
},
|
|
3582
|
+
onDragLeave: () => setIsDragOver(false),
|
|
3583
|
+
onDrop: handleDrop,
|
|
3584
|
+
onPaste: handlePaste,
|
|
3585
|
+
"aria-label": "Khu v\u1EF1c k\xE9o th\u1EA3 \u1EA3nh",
|
|
3586
|
+
role: "region",
|
|
3587
|
+
className: "group relative flex flex-col rounded-2xl bg-white shadow-sm transition-all duration-150 ring-1 ring-slate-200 focus-within:ring-2 focus-within:ring-emerald-400/70 focus-within:shadow-md " + (isDragOver ? "ring-2 ring-emerald-500 bg-emerald-50/40" : ""),
|
|
3588
|
+
children: [
|
|
3589
|
+
image && imagePreview && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 px-3 pt-2.5", children: /* @__PURE__ */ jsxs("div", { className: "group/chip relative", children: [
|
|
3590
|
+
/* @__PURE__ */ jsx(
|
|
3591
|
+
"img",
|
|
3592
|
+
{
|
|
3593
|
+
src: imagePreview,
|
|
3594
|
+
alt: "\u1EA2nh \u0111\u1EC1 b\xE0i",
|
|
3595
|
+
className: "max-h-48 max-w-full h-auto w-auto rounded-lg border border-slate-200 shadow-sm"
|
|
3596
|
+
}
|
|
3597
|
+
),
|
|
3598
|
+
/* @__PURE__ */ jsx(
|
|
3599
|
+
"button",
|
|
3600
|
+
{
|
|
3601
|
+
type: "button",
|
|
3602
|
+
onClick: () => setImage(null),
|
|
3603
|
+
disabled: ocrLoading || isLoading,
|
|
3604
|
+
"aria-label": "Xo\xE1 \u1EA3nh",
|
|
3605
|
+
className: "absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-slate-900/85 text-[11px] font-medium text-white shadow ring-2 ring-white transition hover:bg-slate-900 disabled:opacity-50",
|
|
3606
|
+
children: "\xD7"
|
|
3607
|
+
}
|
|
3608
|
+
)
|
|
3609
|
+
] }) }),
|
|
2695
3610
|
/* @__PURE__ */ jsx(
|
|
2696
3611
|
"textarea",
|
|
2697
3612
|
{
|
|
3613
|
+
ref: textareaRef,
|
|
2698
3614
|
id: "geometry-ai-prompt",
|
|
2699
3615
|
"aria-label": "\u0110\u1EC1 b\xE0i cho AI",
|
|
3616
|
+
"data-testid": "geometry-ai-textarea",
|
|
2700
3617
|
value: prompt,
|
|
2701
|
-
onChange: (
|
|
3618
|
+
onChange: (e) => setPrompt(e.target.value),
|
|
3619
|
+
onKeyDown: (e) => {
|
|
3620
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !sendDisabled) {
|
|
3621
|
+
e.preventDefault();
|
|
3622
|
+
void handleSendClick();
|
|
3623
|
+
}
|
|
3624
|
+
},
|
|
2702
3625
|
disabled: isLoading,
|
|
2703
3626
|
rows: 2,
|
|
2704
|
-
placeholder
|
|
2705
|
-
className: "
|
|
3627
|
+
placeholder,
|
|
3628
|
+
className: "block w-full resize-none rounded-2xl bg-transparent px-3.5 pt-2.5 pb-1 text-sm leading-relaxed text-slate-800 placeholder:text-slate-400 outline-none disabled:opacity-60 field-sizing-content max-h-44"
|
|
2706
3629
|
}
|
|
2707
3630
|
),
|
|
2708
|
-
/* @__PURE__ */
|
|
2709
|
-
"
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
3631
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-2 pb-2 pt-1", children: [
|
|
3632
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
3633
|
+
/* @__PURE__ */ jsx(
|
|
3634
|
+
"button",
|
|
3635
|
+
{
|
|
3636
|
+
type: "button",
|
|
3637
|
+
onClick: () => fileInputRef.current?.click(),
|
|
3638
|
+
disabled: isLoading || ocrLoading,
|
|
3639
|
+
"aria-label": "\u0110\xEDnh \u1EA3nh \u0111\u1EC1 b\xE0i",
|
|
3640
|
+
title: "\u0110\xEDnh \u1EA3nh (c\u0169ng c\xF3 th\u1EC3 d\xE1n b\u1EB1ng Ctrl+V ho\u1EB7c k\xE9o th\u1EA3)",
|
|
3641
|
+
className: "flex h-8 w-8 items-center justify-center rounded-full text-slate-500 transition hover:bg-slate-100 hover:text-emerald-700 disabled:opacity-40",
|
|
3642
|
+
children: /* @__PURE__ */ jsx(PaperclipIcon, { className: "h-[18px] w-[18px]" })
|
|
3643
|
+
}
|
|
3644
|
+
),
|
|
3645
|
+
/* @__PURE__ */ jsx(
|
|
3646
|
+
"input",
|
|
3647
|
+
{
|
|
3648
|
+
ref: fileInputRef,
|
|
3649
|
+
type: "file",
|
|
3650
|
+
accept: "image/png,image/jpeg,image/webp",
|
|
3651
|
+
className: "sr-only",
|
|
3652
|
+
onChange: handleFileInput,
|
|
3653
|
+
disabled: isLoading || ocrLoading,
|
|
3654
|
+
"aria-label": "Ch\u1ECDn \u1EA3nh \u0111\u1EC1 b\xE0i"
|
|
3655
|
+
}
|
|
3656
|
+
)
|
|
3657
|
+
] }),
|
|
3658
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3659
|
+
(isLoading || ocrLoading) && /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] tabular-nums text-slate-500", children: ocrLoading ? "\u0111\u1ECDc \u1EA3nh\u2026" : tokens > 0 ? `${tokens}tok \xB7 ${elapsed}s` : `${elapsed}s` }),
|
|
3660
|
+
isLoading ? /* @__PURE__ */ jsx(
|
|
3661
|
+
"button",
|
|
3662
|
+
{
|
|
3663
|
+
type: "button",
|
|
3664
|
+
onClick: cancel,
|
|
3665
|
+
"aria-label": "Hu\u1EF7 d\u1EF1ng h\xECnh AI",
|
|
3666
|
+
"data-testid": "geometry-ai-cancel",
|
|
3667
|
+
title: `\u0110ang d\u1EF1ng\u2026 ${elapsed}s \u2014 b\u1EA5m \u0111\u1EC3 hu\u1EF7`,
|
|
3668
|
+
className: "flex h-8 w-8 items-center justify-center rounded-full bg-amber-500 text-white shadow-sm transition hover:scale-105 hover:bg-amber-600 active:scale-95",
|
|
3669
|
+
children: /* @__PURE__ */ jsx(StopIcon, { className: "h-3.5 w-3.5" })
|
|
3670
|
+
}
|
|
3671
|
+
) : /* @__PURE__ */ jsx(
|
|
3672
|
+
"button",
|
|
3673
|
+
{
|
|
3674
|
+
type: "button",
|
|
3675
|
+
onClick: () => void handleSendClick(),
|
|
3676
|
+
disabled: sendDisabled,
|
|
3677
|
+
"aria-label": willOcr ? "\u0110\u1ECDc \u0111\u1EC1 t\u1EEB \u1EA3nh" : "D\u1EF1ng b\u1EB1ng AI",
|
|
3678
|
+
title: willOcr ? "\u0110\u1ECDc \u0111\u1EC1 t\u1EEB \u1EA3nh (s\u1EBD \u0111i\u1EC1n v\xE0o \xF4 chat)" : "D\u1EF1ng b\u1EB1ng AI (Ctrl/\u2318+Enter)",
|
|
3679
|
+
"data-testid": willOcr ? "geometry-ai-ocr" : "geometry-ai-submit",
|
|
3680
|
+
className: "flex h-8 w-8 items-center justify-center rounded-full bg-emerald-600 text-white shadow-sm transition hover:scale-105 hover:bg-emerald-700 active:scale-95 disabled:cursor-not-allowed disabled:bg-slate-300 disabled:hover:scale-100",
|
|
3681
|
+
children: /* @__PURE__ */ jsx(ArrowUpIcon, { className: "h-[18px] w-[18px]" })
|
|
3682
|
+
}
|
|
3683
|
+
)
|
|
3684
|
+
] })
|
|
3685
|
+
] }),
|
|
3686
|
+
isDragOver && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center rounded-2xl bg-emerald-50/60 text-xs font-medium text-emerald-700", children: "Th\u1EA3 \u1EA3nh v\xE0o \u0111\xE2y" })
|
|
3687
|
+
]
|
|
3688
|
+
}
|
|
3689
|
+
),
|
|
3690
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1.5 px-1 text-[10px] text-slate-500", children: [
|
|
3691
|
+
"D\xE1n \u1EA3nh (Ctrl+V), k\xE9o th\u1EA3, ho\u1EB7c b\u1EA5m ",
|
|
3692
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u{1F4CE}" }),
|
|
3693
|
+
" \u0111\u1EC3 \u0111\xEDnh \u1EA3nh \u0111\u1EC1."
|
|
3694
|
+
] }),
|
|
3695
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: error }),
|
|
3696
|
+
ocrError && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: ocrError }),
|
|
3697
|
+
ocrWarning && /* @__PURE__ */ jsx("p", { className: "mt-1 rounded bg-amber-50 px-2 py-1 text-[11px] text-amber-700", children: ocrWarning })
|
|
3698
|
+
] });
|
|
2722
3699
|
}
|
|
2723
3700
|
var GeometryEditorPanelInner = forwardRef(
|
|
2724
3701
|
function GeometryEditorPanelInner2({
|
|
@@ -2752,6 +3729,11 @@ var GeometryEditorPanelInner = forwardRef(
|
|
|
2752
3729
|
onSelectionChangeRef.current = onSelectionChange;
|
|
2753
3730
|
}, [onSelectionChange]);
|
|
2754
3731
|
useEditorState({ store, onHistoryChange });
|
|
3732
|
+
const currentSceneState = useSyncExternalStore(
|
|
3733
|
+
(cb) => store.subscribe(cb),
|
|
3734
|
+
() => store.getState(),
|
|
3735
|
+
() => store.getState()
|
|
3736
|
+
);
|
|
2755
3737
|
useEffect(() => {
|
|
2756
3738
|
const sync = () => setHasContent(Object.keys(store.getState().objects).length > 0);
|
|
2757
3739
|
sync();
|
|
@@ -2945,7 +3927,7 @@ var GeometryEditorPanelInner = forwardRef(
|
|
|
2945
3927
|
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
|
|
2946
3928
|
] }) })
|
|
2947
3929
|
] }),
|
|
2948
|
-
generateGeometryFigure && /* @__PURE__ */ jsx(AiFigurePrompt, { generator: generateGeometryFigure, onGenerated: loadAiFigure }),
|
|
3930
|
+
generateGeometryFigure && /* @__PURE__ */ jsx(AiFigurePrompt, { generator: generateGeometryFigure, onGenerated: loadAiFigure, currentState: currentSceneState }),
|
|
2949
3931
|
/* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1", children: /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
|
|
2950
3932
|
MiniBoard2D,
|
|
2951
3933
|
{
|
|
@@ -3018,12 +4000,12 @@ var GeometryEditorPanelInner = forwardRef(
|
|
|
3018
4000
|
onClose: dismissMultiPopover
|
|
3019
4001
|
}
|
|
3020
4002
|
),
|
|
3021
|
-
transformPopover && (transformPopover.tool === "rotate" || transformPopover.tool === "dilate" || transformPopover.tool === "regularPolygon") && /* @__PURE__ */ jsx(
|
|
4003
|
+
transformPopover && (transformPopover.tool === "rotate" || transformPopover.tool === "dilate" || transformPopover.tool === "regularPolygon" || transformPopover.tool === "circleCR") && /* @__PURE__ */ jsx(
|
|
3022
4004
|
TransformParamPopover,
|
|
3023
4005
|
{
|
|
3024
4006
|
kind: transformPopover.tool,
|
|
3025
4007
|
anchor: transformPopover.anchor,
|
|
3026
|
-
defaultValue: transformPopover.tool === "rotate" ? 90 : transformPopover.tool === "dilate" ? 2 : 6,
|
|
4008
|
+
defaultValue: transformPopover.tool === "rotate" ? 90 : transformPopover.tool === "dilate" ? 2 : transformPopover.tool === "circleCR" ? 3 : 6,
|
|
3027
4009
|
isDark,
|
|
3028
4010
|
onConfirm: (v) => {
|
|
3029
4011
|
handleRef.current?.confirmTransformParam(v);
|
|
@@ -3070,6 +4052,36 @@ var GeometryEditorPanel = forwardRef(
|
|
|
3070
4052
|
return /* @__PURE__ */ jsx(ToastProvider, { children: /* @__PURE__ */ jsx(GeometryEditorPanelInner, { ...props, ref }) });
|
|
3071
4053
|
}
|
|
3072
4054
|
);
|
|
4055
|
+
function makeDslRenderRow(store) {
|
|
4056
|
+
return function renderDslRow(obj, defaults) {
|
|
4057
|
+
const state = store.getState();
|
|
4058
|
+
const noop = () => {
|
|
4059
|
+
};
|
|
4060
|
+
return /* @__PURE__ */ jsx(
|
|
4061
|
+
ObjectRow,
|
|
4062
|
+
{
|
|
4063
|
+
obj,
|
|
4064
|
+
state,
|
|
4065
|
+
selected: defaults.selected,
|
|
4066
|
+
onSelect: defaults.onClick,
|
|
4067
|
+
onToggleVisible: (id) => {
|
|
4068
|
+
const o = state.objects[id];
|
|
4069
|
+
if (!o) return;
|
|
4070
|
+
store.dispatch({ type: "UPDATE", payload: { id, patch: { visible: !o.visible } } });
|
|
4071
|
+
},
|
|
4072
|
+
onToggleLocked: (id) => {
|
|
4073
|
+
const o = state.objects[id];
|
|
4074
|
+
if (!o) return;
|
|
4075
|
+
store.dispatch({ type: "UPDATE", payload: { id, patch: { locked: !o.locked } } });
|
|
4076
|
+
},
|
|
4077
|
+
onRename: noop,
|
|
4078
|
+
onChangeColor: noop,
|
|
4079
|
+
onDelete: (id) => store.dispatch({ type: "DELETE", payload: { id } }),
|
|
4080
|
+
describe: describeDsl
|
|
4081
|
+
}
|
|
4082
|
+
);
|
|
4083
|
+
};
|
|
4084
|
+
}
|
|
3073
4085
|
function parseInitialState(data) {
|
|
3074
4086
|
if (!isGeometryCustomData(data)) return null;
|
|
3075
4087
|
return deserializeBoard(data.jsonState);
|
|
@@ -3100,6 +4112,7 @@ var GeometryStampHost = forwardRef(
|
|
|
3100
4112
|
onSelect: (key) => setSelectedTool(key),
|
|
3101
4113
|
enabled: !isMobile
|
|
3102
4114
|
});
|
|
4115
|
+
const renderRow = useMemo(() => makeDslRenderRow(sceneStore), [sceneStore]);
|
|
3103
4116
|
const handleInsert = useCallback(
|
|
3104
4117
|
async (jsonState, svgString) => {
|
|
3105
4118
|
if (!api) return;
|
|
@@ -3161,7 +4174,8 @@ var GeometryStampHost = forwardRef(
|
|
|
3161
4174
|
onObjectSelect: (id) => {
|
|
3162
4175
|
setSelectedObjectId(id ?? void 0);
|
|
3163
4176
|
panelRef.current?.selectObject(id);
|
|
3164
|
-
}
|
|
4177
|
+
},
|
|
4178
|
+
renderRow
|
|
3165
4179
|
},
|
|
3166
4180
|
isMobile,
|
|
3167
4181
|
drawerOpen,
|
|
@@ -3196,5 +4210,5 @@ var GeometryStampHost = forwardRef(
|
|
|
3196
4210
|
);
|
|
3197
4211
|
|
|
3198
4212
|
export { GeometryStampHost };
|
|
3199
|
-
//# sourceMappingURL=host-
|
|
3200
|
-
//# sourceMappingURL=host-
|
|
4213
|
+
//# sourceMappingURL=host-HAYCJJ2T.mjs.map
|
|
4214
|
+
//# sourceMappingURL=host-HAYCJJ2T.mjs.map
|