react-os-shell 0.2.30 → 0.2.35

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.
@@ -1,975 +1,11 @@
1
+ import { ImageAnnotator_default } from './chunk-KUIPWCTJ.js';
1
2
  import { toast_default } from './chunk-WIJ45SYD.js';
2
3
  import { WindowTitle, getActiveModalId } from './chunk-T2NQXP2J.js';
3
- import { forwardRef, useRef, useState, useMemo, useEffect, useImperativeHandle, createContext, useContext } from 'react';
4
+ import { createContext, useState, useEffect, useRef, useContext } from 'react';
4
5
  import { createPortal } from 'react-dom';
5
6
  import * as pdfjsLib from 'pdfjs-dist';
6
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
8
 
8
- var COLORS = ["#ef4444", "#f97316", "#eab308", "#22c55e", "#3b82f6", "#8b5cf6", "#000000", "#ffffff"];
9
- var FONTS = [
10
- { id: "system", label: "System", css: "-apple-system, system-ui, sans-serif" },
11
- { id: "serif", label: "Serif", css: 'Georgia, "Times New Roman", serif' },
12
- { id: "mono", label: "Mono", css: 'ui-monospace, "SF Mono", Menlo, monospace' },
13
- { id: "cursive", label: "Cursive", css: '"Brush Script MT", "Comic Sans MS", cursive' }
14
- ];
15
- var STROKE_DEFAULT = 4;
16
- var TEXT_SIZE_DEFAULT = 24;
17
- var RECT_RADIUS_DEFAULT = 12;
18
- var MOSAIC_BLOCK = 12;
19
- var ZOOM_MIN = 0.25;
20
- var ZOOM_MAX = 4;
21
- var ZOOM_STEP = 0.25;
22
- var newId = () => Math.random().toString(36).slice(2, 9);
23
- var ImageAnnotator = forwardRef(function ImageAnnotator2({ src, filename }, ref) {
24
- const wrapRef = useRef(null);
25
- const canvasRef = useRef(null);
26
- const svgRef = useRef(null);
27
- const imageRef = useRef(null);
28
- const [tool, setTool] = useState("select");
29
- const [color, setColor] = useState(COLORS[0]);
30
- const [stroke, setStroke] = useState(STROKE_DEFAULT);
31
- const [textSize, setTextSize] = useState(TEXT_SIZE_DEFAULT);
32
- const [textFont, setTextFont] = useState(FONTS[0].id);
33
- const [textBold, setTextBold] = useState(true);
34
- const [textItalic, setTextItalic] = useState(false);
35
- const [textUnderline, setTextUnderline] = useState(false);
36
- const [rectRadius, setRectRadius] = useState(RECT_RADIUS_DEFAULT);
37
- const [annotations, setAnnotations] = useState([]);
38
- const [selectedId, setSelectedId] = useState(null);
39
- const [imageSize, setImageSize] = useState(null);
40
- const [fitSize, setFitSize] = useState(null);
41
- const [zoom, setZoom] = useState(1);
42
- const [preview, setPreview] = useState(null);
43
- const [pendingText, setPendingText] = useState(null);
44
- const [pendingCrop, setPendingCrop] = useState(null);
45
- const dragRef = useRef(null);
46
- const [isDragging, setIsDragging] = useState(false);
47
- const displaySize = useMemo(() => {
48
- if (!fitSize) return null;
49
- return { w: fitSize.w * zoom, h: fitSize.h * zoom };
50
- }, [fitSize, zoom]);
51
- const scale = displaySize && imageSize ? displaySize.w / imageSize.w : 1;
52
- const selected = useMemo(
53
- () => annotations.find((a) => a.id === selectedId) ?? null,
54
- [annotations, selectedId]
55
- );
56
- useEffect(() => {
57
- if (tool === "select" && selected) {
58
- if ("color" in selected) setColor(selected.color);
59
- if ("stroke" in selected) setStroke(selected.stroke);
60
- if (selected.type === "rect") setRectRadius(selected.radius);
61
- if (selected.type === "text") {
62
- setTextSize(selected.size);
63
- setTextFont(selected.font);
64
- setTextBold(selected.bold);
65
- setTextItalic(selected.italic);
66
- setTextUnderline(selected.underline);
67
- }
68
- }
69
- }, [selectedId]);
70
- useEffect(() => {
71
- const img = new Image();
72
- img.crossOrigin = "anonymous";
73
- img.onload = () => {
74
- imageRef.current = img;
75
- setImageSize({ w: img.naturalWidth, h: img.naturalHeight });
76
- setAnnotations([]);
77
- setSelectedId(null);
78
- };
79
- img.onerror = () => toast_default.error("Failed to load image");
80
- img.src = src;
81
- }, [src]);
82
- useEffect(() => {
83
- if (!imageSize) return;
84
- const update = () => {
85
- const wrap = wrapRef.current;
86
- if (!wrap) return;
87
- const r = wrap.getBoundingClientRect();
88
- const availW = Math.max(0, r.width - 32);
89
- const availH = Math.max(0, r.height - 32);
90
- if (availW === 0 || availH === 0) return;
91
- const ratio = imageSize.w / imageSize.h;
92
- let w = imageSize.w, h = imageSize.h;
93
- if (w > availW) {
94
- w = availW;
95
- h = w / ratio;
96
- }
97
- if (h > availH) {
98
- h = availH;
99
- w = h * ratio;
100
- }
101
- setFitSize({ w, h });
102
- };
103
- update();
104
- const ro = new ResizeObserver(update);
105
- if (wrapRef.current) ro.observe(wrapRef.current);
106
- return () => ro.disconnect();
107
- }, [imageSize]);
108
- const mosaicAnnos = useMemo(
109
- () => annotations.filter((a) => a.type === "mosaic"),
110
- [annotations]
111
- );
112
- useEffect(() => {
113
- const img = imageRef.current;
114
- const c = canvasRef.current;
115
- if (!img || !c || !imageSize) return;
116
- c.width = imageSize.w;
117
- c.height = imageSize.h;
118
- const ctx = c.getContext("2d");
119
- ctx.drawImage(img, 0, 0);
120
- for (const m of mosaicAnnos) applyMosaic(ctx, m);
121
- }, [imageSize, mosaicAnnos, fitSize]);
122
- const evToImage = (e) => {
123
- const svg = svgRef.current;
124
- if (!svg) return { x: 0, y: 0 };
125
- const pt = svg.createSVGPoint();
126
- pt.x = e.clientX;
127
- pt.y = e.clientY;
128
- const ctm = svg.getScreenCTM();
129
- if (!ctm) return { x: 0, y: 0 };
130
- const t = pt.matrixTransform(ctm.inverse());
131
- return { x: t.x, y: t.y };
132
- };
133
- const beginDrag = (drag) => {
134
- dragRef.current = drag;
135
- setIsDragging(true);
136
- };
137
- useEffect(() => {
138
- if (!isDragging) return;
139
- const onMove = (ev) => {
140
- const drag = dragRef.current;
141
- if (!drag) return;
142
- const p = evToImage(ev);
143
- if (drag.kind === "draw") {
144
- setPreview(makeShape(tool, drag.start, p, color, stroke, rectRadius));
145
- } else if (drag.kind === "pen") {
146
- const last = drag.points[drag.points.length - 1];
147
- if (Math.abs(p.x - last.x) > 1 || Math.abs(p.y - last.y) > 1) {
148
- drag.points.push(p);
149
- setPreview({ id: "", type: "draw", points: drag.points.slice(), color, stroke });
150
- }
151
- } else if (drag.kind === "crop") {
152
- setPendingCrop(normalizeRect(drag.start, p));
153
- } else if (drag.kind === "move") {
154
- const dx = p.x - drag.start.x;
155
- const dy = p.y - drag.start.y;
156
- setAnnotations((prev) => prev.map((a) => a.id === drag.id ? translate(drag.original, dx, dy) : a));
157
- } else if (drag.kind === "resize") {
158
- setAnnotations((prev) => prev.map((a) => a.id === drag.id ? resize(drag.original, drag.corner, p) : a));
159
- }
160
- };
161
- const onUp = () => {
162
- const drag = dragRef.current;
163
- dragRef.current = null;
164
- setIsDragging(false);
165
- if (drag?.kind === "draw") {
166
- setPreview((p) => {
167
- if (!p || isTrivial(p)) return null;
168
- const anno = { ...p, id: newId() };
169
- setAnnotations((prev) => [...prev, anno]);
170
- setSelectedId(anno.id);
171
- setTool("select");
172
- return null;
173
- });
174
- } else if (drag?.kind === "pen") {
175
- if (drag.points.length < 2) {
176
- setPreview(null);
177
- return;
178
- }
179
- const anno = { id: newId(), type: "draw", points: drag.points, color, stroke };
180
- setAnnotations((prev) => [...prev, anno]);
181
- setSelectedId(anno.id);
182
- setTool("select");
183
- setPreview(null);
184
- }
185
- };
186
- window.addEventListener("pointermove", onMove);
187
- window.addEventListener("pointerup", onUp);
188
- window.addEventListener("pointercancel", onUp);
189
- return () => {
190
- window.removeEventListener("pointermove", onMove);
191
- window.removeEventListener("pointerup", onUp);
192
- window.removeEventListener("pointercancel", onUp);
193
- };
194
- }, [isDragging, tool, color, stroke, rectRadius]);
195
- const handleAnnoPointerDown = (e, anno) => {
196
- if (tool !== "select") return;
197
- e.stopPropagation();
198
- setSelectedId(anno.id);
199
- beginDrag({ kind: "move", id: anno.id, start: evToImage(e), original: anno });
200
- };
201
- const handleHandlePointerDown = (e, anno, corner) => {
202
- e.stopPropagation();
203
- beginDrag({ kind: "resize", id: anno.id, corner, start: evToImage(e), original: anno });
204
- };
205
- const handleSvgPointerDown = (e) => {
206
- if (tool === "select") {
207
- setSelectedId(null);
208
- return;
209
- }
210
- if (tool === "text") {
211
- const p = evToImage(e);
212
- setPendingText({ x: p.x, y: p.y, value: "" });
213
- return;
214
- }
215
- const start = evToImage(e);
216
- if (tool === "crop") {
217
- beginDrag({ kind: "crop", start });
218
- return;
219
- }
220
- if (tool === "draw") {
221
- beginDrag({ kind: "pen", points: [start] });
222
- setPreview({ id: "", type: "draw", points: [start], color, stroke });
223
- return;
224
- }
225
- setPreview(makeShape(tool, start, start, color, stroke, rectRadius));
226
- beginDrag({ kind: "draw", start });
227
- };
228
- const handleAnnoDoubleClick = (anno) => {
229
- if (anno.type !== "text") return;
230
- setPendingText({ x: anno.x, y: anno.y, value: anno.text, editingId: anno.id });
231
- };
232
- useEffect(() => {
233
- const onKey = (ev) => {
234
- if (document.activeElement && /^(INPUT|TEXTAREA)$/.test(document.activeElement.tagName)) return;
235
- if ((ev.metaKey || ev.ctrlKey) && (ev.key === "z" || ev.key === "Z") && !ev.shiftKey) {
236
- ev.preventDefault();
237
- setAnnotations((prev) => prev.slice(0, -1));
238
- setSelectedId(null);
239
- return;
240
- }
241
- if (!selectedId) return;
242
- if (ev.key === "Delete" || ev.key === "Backspace") {
243
- ev.preventDefault();
244
- setAnnotations((prev) => prev.filter((a) => a.id !== selectedId));
245
- setSelectedId(null);
246
- } else if (ev.key === "Escape") {
247
- setSelectedId(null);
248
- }
249
- };
250
- window.addEventListener("keydown", onKey);
251
- return () => window.removeEventListener("keydown", onKey);
252
- }, [selectedId]);
253
- const setColorAndApply = (c) => {
254
- setColor(c);
255
- if (!selectedId) return;
256
- setAnnotations((prev) => prev.map((a) => {
257
- if (a.id !== selectedId) return a;
258
- if (a.type === "mosaic") return a;
259
- return { ...a, color: c };
260
- }));
261
- };
262
- const setStrokeAndApply = (s) => {
263
- setStroke(s);
264
- if (!selectedId) return;
265
- setAnnotations((prev) => prev.map((a) => {
266
- if (a.id !== selectedId) return a;
267
- if (a.type === "rect" || a.type === "circle" || a.type === "arrow" || a.type === "draw") {
268
- return { ...a, stroke: s };
269
- }
270
- return a;
271
- }));
272
- };
273
- const setRectRadiusAndApply = (r) => {
274
- setRectRadius(r);
275
- if (!selectedId) return;
276
- setAnnotations((prev) => prev.map(
277
- (a) => a.id === selectedId && a.type === "rect" ? { ...a, radius: r } : a
278
- ));
279
- };
280
- const setTextSizeAndApply = (s) => {
281
- setTextSize(s);
282
- if (!selectedId) return;
283
- setAnnotations((prev) => prev.map(
284
- (a) => a.id === selectedId && a.type === "text" ? { ...a, size: s } : a
285
- ));
286
- };
287
- const setTextFontAndApply = (f) => {
288
- setTextFont(f);
289
- if (!selectedId) return;
290
- setAnnotations((prev) => prev.map(
291
- (a) => a.id === selectedId && a.type === "text" ? { ...a, font: f } : a
292
- ));
293
- };
294
- const toggleTextStyleAndApply = (which) => {
295
- const next = !{ bold: textBold, italic: textItalic, underline: textUnderline }[which];
296
- if (which === "bold") setTextBold(next);
297
- if (which === "italic") setTextItalic(next);
298
- if (which === "underline") setTextUnderline(next);
299
- if (!selectedId) return;
300
- setAnnotations((prev) => prev.map(
301
- (a) => a.id === selectedId && a.type === "text" ? { ...a, [which]: next } : a
302
- ));
303
- };
304
- const commitText = () => {
305
- if (!pendingText) return;
306
- const value = pendingText.value;
307
- if (!value.trim()) {
308
- if (pendingText.editingId) {
309
- setAnnotations((prev) => prev.filter((a) => a.id !== pendingText.editingId));
310
- }
311
- setPendingText(null);
312
- return;
313
- }
314
- if (pendingText.editingId) {
315
- setAnnotations((prev) => prev.map(
316
- (a) => a.id === pendingText.editingId && a.type === "text" ? { ...a, text: value } : a
317
- ));
318
- } else {
319
- const anno = {
320
- id: newId(),
321
- type: "text",
322
- x: pendingText.x,
323
- y: pendingText.y,
324
- text: value,
325
- color,
326
- size: textSize,
327
- font: textFont,
328
- bold: textBold,
329
- italic: textItalic,
330
- underline: textUnderline
331
- };
332
- setAnnotations((prev) => [...prev, anno]);
333
- setSelectedId(anno.id);
334
- setTool("select");
335
- }
336
- setPendingText(null);
337
- };
338
- const applyCrop = () => {
339
- if (!pendingCrop || !imageRef.current || !imageSize) return;
340
- const r = pendingCrop;
341
- const tmp = document.createElement("canvas");
342
- tmp.width = Math.round(r.w);
343
- tmp.height = Math.round(r.h);
344
- const tctx = tmp.getContext("2d");
345
- const sourceCanvas = document.createElement("canvas");
346
- sourceCanvas.width = imageSize.w;
347
- sourceCanvas.height = imageSize.h;
348
- const sctx = sourceCanvas.getContext("2d");
349
- sctx.drawImage(imageRef.current, 0, 0);
350
- for (const m of mosaicAnnos) applyMosaic(sctx, m);
351
- tctx.drawImage(sourceCanvas, r.x, r.y, r.w, r.h, 0, 0, r.w, r.h);
352
- const newImg = new Image();
353
- newImg.onload = () => {
354
- imageRef.current = newImg;
355
- setImageSize({ w: newImg.naturalWidth, h: newImg.naturalHeight });
356
- setAnnotations(
357
- (prev) => prev.filter((a) => a.type !== "mosaic").map((a) => translate(a, -r.x, -r.y)).filter((a) => withinBounds(a, newImg.naturalWidth, newImg.naturalHeight))
358
- );
359
- setPendingCrop(null);
360
- };
361
- newImg.src = tmp.toDataURL("image/png");
362
- };
363
- const cancelCrop = () => setPendingCrop(null);
364
- const undoLast = () => {
365
- setAnnotations((prev) => prev.slice(0, -1));
366
- setSelectedId(null);
367
- };
368
- const compositeToCanvas = async () => {
369
- const c = canvasRef.current;
370
- const svg = svgRef.current;
371
- if (!c || !svg || !imageSize) return null;
372
- const out = document.createElement("canvas");
373
- out.width = imageSize.w;
374
- out.height = imageSize.h;
375
- const octx = out.getContext("2d");
376
- octx.drawImage(c, 0, 0);
377
- const clone = svg.cloneNode(true);
378
- clone.querySelectorAll("[data-chrome]").forEach((n) => n.remove());
379
- const xml = new XMLSerializer().serializeToString(clone);
380
- const svgBlob = new Blob([xml], { type: "image/svg+xml;charset=utf-8" });
381
- const svgUrl = URL.createObjectURL(svgBlob);
382
- const svgImg = new Image();
383
- await new Promise((resolve) => {
384
- svgImg.onload = () => resolve();
385
- svgImg.onerror = () => resolve();
386
- svgImg.src = svgUrl;
387
- });
388
- octx.drawImage(svgImg, 0, 0, imageSize.w, imageSize.h);
389
- URL.revokeObjectURL(svgUrl);
390
- return out;
391
- };
392
- useImperativeHandle(ref, () => ({
393
- save: async () => {
394
- const out = await compositeToCanvas();
395
- if (!out) return;
396
- out.toBlob((blob) => {
397
- if (!blob) {
398
- toast_default.error("Failed to export");
399
- return;
400
- }
401
- const url = URL.createObjectURL(blob);
402
- const a = document.createElement("a");
403
- a.href = url;
404
- const base = filename.replace(/\.[^.]+$/, "");
405
- a.download = `${base}-annotated.png`;
406
- a.click();
407
- URL.revokeObjectURL(url);
408
- }, "image/png");
409
- },
410
- copy: async () => {
411
- if (!("clipboard" in navigator) || typeof ClipboardItem === "undefined") {
412
- toast_default.error("Clipboard images not supported in this browser");
413
- return;
414
- }
415
- const out = await compositeToCanvas();
416
- if (!out) return;
417
- out.toBlob(async (blob) => {
418
- if (!blob) {
419
- toast_default.error("Failed to copy");
420
- return;
421
- }
422
- try {
423
- await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
424
- toast_default.success("Copied to clipboard");
425
- } catch {
426
- toast_default.error("Copy failed (clipboard permission?)");
427
- }
428
- }, "image/png");
429
- }
430
- }), [imageSize, filename]);
431
- const tools = useMemo(() => [
432
- { id: "select", label: "Select / Move", icon: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 3l7 17 2-7 7-2L5 3z" }) }) },
433
- { id: "draw", label: "Pen / Draw", icon: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487z" }) }) },
434
- { id: "rect", label: "Rectangle", icon: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("rect", { x: "4", y: "6", width: "16", height: "12", rx: "3" }) }) },
435
- { id: "circle", label: "Ellipse", icon: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("ellipse", { cx: "12", cy: "12", rx: "8", ry: "6" }) }) },
436
- { id: "arrow", label: "Arrow", icon: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 19L19 5m0 0h-7m7 0v7" }) }) },
437
- { id: "mosaic", label: "Mosaic", icon: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4 4h6v6H4zM14 4h6v6h-6zM4 14h6v6H4zM14 14h6v6h-6z" }) }) },
438
- { id: "text", label: "Text", icon: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4 6h16M12 6v14M9 20h6" }) }) },
439
- { id: "crop", label: "Crop", icon: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 2v15a1 1 0 001 1h15M2 6h15a1 1 0 011 1v15" }) }) }
440
- ], []);
441
- const btnClass = (active) => `p-1.5 rounded transition-colors ${active ? "bg-blue-500 text-white" : "text-gray-700 hover:bg-gray-200"}`;
442
- const ctxType = selected?.type ?? (tool === "select" ? null : tool);
443
- const showStrokeControl = ctxType === "rect" || ctxType === "circle" || ctxType === "arrow" || ctxType === "draw";
444
- const showRectRadius = ctxType === "rect";
445
- const showTextControls = ctxType === "text";
446
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full bg-gray-100", children: [
447
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-3 py-2 border-b border-gray-200 bg-white shrink-0 flex-wrap text-[12px]", children: [
448
- tools.map((t) => /* @__PURE__ */ jsx(
449
- "button",
450
- {
451
- onClick: () => {
452
- setTool(t.id);
453
- setSelectedId(null);
454
- },
455
- title: t.label,
456
- className: btnClass(tool === t.id),
457
- children: t.icon
458
- },
459
- t.id
460
- )),
461
- /* @__PURE__ */ jsx("div", { className: "h-5 w-px bg-gray-300 mx-1" }),
462
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: COLORS.map((c) => /* @__PURE__ */ jsx(
463
- "button",
464
- {
465
- onClick: () => setColorAndApply(c),
466
- title: c,
467
- className: `h-5 w-5 rounded-full border ${color === c ? "ring-2 ring-blue-500 ring-offset-1" : "border-gray-300"}`,
468
- style: { background: c }
469
- },
470
- c
471
- )) }),
472
- showStrokeControl && /* @__PURE__ */ jsxs(Fragment, { children: [
473
- /* @__PURE__ */ jsx("div", { className: "h-5 w-px bg-gray-300 mx-1" }),
474
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-gray-600", children: [
475
- /* @__PURE__ */ jsx("span", { children: "Weight" }),
476
- /* @__PURE__ */ jsx("input", { type: "range", min: 1, max: 20, step: 1, value: stroke, onChange: (e) => setStrokeAndApply(Number(e.target.value)), className: "w-16 accent-blue-500" }),
477
- /* @__PURE__ */ jsx("span", { className: "tabular-nums w-5 text-right", children: stroke })
478
- ] })
479
- ] }),
480
- showRectRadius && /* @__PURE__ */ jsxs(Fragment, { children: [
481
- /* @__PURE__ */ jsx("div", { className: "h-5 w-px bg-gray-300 mx-1" }),
482
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-gray-600", children: [
483
- /* @__PURE__ */ jsx("span", { children: "Radius" }),
484
- /* @__PURE__ */ jsx("input", { type: "range", min: 0, max: 48, step: 1, value: rectRadius, onChange: (e) => setRectRadiusAndApply(Number(e.target.value)), className: "w-16 accent-blue-500" }),
485
- /* @__PURE__ */ jsx("span", { className: "tabular-nums w-6 text-right", children: rectRadius })
486
- ] })
487
- ] }),
488
- showTextControls && /* @__PURE__ */ jsxs(Fragment, { children: [
489
- /* @__PURE__ */ jsx("div", { className: "h-5 w-px bg-gray-300 mx-1" }),
490
- /* @__PURE__ */ jsx("select", { value: textFont, onChange: (e) => setTextFontAndApply(e.target.value), className: "text-xs border border-gray-300 rounded px-1 py-0.5 bg-white", children: FONTS.map((f) => /* @__PURE__ */ jsx("option", { value: f.id, children: f.label }, f.id)) }),
491
- /* @__PURE__ */ jsx("button", { onClick: () => toggleTextStyleAndApply("bold"), className: btnClass(textBold), title: "Bold", children: /* @__PURE__ */ jsx("span", { className: "font-bold", children: "B" }) }),
492
- /* @__PURE__ */ jsx("button", { onClick: () => toggleTextStyleAndApply("italic"), className: btnClass(textItalic), title: "Italic", children: /* @__PURE__ */ jsx("span", { className: "italic", children: "I" }) }),
493
- /* @__PURE__ */ jsx("button", { onClick: () => toggleTextStyleAndApply("underline"), className: btnClass(textUnderline), title: "Underline", children: /* @__PURE__ */ jsx("span", { className: "underline", children: "U" }) }),
494
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-gray-600", children: [
495
- /* @__PURE__ */ jsx("span", { children: "Size" }),
496
- /* @__PURE__ */ jsx("input", { type: "range", min: 10, max: 96, step: 1, value: textSize, onChange: (e) => setTextSizeAndApply(Number(e.target.value)), className: "w-16 accent-blue-500" }),
497
- /* @__PURE__ */ jsx("span", { className: "tabular-nums w-7 text-right", children: textSize })
498
- ] })
499
- ] }),
500
- /* @__PURE__ */ jsx("div", { className: "h-5 w-px bg-gray-300 mx-1" }),
501
- /* @__PURE__ */ jsx("button", { onClick: () => setZoom((z) => Math.max(ZOOM_MIN, Math.round((z - ZOOM_STEP) * 100) / 100)), className: "px-2 py-1 rounded hover:bg-gray-200 text-gray-700", title: "Zoom out", children: "\u2212" }),
502
- /* @__PURE__ */ jsxs("span", { className: "text-gray-600 tabular-nums w-10 text-center", children: [
503
- Math.round(zoom * 100),
504
- "%"
505
- ] }),
506
- /* @__PURE__ */ jsx("button", { onClick: () => setZoom((z) => Math.min(ZOOM_MAX, Math.round((z + ZOOM_STEP) * 100) / 100)), className: "px-2 py-1 rounded hover:bg-gray-200 text-gray-700", title: "Zoom in", children: "+" }),
507
- /* @__PURE__ */ jsx("button", { onClick: () => setZoom(1), className: "px-2 py-1 rounded hover:bg-gray-200 text-gray-700", title: "Fit to area", children: "Fit" }),
508
- /* @__PURE__ */ jsx("div", { className: "h-5 w-px bg-gray-300 mx-1" }),
509
- /* @__PURE__ */ jsx("button", { onClick: undoLast, disabled: annotations.length === 0, className: "px-2 py-1 rounded hover:bg-gray-200 disabled:opacity-30 text-gray-700", children: "Undo" }),
510
- /* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center gap-2", children: pendingCrop && /* @__PURE__ */ jsxs(Fragment, { children: [
511
- /* @__PURE__ */ jsx("button", { onClick: applyCrop, className: "px-2 py-1 rounded bg-blue-500 text-white hover:bg-blue-600", children: "Apply Crop" }),
512
- /* @__PURE__ */ jsx("button", { onClick: cancelCrop, className: "px-2 py-1 rounded hover:bg-gray-200 text-gray-700", children: "Cancel" })
513
- ] }) })
514
- ] }),
515
- /* @__PURE__ */ jsx("div", { ref: wrapRef, className: "flex-1 overflow-auto bg-gray-200 flex items-center justify-center p-4 relative", children: displaySize && imageSize && /* @__PURE__ */ jsxs(
516
- "div",
517
- {
518
- className: "relative shadow-lg rounded overflow-hidden bg-white shrink-0",
519
- style: { width: displaySize.w, height: displaySize.h },
520
- children: [
521
- /* @__PURE__ */ jsx("canvas", { ref: canvasRef, style: { position: "absolute", inset: 0, width: "100%", height: "100%", display: "block" } }),
522
- /* @__PURE__ */ jsxs(
523
- "svg",
524
- {
525
- ref: svgRef,
526
- viewBox: `0 0 ${imageSize.w} ${imageSize.h}`,
527
- preserveAspectRatio: "none",
528
- style: {
529
- position: "absolute",
530
- inset: 0,
531
- width: "100%",
532
- height: "100%",
533
- touchAction: "none",
534
- cursor: tool === "select" ? "default" : tool === "text" ? "text" : "crosshair"
535
- },
536
- onPointerDown: handleSvgPointerDown,
537
- children: [
538
- annotations.map((a) => /* @__PURE__ */ jsx(
539
- AnnotationView,
540
- {
541
- anno: a,
542
- selected: selectedId === a.id,
543
- zoom,
544
- onPointerDown: (e) => handleAnnoPointerDown(e, a),
545
- onDoubleClick: () => handleAnnoDoubleClick(a),
546
- onHandlePointerDown: (e, corner) => handleHandlePointerDown(e, a, corner)
547
- },
548
- a.id
549
- )),
550
- preview && /* @__PURE__ */ jsx(AnnotationView, { anno: { ...preview, id: "__preview" }, preview: true }),
551
- pendingCrop && /* @__PURE__ */ jsx(CropOverlay, { rect: pendingCrop, imageSize })
552
- ]
553
- }
554
- ),
555
- pendingText && /* @__PURE__ */ jsx(
556
- PendingTextEditor,
557
- {
558
- pendingText,
559
- color,
560
- size: textSize,
561
- font: FONTS.find((f) => f.id === textFont)?.css ?? FONTS[0].css,
562
- bold: textBold,
563
- italic: textItalic,
564
- underline: textUnderline,
565
- scale,
566
- onChange: (value) => setPendingText({ ...pendingText, value }),
567
- onCommit: commitText,
568
- onCancel: () => setPendingText(null)
569
- }
570
- )
571
- ]
572
- }
573
- ) }),
574
- /* @__PURE__ */ jsx("div", { className: "px-3 py-1.5 border-t border-gray-200 bg-white text-[11px] text-gray-500 shrink-0", children: tool === "select" ? selectedId ? "Drag to move. Drag a corner to resize. Delete / Backspace removes. Click outside to deselect." : "Tap a shape to select. Double-click text to edit." : tool === "text" ? "Click to drop a text label." : tool === "crop" ? "Drag a rectangle to set the crop region." : tool === "draw" ? "Drag to draw freehand." : "Drag on the image to draw." })
575
- ] });
576
- });
577
- var ImageAnnotator_default = ImageAnnotator;
578
- function AnnotationView({ anno, selected, preview, zoom = 1, onPointerDown, onDoubleClick, onHandlePointerDown }) {
579
- const dim = boundingBox(anno);
580
- const interactive = onPointerDown ? { onPointerDown, pointerEvents: "all", style: { cursor: "move" } } : {};
581
- const dblc = onDoubleClick ? { onDoubleClick } : {};
582
- let body;
583
- if (anno.type === "rect") {
584
- body = /* @__PURE__ */ jsx(
585
- "rect",
586
- {
587
- x: anno.x,
588
- y: anno.y,
589
- width: anno.w,
590
- height: anno.h,
591
- rx: anno.radius,
592
- ry: anno.radius,
593
- fill: "none",
594
- stroke: anno.color,
595
- strokeWidth: anno.stroke,
596
- strokeLinecap: "round",
597
- strokeLinejoin: "round",
598
- ...interactive
599
- }
600
- );
601
- } else if (anno.type === "circle") {
602
- body = /* @__PURE__ */ jsx(
603
- "ellipse",
604
- {
605
- cx: anno.x + anno.w / 2,
606
- cy: anno.y + anno.h / 2,
607
- rx: anno.w / 2,
608
- ry: anno.h / 2,
609
- fill: "none",
610
- stroke: anno.color,
611
- strokeWidth: anno.stroke,
612
- ...interactive
613
- }
614
- );
615
- } else if (anno.type === "arrow") {
616
- body = /* @__PURE__ */ jsx(ArrowShape, { anno, interactive });
617
- } else if (anno.type === "draw") {
618
- body = /* @__PURE__ */ jsx(
619
- "path",
620
- {
621
- d: pointsToPath(anno.points),
622
- fill: "none",
623
- stroke: anno.color,
624
- strokeWidth: anno.stroke,
625
- strokeLinecap: "round",
626
- strokeLinejoin: "round",
627
- ...interactive
628
- }
629
- );
630
- } else if (anno.type === "mosaic") {
631
- body = /* @__PURE__ */ jsx(
632
- "rect",
633
- {
634
- x: anno.x,
635
- y: anno.y,
636
- width: anno.w,
637
- height: anno.h,
638
- fill: "rgba(0,0,0,0.001)",
639
- stroke: selected ? "#3b82f6" : "none",
640
- strokeWidth: selected ? 2 : 0,
641
- strokeDasharray: selected ? "6,4" : void 0,
642
- ...interactive
643
- }
644
- );
645
- } else {
646
- const font = FONTS.find((f) => f.id === anno.font)?.css ?? FONTS[0].css;
647
- body = /* @__PURE__ */ jsx(
648
- "text",
649
- {
650
- x: anno.x,
651
- y: anno.y + anno.size,
652
- fill: anno.color,
653
- fontSize: anno.size,
654
- fontWeight: anno.bold ? 700 : 400,
655
- fontStyle: anno.italic ? "italic" : "normal",
656
- textDecoration: anno.underline ? "underline" : void 0,
657
- fontFamily: font,
658
- style: { userSelect: "none" },
659
- ...interactive,
660
- ...dblc,
661
- children: anno.text.split("\n").map((line, i) => /* @__PURE__ */ jsx("tspan", { x: anno.x, dy: i === 0 ? 0 : anno.size * 1.2, children: line }, i))
662
- }
663
- );
664
- }
665
- return /* @__PURE__ */ jsxs("g", { children: [
666
- body,
667
- selected && !preview && /* @__PURE__ */ jsxs(Fragment, { children: [
668
- /* @__PURE__ */ jsx(
669
- "rect",
670
- {
671
- "data-chrome": "selection",
672
- x: dim.x - 4,
673
- y: dim.y - 4,
674
- width: dim.w + 8,
675
- height: dim.h + 8,
676
- fill: "none",
677
- stroke: "#3b82f6",
678
- strokeWidth: 2 / zoom,
679
- strokeDasharray: `${6 / zoom},${4 / zoom}`,
680
- pointerEvents: "none"
681
- }
682
- ),
683
- onHandlePointerDown && /* @__PURE__ */ jsx(ResizeHandles, { anno, zoom, onHandlePointerDown })
684
- ] })
685
- ] });
686
- }
687
- function ResizeHandles({
688
- anno,
689
- zoom,
690
- onHandlePointerDown
691
- }) {
692
- const r = 6 / zoom;
693
- const sw = 1.5 / zoom;
694
- const handleProps = (cursor) => ({
695
- fill: "#fff",
696
- stroke: "#3b82f6",
697
- strokeWidth: sw,
698
- pointerEvents: "all",
699
- style: { cursor },
700
- "data-chrome": "handle"
701
- });
702
- if (anno.type === "arrow") {
703
- return /* @__PURE__ */ jsxs(Fragment, { children: [
704
- /* @__PURE__ */ jsx("circle", { cx: anno.x1, cy: anno.y1, r, ...handleProps("grab"), onPointerDown: (e) => onHandlePointerDown(e, "start") }),
705
- /* @__PURE__ */ jsx("circle", { cx: anno.x2, cy: anno.y2, r, ...handleProps("grab"), onPointerDown: (e) => onHandlePointerDown(e, "end") })
706
- ] });
707
- }
708
- if (anno.type === "text" || anno.type === "draw") {
709
- return null;
710
- }
711
- const x = anno.x, y = anno.y, w = anno.w, h = anno.h;
712
- return /* @__PURE__ */ jsxs(Fragment, { children: [
713
- /* @__PURE__ */ jsx("circle", { cx: x, cy: y, r, ...handleProps("nwse-resize"), onPointerDown: (e) => onHandlePointerDown(e, "nw") }),
714
- /* @__PURE__ */ jsx("circle", { cx: x + w, cy: y, r, ...handleProps("nesw-resize"), onPointerDown: (e) => onHandlePointerDown(e, "ne") }),
715
- /* @__PURE__ */ jsx("circle", { cx: x + w, cy: y + h, r, ...handleProps("nwse-resize"), onPointerDown: (e) => onHandlePointerDown(e, "se") }),
716
- /* @__PURE__ */ jsx("circle", { cx: x, cy: y + h, r, ...handleProps("nesw-resize"), onPointerDown: (e) => onHandlePointerDown(e, "sw") })
717
- ] });
718
- }
719
- function ArrowShape({ anno, interactive }) {
720
- const headLen = Math.max(12, anno.stroke * 4);
721
- const angle = Math.atan2(anno.y2 - anno.y1, anno.x2 - anno.x1);
722
- const a1 = angle + Math.PI - Math.PI / 7;
723
- const a2 = angle + Math.PI + Math.PI / 7;
724
- const head = `M${anno.x2},${anno.y2} L${anno.x2 + headLen * Math.cos(a1)},${anno.y2 + headLen * Math.sin(a1)} L${anno.x2 + headLen * Math.cos(a2)},${anno.y2 + headLen * Math.sin(a2)} Z`;
725
- return /* @__PURE__ */ jsxs("g", { ...interactive, children: [
726
- /* @__PURE__ */ jsx(
727
- "line",
728
- {
729
- x1: anno.x1,
730
- y1: anno.y1,
731
- x2: anno.x2,
732
- y2: anno.y2,
733
- stroke: anno.color,
734
- strokeWidth: anno.stroke,
735
- strokeLinecap: "round"
736
- }
737
- ),
738
- /* @__PURE__ */ jsx("path", { d: head, fill: anno.color })
739
- ] });
740
- }
741
- function CropOverlay({ rect, imageSize }) {
742
- return /* @__PURE__ */ jsxs("g", { pointerEvents: "none", children: [
743
- /* @__PURE__ */ jsx(
744
- "path",
745
- {
746
- d: `M0,0 H${imageSize.w} V${imageSize.h} H0 Z M${rect.x},${rect.y} V${rect.y + rect.h} H${rect.x + rect.w} V${rect.y} Z`,
747
- fill: "rgba(0,0,0,0.45)",
748
- fillRule: "evenodd"
749
- }
750
- ),
751
- /* @__PURE__ */ jsx(
752
- "rect",
753
- {
754
- x: rect.x,
755
- y: rect.y,
756
- width: rect.w,
757
- height: rect.h,
758
- fill: "none",
759
- stroke: "#fff",
760
- strokeWidth: 2,
761
- strokeDasharray: "8,6"
762
- }
763
- )
764
- ] });
765
- }
766
- function PendingTextEditor({
767
- pendingText,
768
- color,
769
- size,
770
- font,
771
- bold,
772
- italic,
773
- underline,
774
- scale,
775
- onChange,
776
- onCommit,
777
- onCancel
778
- }) {
779
- const ref = useRef(null);
780
- useEffect(() => {
781
- const id = requestAnimationFrame(() => {
782
- ref.current?.focus();
783
- ref.current?.select?.();
784
- });
785
- return () => cancelAnimationFrame(id);
786
- }, []);
787
- return /* @__PURE__ */ jsx(
788
- "div",
789
- {
790
- style: {
791
- position: "absolute",
792
- left: `${pendingText.x * scale}px`,
793
- top: `${pendingText.y * scale}px`,
794
- transform: "translateY(-2px)",
795
- zIndex: 5
796
- },
797
- onPointerDown: (e) => e.stopPropagation(),
798
- children: /* @__PURE__ */ jsx(
799
- "textarea",
800
- {
801
- ref,
802
- value: pendingText.value,
803
- onChange: (e) => onChange(e.target.value),
804
- onBlur: onCommit,
805
- onKeyDown: (e) => {
806
- if (e.key === "Escape") {
807
- e.preventDefault();
808
- onCancel();
809
- } else if (e.key === "Enter" && !e.shiftKey) {
810
- e.preventDefault();
811
- onCommit();
812
- }
813
- },
814
- placeholder: "Type then Enter\u2026",
815
- rows: 1,
816
- className: "bg-white/95 border border-blue-400 rounded px-1 py-0.5 outline-none resize-none shadow-md",
817
- style: {
818
- color,
819
- fontSize: `${size * scale}px`,
820
- fontFamily: font,
821
- fontWeight: bold ? 700 : 400,
822
- fontStyle: italic ? "italic" : "normal",
823
- textDecoration: underline ? "underline" : void 0,
824
- minWidth: 80
825
- }
826
- }
827
- )
828
- }
829
- );
830
- }
831
- function makeShape(tool, start, end, color, stroke, rectRadius) {
832
- if (tool === "rect") {
833
- const r = normalizeRect(start, end);
834
- return { id: "", type: "rect", x: r.x, y: r.y, w: r.w, h: r.h, color, stroke, radius: rectRadius };
835
- }
836
- if (tool === "circle") {
837
- const r = normalizeRect(start, end);
838
- return { id: "", type: "circle", x: r.x, y: r.y, w: r.w, h: r.h, color, stroke };
839
- }
840
- if (tool === "arrow") {
841
- return { id: "", type: "arrow", x1: start.x, y1: start.y, x2: end.x, y2: end.y, color, stroke };
842
- }
843
- if (tool === "mosaic") {
844
- const r = normalizeRect(start, end);
845
- return { id: "", type: "mosaic", x: r.x, y: r.y, w: r.w, h: r.h };
846
- }
847
- return null;
848
- }
849
- function isTrivial(a) {
850
- if (a.type === "arrow") return Math.abs(a.x2 - a.x1) < 4 && Math.abs(a.y2 - a.y1) < 4;
851
- if (a.type === "draw") return a.points.length < 2;
852
- if ("w" in a && "h" in a) return a.w < 4 || a.h < 4;
853
- return false;
854
- }
855
- function translate(a, dx, dy) {
856
- if (a.type === "arrow") return { ...a, x1: a.x1 + dx, y1: a.y1 + dy, x2: a.x2 + dx, y2: a.y2 + dy };
857
- if (a.type === "draw") return { ...a, points: a.points.map((p) => ({ x: p.x + dx, y: p.y + dy })) };
858
- if (a.type === "text") return { ...a, x: a.x + dx, y: a.y + dy };
859
- return { ...a, x: a.x + dx, y: a.y + dy };
860
- }
861
- function resize(original, corner, p) {
862
- if (original.type === "arrow") {
863
- if (corner === "start") return { ...original, x1: p.x, y1: p.y };
864
- if (corner === "end") return { ...original, x2: p.x, y2: p.y };
865
- return original;
866
- }
867
- if (original.type === "text" || original.type === "draw") return original;
868
- const left = original.x;
869
- const right = original.x + original.w;
870
- const top = original.y;
871
- const bottom = original.y + original.h;
872
- let x1 = left, y1 = top, x2 = right, y2 = bottom;
873
- if (corner === "nw") {
874
- x1 = p.x;
875
- y1 = p.y;
876
- }
877
- if (corner === "ne") {
878
- x2 = p.x;
879
- y1 = p.y;
880
- }
881
- if (corner === "se") {
882
- x2 = p.x;
883
- y2 = p.y;
884
- }
885
- if (corner === "sw") {
886
- x1 = p.x;
887
- y2 = p.y;
888
- }
889
- const r = normalizeRect({ x: x1, y: y1 }, { x: x2, y: y2 });
890
- return { ...original, x: r.x, y: r.y, w: r.w, h: r.h };
891
- }
892
- function boundingBox(a) {
893
- if (a.type === "arrow") {
894
- const x = Math.min(a.x1, a.x2);
895
- const y = Math.min(a.y1, a.y2);
896
- return { x, y, w: Math.abs(a.x2 - a.x1), h: Math.abs(a.y2 - a.y1) };
897
- }
898
- if (a.type === "draw") {
899
- if (a.points.length === 0) return { x: 0, y: 0, w: 0, h: 0 };
900
- let minX = a.points[0].x, maxX = a.points[0].x, minY = a.points[0].y, maxY = a.points[0].y;
901
- for (const p of a.points) {
902
- if (p.x < minX) minX = p.x;
903
- if (p.x > maxX) maxX = p.x;
904
- if (p.y < minY) minY = p.y;
905
- if (p.y > maxY) maxY = p.y;
906
- }
907
- return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
908
- }
909
- if (a.type === "text") {
910
- const lines = a.text.split("\n");
911
- const longest = lines.reduce((m, l) => Math.max(m, l.length), 0);
912
- return { x: a.x, y: a.y, w: longest * a.size * 0.55, h: lines.length * a.size * 1.2 };
913
- }
914
- return { x: a.x, y: a.y, w: a.w, h: a.h };
915
- }
916
- function normalizeRect(a, b) {
917
- const x = Math.min(a.x, b.x);
918
- const y = Math.min(a.y, b.y);
919
- const w = Math.abs(a.x - b.x);
920
- const h = Math.abs(a.y - b.y);
921
- return { x, y, w, h };
922
- }
923
- function withinBounds(a, w, h) {
924
- const bb = boundingBox(a);
925
- return bb.x + bb.w > 0 && bb.y + bb.h > 0 && bb.x < w && bb.y < h;
926
- }
927
- function pointsToPath(points) {
928
- if (points.length === 0) return "";
929
- let d = `M ${points[0].x} ${points[0].y}`;
930
- for (let i = 1; i < points.length; i++) d += ` L ${points[i].x} ${points[i].y}`;
931
- return d;
932
- }
933
- function applyMosaic(ctx, rect) {
934
- const x = Math.round(Math.max(0, rect.x));
935
- const y = Math.round(Math.max(0, rect.y));
936
- const w = Math.round(Math.min(ctx.canvas.width - x, rect.w));
937
- const h = Math.round(Math.min(ctx.canvas.height - y, rect.h));
938
- if (w < 2 || h < 2) return;
939
- const data = ctx.getImageData(x, y, w, h);
940
- const block = MOSAIC_BLOCK;
941
- for (let by = 0; by < h; by += block) {
942
- for (let bx = 0; bx < w; bx += block) {
943
- let r = 0, g = 0, b = 0, a = 0, n = 0;
944
- const blockW = Math.min(block, w - bx);
945
- const blockH = Math.min(block, h - by);
946
- for (let yy = 0; yy < blockH; yy++) {
947
- for (let xx = 0; xx < blockW; xx++) {
948
- const i = ((by + yy) * w + (bx + xx)) * 4;
949
- r += data.data[i];
950
- g += data.data[i + 1];
951
- b += data.data[i + 2];
952
- a += data.data[i + 3];
953
- n++;
954
- }
955
- }
956
- r = Math.round(r / n);
957
- g = Math.round(g / n);
958
- b = Math.round(b / n);
959
- a = Math.round(a / n);
960
- for (let yy = 0; yy < blockH; yy++) {
961
- for (let xx = 0; xx < blockW; xx++) {
962
- const i = ((by + yy) * w + (bx + xx)) * 4;
963
- data.data[i] = r;
964
- data.data[i + 1] = g;
965
- data.data[i + 2] = b;
966
- data.data[i + 3] = a;
967
- }
968
- }
969
- }
970
- }
971
- ctx.putImageData(data, x, y);
972
- }
973
9
  var ToolbarSlotContext = createContext(null);
974
10
  function PanelActions({ children }) {
975
11
  const slot = useContext(ToolbarSlotContext);
@@ -2410,5 +1446,5 @@ function ImagePanel({ url, filename, onDownload, onEmail }) {
2410
1446
  }
2411
1447
 
2412
1448
  export { Preview, setPdfPreview };
2413
- //# sourceMappingURL=chunk-VOD2AMU4.js.map
2414
- //# sourceMappingURL=chunk-VOD2AMU4.js.map
1449
+ //# sourceMappingURL=chunk-IY7JJVHR.js.map
1450
+ //# sourceMappingURL=chunk-IY7JJVHR.js.map