@objectifthunes/whiteboard 0.1.1 → 0.2.1
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/index.d.ts +618 -185
- package/dist/index.js +978 -710
- package/dist/whiteboard.css +751 -0
- package/package.json +12 -20
- package/LICENSE +0 -21
- package/dist/index.cjs +0 -939
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -185
- package/dist/index.js.map +0 -1
package/dist/index.cjs
DELETED
|
@@ -1,939 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
ConfirmDialog: () => ConfirmDialog,
|
|
24
|
-
FloatingPanel: () => FloatingPanel,
|
|
25
|
-
Minimap: () => Minimap,
|
|
26
|
-
PanelErrorBoundary: () => PanelErrorBoundary,
|
|
27
|
-
WHITEBOARD_GRID: () => WHITEBOARD_GRID,
|
|
28
|
-
WhiteboardShell: () => WhiteboardShell,
|
|
29
|
-
ZoomBar: () => ZoomBar,
|
|
30
|
-
belowPanel: () => belowPanel,
|
|
31
|
-
cn: () => cn,
|
|
32
|
-
computeWhiteboardFit: () => computeWhiteboardFit,
|
|
33
|
-
computeWhiteboardRectFocus: () => computeWhiteboardRectFocus,
|
|
34
|
-
snapToWhiteboardGrid: () => snapToWhiteboardGrid,
|
|
35
|
-
usePanelRect: () => usePanelRect,
|
|
36
|
-
useWhiteboardLayout: () => useWhiteboardLayout,
|
|
37
|
-
useWhiteboardStore: () => useWhiteboardStore
|
|
38
|
-
});
|
|
39
|
-
module.exports = __toCommonJS(index_exports);
|
|
40
|
-
|
|
41
|
-
// src/WhiteboardShell.tsx
|
|
42
|
-
var import_react3 = require("react");
|
|
43
|
-
|
|
44
|
-
// src/ZoomBar.tsx
|
|
45
|
-
var import_react = require("react");
|
|
46
|
-
|
|
47
|
-
// src/icons.tsx
|
|
48
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
49
|
-
var defaults = (size = 14) => ({
|
|
50
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
51
|
-
width: size,
|
|
52
|
-
height: size,
|
|
53
|
-
viewBox: "0 0 24 24",
|
|
54
|
-
fill: "none",
|
|
55
|
-
stroke: "currentColor",
|
|
56
|
-
strokeWidth: 2,
|
|
57
|
-
strokeLinecap: "round",
|
|
58
|
-
strokeLinejoin: "round"
|
|
59
|
-
});
|
|
60
|
-
function Minus({ size, ...props }) {
|
|
61
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...defaults(size), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 12h14" }) });
|
|
62
|
-
}
|
|
63
|
-
function Plus({ size, ...props }) {
|
|
64
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...defaults(size), ...props, children: [
|
|
65
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 12h14" }),
|
|
66
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 5v14" })
|
|
67
|
-
] });
|
|
68
|
-
}
|
|
69
|
-
function ScanSearch({ size, ...props }) {
|
|
70
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...defaults(size), ...props, children: [
|
|
71
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z", fill: "none", stroke: "none" }),
|
|
72
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }),
|
|
73
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }),
|
|
74
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }),
|
|
75
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }),
|
|
76
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "3" }),
|
|
77
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m16 16-1.9-1.9" })
|
|
78
|
-
] });
|
|
79
|
-
}
|
|
80
|
-
function RotateCcw({ size, ...props }) {
|
|
81
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...defaults(size), ...props, children: [
|
|
82
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
|
|
83
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 3v5h5" })
|
|
84
|
-
] });
|
|
85
|
-
}
|
|
86
|
-
function Grid3x3({ size, ...props }) {
|
|
87
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...defaults(size), ...props, children: [
|
|
88
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
|
|
89
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 9h18" }),
|
|
90
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 15h18" }),
|
|
91
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 3v18" }),
|
|
92
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15 3v18" })
|
|
93
|
-
] });
|
|
94
|
-
}
|
|
95
|
-
function Maximize2({ size, ...props }) {
|
|
96
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...defaults(size), ...props, children: [
|
|
97
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "15 3 21 3 21 9" }),
|
|
98
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "9 21 3 21 3 15" }),
|
|
99
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "21", x2: "14", y1: "3", y2: "10" }),
|
|
100
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "3", x2: "10", y1: "21", y2: "14" })
|
|
101
|
-
] });
|
|
102
|
-
}
|
|
103
|
-
function Loader2({ size, ...props }) {
|
|
104
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...defaults(size), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
|
|
105
|
-
}
|
|
106
|
-
function AlertTriangle({ size, ...props }) {
|
|
107
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...defaults(size), ...props, children: [
|
|
108
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }),
|
|
109
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 9v4" }),
|
|
110
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 17h.01" })
|
|
111
|
-
] });
|
|
112
|
-
}
|
|
113
|
-
function Check({ size, ...props }) {
|
|
114
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...defaults(size), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20 6 9 17l-5-5" }) });
|
|
115
|
-
}
|
|
116
|
-
function X({ size, ...props }) {
|
|
117
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...defaults(size), ...props, children: [
|
|
118
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 6 6 18" }),
|
|
119
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })
|
|
120
|
-
] });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// src/store.ts
|
|
124
|
-
var import_zustand = require("zustand");
|
|
125
|
-
|
|
126
|
-
// src/grid.ts
|
|
127
|
-
var WHITEBOARD_GRID = 20;
|
|
128
|
-
function snapToWhiteboardGrid(value) {
|
|
129
|
-
return Math.round(value / WHITEBOARD_GRID) * WHITEBOARD_GRID;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// src/store.ts
|
|
133
|
-
function computeWhiteboardFit(panels, viewportSize, padding = 60) {
|
|
134
|
-
if (panels.size === 0) return null;
|
|
135
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
136
|
-
for (const r of panels.values()) {
|
|
137
|
-
minX = Math.min(minX, r.x);
|
|
138
|
-
minY = Math.min(minY, r.y);
|
|
139
|
-
maxX = Math.max(maxX, r.x + r.width);
|
|
140
|
-
maxY = Math.max(maxY, r.y + r.height);
|
|
141
|
-
}
|
|
142
|
-
const contentW = maxX - minX + padding * 2;
|
|
143
|
-
const contentH = maxY - minY + padding * 2;
|
|
144
|
-
const vpW = viewportSize.width || window.innerWidth;
|
|
145
|
-
const vpH = viewportSize.height || window.innerHeight;
|
|
146
|
-
const fitScale = Math.min(vpW / contentW, vpH / contentH, 1.5);
|
|
147
|
-
const centerX = (minX + maxX) / 2;
|
|
148
|
-
const centerY = (minY + maxY) / 2;
|
|
149
|
-
return {
|
|
150
|
-
scale: Math.min(3, Math.max(0.2, fitScale)),
|
|
151
|
-
offset: {
|
|
152
|
-
x: vpW / 2 - centerX * fitScale,
|
|
153
|
-
y: vpH / 2 - centerY * fitScale
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
function computeWhiteboardRectFocus(rect, viewportSize, padding = 40, maxScale = 1.5) {
|
|
158
|
-
const vpW = viewportSize.width || window.innerWidth;
|
|
159
|
-
const vpH = viewportSize.height || window.innerHeight;
|
|
160
|
-
const safeWidth = Math.max(1, rect.width);
|
|
161
|
-
const safeHeight = Math.max(1, rect.height);
|
|
162
|
-
const fitScale = Math.min(
|
|
163
|
-
(vpW - padding * 2) / safeWidth,
|
|
164
|
-
(vpH - padding * 2) / safeHeight,
|
|
165
|
-
maxScale
|
|
166
|
-
);
|
|
167
|
-
const nextScale = Math.min(3, Math.max(0.2, fitScale));
|
|
168
|
-
return {
|
|
169
|
-
scale: nextScale,
|
|
170
|
-
offset: {
|
|
171
|
-
x: vpW / 2 - (rect.x + safeWidth / 2) * nextScale,
|
|
172
|
-
y: vpH / 2 - (rect.y + safeHeight / 2) * nextScale
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
var useWhiteboardStore = (0, import_zustand.create)((set, get) => ({
|
|
177
|
-
offset: { x: 0, y: 0 },
|
|
178
|
-
scale: 1,
|
|
179
|
-
viewportSize: { width: 0, height: 0 },
|
|
180
|
-
snapToGrid: false,
|
|
181
|
-
snapGridSize: WHITEBOARD_GRID,
|
|
182
|
-
panels: /* @__PURE__ */ new Map(),
|
|
183
|
-
resetFns: /* @__PURE__ */ new Map(),
|
|
184
|
-
registryVersion: 0,
|
|
185
|
-
setOffset: (v) => set((s) => ({ offset: typeof v === "function" ? v(s.offset) : v })),
|
|
186
|
-
setScale: (v) => set((s) => ({ scale: typeof v === "function" ? v(s.scale) : v })),
|
|
187
|
-
setViewportSize: (v) => set({ viewportSize: v }),
|
|
188
|
-
setSnapToGrid: (v) => set((s) => ({ snapToGrid: typeof v === "function" ? v(s.snapToGrid) : v })),
|
|
189
|
-
register: (id, rect) => {
|
|
190
|
-
get().panels.set(id, rect);
|
|
191
|
-
set((s) => ({ registryVersion: s.registryVersion + 1 }));
|
|
192
|
-
},
|
|
193
|
-
unregister: (id) => {
|
|
194
|
-
get().panels.delete(id);
|
|
195
|
-
set((s) => ({ registryVersion: s.registryVersion + 1 }));
|
|
196
|
-
},
|
|
197
|
-
registerReset: (id, fn) => {
|
|
198
|
-
get().resetFns.set(id, fn);
|
|
199
|
-
},
|
|
200
|
-
unregisterReset: (id) => {
|
|
201
|
-
get().resetFns.delete(id);
|
|
202
|
-
},
|
|
203
|
-
fitToContent: () => {
|
|
204
|
-
const { panels, viewportSize } = get();
|
|
205
|
-
const fit = computeWhiteboardFit(panels, viewportSize);
|
|
206
|
-
if (fit) set({ scale: fit.scale, offset: fit.offset });
|
|
207
|
-
},
|
|
208
|
-
focusPanel: (rect, options) => {
|
|
209
|
-
const { viewportSize } = get();
|
|
210
|
-
const fit = computeWhiteboardRectFocus(
|
|
211
|
-
rect,
|
|
212
|
-
viewportSize,
|
|
213
|
-
options?.padding ?? 40,
|
|
214
|
-
options?.maxScale ?? 1.5
|
|
215
|
-
);
|
|
216
|
-
set({ scale: fit.scale, offset: fit.offset });
|
|
217
|
-
},
|
|
218
|
-
resetWidgets: () => {
|
|
219
|
-
for (const fn of get().resetFns.values()) fn();
|
|
220
|
-
const attempt = (tries = 0) => {
|
|
221
|
-
const { panels, viewportSize } = get();
|
|
222
|
-
const fit = computeWhiteboardFit(panels, viewportSize);
|
|
223
|
-
if (fit) {
|
|
224
|
-
set({ scale: fit.scale, offset: fit.offset });
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
if (tries < 6) requestAnimationFrame(() => attempt(tries + 1));
|
|
228
|
-
};
|
|
229
|
-
requestAnimationFrame(() => requestAnimationFrame(() => attempt()));
|
|
230
|
-
},
|
|
231
|
-
resetSession: () => set({
|
|
232
|
-
offset: { x: 0, y: 0 },
|
|
233
|
-
scale: 1,
|
|
234
|
-
viewportSize: { width: 0, height: 0 },
|
|
235
|
-
snapToGrid: false,
|
|
236
|
-
registryVersion: 0,
|
|
237
|
-
panels: /* @__PURE__ */ new Map(),
|
|
238
|
-
resetFns: /* @__PURE__ */ new Map()
|
|
239
|
-
})
|
|
240
|
-
}));
|
|
241
|
-
|
|
242
|
-
// src/ZoomBar.tsx
|
|
243
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
244
|
-
function ZoomBar({ extraActions }) {
|
|
245
|
-
const scale = useWhiteboardStore((s) => s.scale);
|
|
246
|
-
const viewportSize = useWhiteboardStore((s) => s.viewportSize);
|
|
247
|
-
const snapToGrid = useWhiteboardStore((s) => s.snapToGrid);
|
|
248
|
-
const setScale = useWhiteboardStore((s) => s.setScale);
|
|
249
|
-
const setOffset = useWhiteboardStore((s) => s.setOffset);
|
|
250
|
-
const setSnapToGrid = useWhiteboardStore((s) => s.setSnapToGrid);
|
|
251
|
-
const fitToContent = useWhiteboardStore((s) => s.fitToContent);
|
|
252
|
-
const resetWidgets = useWhiteboardStore((s) => s.resetWidgets);
|
|
253
|
-
(0, import_react.useEffect)(() => {
|
|
254
|
-
if (!snapToGrid) return;
|
|
255
|
-
window.dispatchEvent(new Event("whiteboard-snap-now"));
|
|
256
|
-
}, [snapToGrid]);
|
|
257
|
-
const zoomTo = (nextScale) => {
|
|
258
|
-
const clamped = Math.min(3, Math.max(0.2, nextScale));
|
|
259
|
-
const ratio = clamped / scale;
|
|
260
|
-
const cx = (viewportSize.width || window.innerWidth) / 2;
|
|
261
|
-
const cy = (viewportSize.height || window.innerHeight) / 2;
|
|
262
|
-
setOffset((prev) => ({
|
|
263
|
-
x: cx - (cx - prev.x) * ratio,
|
|
264
|
-
y: cy - (cy - prev.y) * ratio
|
|
265
|
-
}));
|
|
266
|
-
setScale(clamped);
|
|
267
|
-
};
|
|
268
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
269
|
-
"div",
|
|
270
|
-
{
|
|
271
|
-
className: "zoom-bar",
|
|
272
|
-
onPointerDown: (e) => e.stopPropagation(),
|
|
273
|
-
onWheel: (e) => e.stopPropagation(),
|
|
274
|
-
children: [
|
|
275
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "wb-btn wb-btn--secondary wb-btn--icon-only zoom-bar__icon", onClick: () => zoomTo(scale * 0.8), title: "Zoom out", "aria-label": "Zoom out", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Minus, { size: 14 }) }),
|
|
276
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "zoom-bar__value", children: [
|
|
277
|
-
Math.round(scale * 100),
|
|
278
|
-
"%"
|
|
279
|
-
] }),
|
|
280
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "wb-btn wb-btn--secondary wb-btn--icon-only zoom-bar__icon", onClick: () => zoomTo(scale * 1.2), title: "Zoom in", "aria-label": "Zoom in", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Plus, { size: 14 }) }),
|
|
281
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "wb-btn wb-btn--secondary wb-btn--icon-only zoom-bar__action", onClick: fitToContent, title: "Fit camera to all panels", "aria-label": "Fit camera to all panels", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ScanSearch, { size: 14 }) }),
|
|
282
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "wb-btn wb-btn--secondary wb-btn--icon-only zoom-bar__action", onClick: resetWidgets, title: "Reset panel positions", "aria-label": "Reset panel positions", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RotateCcw, { size: 14 }) }),
|
|
283
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
284
|
-
"button",
|
|
285
|
-
{
|
|
286
|
-
type: "button",
|
|
287
|
-
className: `wb-btn wb-btn--secondary wb-btn--icon-only zoom-bar__action${snapToGrid ? " is-active" : ""}`,
|
|
288
|
-
onClick: () => setSnapToGrid((prev) => !prev),
|
|
289
|
-
title: snapToGrid ? "Disable snap to grid" : "Enable snap to grid",
|
|
290
|
-
"aria-label": snapToGrid ? "Disable snap to grid" : "Enable snap to grid",
|
|
291
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Grid3x3, { size: 14 })
|
|
292
|
-
}
|
|
293
|
-
),
|
|
294
|
-
extraActions
|
|
295
|
-
]
|
|
296
|
-
}
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// src/Minimap.tsx
|
|
301
|
-
var import_react2 = require("react");
|
|
302
|
-
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
303
|
-
var MAP_W = 200;
|
|
304
|
-
var MAP_H = 150;
|
|
305
|
-
var PADDING = 40;
|
|
306
|
-
var MIN_WORLD_SIZE = 1;
|
|
307
|
-
function Minimap({ loading = false }) {
|
|
308
|
-
useWhiteboardStore((s) => s.registryVersion);
|
|
309
|
-
const offset = useWhiteboardStore((s) => s.offset);
|
|
310
|
-
const scale = useWhiteboardStore((s) => s.scale);
|
|
311
|
-
const viewportSize = useWhiteboardStore((s) => s.viewportSize);
|
|
312
|
-
const panels = useWhiteboardStore((s) => s.panels);
|
|
313
|
-
const setOffset = useWhiteboardStore((s) => s.setOffset);
|
|
314
|
-
const setScale = useWhiteboardStore((s) => s.setScale);
|
|
315
|
-
const focusPanel = useWhiteboardStore((s) => s.focusPanel);
|
|
316
|
-
const containerRef = (0, import_react2.useRef)(null);
|
|
317
|
-
const dragging = (0, import_react2.useRef)(false);
|
|
318
|
-
const lastPanelTapRef = (0, import_react2.useRef)(null);
|
|
319
|
-
const rectEntries = Array.from(panels.entries());
|
|
320
|
-
const visibleRectEntries = rectEntries.filter(([, rect]) => Number.isFinite(rect.width) && Number.isFinite(rect.height));
|
|
321
|
-
const rects = visibleRectEntries.map(([, rect]) => rect);
|
|
322
|
-
if (loading || rects.length === 0) {
|
|
323
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "minimap minimap--loading", style: { width: MAP_W, height: MAP_H }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "minimap__loading", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Loader2, { size: 14, className: "icon-spin" }) }) });
|
|
324
|
-
}
|
|
325
|
-
const viewportWidth = viewportSize.width || window.innerWidth;
|
|
326
|
-
const viewportHeight = viewportSize.height || window.innerHeight;
|
|
327
|
-
const vpW = viewportWidth / scale;
|
|
328
|
-
const vpH = viewportHeight / scale;
|
|
329
|
-
const vpX = -offset.x / scale;
|
|
330
|
-
const vpY = -offset.y / scale;
|
|
331
|
-
let minX = Infinity;
|
|
332
|
-
let minY = Infinity;
|
|
333
|
-
let maxX = -Infinity;
|
|
334
|
-
let maxY = -Infinity;
|
|
335
|
-
for (const r of rects) {
|
|
336
|
-
minX = Math.min(minX, r.x);
|
|
337
|
-
minY = Math.min(minY, r.y);
|
|
338
|
-
maxX = Math.max(maxX, r.x + r.width);
|
|
339
|
-
maxY = Math.max(maxY, r.y + r.height);
|
|
340
|
-
}
|
|
341
|
-
minX -= PADDING;
|
|
342
|
-
minY -= PADDING;
|
|
343
|
-
maxX += PADDING;
|
|
344
|
-
maxY += PADDING;
|
|
345
|
-
const worldW = Math.max(MIN_WORLD_SIZE, maxX - minX);
|
|
346
|
-
const worldH = Math.max(MIN_WORLD_SIZE, maxY - minY);
|
|
347
|
-
const mapScale = Math.min(MAP_W / worldW, MAP_H / worldH);
|
|
348
|
-
const contentW = worldW * mapScale;
|
|
349
|
-
const contentH = worldH * mapScale;
|
|
350
|
-
const mapOffsetX = (MAP_W - contentW) / 2;
|
|
351
|
-
const mapOffsetY = (MAP_H - contentH) / 2;
|
|
352
|
-
const toMapX = (wx) => mapOffsetX + (wx - minX) * mapScale;
|
|
353
|
-
const toMapY = (wy) => mapOffsetY + (wy - minY) * mapScale;
|
|
354
|
-
const clientToWorld = (clientX, clientY) => {
|
|
355
|
-
if (!containerRef.current) return;
|
|
356
|
-
const mapRect = containerRef.current.getBoundingClientRect();
|
|
357
|
-
const localX = Math.min(MAP_W, Math.max(0, clientX - mapRect.left));
|
|
358
|
-
const localY = Math.min(MAP_H, Math.max(0, clientY - mapRect.top));
|
|
359
|
-
const boundedX = Math.min(contentW + mapOffsetX, Math.max(mapOffsetX, localX));
|
|
360
|
-
const boundedY = Math.min(contentH + mapOffsetY, Math.max(mapOffsetY, localY));
|
|
361
|
-
const worldX = (boundedX - mapOffsetX) / mapScale + minX;
|
|
362
|
-
const worldY = (boundedY - mapOffsetY) / mapScale + minY;
|
|
363
|
-
return { worldX, worldY };
|
|
364
|
-
};
|
|
365
|
-
const centerToWorld = (worldX, worldY, targetScale) => {
|
|
366
|
-
const clampedScale = Math.min(3, Math.max(0.2, targetScale));
|
|
367
|
-
setScale(clampedScale);
|
|
368
|
-
setOffset({
|
|
369
|
-
x: viewportWidth / 2 - worldX * clampedScale,
|
|
370
|
-
y: viewportHeight / 2 - worldY * clampedScale
|
|
371
|
-
});
|
|
372
|
-
};
|
|
373
|
-
const panTo = (clientX, clientY) => {
|
|
374
|
-
const world = clientToWorld(clientX, clientY);
|
|
375
|
-
if (!world) return;
|
|
376
|
-
centerToWorld(world.worldX, world.worldY, scale);
|
|
377
|
-
};
|
|
378
|
-
const onDown = (e) => {
|
|
379
|
-
e.stopPropagation();
|
|
380
|
-
e.preventDefault();
|
|
381
|
-
dragging.current = true;
|
|
382
|
-
e.currentTarget.setPointerCapture(e.pointerId);
|
|
383
|
-
panTo(e.clientX, e.clientY);
|
|
384
|
-
};
|
|
385
|
-
const onMove = (e) => {
|
|
386
|
-
if (!dragging.current) return;
|
|
387
|
-
panTo(e.clientX, e.clientY);
|
|
388
|
-
};
|
|
389
|
-
const onUp = () => {
|
|
390
|
-
dragging.current = false;
|
|
391
|
-
};
|
|
392
|
-
const onWheel = (e) => {
|
|
393
|
-
e.preventDefault();
|
|
394
|
-
e.stopPropagation();
|
|
395
|
-
const world = clientToWorld(e.clientX, e.clientY);
|
|
396
|
-
if (!world) return;
|
|
397
|
-
const factor = e.deltaY > 0 ? 0.9 : 1.1;
|
|
398
|
-
centerToWorld(world.worldX, world.worldY, scale * factor);
|
|
399
|
-
};
|
|
400
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
401
|
-
"div",
|
|
402
|
-
{
|
|
403
|
-
ref: containerRef,
|
|
404
|
-
onPointerDown: onDown,
|
|
405
|
-
onPointerMove: onMove,
|
|
406
|
-
onPointerUp: onUp,
|
|
407
|
-
onPointerCancel: onUp,
|
|
408
|
-
onWheel,
|
|
409
|
-
className: "minimap",
|
|
410
|
-
style: {
|
|
411
|
-
width: MAP_W,
|
|
412
|
-
height: MAP_H
|
|
413
|
-
},
|
|
414
|
-
children: [
|
|
415
|
-
visibleRectEntries.map(([id, r]) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
416
|
-
"div",
|
|
417
|
-
{
|
|
418
|
-
className: "minimap__panel",
|
|
419
|
-
onPointerDown: (event) => {
|
|
420
|
-
event.stopPropagation();
|
|
421
|
-
},
|
|
422
|
-
onPointerUp: (event) => {
|
|
423
|
-
event.stopPropagation();
|
|
424
|
-
const now = Date.now();
|
|
425
|
-
const last = lastPanelTapRef.current;
|
|
426
|
-
if (last && last.id === id && now - last.time < 300) {
|
|
427
|
-
event.preventDefault();
|
|
428
|
-
focusPanel(r, { padding: r.focusPadding, maxScale: r.focusMaxScale });
|
|
429
|
-
lastPanelTapRef.current = null;
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
lastPanelTapRef.current = { id, time: now };
|
|
433
|
-
},
|
|
434
|
-
onDoubleClick: (event) => {
|
|
435
|
-
event.preventDefault();
|
|
436
|
-
event.stopPropagation();
|
|
437
|
-
focusPanel(r, { padding: r.focusPadding, maxScale: r.focusMaxScale });
|
|
438
|
-
},
|
|
439
|
-
style: {
|
|
440
|
-
left: toMapX(r.x),
|
|
441
|
-
top: toMapY(r.y),
|
|
442
|
-
width: Math.max(1, r.width * mapScale),
|
|
443
|
-
height: Math.max(1, r.height * mapScale)
|
|
444
|
-
}
|
|
445
|
-
},
|
|
446
|
-
id
|
|
447
|
-
)),
|
|
448
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
449
|
-
"div",
|
|
450
|
-
{
|
|
451
|
-
className: "minimap__viewport",
|
|
452
|
-
style: {
|
|
453
|
-
left: toMapX(vpX),
|
|
454
|
-
top: toMapY(vpY),
|
|
455
|
-
width: vpW * mapScale,
|
|
456
|
-
height: vpH * mapScale
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
)
|
|
460
|
-
]
|
|
461
|
-
}
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// src/WhiteboardShell.tsx
|
|
466
|
-
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
467
|
-
function WhiteboardShell({ children, showMinimap = true, minimapLoading = false, extraActions }) {
|
|
468
|
-
const offset = useWhiteboardStore((s) => s.offset);
|
|
469
|
-
const scale = useWhiteboardStore((s) => s.scale);
|
|
470
|
-
const registryVersion = useWhiteboardStore((s) => s.registryVersion);
|
|
471
|
-
const viewportSize = useWhiteboardStore((s) => s.viewportSize);
|
|
472
|
-
const setOffset = useWhiteboardStore((s) => s.setOffset);
|
|
473
|
-
const setScale = useWhiteboardStore((s) => s.setScale);
|
|
474
|
-
const setViewportSize = useWhiteboardStore((s) => s.setViewportSize);
|
|
475
|
-
const shellRef = (0, import_react3.useRef)(null);
|
|
476
|
-
const panning = (0, import_react3.useRef)(false);
|
|
477
|
-
const last = (0, import_react3.useRef)({ x: 0, y: 0 });
|
|
478
|
-
const activePointerId = (0, import_react3.useRef)(null);
|
|
479
|
-
const scaleRef = (0, import_react3.useRef)(scale);
|
|
480
|
-
const hasFitted = (0, import_react3.useRef)(false);
|
|
481
|
-
(0, import_react3.useEffect)(() => {
|
|
482
|
-
return () => {
|
|
483
|
-
useWhiteboardStore.getState().resetSession();
|
|
484
|
-
};
|
|
485
|
-
}, []);
|
|
486
|
-
(0, import_react3.useEffect)(() => {
|
|
487
|
-
scaleRef.current = scale;
|
|
488
|
-
}, [scale]);
|
|
489
|
-
(0, import_react3.useEffect)(() => {
|
|
490
|
-
const shell = shellRef.current;
|
|
491
|
-
if (!shell) return;
|
|
492
|
-
const updateViewport = () => {
|
|
493
|
-
const rect = shell.getBoundingClientRect();
|
|
494
|
-
setViewportSize({
|
|
495
|
-
width: Math.max(0, rect.width),
|
|
496
|
-
height: Math.max(0, rect.height)
|
|
497
|
-
});
|
|
498
|
-
};
|
|
499
|
-
updateViewport();
|
|
500
|
-
if (typeof ResizeObserver === "undefined") return;
|
|
501
|
-
const observer = new ResizeObserver(updateViewport);
|
|
502
|
-
observer.observe(shell);
|
|
503
|
-
return () => observer.disconnect();
|
|
504
|
-
}, [setViewportSize]);
|
|
505
|
-
(0, import_react3.useEffect)(() => {
|
|
506
|
-
if (hasFitted.current) return;
|
|
507
|
-
const { panels } = useWhiteboardStore.getState();
|
|
508
|
-
if (panels.size === 0 || viewportSize.width <= 0 || viewportSize.height <= 0) return;
|
|
509
|
-
hasFitted.current = true;
|
|
510
|
-
requestAnimationFrame(() => {
|
|
511
|
-
useWhiteboardStore.getState().fitToContent();
|
|
512
|
-
});
|
|
513
|
-
}, [registryVersion, viewportSize]);
|
|
514
|
-
const onDown = (0, import_react3.useCallback)((e) => {
|
|
515
|
-
if (e.target !== e.currentTarget) return;
|
|
516
|
-
if (e.button === 0 || e.button === 1) {
|
|
517
|
-
panning.current = true;
|
|
518
|
-
activePointerId.current = e.pointerId;
|
|
519
|
-
last.current = { x: e.clientX, y: e.clientY };
|
|
520
|
-
e.currentTarget.setPointerCapture(e.pointerId);
|
|
521
|
-
e.preventDefault();
|
|
522
|
-
}
|
|
523
|
-
}, []);
|
|
524
|
-
const onMove = (0, import_react3.useCallback)((e) => {
|
|
525
|
-
if (!panning.current || activePointerId.current !== e.pointerId) return;
|
|
526
|
-
const dx = e.movementX ?? e.clientX - last.current.x;
|
|
527
|
-
const dy = e.movementY ?? e.clientY - last.current.y;
|
|
528
|
-
setOffset((p) => ({ x: p.x + dx, y: p.y + dy }));
|
|
529
|
-
last.current = { x: e.clientX, y: e.clientY };
|
|
530
|
-
}, [setOffset]);
|
|
531
|
-
const stopPan = (0, import_react3.useCallback)((e) => {
|
|
532
|
-
if (activePointerId.current !== null) {
|
|
533
|
-
try {
|
|
534
|
-
e.currentTarget.releasePointerCapture(activePointerId.current);
|
|
535
|
-
} catch {
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
panning.current = false;
|
|
539
|
-
activePointerId.current = null;
|
|
540
|
-
}, []);
|
|
541
|
-
const onWheel = (0, import_react3.useCallback)((e) => {
|
|
542
|
-
const s = scaleRef.current;
|
|
543
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
544
|
-
const anchor = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
545
|
-
const nextScale = Math.min(3, Math.max(0.2, s * (e.deltaY > 0 ? 0.9 : 1.1)));
|
|
546
|
-
const ratio = nextScale / s;
|
|
547
|
-
setOffset((prev) => ({
|
|
548
|
-
x: anchor.x - (anchor.x - prev.x) * ratio,
|
|
549
|
-
y: anchor.y - (anchor.y - prev.y) * ratio
|
|
550
|
-
}));
|
|
551
|
-
setScale(nextScale);
|
|
552
|
-
}, [setOffset, setScale]);
|
|
553
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
554
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
555
|
-
"div",
|
|
556
|
-
{
|
|
557
|
-
ref: shellRef,
|
|
558
|
-
onPointerDown: onDown,
|
|
559
|
-
onPointerMove: onMove,
|
|
560
|
-
onPointerUp: stopPan,
|
|
561
|
-
onPointerCancel: stopPan,
|
|
562
|
-
onWheel,
|
|
563
|
-
onContextMenu: (e) => e.preventDefault(),
|
|
564
|
-
className: "whiteboard-shell",
|
|
565
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
566
|
-
"div",
|
|
567
|
-
{
|
|
568
|
-
className: "whiteboard-canvas",
|
|
569
|
-
style: { transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})` },
|
|
570
|
-
children: [
|
|
571
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "whiteboard-grid", "aria-hidden": true }),
|
|
572
|
-
children
|
|
573
|
-
]
|
|
574
|
-
}
|
|
575
|
-
)
|
|
576
|
-
}
|
|
577
|
-
),
|
|
578
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ZoomBar, { extraActions }),
|
|
579
|
-
showMinimap ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Minimap, { loading: minimapLoading }) : null
|
|
580
|
-
] });
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// src/FloatingPanel.tsx
|
|
584
|
-
var import_react4 = require("react");
|
|
585
|
-
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
586
|
-
var FloatingPanel = (0, import_react4.memo)(function FloatingPanel2({
|
|
587
|
-
title,
|
|
588
|
-
defaultPosition,
|
|
589
|
-
width = 300,
|
|
590
|
-
className,
|
|
591
|
-
trackRect: trackRectRef,
|
|
592
|
-
focusable,
|
|
593
|
-
focusPadding = 40,
|
|
594
|
-
focusMaxScale = 1.5,
|
|
595
|
-
headerActions,
|
|
596
|
-
children
|
|
597
|
-
}) {
|
|
598
|
-
const panelId = (0, import_react4.useId)();
|
|
599
|
-
const [pos, setPos] = (0, import_react4.useState)(defaultPosition);
|
|
600
|
-
const dragging = (0, import_react4.useRef)(false);
|
|
601
|
-
const posRef = (0, import_react4.useRef)(pos);
|
|
602
|
-
const elRef = (0, import_react4.useRef)(null);
|
|
603
|
-
const lastRegisteredRectRef = (0, import_react4.useRef)(null);
|
|
604
|
-
const scale = useWhiteboardStore((s) => s.scale);
|
|
605
|
-
const snapToGrid = useWhiteboardStore((s) => s.snapToGrid);
|
|
606
|
-
const snapGridSize = useWhiteboardStore((s) => s.snapGridSize);
|
|
607
|
-
const register = useWhiteboardStore((s) => s.register);
|
|
608
|
-
const unregister = useWhiteboardStore((s) => s.unregister);
|
|
609
|
-
const registerReset = useWhiteboardStore((s) => s.registerReset);
|
|
610
|
-
const unregisterReset = useWhiteboardStore((s) => s.unregisterReset);
|
|
611
|
-
const focusPanel = useWhiteboardStore((s) => s.focusPanel);
|
|
612
|
-
const scaleRef = (0, import_react4.useRef)(scale);
|
|
613
|
-
const snapToGridRef = (0, import_react4.useRef)(false);
|
|
614
|
-
const snapGridSizeRef = (0, import_react4.useRef)(20);
|
|
615
|
-
const defaultPosRef = (0, import_react4.useRef)(defaultPosition);
|
|
616
|
-
const cleanupRef = (0, import_react4.useRef)(null);
|
|
617
|
-
const lastTapRef = (0, import_react4.useRef)(null);
|
|
618
|
-
(0, import_react4.useEffect)(() => {
|
|
619
|
-
scaleRef.current = scale;
|
|
620
|
-
}, [scale]);
|
|
621
|
-
(0, import_react4.useEffect)(() => {
|
|
622
|
-
snapToGridRef.current = snapToGrid;
|
|
623
|
-
snapGridSizeRef.current = snapGridSize;
|
|
624
|
-
}, [snapToGrid, snapGridSize]);
|
|
625
|
-
(0, import_react4.useEffect)(() => {
|
|
626
|
-
const onSnapNow = () => {
|
|
627
|
-
const snapSize = snapGridSizeRef.current;
|
|
628
|
-
setPos((current) => {
|
|
629
|
-
const next = {
|
|
630
|
-
x: Math.round(current.x / snapSize) * snapSize,
|
|
631
|
-
y: Math.round(current.y / snapSize) * snapSize
|
|
632
|
-
};
|
|
633
|
-
return next.x === current.x && next.y === current.y ? current : next;
|
|
634
|
-
});
|
|
635
|
-
};
|
|
636
|
-
window.addEventListener("whiteboard-snap-now", onSnapNow);
|
|
637
|
-
return () => window.removeEventListener("whiteboard-snap-now", onSnapNow);
|
|
638
|
-
}, []);
|
|
639
|
-
(0, import_react4.useEffect)(() => {
|
|
640
|
-
posRef.current = pos;
|
|
641
|
-
}, [pos]);
|
|
642
|
-
const registerRect = (0, import_react4.useCallback)(() => {
|
|
643
|
-
const el = elRef.current;
|
|
644
|
-
if (!el) return;
|
|
645
|
-
const nextRect = {
|
|
646
|
-
x: posRef.current.x,
|
|
647
|
-
y: posRef.current.y,
|
|
648
|
-
width: el.offsetWidth,
|
|
649
|
-
height: el.offsetHeight,
|
|
650
|
-
focusPadding,
|
|
651
|
-
focusMaxScale
|
|
652
|
-
};
|
|
653
|
-
const prev = lastRegisteredRectRef.current;
|
|
654
|
-
if (prev && prev.x === nextRect.x && prev.y === nextRect.y && prev.width === nextRect.width && prev.height === nextRect.height && prev.focusPadding === nextRect.focusPadding && prev.focusMaxScale === nextRect.focusMaxScale) {
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
lastRegisteredRectRef.current = nextRect;
|
|
658
|
-
register(panelId, nextRect);
|
|
659
|
-
if (trackRectRef) {
|
|
660
|
-
trackRectRef.current = nextRect;
|
|
661
|
-
}
|
|
662
|
-
}, [focusMaxScale, focusPadding, panelId, register, trackRectRef]);
|
|
663
|
-
const getCurrentRect = (0, import_react4.useCallback)(() => {
|
|
664
|
-
const el = elRef.current;
|
|
665
|
-
if (!el) return null;
|
|
666
|
-
return {
|
|
667
|
-
x: posRef.current.x,
|
|
668
|
-
y: posRef.current.y,
|
|
669
|
-
width: el.offsetWidth,
|
|
670
|
-
height: el.offsetHeight,
|
|
671
|
-
focusPadding,
|
|
672
|
-
focusMaxScale
|
|
673
|
-
};
|
|
674
|
-
}, [focusMaxScale, focusPadding]);
|
|
675
|
-
const handleFocus = (0, import_react4.useCallback)(() => {
|
|
676
|
-
const rect = getCurrentRect();
|
|
677
|
-
if (!rect) return;
|
|
678
|
-
focusPanel(rect, { padding: focusPadding, maxScale: focusMaxScale });
|
|
679
|
-
}, [focusPanel, focusPadding, focusMaxScale, getCurrentRect]);
|
|
680
|
-
(0, import_react4.useEffect)(() => {
|
|
681
|
-
registerReset(panelId, () => setPos(defaultPosRef.current));
|
|
682
|
-
return () => {
|
|
683
|
-
cleanupRef.current?.();
|
|
684
|
-
unregister(panelId);
|
|
685
|
-
unregisterReset(panelId);
|
|
686
|
-
lastRegisteredRectRef.current = null;
|
|
687
|
-
};
|
|
688
|
-
}, [panelId, registerReset, unregister, unregisterReset]);
|
|
689
|
-
(0, import_react4.useLayoutEffect)(() => {
|
|
690
|
-
registerRect();
|
|
691
|
-
}, [pos.x, pos.y, width, registerRect]);
|
|
692
|
-
(0, import_react4.useEffect)(() => {
|
|
693
|
-
const el = elRef.current;
|
|
694
|
-
if (!el) return;
|
|
695
|
-
if (typeof ResizeObserver === "undefined") return;
|
|
696
|
-
const obs = new ResizeObserver(() => {
|
|
697
|
-
registerRect();
|
|
698
|
-
});
|
|
699
|
-
obs.observe(el);
|
|
700
|
-
return () => obs.disconnect();
|
|
701
|
-
}, [registerRect]);
|
|
702
|
-
const onDown = (0, import_react4.useCallback)((e) => {
|
|
703
|
-
if (e.button !== 0) return;
|
|
704
|
-
dragging.current = true;
|
|
705
|
-
const startX = e.clientX;
|
|
706
|
-
const startY = e.clientY;
|
|
707
|
-
const startPosX = posRef.current.x;
|
|
708
|
-
const startPosY = posRef.current.y;
|
|
709
|
-
const startScale = scaleRef.current;
|
|
710
|
-
e.preventDefault();
|
|
711
|
-
e.stopPropagation();
|
|
712
|
-
e.target.setPointerCapture(e.pointerId);
|
|
713
|
-
const move = (ev) => {
|
|
714
|
-
if (!dragging.current) return;
|
|
715
|
-
const rawX = startPosX + (ev.clientX - startX) / startScale;
|
|
716
|
-
const rawY = startPosY + (ev.clientY - startY) / startScale;
|
|
717
|
-
const snapSize = snapGridSizeRef.current;
|
|
718
|
-
const nextX = snapToGridRef.current ? Math.round(rawX / snapSize) * snapSize : rawX;
|
|
719
|
-
const nextY = snapToGridRef.current ? Math.round(rawY / snapSize) * snapSize : rawY;
|
|
720
|
-
setPos({
|
|
721
|
-
x: nextX,
|
|
722
|
-
y: nextY
|
|
723
|
-
});
|
|
724
|
-
};
|
|
725
|
-
const up = () => {
|
|
726
|
-
dragging.current = false;
|
|
727
|
-
window.removeEventListener("pointermove", move);
|
|
728
|
-
window.removeEventListener("pointerup", up);
|
|
729
|
-
cleanupRef.current = null;
|
|
730
|
-
};
|
|
731
|
-
cleanupRef.current?.();
|
|
732
|
-
window.addEventListener("pointermove", move);
|
|
733
|
-
window.addEventListener("pointerup", up);
|
|
734
|
-
cleanupRef.current = up;
|
|
735
|
-
}, []);
|
|
736
|
-
const panelClassName = className ? `floating-panel ${className}` : "floating-panel";
|
|
737
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
738
|
-
"div",
|
|
739
|
-
{
|
|
740
|
-
ref: elRef,
|
|
741
|
-
className: panelClassName,
|
|
742
|
-
style: { left: pos.x, top: pos.y, width },
|
|
743
|
-
onPointerDown: (e) => e.stopPropagation(),
|
|
744
|
-
onPointerUp: (e) => {
|
|
745
|
-
if (dragging.current) return;
|
|
746
|
-
const now = Date.now();
|
|
747
|
-
const last = lastTapRef.current;
|
|
748
|
-
if (last && now - last.time < 300) {
|
|
749
|
-
const dx = e.clientX - last.x;
|
|
750
|
-
const dy = e.clientY - last.y;
|
|
751
|
-
if (dx * dx + dy * dy < 100) {
|
|
752
|
-
e.stopPropagation();
|
|
753
|
-
handleFocus();
|
|
754
|
-
lastTapRef.current = null;
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
lastTapRef.current = { time: now, x: e.clientX, y: e.clientY };
|
|
759
|
-
},
|
|
760
|
-
onWheel: (e) => e.stopPropagation(),
|
|
761
|
-
onDoubleClick: (e) => {
|
|
762
|
-
e.stopPropagation();
|
|
763
|
-
handleFocus();
|
|
764
|
-
},
|
|
765
|
-
children: [
|
|
766
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { onPointerDown: onDown, className: "floating-panel__header", children: [
|
|
767
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { className: "floating-panel__title", children: title }),
|
|
768
|
-
headerActions,
|
|
769
|
-
focusable && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
770
|
-
"button",
|
|
771
|
-
{
|
|
772
|
-
type: "button",
|
|
773
|
-
className: "wb-btn wb-btn--secondary wb-btn--icon-only floating-panel__focus",
|
|
774
|
-
onClick: handleFocus,
|
|
775
|
-
onPointerDown: (e) => e.stopPropagation(),
|
|
776
|
-
title: "Focus on this panel",
|
|
777
|
-
"aria-label": "Focus on this panel",
|
|
778
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Maximize2, { size: 14 })
|
|
779
|
-
}
|
|
780
|
-
)
|
|
781
|
-
] }),
|
|
782
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "floating-panel__body", children })
|
|
783
|
-
]
|
|
784
|
-
}
|
|
785
|
-
);
|
|
786
|
-
});
|
|
787
|
-
function usePanelRect(initial) {
|
|
788
|
-
const ref = (0, import_react4.useRef)({ ...initial, width: 0, height: 0 });
|
|
789
|
-
return ref;
|
|
790
|
-
}
|
|
791
|
-
function belowPanel(rect, gap = WHITEBOARD_GRID) {
|
|
792
|
-
return { x: rect.x, y: rect.y + rect.height + gap };
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// src/ConfirmDialog.tsx
|
|
796
|
-
var import_react5 = require("react");
|
|
797
|
-
var import_react_dom = require("react-dom");
|
|
798
|
-
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
799
|
-
function ConfirmDialog({
|
|
800
|
-
open,
|
|
801
|
-
title,
|
|
802
|
-
message,
|
|
803
|
-
onConfirm,
|
|
804
|
-
onCancel,
|
|
805
|
-
confirmLabel = "Confirm",
|
|
806
|
-
loading,
|
|
807
|
-
error
|
|
808
|
-
}) {
|
|
809
|
-
(0, import_react5.useEffect)(() => {
|
|
810
|
-
if (!open) return;
|
|
811
|
-
const onKey = (e) => {
|
|
812
|
-
if (e.key === "Escape") onCancel();
|
|
813
|
-
};
|
|
814
|
-
window.addEventListener("keydown", onKey);
|
|
815
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
816
|
-
}, [open, onCancel]);
|
|
817
|
-
if (!open || typeof document === "undefined") return null;
|
|
818
|
-
return (0, import_react_dom.createPortal)(
|
|
819
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "confirm-modal-overlay", onMouseDown: onCancel, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
820
|
-
"div",
|
|
821
|
-
{
|
|
822
|
-
className: "confirm-modal",
|
|
823
|
-
role: "dialog",
|
|
824
|
-
"aria-modal": "true",
|
|
825
|
-
"aria-label": title,
|
|
826
|
-
onMouseDown: (e) => e.stopPropagation(),
|
|
827
|
-
children: [
|
|
828
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "confirm-modal__header", children: [
|
|
829
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "confirm-modal__title", children: [
|
|
830
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AlertTriangle, { size: 16 }),
|
|
831
|
-
title
|
|
832
|
-
] }),
|
|
833
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "wb-btn wb-btn--secondary wb-btn--icon-only", onClick: onCancel, "aria-label": "Close dialog", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(X, { size: 14 }) })
|
|
834
|
-
] }),
|
|
835
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "confirm-modal__message", children: message }),
|
|
836
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "wb-alert wb-alert--error", children: error }),
|
|
837
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "wb-btn-row", children: [
|
|
838
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "wb-btn wb-btn--secondary", onClick: onCancel, children: "Cancel" }),
|
|
839
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "wb-btn wb-btn--danger", onClick: onConfirm, disabled: loading, children: loading ? "Deleting..." : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
840
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Check, { size: 14 }),
|
|
841
|
-
confirmLabel
|
|
842
|
-
] }) })
|
|
843
|
-
] })
|
|
844
|
-
]
|
|
845
|
-
}
|
|
846
|
-
) }),
|
|
847
|
-
document.body
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
// src/PanelErrorBoundary.tsx
|
|
852
|
-
var import_react6 = require("react");
|
|
853
|
-
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
854
|
-
var PanelErrorBoundary = class extends import_react6.Component {
|
|
855
|
-
constructor() {
|
|
856
|
-
super(...arguments);
|
|
857
|
-
this.state = { error: null };
|
|
858
|
-
}
|
|
859
|
-
static getDerivedStateFromError(error) {
|
|
860
|
-
return { error };
|
|
861
|
-
}
|
|
862
|
-
render() {
|
|
863
|
-
if (this.state.error) {
|
|
864
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "wb-stack wb-stack--sm", children: [
|
|
865
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "wb-alert wb-alert--error", children: this.props.fallbackMessage ?? "This panel crashed." }),
|
|
866
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
867
|
-
"button",
|
|
868
|
-
{
|
|
869
|
-
type: "button",
|
|
870
|
-
className: "wb-btn wb-btn--secondary",
|
|
871
|
-
onClick: () => this.setState({ error: null }),
|
|
872
|
-
children: "Retry"
|
|
873
|
-
}
|
|
874
|
-
)
|
|
875
|
-
] });
|
|
876
|
-
}
|
|
877
|
-
return this.props.children;
|
|
878
|
-
}
|
|
879
|
-
};
|
|
880
|
-
|
|
881
|
-
// src/useWhiteboardLayout.ts
|
|
882
|
-
var import_react7 = require("react");
|
|
883
|
-
function useWhiteboardLayout({
|
|
884
|
-
widths,
|
|
885
|
-
startX = 20,
|
|
886
|
-
y = 40,
|
|
887
|
-
gap = 20
|
|
888
|
-
}) {
|
|
889
|
-
const panelWidth = (0, import_react7.useMemo)(() => {
|
|
890
|
-
const normalized = {};
|
|
891
|
-
for (const [key, value] of Object.entries(widths)) {
|
|
892
|
-
normalized[key] = snapToWhiteboardGrid(value);
|
|
893
|
-
}
|
|
894
|
-
return normalized;
|
|
895
|
-
}, [widths]);
|
|
896
|
-
const layout = (0, import_react7.useMemo)(
|
|
897
|
-
() => ({
|
|
898
|
-
startX: snapToWhiteboardGrid(startX),
|
|
899
|
-
y: snapToWhiteboardGrid(y),
|
|
900
|
-
gap: snapToWhiteboardGrid(gap)
|
|
901
|
-
}),
|
|
902
|
-
[startX, y, gap]
|
|
903
|
-
);
|
|
904
|
-
const positions = (0, import_react7.useMemo)(() => {
|
|
905
|
-
const next = {};
|
|
906
|
-
let x = layout.startX;
|
|
907
|
-
for (const [key, width] of Object.entries(panelWidth)) {
|
|
908
|
-
;
|
|
909
|
-
next[key] = { x, y: layout.y };
|
|
910
|
-
x += width + layout.gap;
|
|
911
|
-
}
|
|
912
|
-
return next;
|
|
913
|
-
}, [layout.gap, layout.startX, layout.y, panelWidth]);
|
|
914
|
-
return { layout, panelWidth, positions };
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
// src/cn.ts
|
|
918
|
-
function cn(...args) {
|
|
919
|
-
return args.filter(Boolean).join(" ");
|
|
920
|
-
}
|
|
921
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
922
|
-
0 && (module.exports = {
|
|
923
|
-
ConfirmDialog,
|
|
924
|
-
FloatingPanel,
|
|
925
|
-
Minimap,
|
|
926
|
-
PanelErrorBoundary,
|
|
927
|
-
WHITEBOARD_GRID,
|
|
928
|
-
WhiteboardShell,
|
|
929
|
-
ZoomBar,
|
|
930
|
-
belowPanel,
|
|
931
|
-
cn,
|
|
932
|
-
computeWhiteboardFit,
|
|
933
|
-
computeWhiteboardRectFocus,
|
|
934
|
-
snapToWhiteboardGrid,
|
|
935
|
-
usePanelRect,
|
|
936
|
-
useWhiteboardLayout,
|
|
937
|
-
useWhiteboardStore
|
|
938
|
-
});
|
|
939
|
-
//# sourceMappingURL=index.cjs.map
|