@xom11/whiteboard 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ExcalidrawWithMenus-WENZRYYE.mjs → ExcalidrawWithMenus-2QPPTXJM.mjs} +3 -2
- package/dist/ExcalidrawWithMenus-2QPPTXJM.mjs.map +1 -0
- package/dist/ai.d.mts +3035 -108
- package/dist/ai.d.ts +3035 -108
- package/dist/ai.js +6736 -775
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +5110 -578
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +5 -5
- package/dist/{chunk-ESVPQWHX.mjs → chunk-4ETJ4CDY.mjs} +5 -5
- package/dist/{chunk-ESVPQWHX.mjs.map → chunk-4ETJ4CDY.mjs.map} +1 -1
- package/dist/{chunk-VNCCIV6O.mjs → chunk-AJAHD35N.mjs} +779 -9
- package/dist/chunk-AJAHD35N.mjs.map +1 -0
- package/dist/chunk-AYJPOHCI.mjs +265 -0
- package/dist/chunk-AYJPOHCI.mjs.map +1 -0
- package/dist/{chunk-M42TGYT6.mjs → chunk-BNBOIDO5.mjs} +3 -3
- package/dist/{chunk-M42TGYT6.mjs.map → chunk-BNBOIDO5.mjs.map} +1 -1
- package/dist/{chunk-CJBLJUWG.mjs → chunk-CXHNVYMD.mjs} +4 -4
- package/dist/{chunk-CJBLJUWG.mjs.map → chunk-CXHNVYMD.mjs.map} +1 -1
- package/dist/{chunk-NDEZJKNY.mjs → chunk-D5JLJ3PT.mjs} +4 -4
- package/dist/{chunk-NDEZJKNY.mjs.map → chunk-D5JLJ3PT.mjs.map} +1 -1
- package/dist/{chunk-REIJZDVZ.mjs → chunk-D5LWSN2Y.mjs} +942 -195
- package/dist/chunk-D5LWSN2Y.mjs.map +1 -0
- package/dist/{chunk-I24QOHPU.mjs → chunk-HLAOGXEK.mjs} +3 -3
- package/dist/{chunk-I24QOHPU.mjs.map → chunk-HLAOGXEK.mjs.map} +1 -1
- package/dist/{chunk-TB4CL25L.mjs → chunk-I3L56GVH.mjs} +206 -66
- package/dist/chunk-I3L56GVH.mjs.map +1 -0
- package/dist/chunk-J5LGTIGS.mjs +10 -0
- package/dist/chunk-J5LGTIGS.mjs.map +1 -0
- package/dist/{chunk-ONBCUWVI.mjs → chunk-KYMBUTPO.mjs} +3 -3
- package/dist/{chunk-ONBCUWVI.mjs.map → chunk-KYMBUTPO.mjs.map} +1 -1
- package/dist/{chunk-YSJOVBCD.mjs → chunk-KZGPSTZI.mjs} +4 -4
- package/dist/{chunk-YSJOVBCD.mjs.map → chunk-KZGPSTZI.mjs.map} +1 -1
- package/dist/{chunk-SGFJLHHG.mjs → chunk-PPKHCRRE.mjs} +3 -3
- package/dist/{chunk-SGFJLHHG.mjs.map → chunk-PPKHCRRE.mjs.map} +1 -1
- package/dist/{chunk-AYSFWUPK.mjs → chunk-SZDAS7LK.mjs} +79 -2
- package/dist/chunk-SZDAS7LK.mjs.map +1 -0
- package/dist/chunk-T3SOHYB2.mjs +851 -0
- package/dist/chunk-T3SOHYB2.mjs.map +1 -0
- package/dist/geometry-2d.d.mts +1 -1
- package/dist/geometry-2d.d.ts +1 -1
- package/dist/geometry-2d.js +5389 -1362
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +5 -4
- package/dist/geometry-3d.d.mts +1 -1
- package/dist/geometry-3d.d.ts +1 -1
- package/dist/geometry-3d.js +1333 -251
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +4 -3
- package/dist/graph-2d.d.mts +1 -1
- package/dist/graph-2d.d.ts +1 -1
- package/dist/graph-2d.js +1499 -340
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +7 -6
- package/dist/handleExtractProblem-C-U5KluK.d.mts +158 -0
- package/dist/handleExtractProblem-C-U5KluK.d.ts +158 -0
- package/dist/{host-L7FMFZUW.mjs → host-HAYCJJ2T.mjs} +1258 -441
- package/dist/host-HAYCJJ2T.mjs.map +1 -0
- package/dist/{host-QK53UYMD.mjs → host-LTJHAY5A.mjs} +10 -9
- package/dist/host-LTJHAY5A.mjs.map +1 -0
- package/dist/{host-A64ITWVX.mjs → host-M26FS244.mjs} +6 -5
- package/dist/host-M26FS244.mjs.map +1 -0
- package/dist/{host-QS2EOTRJ.mjs → host-ZQCDAT6O.mjs} +3 -2
- package/dist/host-ZQCDAT6O.mjs.map +1 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +5621 -1590
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +22 -20
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +1 -1
- package/dist/latex.d.ts +1 -1
- package/dist/latex.mjs +2 -1
- package/dist/render-ZX2O2IK7.mjs +10 -0
- package/dist/{render-3WTY7NZB.mjs.map → render-ZX2O2IK7.mjs.map} +1 -1
- package/dist/serialize-C3LSUMSA.mjs +9 -0
- package/dist/{serialize-SRJVKYUG.mjs.map → serialize-C3LSUMSA.mjs.map} +1 -1
- package/dist/{types-DWRyCa2m.d.ts → types-zc_Pa0mp.d.mts} +105 -0
- package/dist/{types-DWRyCa2m.d.mts → types-zc_Pa0mp.d.ts} +105 -0
- package/package.json +10 -1
- package/dist/ExcalidrawWithMenus-WENZRYYE.mjs.map +0 -1
- package/dist/chunk-AYSFWUPK.mjs.map +0 -1
- package/dist/chunk-REIJZDVZ.mjs.map +0 -1
- package/dist/chunk-TB4CL25L.mjs.map +0 -1
- package/dist/chunk-VNCCIV6O.mjs.map +0 -1
- package/dist/chunk-VRHWDZ66.mjs +0 -96
- package/dist/chunk-VRHWDZ66.mjs.map +0 -1
- package/dist/host-A64ITWVX.mjs.map +0 -1
- package/dist/host-L7FMFZUW.mjs.map +0 -1
- package/dist/host-QK53UYMD.mjs.map +0 -1
- package/dist/host-QS2EOTRJ.mjs.map +0 -1
- package/dist/render-3WTY7NZB.mjs +0 -9
- package/dist/serialize-SRJVKYUG.mjs +0 -8
|
@@ -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,13 +1,30 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { listObjects } from './chunk-
|
|
2
|
+
import { listObjects } from './chunk-D5LWSN2Y.mjs';
|
|
3
3
|
import { createStore } from './chunk-IHUFOV7L.mjs';
|
|
4
4
|
import { createEmptyState } from './chunk-73Q7ADVL.mjs';
|
|
5
5
|
import { getKind } from './chunk-B4NJJZFR.mjs';
|
|
6
|
-
import * as
|
|
7
|
-
import
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import React__default, { createContext, useRef, useReducer, useCallback, useEffect, useMemo, useContext, useState } from 'react';
|
|
8
8
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
9
|
import { createPortal } from 'react-dom';
|
|
10
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
|
+
}
|
|
11
28
|
function CloseIcon() {
|
|
12
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: [
|
|
13
30
|
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
@@ -15,8 +32,64 @@ function CloseIcon() {
|
|
|
15
32
|
] });
|
|
16
33
|
}
|
|
17
34
|
function LeftPanelShell(props) {
|
|
18
|
-
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;
|
|
19
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]);
|
|
20
93
|
return /* @__PURE__ */ jsxs(
|
|
21
94
|
"aside",
|
|
22
95
|
{
|
|
@@ -24,10 +97,12 @@ function LeftPanelShell(props) {
|
|
|
24
97
|
"aria-label": title,
|
|
25
98
|
"data-testid": testId ?? "left-panel",
|
|
26
99
|
"data-stamp-area": "true",
|
|
100
|
+
style: resizable ? { width: `${width}px` } : void 0,
|
|
27
101
|
className: [
|
|
28
102
|
isDark ? "theme--dark " : "",
|
|
29
|
-
"absolute left-0 top-0 z-30 flex h-full
|
|
30
|
-
|
|
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(" "),
|
|
31
106
|
children: [
|
|
32
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: [
|
|
33
108
|
/* @__PURE__ */ jsxs("h3", { className: "flex items-center gap-2 text-sm font-semibold text-slate-800", children: [
|
|
@@ -62,6 +137,20 @@ function LeftPanelShell(props) {
|
|
|
62
137
|
className: "min-h-0 flex-1 overflow-y-auto p-3 space-y-3",
|
|
63
138
|
children
|
|
64
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
|
+
}
|
|
65
154
|
)
|
|
66
155
|
]
|
|
67
156
|
}
|
|
@@ -134,7 +223,7 @@ function getKindUiMeta(kind) {
|
|
|
134
223
|
}
|
|
135
224
|
function ObjectRowMenu(props) {
|
|
136
225
|
const { locked, onToggleLocked, onRename, onChangeColor, onDelete } = props;
|
|
137
|
-
const [open, setOpen] =
|
|
226
|
+
const [open, setOpen] = React.useState(false);
|
|
138
227
|
return /* @__PURE__ */ jsxs("div", { className: "relative inline-block", children: [
|
|
139
228
|
/* @__PURE__ */ jsx(
|
|
140
229
|
"button",
|
|
@@ -274,11 +363,11 @@ function ObjectRow(props) {
|
|
|
274
363
|
}
|
|
275
364
|
function ObjectListPanel(props) {
|
|
276
365
|
const { store, selectedId, onSelect, renderRow } = props;
|
|
277
|
-
const subscribe =
|
|
366
|
+
const subscribe = React.useCallback(
|
|
278
367
|
(cb) => store.subscribe(() => cb()),
|
|
279
368
|
[store]
|
|
280
369
|
);
|
|
281
|
-
const state =
|
|
370
|
+
const state = React.useSyncExternalStore(subscribe, store.getState, store.getState);
|
|
282
371
|
const objects = listObjects(state);
|
|
283
372
|
function handleSelect(id) {
|
|
284
373
|
onSelect?.(id === selectedId ? null : id);
|
|
@@ -309,7 +398,7 @@ function ObjectListPanel(props) {
|
|
|
309
398
|
if (renderRow) {
|
|
310
399
|
const custom = renderRow(obj, { selected, onClick });
|
|
311
400
|
if (custom != null) {
|
|
312
|
-
return /* @__PURE__ */ jsx(
|
|
401
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: custom }, obj.id);
|
|
313
402
|
}
|
|
314
403
|
}
|
|
315
404
|
return /* @__PURE__ */ jsx(
|
|
@@ -437,24 +526,115 @@ function useToolHoverTooltip() {
|
|
|
437
526
|
}, []);
|
|
438
527
|
return { hover, portalReady, showHover, hideHover };
|
|
439
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
|
+
}
|
|
440
572
|
function ToolGrid(props) {
|
|
441
573
|
const { tools, groupOrder, groupLabels, activeTool, onToolChange, chord } = props;
|
|
442
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]);
|
|
443
585
|
const grouped = useMemo(() => {
|
|
444
586
|
var _a;
|
|
445
587
|
const acc = {};
|
|
446
|
-
for (const t of
|
|
588
|
+
for (const t of filteredTools) {
|
|
447
589
|
(acc[_a = t.group] ?? (acc[_a] = [])).push(t);
|
|
448
590
|
}
|
|
449
591
|
return acc;
|
|
450
|
-
}, [
|
|
592
|
+
}, [filteredTools]);
|
|
451
593
|
const groupKeys = useMemo(
|
|
452
|
-
() => groupOrder.filter((g) => grouped[g]),
|
|
594
|
+
() => groupOrder.filter((g) => grouped[g] && grouped[g].length > 0),
|
|
453
595
|
[grouped, groupOrder]
|
|
454
596
|
);
|
|
455
|
-
const
|
|
597
|
+
const noMatch = normalizedQuery !== "" && groupKeys.length === 0;
|
|
456
598
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
457
|
-
|
|
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) => {
|
|
458
638
|
const isChordActive = chord?.activeGroup === group;
|
|
459
639
|
const dimmed = chord?.activeGroup != null && !isChordActive;
|
|
460
640
|
return /* @__PURE__ */ jsxs(
|
|
@@ -468,23 +648,10 @@ function ToolGrid(props) {
|
|
|
468
648
|
dimmed ? "opacity-55" : "opacity-100"
|
|
469
649
|
].join(" "),
|
|
470
650
|
children: [
|
|
471
|
-
/* @__PURE__ */
|
|
472
|
-
|
|
473
|
-
chord && /* @__PURE__ */ jsx(
|
|
474
|
-
"span",
|
|
475
|
-
{
|
|
476
|
-
"data-testid": `chord-letter-${group}`,
|
|
477
|
-
className: [
|
|
478
|
-
"font-mono text-[10px] leading-none transition",
|
|
479
|
-
isChordActive ? "text-emerald-700 font-bold" : "text-slate-400"
|
|
480
|
-
].join(" "),
|
|
481
|
-
children: chord.letterForGroup(group)
|
|
482
|
-
}
|
|
483
|
-
)
|
|
484
|
-
] }),
|
|
485
|
-
/* @__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) => {
|
|
486
653
|
const active = activeTool === t.key;
|
|
487
|
-
return /* @__PURE__ */
|
|
654
|
+
return /* @__PURE__ */ jsx(
|
|
488
655
|
"button",
|
|
489
656
|
{
|
|
490
657
|
type: "button",
|
|
@@ -501,20 +668,7 @@ function ToolGrid(props) {
|
|
|
501
668
|
"relative flex h-10 items-center justify-center rounded-md transition",
|
|
502
669
|
active ? "bg-emerald-600 text-white shadow-sm" : "text-slate-700 hover:bg-slate-100 hover:text-slate-900"
|
|
503
670
|
].join(" "),
|
|
504
|
-
children:
|
|
505
|
-
t.icon,
|
|
506
|
-
chord && /* @__PURE__ */ jsx(
|
|
507
|
-
"span",
|
|
508
|
-
{
|
|
509
|
-
"data-testid": `chord-num-${t.key}`,
|
|
510
|
-
className: [
|
|
511
|
-
"pointer-events-none absolute bottom-0 right-0.5 font-mono text-[9px] leading-none transition",
|
|
512
|
-
active ? "text-white/70" : isChordActive ? "text-emerald-700 font-bold" : "text-slate-400"
|
|
513
|
-
].join(" "),
|
|
514
|
-
children: i + 1
|
|
515
|
-
}
|
|
516
|
-
)
|
|
517
|
-
]
|
|
671
|
+
children: t.icon
|
|
518
672
|
},
|
|
519
673
|
t.key
|
|
520
674
|
);
|
|
@@ -524,22 +678,6 @@ function ToolGrid(props) {
|
|
|
524
678
|
group
|
|
525
679
|
);
|
|
526
680
|
}),
|
|
527
|
-
chord?.activeGroup && activeGroupTools && /* @__PURE__ */ jsxs(
|
|
528
|
-
"div",
|
|
529
|
-
{
|
|
530
|
-
"data-testid": "chord-hint",
|
|
531
|
-
className: "mt-1 rounded border border-emerald-200 bg-emerald-50/60 px-2 py-1 text-[11px] leading-snug text-slate-600",
|
|
532
|
-
children: [
|
|
533
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-emerald-700", children: chord.letterForGroup(chord.activeGroup) }),
|
|
534
|
-
/* @__PURE__ */ jsx("span", { className: "mx-1 text-slate-400", children: "\u2192" }),
|
|
535
|
-
activeGroupTools.map((t, i) => /* @__PURE__ */ jsxs("span", { className: "mr-2 inline-block", children: [
|
|
536
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-emerald-700", children: i + 1 }),
|
|
537
|
-
/* @__PURE__ */ jsx("span", { className: "ml-1", children: t.label })
|
|
538
|
-
] }, t.key)),
|
|
539
|
-
/* @__PURE__ */ jsx("span", { className: "text-slate-400", children: "Esc hu\u1EF7" })
|
|
540
|
-
]
|
|
541
|
-
}
|
|
542
|
-
),
|
|
543
681
|
portalReady && hover && typeof document !== "undefined" ? createPortal(
|
|
544
682
|
/* @__PURE__ */ jsxs(
|
|
545
683
|
"div",
|
|
@@ -600,6 +738,8 @@ function StampLeftPanelDesktop(props) {
|
|
|
600
738
|
tabs: tabSpecs,
|
|
601
739
|
activeTab: hasObjects ? tab : void 0,
|
|
602
740
|
onTabChange: hasObjects ? setTab : void 0,
|
|
741
|
+
resizable: true,
|
|
742
|
+
widthStorageKey: "xom11.stamp-left-panel.width",
|
|
603
743
|
children: !hasObjects || tab === "tools" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
604
744
|
/* @__PURE__ */ jsx(AxisGridSection, { view, history }),
|
|
605
745
|
/* @__PURE__ */ jsx(
|
|
@@ -652,9 +792,9 @@ function MobileToolDrawer({
|
|
|
652
792
|
testId,
|
|
653
793
|
objectsTab
|
|
654
794
|
}) {
|
|
655
|
-
const [mobileTab, setMobileTab] =
|
|
656
|
-
const prevOpenRef =
|
|
657
|
-
|
|
795
|
+
const [mobileTab, setMobileTab] = React__default.useState("tools");
|
|
796
|
+
const prevOpenRef = React__default.useRef(drawerOpen);
|
|
797
|
+
React__default.useEffect(() => {
|
|
658
798
|
if (!prevOpenRef.current && drawerOpen) setMobileTab("tools");
|
|
659
799
|
prevOpenRef.current = drawerOpen;
|
|
660
800
|
}, [drawerOpen]);
|
|
@@ -1165,5 +1305,5 @@ async function initJxgBoard(target, config) {
|
|
|
1165
1305
|
}
|
|
1166
1306
|
|
|
1167
1307
|
export { ObjectRow, STAMP_PANEL_DESKTOP, StampLeftPanel, ToastHost, ToastProvider, attachJxgWheelZoom, initJxgBoard, safeJsx, useStampStore, useToast };
|
|
1168
|
-
//# sourceMappingURL=chunk-
|
|
1169
|
-
//# sourceMappingURL=chunk-
|
|
1308
|
+
//# sourceMappingURL=chunk-I3L56GVH.mjs.map
|
|
1309
|
+
//# sourceMappingURL=chunk-I3L56GVH.mjs.map
|