react-os-shell 0.2.27 → 0.2.32

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.
Files changed (48) hide show
  1. package/dist/{Browser-FZTIGZJH.js → Browser-5ZCLRIRU.js} +3 -3
  2. package/dist/{Browser-FZTIGZJH.js.map → Browser-5ZCLRIRU.js.map} +1 -1
  3. package/dist/{Calculator-4J7SP6HW.js → Calculator-OHL2AZ52.js} +4 -4
  4. package/dist/{Calculator-4J7SP6HW.js.map → Calculator-OHL2AZ52.js.map} +1 -1
  5. package/dist/{Calendar-QCQJEO2E.js → Calendar-6AHL3UJY.js} +3 -3
  6. package/dist/{Calendar-QCQJEO2E.js.map → Calendar-6AHL3UJY.js.map} +1 -1
  7. package/dist/{CurrencyConverter-WP6KZMEU.js → CurrencyConverter-XZVZ7XOF.js} +4 -4
  8. package/dist/{CurrencyConverter-WP6KZMEU.js.map → CurrencyConverter-XZVZ7XOF.js.map} +1 -1
  9. package/dist/{Documents-SWUWWRRB.js → Documents-IPVZ47JX.js} +3 -3
  10. package/dist/{Documents-SWUWWRRB.js.map → Documents-IPVZ47JX.js.map} +1 -1
  11. package/dist/{Email-ERTPCJOX.js → Email-Z7FDKAFD.js} +4 -4
  12. package/dist/{Email-ERTPCJOX.js.map → Email-Z7FDKAFD.js.map} +1 -1
  13. package/dist/Files-KEHRZ2UY.js +8 -0
  14. package/dist/{Files-3ZGMJXTR.js.map → Files-KEHRZ2UY.js.map} +1 -1
  15. package/dist/ImageAnnotator-CTTMAY5Z.js +4 -0
  16. package/dist/ImageAnnotator-CTTMAY5Z.js.map +1 -0
  17. package/dist/{Minesweeper-WVKECHOL.js → Minesweeper-NGN4O6C4.js} +3 -3
  18. package/dist/{Minesweeper-WVKECHOL.js.map → Minesweeper-NGN4O6C4.js.map} +1 -1
  19. package/dist/{Notepad-I5QTIZDV.js → Notepad-W3YYZ3GS.js} +3 -3
  20. package/dist/{Notepad-I5QTIZDV.js.map → Notepad-W3YYZ3GS.js.map} +1 -1
  21. package/dist/{PomodoroTimer-DGAITQZR.js → PomodoroTimer-T2J5NDJR.js} +4 -4
  22. package/dist/{PomodoroTimer-DGAITQZR.js.map → PomodoroTimer-T2J5NDJR.js.map} +1 -1
  23. package/dist/Preview-4354N46U.js +7 -0
  24. package/dist/{Preview-S7AGTRVP.js.map → Preview-4354N46U.js.map} +1 -1
  25. package/dist/{Spreadsheet-R2VRFGAS.js → Spreadsheet-FCFII6DW.js} +3 -3
  26. package/dist/{Spreadsheet-R2VRFGAS.js.map → Spreadsheet-FCFII6DW.js.map} +1 -1
  27. package/dist/{Weather-KZPNE4TY.js → Weather-JZHYW5XF.js} +4 -4
  28. package/dist/{Weather-KZPNE4TY.js.map → Weather-JZHYW5XF.js.map} +1 -1
  29. package/dist/apps/index.js +17 -16
  30. package/dist/apps/index.js.map +1 -1
  31. package/dist/{chunk-3DJAOGL7.js → chunk-2SRU4BYH.js} +4 -4
  32. package/dist/{chunk-3DJAOGL7.js.map → chunk-2SRU4BYH.js.map} +1 -1
  33. package/dist/{chunk-6PX6HT6T.js → chunk-IY7JJVHR.js} +6 -970
  34. package/dist/chunk-IY7JJVHR.js.map +1 -0
  35. package/dist/chunk-KUIPWCTJ.js +999 -0
  36. package/dist/chunk-KUIPWCTJ.js.map +1 -0
  37. package/dist/{chunk-I7GRSFIL.js → chunk-T2NQXP2J.js} +27 -5
  38. package/dist/chunk-T2NQXP2J.js.map +1 -0
  39. package/dist/{chunk-EQ3MNYLX.js → chunk-TVOBLSSV.js} +3 -3
  40. package/dist/{chunk-EQ3MNYLX.js.map → chunk-TVOBLSSV.js.map} +1 -1
  41. package/dist/index.d.ts +12 -3
  42. package/dist/index.js +131 -31
  43. package/dist/index.js.map +1 -1
  44. package/package.json +1 -1
  45. package/dist/Files-3ZGMJXTR.js +0 -7
  46. package/dist/Preview-S7AGTRVP.js +0 -6
  47. package/dist/chunk-6PX6HT6T.js.map +0 -1
  48. package/dist/chunk-I7GRSFIL.js.map +0 -1
@@ -1,975 +1,11 @@
1
+ import { ImageAnnotator_default } from './chunk-KUIPWCTJ.js';
1
2
  import { toast_default } from './chunk-WIJ45SYD.js';
2
- import { WindowTitle, getActiveModalId } from './chunk-I7GRSFIL.js';
3
- import { forwardRef, useRef, useState, useMemo, useEffect, useImperativeHandle, createContext, useContext } from 'react';
3
+ import { WindowTitle, getActiveModalId } from './chunk-T2NQXP2J.js';
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-6PX6HT6T.js.map
2414
- //# sourceMappingURL=chunk-6PX6HT6T.js.map
1449
+ //# sourceMappingURL=chunk-IY7JJVHR.js.map
1450
+ //# sourceMappingURL=chunk-IY7JJVHR.js.map