@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,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { serializeScene } from './chunk-
|
|
2
|
+
import { serializeScene } from './chunk-D5LWSN2Y.mjs';
|
|
3
3
|
|
|
4
4
|
// src/stamps/graph-2d/serialize.ts
|
|
5
5
|
function stringifySceneState(state) {
|
|
@@ -24,5 +24,5 @@ function parseSceneState(json) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export { parseSceneState, stringifySceneState };
|
|
27
|
-
//# sourceMappingURL=chunk-
|
|
28
|
-
//# sourceMappingURL=chunk-
|
|
27
|
+
//# sourceMappingURL=chunk-HLAOGXEK.mjs.map
|
|
28
|
+
//# sourceMappingURL=chunk-HLAOGXEK.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/stamps/graph-2d/serialize.ts"],"names":[],"mappings":";;;AASO,SAAS,oBAAoB,KAAA,EAAsB;AACxD,EAAA,OAAO,eAAe,KAAK,CAAA;AAC7B;AAEO,SAAS,gBAAgB,IAAA,EAA4B;AAC1D,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,UAAU,OAAO,IAAA;AAC5C,EAAA,MAAM,CAAA,GAAI,GAAA;AACV,EAAA,IAAI,CAAA,CAAE,IAAA,EAAM,MAAA,KAAW,SAAA,EAAW,OAAO,IAAA;AACzC,EAAA,IAAI,CAAC,EAAE,IAAA,EAAM,IAAA,IAAQ,OAAO,CAAA,CAAE,IAAA,CAAK,IAAA,KAAS,QAAA,EAAU,OAAO,IAAA;AAC7D,EAAA,IAAI,OAAO,CAAA,CAAE,OAAA,KAAY,QAAA,EAAU,OAAO,IAAA;AAC1C,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,KAAK,GAAG,OAAO,IAAA;AACpC,EAAA,IAAI,CAAC,CAAA,CAAE,OAAA,IAAW,OAAO,CAAA,CAAE,OAAA,KAAY,UAAU,OAAO,IAAA;AACxD,EAAA,OAAO,GAAA;AACT","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/stamps/graph-2d/serialize.ts"],"names":[],"mappings":";;;AASO,SAAS,oBAAoB,KAAA,EAAsB;AACxD,EAAA,OAAO,eAAe,KAAK,CAAA;AAC7B;AAEO,SAAS,gBAAgB,IAAA,EAA4B;AAC1D,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,UAAU,OAAO,IAAA;AAC5C,EAAA,MAAM,CAAA,GAAI,GAAA;AACV,EAAA,IAAI,CAAA,CAAE,IAAA,EAAM,MAAA,KAAW,SAAA,EAAW,OAAO,IAAA;AACzC,EAAA,IAAI,CAAC,EAAE,IAAA,EAAM,IAAA,IAAQ,OAAO,CAAA,CAAE,IAAA,CAAK,IAAA,KAAS,QAAA,EAAU,OAAO,IAAA;AAC7D,EAAA,IAAI,OAAO,CAAA,CAAE,OAAA,KAAY,QAAA,EAAU,OAAO,IAAA;AAC1C,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,KAAK,GAAG,OAAO,IAAA;AACpC,EAAA,IAAI,CAAC,CAAA,CAAE,OAAA,IAAW,OAAO,CAAA,CAAE,OAAA,KAAY,UAAU,OAAO,IAAA;AACxD,EAAA,OAAO,GAAA;AACT","file":"chunk-HLAOGXEK.mjs","sourcesContent":["// src/stamps/graph-2d/serialize.ts\n//\n// graph-2d đã dùng plain State (không envelope) ngay từ đầu. Sau Tier D PR 3,\n// thin wrapper qua shared helper cho serialize. parseSceneState giữ behavior\n// null-on-invalid để host/index.tsx có thể discriminate \"customData hỏng\".\n\nimport { serializeScene } from '../shared/serializeScene';\nimport type { State } from '../../core/scene/types';\n\nexport function stringifySceneState(state: State): string {\n return serializeScene(state);\n}\n\nexport function parseSceneState(json: string): State | null {\n if (!json) return null;\n let raw: unknown;\n try {\n raw = JSON.parse(json);\n } catch {\n return null;\n }\n if (!raw || typeof raw !== 'object') return null;\n const v = raw as Partial<State>;\n if (v.meta?.domain !== 'graph2d') return null;\n if (!v.meta?.view || typeof v.meta.view !== 'object') return null;\n if (typeof v.counter !== 'number') return null;\n if (!Array.isArray(v.order)) return null;\n if (!v.objects || typeof v.objects !== 'object') return null;\n return raw as State;\n}\n"]}
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { listObjects } from './chunk-
|
|
3
|
-
import { createStore
|
|
2
|
+
import { listObjects } from './chunk-D5LWSN2Y.mjs';
|
|
3
|
+
import { createStore } from './chunk-IHUFOV7L.mjs';
|
|
4
4
|
import { createEmptyState } from './chunk-73Q7ADVL.mjs';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
5
|
+
import { getKind } from './chunk-B4NJJZFR.mjs';
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import React__default, { createContext, useRef, useReducer, useCallback, useEffect, useMemo, useContext, useState } from 'react';
|
|
8
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
8
9
|
import { createPortal } from 'react-dom';
|
|
9
10
|
|
|
11
|
+
var FALLBACK_DEFAULT_WIDTH = 240;
|
|
12
|
+
var FALLBACK_MIN_WIDTH = 220;
|
|
13
|
+
var FALLBACK_MAX_WIDTH = 480;
|
|
14
|
+
function clamp(n, min, max) {
|
|
15
|
+
return Math.max(min, Math.min(max, n));
|
|
16
|
+
}
|
|
17
|
+
function readStoredWidth(key, fallback, min, max) {
|
|
18
|
+
if (!key || typeof window === "undefined") return fallback;
|
|
19
|
+
try {
|
|
20
|
+
const raw = window.localStorage.getItem(key);
|
|
21
|
+
if (!raw) return fallback;
|
|
22
|
+
const n = parseInt(raw, 10);
|
|
23
|
+
if (Number.isFinite(n)) return clamp(n, min, max);
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
return fallback;
|
|
27
|
+
}
|
|
10
28
|
function CloseIcon() {
|
|
11
29
|
return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
12
30
|
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
@@ -14,8 +32,64 @@ function CloseIcon() {
|
|
|
14
32
|
] });
|
|
15
33
|
}
|
|
16
34
|
function LeftPanelShell(props) {
|
|
17
|
-
const {
|
|
35
|
+
const {
|
|
36
|
+
title,
|
|
37
|
+
icon,
|
|
38
|
+
onClose,
|
|
39
|
+
isDark,
|
|
40
|
+
tabs,
|
|
41
|
+
activeTab,
|
|
42
|
+
onTabChange,
|
|
43
|
+
testId,
|
|
44
|
+
resizable,
|
|
45
|
+
widthStorageKey,
|
|
46
|
+
defaultWidth,
|
|
47
|
+
minWidth,
|
|
48
|
+
maxWidth,
|
|
49
|
+
children
|
|
50
|
+
} = props;
|
|
18
51
|
const showTabs = !!tabs && tabs.length >= 2;
|
|
52
|
+
const min = minWidth ?? FALLBACK_MIN_WIDTH;
|
|
53
|
+
const max = maxWidth ?? FALLBACK_MAX_WIDTH;
|
|
54
|
+
const initial = clamp(defaultWidth ?? FALLBACK_DEFAULT_WIDTH, min, max);
|
|
55
|
+
const [width, setWidth] = React.useState(
|
|
56
|
+
() => resizable ? readStoredWidth(widthStorageKey, initial, min, max) : initial
|
|
57
|
+
);
|
|
58
|
+
const widthRef = React.useRef(width);
|
|
59
|
+
widthRef.current = width;
|
|
60
|
+
React.useEffect(() => {
|
|
61
|
+
if (!resizable || !widthStorageKey || typeof window === "undefined") return;
|
|
62
|
+
try {
|
|
63
|
+
window.localStorage.setItem(widthStorageKey, String(width));
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}, [resizable, widthStorageKey, width]);
|
|
67
|
+
const onResizeStart = React.useCallback(
|
|
68
|
+
(e) => {
|
|
69
|
+
if (!resizable) return;
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
const startX = e.clientX;
|
|
72
|
+
const startW = widthRef.current;
|
|
73
|
+
const onMove = (ev) => {
|
|
74
|
+
setWidth(clamp(startW + (ev.clientX - startX), min, max));
|
|
75
|
+
};
|
|
76
|
+
const onUp = () => {
|
|
77
|
+
window.removeEventListener("mousemove", onMove);
|
|
78
|
+
window.removeEventListener("mouseup", onUp);
|
|
79
|
+
document.body.style.cursor = "";
|
|
80
|
+
document.body.style.userSelect = "";
|
|
81
|
+
};
|
|
82
|
+
window.addEventListener("mousemove", onMove);
|
|
83
|
+
window.addEventListener("mouseup", onUp);
|
|
84
|
+
document.body.style.cursor = "ew-resize";
|
|
85
|
+
document.body.style.userSelect = "none";
|
|
86
|
+
},
|
|
87
|
+
[resizable, min, max]
|
|
88
|
+
);
|
|
89
|
+
const onResizeDoubleClick = React.useCallback(() => {
|
|
90
|
+
if (!resizable) return;
|
|
91
|
+
setWidth(initial);
|
|
92
|
+
}, [resizable, initial]);
|
|
19
93
|
return /* @__PURE__ */ jsxs(
|
|
20
94
|
"aside",
|
|
21
95
|
{
|
|
@@ -23,10 +97,12 @@ function LeftPanelShell(props) {
|
|
|
23
97
|
"aria-label": title,
|
|
24
98
|
"data-testid": testId ?? "left-panel",
|
|
25
99
|
"data-stamp-area": "true",
|
|
100
|
+
style: resizable ? { width: `${width}px` } : void 0,
|
|
26
101
|
className: [
|
|
27
102
|
isDark ? "theme--dark " : "",
|
|
28
|
-
"absolute left-0 top-0 z-30 flex h-full
|
|
29
|
-
|
|
103
|
+
"absolute left-0 top-0 z-30 flex h-full flex-col border-r border-slate-200 bg-white shadow-md animate-in slide-in-from-left duration-200",
|
|
104
|
+
resizable ? "" : "w-60"
|
|
105
|
+
].join(" "),
|
|
30
106
|
children: [
|
|
31
107
|
/* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between border-b border-slate-200 bg-gradient-to-r from-slate-50 to-white px-3 py-2", children: [
|
|
32
108
|
/* @__PURE__ */ jsxs("h3", { className: "flex items-center gap-2 text-sm font-semibold text-slate-800", children: [
|
|
@@ -61,6 +137,20 @@ function LeftPanelShell(props) {
|
|
|
61
137
|
className: "min-h-0 flex-1 overflow-y-auto p-3 space-y-3",
|
|
62
138
|
children
|
|
63
139
|
}
|
|
140
|
+
),
|
|
141
|
+
resizable && /* @__PURE__ */ jsx(
|
|
142
|
+
"div",
|
|
143
|
+
{
|
|
144
|
+
role: "separator",
|
|
145
|
+
"aria-orientation": "vertical",
|
|
146
|
+
"aria-label": "K\xE9o \u0111\u1EC3 \u0111\u1ED5i r\u1ED9ng panel",
|
|
147
|
+
"data-testid": "left-panel-resizer",
|
|
148
|
+
onMouseDown: onResizeStart,
|
|
149
|
+
onDoubleClick: onResizeDoubleClick,
|
|
150
|
+
className: "group absolute right-0 top-0 z-40 h-full w-1.5 -mr-0.5 cursor-ew-resize select-none",
|
|
151
|
+
title: "K\xE9o \u0111\u1EC3 \u0111\u1ED5i r\u1ED9ng (double-click \u0111\u1EC3 reset)",
|
|
152
|
+
children: /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-y-0 right-0 w-px bg-slate-200 transition group-hover:bg-emerald-400 group-hover:w-0.5 group-active:bg-emerald-500 group-active:w-0.5" })
|
|
153
|
+
}
|
|
64
154
|
)
|
|
65
155
|
]
|
|
66
156
|
}
|
|
@@ -133,7 +223,7 @@ function getKindUiMeta(kind) {
|
|
|
133
223
|
}
|
|
134
224
|
function ObjectRowMenu(props) {
|
|
135
225
|
const { locked, onToggleLocked, onRename, onChangeColor, onDelete } = props;
|
|
136
|
-
const [open, setOpen] =
|
|
226
|
+
const [open, setOpen] = React.useState(false);
|
|
137
227
|
return /* @__PURE__ */ jsxs("div", { className: "relative inline-block", children: [
|
|
138
228
|
/* @__PURE__ */ jsx(
|
|
139
229
|
"button",
|
|
@@ -203,11 +293,11 @@ function formatMeasure(items) {
|
|
|
203
293
|
return items.map((it) => `${it.label} = ${it.value.toFixed(2)}`).join(", ");
|
|
204
294
|
}
|
|
205
295
|
function ObjectRow(props) {
|
|
206
|
-
const { obj, state, selected, onSelect, onToggleVisible, onToggleLocked, onRename, onChangeColor, onDelete } = props;
|
|
296
|
+
const { obj, state, selected, onSelect, onToggleVisible, onToggleLocked, onRename, onChangeColor, onDelete, describe } = props;
|
|
207
297
|
const meta = getKindUiMeta(obj.kind);
|
|
208
298
|
let title = "";
|
|
209
299
|
try {
|
|
210
|
-
title = getKind(obj.kind).describe(obj, state);
|
|
300
|
+
title = describe ? describe(obj, state) : getKind(obj.kind).describe(obj, state);
|
|
211
301
|
} catch {
|
|
212
302
|
title = `${meta.displayName} ${obj.label}`;
|
|
213
303
|
}
|
|
@@ -273,11 +363,11 @@ function ObjectRow(props) {
|
|
|
273
363
|
}
|
|
274
364
|
function ObjectListPanel(props) {
|
|
275
365
|
const { store, selectedId, onSelect, renderRow } = props;
|
|
276
|
-
const subscribe =
|
|
366
|
+
const subscribe = React.useCallback(
|
|
277
367
|
(cb) => store.subscribe(() => cb()),
|
|
278
368
|
[store]
|
|
279
369
|
);
|
|
280
|
-
const state =
|
|
370
|
+
const state = React.useSyncExternalStore(subscribe, store.getState, store.getState);
|
|
281
371
|
const objects = listObjects(state);
|
|
282
372
|
function handleSelect(id) {
|
|
283
373
|
onSelect?.(id === selectedId ? null : id);
|
|
@@ -308,7 +398,7 @@ function ObjectListPanel(props) {
|
|
|
308
398
|
if (renderRow) {
|
|
309
399
|
const custom = renderRow(obj, { selected, onClick });
|
|
310
400
|
if (custom != null) {
|
|
311
|
-
return /* @__PURE__ */ jsx(
|
|
401
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: custom }, obj.id);
|
|
312
402
|
}
|
|
313
403
|
}
|
|
314
404
|
return /* @__PURE__ */ jsx(
|
|
@@ -436,24 +526,115 @@ function useToolHoverTooltip() {
|
|
|
436
526
|
}, []);
|
|
437
527
|
return { hover, portalReady, showHover, hideHover };
|
|
438
528
|
}
|
|
529
|
+
function normalize(s) {
|
|
530
|
+
return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/đ/g, "d").replace(/Đ/g, "d");
|
|
531
|
+
}
|
|
532
|
+
function SearchIcon() {
|
|
533
|
+
return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
534
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
535
|
+
/* @__PURE__ */ jsx("line", { x1: "20", y1: "20", x2: "16.5", y2: "16.5" })
|
|
536
|
+
] });
|
|
537
|
+
}
|
|
538
|
+
function ClearIcon() {
|
|
539
|
+
return /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
540
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
541
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
|
|
542
|
+
] });
|
|
543
|
+
}
|
|
544
|
+
function ToolResultList(props) {
|
|
545
|
+
const { tools, activeTool, onToolChange } = props;
|
|
546
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", "data-testid": "tool-result-list", children: tools.map((t) => {
|
|
547
|
+
const active = activeTool === t.key;
|
|
548
|
+
return /* @__PURE__ */ jsxs(
|
|
549
|
+
"button",
|
|
550
|
+
{
|
|
551
|
+
type: "button",
|
|
552
|
+
"data-tool": t.key,
|
|
553
|
+
"aria-label": t.label,
|
|
554
|
+
"aria-pressed": active,
|
|
555
|
+
onClick: () => onToolChange(t.key),
|
|
556
|
+
className: [
|
|
557
|
+
"flex items-center gap-2 rounded-md px-2 py-1.5 text-left transition",
|
|
558
|
+
active ? "bg-emerald-600 text-white" : "text-slate-700 hover:bg-slate-100"
|
|
559
|
+
].join(" "),
|
|
560
|
+
children: [
|
|
561
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center", children: t.icon }),
|
|
562
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
563
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-[12px] font-medium leading-tight", children: t.label }),
|
|
564
|
+
t.hint && /* @__PURE__ */ jsx("span", { className: ["block truncate text-[10px] leading-tight", active ? "text-emerald-50" : "text-slate-400"].join(" "), children: t.hint })
|
|
565
|
+
] })
|
|
566
|
+
]
|
|
567
|
+
},
|
|
568
|
+
t.key
|
|
569
|
+
);
|
|
570
|
+
}) });
|
|
571
|
+
}
|
|
439
572
|
function ToolGrid(props) {
|
|
440
573
|
const { tools, groupOrder, groupLabels, activeTool, onToolChange, chord } = props;
|
|
441
574
|
const { hover, portalReady, showHover, hideHover } = useToolHoverTooltip();
|
|
575
|
+
const [query, setQuery] = useState("");
|
|
576
|
+
const normalizedQuery = useMemo(() => normalize(query.trim()), [query]);
|
|
577
|
+
const filteredTools = useMemo(() => {
|
|
578
|
+
if (!normalizedQuery) return tools;
|
|
579
|
+
return tools.filter((t) => {
|
|
580
|
+
if (normalize(t.label).includes(normalizedQuery)) return true;
|
|
581
|
+
if (t.hint && normalize(t.hint).includes(normalizedQuery)) return true;
|
|
582
|
+
return false;
|
|
583
|
+
});
|
|
584
|
+
}, [tools, normalizedQuery]);
|
|
442
585
|
const grouped = useMemo(() => {
|
|
443
586
|
var _a;
|
|
444
587
|
const acc = {};
|
|
445
|
-
for (const t of
|
|
588
|
+
for (const t of filteredTools) {
|
|
446
589
|
(acc[_a = t.group] ?? (acc[_a] = [])).push(t);
|
|
447
590
|
}
|
|
448
591
|
return acc;
|
|
449
|
-
}, [
|
|
592
|
+
}, [filteredTools]);
|
|
450
593
|
const groupKeys = useMemo(
|
|
451
|
-
() => groupOrder.filter((g) => grouped[g]),
|
|
594
|
+
() => groupOrder.filter((g) => grouped[g] && grouped[g].length > 0),
|
|
452
595
|
[grouped, groupOrder]
|
|
453
596
|
);
|
|
454
|
-
const
|
|
597
|
+
const noMatch = normalizedQuery !== "" && groupKeys.length === 0;
|
|
455
598
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
456
|
-
|
|
599
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
600
|
+
/* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-slate-400", children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
|
|
601
|
+
/* @__PURE__ */ jsx(
|
|
602
|
+
"input",
|
|
603
|
+
{
|
|
604
|
+
type: "search",
|
|
605
|
+
value: query,
|
|
606
|
+
onChange: (e) => setQuery(e.target.value),
|
|
607
|
+
placeholder: "T\xECm c\xF4ng c\u1EE5\u2026",
|
|
608
|
+
"aria-label": "T\xECm c\xF4ng c\u1EE5",
|
|
609
|
+
"data-testid": "tool-search-input",
|
|
610
|
+
className: "w-full rounded-md border border-slate-200 bg-slate-50 py-1.5 pl-7 pr-7 text-[12px] text-slate-800 placeholder:text-slate-400 focus:border-emerald-400 focus:bg-white focus:outline-none focus:ring-1 focus:ring-emerald-300"
|
|
611
|
+
}
|
|
612
|
+
),
|
|
613
|
+
query && /* @__PURE__ */ jsx(
|
|
614
|
+
"button",
|
|
615
|
+
{
|
|
616
|
+
type: "button",
|
|
617
|
+
onClick: () => setQuery(""),
|
|
618
|
+
"aria-label": "Xo\xE1 t\xECm ki\u1EBFm",
|
|
619
|
+
"data-testid": "tool-search-clear",
|
|
620
|
+
className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-slate-400 transition hover:bg-slate-200 hover:text-slate-700",
|
|
621
|
+
children: /* @__PURE__ */ jsx(ClearIcon, {})
|
|
622
|
+
}
|
|
623
|
+
)
|
|
624
|
+
] }),
|
|
625
|
+
noMatch && /* @__PURE__ */ jsxs(
|
|
626
|
+
"div",
|
|
627
|
+
{
|
|
628
|
+
"data-testid": "tool-search-empty",
|
|
629
|
+
className: "rounded-md border border-dashed border-slate-200 bg-slate-50 px-3 py-4 text-center text-[11px] text-slate-500",
|
|
630
|
+
children: [
|
|
631
|
+
"Kh\xF4ng c\xF3 c\xF4ng c\u1EE5 n\xE0o kh\u1EDBp \u201C",
|
|
632
|
+
query.trim(),
|
|
633
|
+
"\u201D."
|
|
634
|
+
]
|
|
635
|
+
}
|
|
636
|
+
),
|
|
637
|
+
normalizedQuery !== "" && !noMatch ? /* @__PURE__ */ jsx(ToolResultList, { tools: filteredTools, activeTool, onToolChange }) : groupKeys.map((group) => {
|
|
457
638
|
const isChordActive = chord?.activeGroup === group;
|
|
458
639
|
const dimmed = chord?.activeGroup != null && !isChordActive;
|
|
459
640
|
return /* @__PURE__ */ jsxs(
|
|
@@ -467,23 +648,10 @@ function ToolGrid(props) {
|
|
|
467
648
|
dimmed ? "opacity-55" : "opacity-100"
|
|
468
649
|
].join(" "),
|
|
469
650
|
children: [
|
|
470
|
-
/* @__PURE__ */
|
|
471
|
-
|
|
472
|
-
chord && /* @__PURE__ */ jsx(
|
|
473
|
-
"span",
|
|
474
|
-
{
|
|
475
|
-
"data-testid": `chord-letter-${group}`,
|
|
476
|
-
className: [
|
|
477
|
-
"font-mono text-[10px] leading-none transition",
|
|
478
|
-
isChordActive ? "text-emerald-700 font-bold" : "text-slate-400"
|
|
479
|
-
].join(" "),
|
|
480
|
-
children: chord.letterForGroup(group)
|
|
481
|
-
}
|
|
482
|
-
)
|
|
483
|
-
] }),
|
|
484
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 gap-1", children: grouped[group].map((t, i) => {
|
|
651
|
+
/* @__PURE__ */ jsx("h4", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-slate-500", children: groupLabels[group] }),
|
|
652
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 gap-1", children: grouped[group].map((t) => {
|
|
485
653
|
const active = activeTool === t.key;
|
|
486
|
-
return /* @__PURE__ */
|
|
654
|
+
return /* @__PURE__ */ jsx(
|
|
487
655
|
"button",
|
|
488
656
|
{
|
|
489
657
|
type: "button",
|
|
@@ -500,20 +668,7 @@ function ToolGrid(props) {
|
|
|
500
668
|
"relative flex h-10 items-center justify-center rounded-md transition",
|
|
501
669
|
active ? "bg-emerald-600 text-white shadow-sm" : "text-slate-700 hover:bg-slate-100 hover:text-slate-900"
|
|
502
670
|
].join(" "),
|
|
503
|
-
children:
|
|
504
|
-
t.icon,
|
|
505
|
-
chord && /* @__PURE__ */ jsx(
|
|
506
|
-
"span",
|
|
507
|
-
{
|
|
508
|
-
"data-testid": `chord-num-${t.key}`,
|
|
509
|
-
className: [
|
|
510
|
-
"pointer-events-none absolute bottom-0 right-0.5 font-mono text-[9px] leading-none transition",
|
|
511
|
-
active ? "text-white/70" : isChordActive ? "text-emerald-700 font-bold" : "text-slate-400"
|
|
512
|
-
].join(" "),
|
|
513
|
-
children: i + 1
|
|
514
|
-
}
|
|
515
|
-
)
|
|
516
|
-
]
|
|
671
|
+
children: t.icon
|
|
517
672
|
},
|
|
518
673
|
t.key
|
|
519
674
|
);
|
|
@@ -523,22 +678,6 @@ function ToolGrid(props) {
|
|
|
523
678
|
group
|
|
524
679
|
);
|
|
525
680
|
}),
|
|
526
|
-
chord?.activeGroup && activeGroupTools && /* @__PURE__ */ jsxs(
|
|
527
|
-
"div",
|
|
528
|
-
{
|
|
529
|
-
"data-testid": "chord-hint",
|
|
530
|
-
className: "mt-1 rounded border border-emerald-200 bg-emerald-50/60 px-2 py-1 text-[11px] leading-snug text-slate-600",
|
|
531
|
-
children: [
|
|
532
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-emerald-700", children: chord.letterForGroup(chord.activeGroup) }),
|
|
533
|
-
/* @__PURE__ */ jsx("span", { className: "mx-1 text-slate-400", children: "\u2192" }),
|
|
534
|
-
activeGroupTools.map((t, i) => /* @__PURE__ */ jsxs("span", { className: "mr-2 inline-block", children: [
|
|
535
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-emerald-700", children: i + 1 }),
|
|
536
|
-
/* @__PURE__ */ jsx("span", { className: "ml-1", children: t.label })
|
|
537
|
-
] }, t.key)),
|
|
538
|
-
/* @__PURE__ */ jsx("span", { className: "text-slate-400", children: "Esc hu\u1EF7" })
|
|
539
|
-
]
|
|
540
|
-
}
|
|
541
|
-
),
|
|
542
681
|
portalReady && hover && typeof document !== "undefined" ? createPortal(
|
|
543
682
|
/* @__PURE__ */ jsxs(
|
|
544
683
|
"div",
|
|
@@ -599,6 +738,8 @@ function StampLeftPanelDesktop(props) {
|
|
|
599
738
|
tabs: tabSpecs,
|
|
600
739
|
activeTab: hasObjects ? tab : void 0,
|
|
601
740
|
onTabChange: hasObjects ? setTab : void 0,
|
|
741
|
+
resizable: true,
|
|
742
|
+
widthStorageKey: "xom11.stamp-left-panel.width",
|
|
602
743
|
children: !hasObjects || tab === "tools" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
603
744
|
/* @__PURE__ */ jsx(AxisGridSection, { view, history }),
|
|
604
745
|
/* @__PURE__ */ jsx(
|
|
@@ -651,9 +792,9 @@ function MobileToolDrawer({
|
|
|
651
792
|
testId,
|
|
652
793
|
objectsTab
|
|
653
794
|
}) {
|
|
654
|
-
const [mobileTab, setMobileTab] =
|
|
655
|
-
const prevOpenRef =
|
|
656
|
-
|
|
795
|
+
const [mobileTab, setMobileTab] = React__default.useState("tools");
|
|
796
|
+
const prevOpenRef = React__default.useRef(drawerOpen);
|
|
797
|
+
React__default.useEffect(() => {
|
|
657
798
|
if (!prevOpenRef.current && drawerOpen) setMobileTab("tools");
|
|
658
799
|
prevOpenRef.current = drawerOpen;
|
|
659
800
|
}, [drawerOpen]);
|
|
@@ -1163,6 +1304,6 @@ async function initJxgBoard(target, config) {
|
|
|
1163
1304
|
return { JXG, board, cleanup };
|
|
1164
1305
|
}
|
|
1165
1306
|
|
|
1166
|
-
export { STAMP_PANEL_DESKTOP, StampLeftPanel, ToastHost, ToastProvider, attachJxgWheelZoom, initJxgBoard, safeJsx, useStampStore, useToast };
|
|
1167
|
-
//# sourceMappingURL=chunk-
|
|
1168
|
-
//# sourceMappingURL=chunk-
|
|
1307
|
+
export { ObjectRow, STAMP_PANEL_DESKTOP, StampLeftPanel, ToastHost, ToastProvider, attachJxgWheelZoom, initJxgBoard, safeJsx, useStampStore, useToast };
|
|
1308
|
+
//# sourceMappingURL=chunk-I3L56GVH.mjs.map
|
|
1309
|
+
//# sourceMappingURL=chunk-I3L56GVH.mjs.map
|