@elixpo/lixsketch 5.4.7 → 5.5.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.
Files changed (218) hide show
  1. package/dist/react/AIRenderer-M4LMJ66J.js +1105 -0
  2. package/dist/react/AIRenderer-M4LMJ66J.js.map +7 -0
  3. package/dist/react/Arrow-BJDAXPMH.js +8 -0
  4. package/dist/react/Arrow-BJDAXPMH.js.map +7 -0
  5. package/dist/react/Circle-KPG7TKIX.js +8 -0
  6. package/dist/react/Circle-KPG7TKIX.js.map +7 -0
  7. package/dist/react/CodeShape-K4DIZAIV.js +8 -0
  8. package/dist/react/CodeShape-K4DIZAIV.js.map +7 -0
  9. package/dist/react/CopyPaste-4CKJRHJ3.js +569 -0
  10. package/dist/react/CopyPaste-4CKJRHJ3.js.map +7 -0
  11. package/dist/react/EraserTrail-PA6IQLQN.js +20 -0
  12. package/dist/react/EraserTrail-PA6IQLQN.js.map +7 -0
  13. package/dist/react/EventDispatcher-SRFH6LQZ.js +463 -0
  14. package/dist/react/EventDispatcher-SRFH6LQZ.js.map +7 -0
  15. package/dist/react/Frame-6UR5XPAX.js +10 -0
  16. package/dist/react/Frame-6UR5XPAX.js.map +7 -0
  17. package/dist/react/FreehandStroke-CK24NUT3.js +8 -0
  18. package/dist/react/FreehandStroke-CK24NUT3.js.map +7 -0
  19. package/dist/react/GraphEngine-QE5B2PMR.js +340 -0
  20. package/dist/react/GraphEngine-QE5B2PMR.js.map +7 -0
  21. package/dist/react/GraphMathParser-2WMTY4IE.js +10 -0
  22. package/dist/react/GraphMathParser-2WMTY4IE.js.map +7 -0
  23. package/dist/react/IconShape-KMIA6T2H.js +10 -0
  24. package/dist/react/IconShape-KMIA6T2H.js.map +7 -0
  25. package/dist/react/ImageShape-CHHGBXUQ.js +10 -0
  26. package/dist/react/ImageShape-CHHGBXUQ.js.map +7 -0
  27. package/dist/react/LayerOrder-XCHF4MSS.js +66 -0
  28. package/dist/react/LayerOrder-XCHF4MSS.js.map +7 -0
  29. package/dist/react/Line-2KOQEEGA.js +10 -0
  30. package/dist/react/Line-2KOQEEGA.js.map +7 -0
  31. package/dist/react/LixScriptParser-JYWJX375.js +1033 -0
  32. package/dist/react/LixScriptParser-JYWJX375.js.map +7 -0
  33. package/dist/react/MermaidFlowchartRenderer-4QBAZ5MB.js +412 -0
  34. package/dist/react/MermaidFlowchartRenderer-4QBAZ5MB.js.map +7 -0
  35. package/dist/react/MermaidSequenceParser-UKG2NSZY.js +8 -0
  36. package/dist/react/MermaidSequenceParser-UKG2NSZY.js.map +7 -0
  37. package/dist/react/MermaidSequenceRenderer-HPLHY5II.js +387 -0
  38. package/dist/react/MermaidSequenceRenderer-HPLHY5II.js.map +7 -0
  39. package/dist/react/Rectangle-6FE7RN6W.js +8 -0
  40. package/dist/react/Rectangle-6FE7RN6W.js.map +7 -0
  41. package/dist/react/ResizeCode-WAMDTKSW.js +2 -0
  42. package/dist/react/ResizeCode-WAMDTKSW.js.map +7 -0
  43. package/dist/react/ResizeShapes-LKEQTITP.js +250 -0
  44. package/dist/react/ResizeShapes-LKEQTITP.js.map +7 -0
  45. package/dist/react/SceneSerializer-4U4B24JM.js +739 -0
  46. package/dist/react/SceneSerializer-4U4B24JM.js.map +7 -0
  47. package/dist/react/Selection-VVUHY5YJ.js +34 -0
  48. package/dist/react/Selection-VVUHY5YJ.js.map +7 -0
  49. package/dist/react/SketchEngine-YA4RG3KX.js +413 -0
  50. package/dist/react/SketchEngine-YA4RG3KX.js.map +7 -0
  51. package/dist/react/TextShape-54ZJ66E3.js +8 -0
  52. package/dist/react/TextShape-54ZJ66E3.js.map +7 -0
  53. package/dist/react/UndoRedo-M2JZUBKN.js +30 -0
  54. package/dist/react/UndoRedo-M2JZUBKN.js.map +7 -0
  55. package/dist/react/ZoomPan-U3TXC5AJ.js +186 -0
  56. package/dist/react/ZoomPan-U3TXC5AJ.js.map +7 -0
  57. package/dist/react/arrowTool-CWEMIRXK.js +17 -0
  58. package/dist/react/arrowTool-CWEMIRXK.js.map +7 -0
  59. package/dist/react/boxicons-5JNJQPUP.woff +0 -0
  60. package/dist/react/boxicons-6NVWQSCQ.eot +0 -0
  61. package/dist/react/boxicons-AQ5N6WP7.woff2 +0 -0
  62. package/dist/react/boxicons-GDR4JU3X.ttf +0 -0
  63. package/dist/react/boxicons-NNFHLMOR.svg +1660 -0
  64. package/dist/react/chunk-2OQXI7QB.js +564 -0
  65. package/dist/react/chunk-2OQXI7QB.js.map +7 -0
  66. package/dist/react/chunk-5OUBTHHO.js +187 -0
  67. package/dist/react/chunk-5OUBTHHO.js.map +7 -0
  68. package/dist/react/chunk-6PFXZKL2.js +628 -0
  69. package/dist/react/chunk-6PFXZKL2.js.map +7 -0
  70. package/dist/react/chunk-6SFNWGGZ.js +453 -0
  71. package/dist/react/chunk-6SFNWGGZ.js.map +7 -0
  72. package/dist/react/chunk-7TZSIR6V.js +210 -0
  73. package/dist/react/chunk-7TZSIR6V.js.map +7 -0
  74. package/dist/react/chunk-AJAZC37E.js +662 -0
  75. package/dist/react/chunk-AJAZC37E.js.map +7 -0
  76. package/dist/react/chunk-BU4D2WYQ.js +1213 -0
  77. package/dist/react/chunk-BU4D2WYQ.js.map +7 -0
  78. package/dist/react/chunk-BZE4CEBG.js +747 -0
  79. package/dist/react/chunk-BZE4CEBG.js.map +7 -0
  80. package/dist/react/chunk-CB2RANCB.js +212 -0
  81. package/dist/react/chunk-CB2RANCB.js.map +7 -0
  82. package/dist/react/chunk-DNAG7ML6.js +128 -0
  83. package/dist/react/chunk-DNAG7ML6.js.map +7 -0
  84. package/dist/react/chunk-HZWLPXFN.js +198 -0
  85. package/dist/react/chunk-HZWLPXFN.js.map +7 -0
  86. package/dist/react/chunk-I6HCCIXB.js +572 -0
  87. package/dist/react/chunk-I6HCCIXB.js.map +7 -0
  88. package/dist/react/chunk-IZ5OGWGW.js +235 -0
  89. package/dist/react/chunk-IZ5OGWGW.js.map +7 -0
  90. package/dist/react/chunk-JJCJNLNN.js +1490 -0
  91. package/dist/react/chunk-JJCJNLNN.js.map +7 -0
  92. package/dist/react/chunk-JQ3IRFDP.js +222 -0
  93. package/dist/react/chunk-JQ3IRFDP.js.map +7 -0
  94. package/dist/react/chunk-L5OARZJ7.js +1134 -0
  95. package/dist/react/chunk-L5OARZJ7.js.map +7 -0
  96. package/dist/react/chunk-LHRTXNVE.js +1600 -0
  97. package/dist/react/chunk-LHRTXNVE.js.map +7 -0
  98. package/dist/react/chunk-Q2GA4MBZ.js +1653 -0
  99. package/dist/react/chunk-Q2GA4MBZ.js.map +7 -0
  100. package/dist/react/chunk-QNSE5BAY.js +519 -0
  101. package/dist/react/chunk-QNSE5BAY.js.map +7 -0
  102. package/dist/react/chunk-QXFBG7OM.js +191 -0
  103. package/dist/react/chunk-QXFBG7OM.js.map +7 -0
  104. package/dist/react/chunk-T6ZVV5ZO.js +714 -0
  105. package/dist/react/chunk-T6ZVV5ZO.js.map +7 -0
  106. package/dist/react/chunk-URB6MY46.js +974 -0
  107. package/dist/react/chunk-URB6MY46.js.map +7 -0
  108. package/dist/react/chunk-VN62KLOC.js +931 -0
  109. package/dist/react/chunk-VN62KLOC.js.map +7 -0
  110. package/dist/react/chunk-VQVEPJSA.js +158 -0
  111. package/dist/react/chunk-VQVEPJSA.js.map +7 -0
  112. package/dist/react/chunk-XISEAGBV.js +1632 -0
  113. package/dist/react/chunk-XISEAGBV.js.map +7 -0
  114. package/dist/react/chunk-YKJUBFHE.js +575 -0
  115. package/dist/react/chunk-YKJUBFHE.js.map +7 -0
  116. package/dist/react/circleTool-NUATSEI2.js +14 -0
  117. package/dist/react/circleTool-NUATSEI2.js.map +7 -0
  118. package/dist/react/codeTool-MHCB2FNB.js +38 -0
  119. package/dist/react/codeTool-MHCB2FNB.js.map +7 -0
  120. package/dist/react/eraserTool-2GS2LQCS.js +103 -0
  121. package/dist/react/eraserTool-2GS2LQCS.js.map +7 -0
  122. package/dist/react/frameTool-PWVKKXSP.js +14 -0
  123. package/dist/react/frameTool-PWVKKXSP.js.map +7 -0
  124. package/dist/react/freehandTool-SLYGASVP.js +14 -0
  125. package/dist/react/freehandTool-SLYGASVP.js.map +7 -0
  126. package/dist/react/iconTool-GPZ7O7NH.js +18 -0
  127. package/dist/react/iconTool-GPZ7O7NH.js.map +7 -0
  128. package/dist/react/imageTool-D5R4XGEU.js +15 -0
  129. package/dist/react/imageTool-D5R4XGEU.js.map +7 -0
  130. package/dist/react/index.js +5349 -0
  131. package/dist/react/index.js.map +7 -0
  132. package/dist/react/laserTool-XVMV63AV.js +239 -0
  133. package/dist/react/laserTool-XVMV63AV.js.map +7 -0
  134. package/dist/react/lineTool-WNI5EMZ5.js +14 -0
  135. package/dist/react/lineTool-WNI5EMZ5.js.map +7 -0
  136. package/dist/react/lixCode-OJMVUXXI.ttf +0 -0
  137. package/dist/react/lixDefault-JEAPAUJL.ttf +0 -0
  138. package/dist/react/lixFancy-BPXYM5CQ.ttf +0 -0
  139. package/dist/react/lixFont-CPKEUDVM.woff2 +0 -0
  140. package/dist/react/rectangleTool-U7DZ6ZTA.js +14 -0
  141. package/dist/react/rectangleTool-U7DZ6ZTA.js.map +7 -0
  142. package/dist/react/styles.css +6627 -0
  143. package/dist/react/textTool-M7S6F3VC.js +21 -0
  144. package/dist/react/textTool-M7S6F3VC.js.map +7 -0
  145. package/package.json +39 -5
  146. package/src/EngineShortcuts.js +83 -27
  147. package/src/core/AIRenderer.js +30 -18
  148. package/src/core/GraphEngine.js +64 -41
  149. package/src/core/MermaidFlowchartRenderer.js +153 -88
  150. package/src/core/MermaidSequenceRenderer.js +168 -93
  151. package/src/core/SceneSerializer.js +86 -2
  152. package/src/core/Selection.js +119 -19
  153. package/src/core/UndoRedo.js +82 -15
  154. package/src/core/ZoomPan.js +15 -5
  155. package/src/index.js +14 -1
  156. package/src/react/LixSketchCanvas.jsx +347 -0
  157. package/src/react/components/AppMenu.jsx +482 -0
  158. package/src/react/components/Footer.jsx +85 -0
  159. package/src/react/components/Toolbar.jsx +140 -0
  160. package/src/react/components/canvas/CanvasLoadingOverlay.jsx +76 -0
  161. package/src/react/components/canvas/ContextMenu.jsx +466 -0
  162. package/src/react/components/canvas/FindBar.jsx +177 -0
  163. package/src/react/components/canvas/ImageSourcePicker.jsx +133 -0
  164. package/src/react/components/canvas/MultiSelectActions.jsx +120 -0
  165. package/src/react/components/canvas/SVGCanvas.jsx +145 -0
  166. package/src/react/components/canvas/SaveStatusIndicator.jsx +29 -0
  167. package/src/react/components/modals/CanvasPropertiesModal.jsx +358 -0
  168. package/src/react/components/modals/CommandPalette.jsx +307 -0
  169. package/src/react/components/modals/ExportImageModal.jsx +287 -0
  170. package/src/react/components/modals/GraphModal.jsx +174 -0
  171. package/src/react/components/modals/HelpModal.jsx +208 -0
  172. package/src/react/components/modals/LixScriptModal.jsx +130 -0
  173. package/src/react/components/modals/ShortcutsModal.jsx +125 -0
  174. package/src/react/components/sidebars/ArrowSidebar.jsx +173 -0
  175. package/src/react/components/sidebars/CircleSidebar.jsx +134 -0
  176. package/src/react/components/sidebars/FrameSidebar.jsx +263 -0
  177. package/src/react/components/sidebars/IconSidebar.jsx +279 -0
  178. package/src/react/components/sidebars/ImageSidebar.jsx +69 -0
  179. package/src/react/components/sidebars/LineSidebar.jsx +109 -0
  180. package/src/react/components/sidebars/PaintbrushSidebar.jsx +119 -0
  181. package/src/react/components/sidebars/RectangleSidebar.jsx +165 -0
  182. package/src/react/components/sidebars/ShapeSidebar.jsx +130 -0
  183. package/src/react/components/sidebars/TextSidebar.jsx +174 -0
  184. package/src/react/hooks/inertStores.js +58 -0
  185. package/src/react/hooks/useSketchEngine.js +76 -0
  186. package/src/react/hooks/useTranslation.js +92 -0
  187. package/src/react/index.js +41 -0
  188. package/src/react/store/useSketchStore.js +234 -0
  189. package/src/react/store/useUIStore.js +196 -0
  190. package/src/react/styles/arrowSidebar.css +263 -0
  191. package/src/react/styles/canvasStroke.css +47 -0
  192. package/src/react/styles/canvasTools.css +769 -0
  193. package/src/react/styles/circleSidebar.css +206 -0
  194. package/src/react/styles/fonts/fonts.css +29 -0
  195. package/src/react/styles/fonts/lixCode.ttf +0 -0
  196. package/src/react/styles/fonts/lixDefault.ttf +0 -0
  197. package/src/react/styles/fonts/lixDocs.ttf +0 -0
  198. package/src/react/styles/fonts/lixFancy.ttf +0 -0
  199. package/src/react/styles/fonts/lixFont.woff2 +0 -0
  200. package/src/react/styles/lineSidebar.css +207 -0
  201. package/src/react/styles/paintbrushSidebar.css +247 -0
  202. package/src/react/styles/squareSidebar.css +206 -0
  203. package/src/react/styles/writeText.css +149 -0
  204. package/src/react/styles.css +175 -0
  205. package/src/react/toolsEnum.js +20 -0
  206. package/src/react/utils/toast.js +18 -0
  207. package/src/shapes/Frame.js +7 -1
  208. package/src/shapes/Line.js +11 -5
  209. package/src/tools/arrowTool.js +30 -4
  210. package/src/tools/circleTool.js +33 -9
  211. package/src/tools/codeTool.js +5 -3
  212. package/src/tools/frameTool.js +47 -7
  213. package/src/tools/freehandTool.js +12 -4
  214. package/src/tools/iconTool.js +7 -1
  215. package/src/tools/imageTool.js +7 -1
  216. package/src/tools/lineTool.js +27 -3
  217. package/src/tools/rectangleTool.js +45 -8
  218. package/src/tools/textTool.js +23 -6
@@ -0,0 +1,974 @@
1
+ "use client";
2
+ import {
3
+ IMAGE_ACCEPT_ATTR,
4
+ compressImage,
5
+ isAllowedImage
6
+ } from "./chunk-DNAG7ML6.js";
7
+ import {
8
+ cleanupAttachments,
9
+ updateAttachedArrows
10
+ } from "./chunk-BZE4CEBG.js";
11
+ import {
12
+ pushCreateAction,
13
+ pushDeleteAction,
14
+ pushFrameAttachmentAction,
15
+ pushTransformAction
16
+ } from "./chunk-L5OARZJ7.js";
17
+
18
+ // src/tools/imageTool.js
19
+ var isDraggingImage = false;
20
+ var imageToPlace = null;
21
+ var imageX = 0;
22
+ var imageY = 0;
23
+ var currentImageElement = null;
24
+ var selectedImage = null;
25
+ var originalX;
26
+ var originalY;
27
+ var originalWidth;
28
+ var originalHeight;
29
+ var currentAnchor = null;
30
+ var isDragging = false;
31
+ var isRotatingImage = false;
32
+ var dragOffsetX;
33
+ var dragOffsetY;
34
+ var startRotationMouseAngle = null;
35
+ var startImageRotation = null;
36
+ var imageRotation = 0;
37
+ var aspect_ratio_lock = true;
38
+ var minImageSize = 20;
39
+ var draggedShapeInitialFrameImage = null;
40
+ var hoveredFrameImage = null;
41
+ var ROOM_IMAGE_LIMIT_BYTES = 5 * 1024 * 1024;
42
+ if (!window.__roomImageBytesUsed) window.__roomImageBytesUsed = 0;
43
+ function wrapImageElement(element) {
44
+ const imageShape = new ImageShape(element);
45
+ return imageShape;
46
+ }
47
+ async function uploadImageToCloudinary(imageShape) {
48
+ const workerUrl = window.__WORKER_URL;
49
+ const sessionId = window.__sessionID;
50
+ if (!workerUrl || !sessionId) return;
51
+ const href = imageShape.element.getAttribute("href") || "";
52
+ if (!href.startsWith("data:")) return;
53
+ imageShape.uploadStatus = "uploading";
54
+ imageShape.uploadAbortController = new AbortController();
55
+ const signal = imageShape.uploadAbortController.signal;
56
+ imageShape.showUploadIndicator();
57
+ try {
58
+ const compressed = await compressImage(href);
59
+ if (signal.aborted) return;
60
+ const signRes = await fetch(`${workerUrl}/api/images/sign`, {
61
+ method: "POST",
62
+ headers: { "Content-Type": "application/json" },
63
+ body: JSON.stringify({
64
+ sessionId,
65
+ filename: `img_${Date.now()}`
66
+ }),
67
+ signal
68
+ });
69
+ if (!signRes.ok) throw new Error("Failed to get upload signature");
70
+ const signData = await signRes.json();
71
+ if (signal.aborted) return;
72
+ const formData = new FormData();
73
+ formData.append("file", compressed.blob);
74
+ formData.append("api_key", signData.apiKey);
75
+ formData.append("timestamp", String(signData.timestamp));
76
+ formData.append("signature", signData.signature);
77
+ formData.append("folder", signData.folder);
78
+ formData.append("public_id", signData.publicId);
79
+ const uploadRes = await fetch(
80
+ `https://api.cloudinary.com/v1_1/${signData.cloudName}/image/upload`,
81
+ { method: "POST", body: formData, signal }
82
+ );
83
+ if (!uploadRes.ok) throw new Error("Cloudinary upload failed");
84
+ const uploadData = await uploadRes.json();
85
+ if (signal.aborted) return;
86
+ const cloudUrl = uploadData.secure_url || uploadData.url;
87
+ imageShape.element.setAttribute("href", cloudUrl);
88
+ imageShape.element.setAttribute("data-href", cloudUrl);
89
+ imageShape.element.setAttribute("data-cloudinary-id", uploadData.public_id);
90
+ const oldSize = imageShape.element.__fileSize || 0;
91
+ const newSize = uploadData.bytes || compressed.compressedSize;
92
+ imageShape.element.__fileSize = newSize;
93
+ window.__roomImageBytesUsed = Math.max(0, (window.__roomImageBytesUsed || 0) - oldSize + newSize);
94
+ imageShape.uploadStatus = "done";
95
+ console.log(`[ImageUpload] Uploaded to Cloudinary: ${cloudUrl} (${(newSize / 1024).toFixed(1)}KB)`);
96
+ } catch (err) {
97
+ if (signal.aborted) {
98
+ console.log("[ImageUpload] Upload aborted (image deleted)");
99
+ return;
100
+ }
101
+ console.warn("[ImageUpload] Upload failed:", err);
102
+ imageShape.uploadStatus = "failed";
103
+ } finally {
104
+ imageShape.removeUploadIndicator();
105
+ imageShape.uploadAbortController = null;
106
+ }
107
+ }
108
+ document.getElementById("importImage")?.addEventListener("click", () => {
109
+ console.log("Import image clicked");
110
+ isImageToolActive = true;
111
+ console.log("isImageToolActive set to:", isImageToolActive);
112
+ const fileInput = document.createElement("input");
113
+ fileInput.type = "file";
114
+ fileInput.accept = IMAGE_ACCEPT_ATTR;
115
+ fileInput.style.display = "none";
116
+ document.body.appendChild(fileInput);
117
+ let fileSelected = false;
118
+ fileInput.addEventListener("change", (event) => {
119
+ const file = event.target.files[0];
120
+ if (file) {
121
+ fileSelected = true;
122
+ handleImageUpload(file);
123
+ }
124
+ document.body.removeChild(fileInput);
125
+ });
126
+ const onFocus = () => {
127
+ window.removeEventListener("focus", onFocus);
128
+ setTimeout(() => {
129
+ if (!fileSelected) {
130
+ isImageToolActive = false;
131
+ if (window.__sketchEngine?.setActiveTool) {
132
+ window.__sketchEngine.setActiveTool("select");
133
+ }
134
+ if (fileInput.parentNode) {
135
+ document.body.removeChild(fileInput);
136
+ }
137
+ }
138
+ }, 300);
139
+ };
140
+ window.addEventListener("focus", onFocus);
141
+ fileInput.click();
142
+ });
143
+ var handleImageUpload = async (file) => {
144
+ if (!file || !isImageToolActive) return;
145
+ if (!isAllowedImage(file)) {
146
+ console.error("Rejected file type:", file.type, file.name);
147
+ alert("Unsupported file type. Allowed: AVIF, JPEG, PNG, BMP, SVG, WebP.");
148
+ isImageToolActive = false;
149
+ return;
150
+ }
151
+ if (window.__roomImageBytesUsed + file.size > ROOM_IMAGE_LIMIT_BYTES) {
152
+ const usedMB = (window.__roomImageBytesUsed / (1024 * 1024)).toFixed(2);
153
+ const fileMB = (file.size / (1024 * 1024)).toFixed(2);
154
+ alert(`Room image limit reached (5 MB). Used: ${usedMB} MB, this file: ${fileMB} MB. Delete some images to free space.`);
155
+ isImageToolActive = false;
156
+ return;
157
+ }
158
+ const maxSize = 5 * 1024 * 1024;
159
+ if (file.size > maxSize) {
160
+ console.error("File size too large");
161
+ alert("Image file is too large. Please select an image smaller than 5 MB.");
162
+ return;
163
+ }
164
+ console.log("Processing image file:", file.name, "Size:", file.size, "Type:", file.type);
165
+ try {
166
+ const rawDataUrl = await readFileAsDataUrl(file);
167
+ const isSvg = (file.type || "").toLowerCase() === "image/svg+xml" || (file.name || "").toLowerCase().endsWith(".svg");
168
+ let placedDataUrl = rawDataUrl;
169
+ let placedSize = file.size;
170
+ if (!isSvg) {
171
+ try {
172
+ const compressed = await compressImage(rawDataUrl);
173
+ if (compressed?.dataUrl) {
174
+ placedDataUrl = compressed.dataUrl;
175
+ placedSize = compressed.compressedSize || placedSize;
176
+ }
177
+ } catch (err) {
178
+ console.warn("[ImageTool] Pre-placement compression failed, using raw:", err);
179
+ }
180
+ }
181
+ window.__pendingImageFileSize = placedSize;
182
+ imageToPlace = placedDataUrl;
183
+ isDraggingImage = true;
184
+ console.log("Image loaded and ready to place", { size: placedSize, isSvg });
185
+ } catch (err) {
186
+ console.error("Error reading file:", err);
187
+ alert("Error reading the image file. Please try again.");
188
+ isImageToolActive = false;
189
+ }
190
+ };
191
+ function readFileAsDataUrl(file) {
192
+ return new Promise((resolve, reject) => {
193
+ const reader = new FileReader();
194
+ reader.onload = (e) => resolve(e.target.result);
195
+ reader.onerror = (err) => reject(err);
196
+ reader.readAsDataURL(file);
197
+ });
198
+ }
199
+ function getSVGCoordsFromMouse(e) {
200
+ const viewBox = svg.viewBox.baseVal;
201
+ const rect = svg.getBoundingClientRect();
202
+ const mouseX = e.clientX - rect.left;
203
+ const mouseY = e.clientY - rect.top;
204
+ const svgX = viewBox.x + mouseX / rect.width * viewBox.width;
205
+ const svgY = viewBox.y + mouseY / rect.height * viewBox.height;
206
+ return { x: svgX, y: svgY };
207
+ }
208
+ var handleMouseMoveImage = (e) => {
209
+ if (!isDraggingImage || !imageToPlace || !isImageToolActive) return;
210
+ const { x, y } = getSVGCoordsFromMouse(e);
211
+ imageX = x;
212
+ imageY = y;
213
+ drawMiniatureImage();
214
+ if (typeof shapes !== "undefined" && Array.isArray(shapes)) {
215
+ shapes.forEach((frame) => {
216
+ if (frame.shapeName === "frame") {
217
+ const tempImageBounds = {
218
+ x: imageX - 50,
219
+ // Half of miniature width
220
+ y: imageY - 50,
221
+ // Approximate half height
222
+ width: 100,
223
+ height: 100
224
+ };
225
+ if (frame.isShapeInFrame(tempImageBounds)) {
226
+ frame.highlightFrame();
227
+ hoveredFrameImage = frame;
228
+ } else if (hoveredFrameImage === frame) {
229
+ frame.removeHighlight();
230
+ hoveredFrameImage = null;
231
+ }
232
+ }
233
+ });
234
+ }
235
+ };
236
+ var drawMiniatureImage = () => {
237
+ if (!isDraggingImage || !imageToPlace || !isImageToolActive) return;
238
+ const miniatureWidth = 100;
239
+ getImageAspectRatio(imageToPlace).then((aspectRatio) => {
240
+ const miniatureHeight = miniatureWidth * aspectRatio;
241
+ if (currentImageElement) {
242
+ svg.removeChild(currentImageElement);
243
+ currentImageElement = null;
244
+ }
245
+ currentImageElement = document.createElementNS("http://www.w3.org/2000/svg", "image");
246
+ currentImageElement.setAttribute("href", imageToPlace);
247
+ currentImageElement.setAttribute("x", imageX - miniatureWidth / 2);
248
+ currentImageElement.setAttribute("y", imageY - miniatureHeight / 2);
249
+ currentImageElement.setAttribute("width", miniatureWidth);
250
+ currentImageElement.setAttribute("height", miniatureHeight);
251
+ currentImageElement.setAttribute("preserveAspectRatio", "xMidYMid meet");
252
+ svg.appendChild(currentImageElement);
253
+ }).catch((error) => {
254
+ console.error("Error getting aspect ratio:", error);
255
+ });
256
+ };
257
+ function getImageAspectRatio(dataUrl) {
258
+ return new Promise((resolve, reject) => {
259
+ const img = new Image();
260
+ img.onload = () => {
261
+ resolve(img.height / img.width);
262
+ };
263
+ img.onerror = () => {
264
+ reject(new Error("Failed to load image for aspect ratio calculation."));
265
+ };
266
+ img.src = dataUrl;
267
+ });
268
+ }
269
+ var handleMouseDownImage = async (e) => {
270
+ if (!isDraggingImage || !imageToPlace || !isImageToolActive) {
271
+ if (isSelectionToolActive) {
272
+ const clickedImage = e.target.closest("image");
273
+ if (clickedImage) {
274
+ selectImage({ target: clickedImage, stopPropagation: () => e.stopPropagation() });
275
+ return;
276
+ }
277
+ }
278
+ return;
279
+ }
280
+ try {
281
+ let aspectRatio = await getImageAspectRatio(imageToPlace);
282
+ if (currentImageElement) {
283
+ svg.removeChild(currentImageElement);
284
+ currentImageElement = null;
285
+ }
286
+ const placedImageWidth = 200;
287
+ const placedImageHeight = placedImageWidth * aspectRatio;
288
+ const { x: placedX, y: placedY } = getSVGCoordsFromMouse(e);
289
+ const finalImage = document.createElementNS("http://www.w3.org/2000/svg", "image");
290
+ finalImage.setAttribute("href", imageToPlace);
291
+ finalImage.setAttribute("x", placedX - placedImageWidth / 2);
292
+ finalImage.setAttribute("y", placedY - placedImageHeight / 2);
293
+ finalImage.setAttribute("width", placedImageWidth);
294
+ finalImage.setAttribute("height", placedImageHeight);
295
+ finalImage.setAttribute("preserveAspectRatio", "xMidYMid meet");
296
+ finalImage.setAttribute("data-href", imageToPlace);
297
+ finalImage.setAttribute("data-x", placedX - placedImageWidth / 2);
298
+ finalImage.setAttribute("data-y", placedY - placedImageHeight / 2);
299
+ finalImage.setAttribute("data-width", placedImageWidth);
300
+ finalImage.setAttribute("data-height", placedImageHeight);
301
+ finalImage.setAttribute("type", "image");
302
+ finalImage.setAttribute("data-shape-x", placedX - placedImageWidth / 2);
303
+ finalImage.setAttribute("data-shape-y", placedY - placedImageHeight / 2);
304
+ finalImage.setAttribute("data-shape-width", placedImageWidth);
305
+ finalImage.setAttribute("data-shape-height", placedImageHeight);
306
+ finalImage.setAttribute("data-shape-rotation", 0);
307
+ finalImage.shapeID = `image-${String(Date.now()).slice(0, 8)}-${Math.floor(Math.random() * 1e4)}`;
308
+ const imageShape = wrapImageElement(finalImage);
309
+ if (typeof shapes !== "undefined" && Array.isArray(shapes)) {
310
+ shapes.push(imageShape);
311
+ console.log("Image added to shapes array for arrow attachment and frame functionality");
312
+ } else {
313
+ console.warn("shapes array not found - arrows and frames may not work with images");
314
+ }
315
+ const finalFrame = hoveredFrameImage;
316
+ if (finalFrame) {
317
+ finalFrame.addShapeToFrame(imageShape);
318
+ pushFrameAttachmentAction(finalFrame, imageShape, "attach", null);
319
+ }
320
+ pushCreateAction({
321
+ type: "image",
322
+ element: imageShape,
323
+ remove: () => {
324
+ if (imageShape.group && imageShape.group.parentNode) {
325
+ imageShape.group.parentNode.removeChild(imageShape.group);
326
+ }
327
+ if (typeof shapes !== "undefined" && Array.isArray(shapes)) {
328
+ const idx = shapes.indexOf(imageShape);
329
+ if (idx !== -1) shapes.splice(idx, 1);
330
+ }
331
+ if (typeof cleanupAttachments === "function") {
332
+ cleanupAttachments(finalImage);
333
+ }
334
+ },
335
+ restore: () => {
336
+ svg.appendChild(imageShape.group);
337
+ if (typeof shapes !== "undefined" && Array.isArray(shapes)) {
338
+ if (shapes.indexOf(imageShape) === -1) {
339
+ shapes.push(imageShape);
340
+ }
341
+ }
342
+ }
343
+ });
344
+ const placedFileSize = window.__pendingImageFileSize || 0;
345
+ finalImage.__fileSize = placedFileSize;
346
+ window.__roomImageBytesUsed = (window.__roomImageBytesUsed || 0) + placedFileSize;
347
+ window.__pendingImageFileSize = 0;
348
+ finalImage.addEventListener("click", selectImage);
349
+ if (hoveredFrameImage) {
350
+ hoveredFrameImage.removeHighlight();
351
+ hoveredFrameImage = null;
352
+ }
353
+ const placedShape = imageShape;
354
+ if (window.__sketchStoreApi) window.__sketchStoreApi.setActiveTool("select", { afterDraw: true });
355
+ currentShape = placedShape;
356
+ currentShape.isSelected = true;
357
+ placedShape.selectShape();
358
+ const uploader = typeof window !== "undefined" && window.uploadImageToCloudinary || uploadImageToCloudinary;
359
+ uploader(imageShape).catch((err) => {
360
+ console.warn("[ImageTool] Upload pipeline error:", err);
361
+ });
362
+ } catch (error) {
363
+ console.error("Error placing image:", error);
364
+ isDraggingImage = false;
365
+ imageToPlace = null;
366
+ isImageToolActive = false;
367
+ } finally {
368
+ isDraggingImage = false;
369
+ imageToPlace = null;
370
+ isImageToolActive = false;
371
+ }
372
+ };
373
+ var handleMouseUpImage = (e) => {
374
+ if (isSelectionToolActive && selectedImage) {
375
+ const clickedElement = e.target;
376
+ const isOnSVG = clickedElement === svg || clickedElement.ownerSVGElement === svg;
377
+ const isImageElement = clickedElement.tagName === "image";
378
+ const isAnchorElement = clickedElement.classList.contains("resize-anchor") || clickedElement.classList.contains("rotation-anchor") || clickedElement.classList.contains("selection-outline");
379
+ if (isOnSVG && !isImageElement && !isAnchorElement && clickedElement === svg && !isDragging && !isRotatingImage && !currentAnchor) {
380
+ removeSelectionOutline();
381
+ selectedImage = null;
382
+ if (window.__sketchStoreApi) {
383
+ window.__sketchStoreApi.clearSelectedShapeSidebar();
384
+ }
385
+ }
386
+ }
387
+ if (hoveredFrameImage) {
388
+ hoveredFrameImage.removeHighlight();
389
+ hoveredFrameImage = null;
390
+ }
391
+ };
392
+ function selectImage(event) {
393
+ if (!isSelectionToolActive) return;
394
+ event.stopPropagation();
395
+ if (selectedImage) {
396
+ removeSelectionOutline();
397
+ }
398
+ selectedImage = event.target;
399
+ const transform = selectedImage.getAttribute("transform");
400
+ if (transform) {
401
+ const rotateMatch = transform.match(/rotate\(([^,]+)/);
402
+ if (rotateMatch) {
403
+ imageRotation = parseFloat(rotateMatch[1]);
404
+ }
405
+ } else {
406
+ imageRotation = 0;
407
+ }
408
+ addSelectionOutline();
409
+ originalX = parseFloat(selectedImage.getAttribute("x"));
410
+ originalY = parseFloat(selectedImage.getAttribute("y"));
411
+ originalWidth = parseFloat(selectedImage.getAttribute("width"));
412
+ originalHeight = parseFloat(selectedImage.getAttribute("height"));
413
+ selectedImage.addEventListener("pointerdown", startDrag);
414
+ selectedImage.addEventListener("pointerup", stopDrag);
415
+ selectedImage.addEventListener("pointerleave", stopDrag);
416
+ const imageShape = typeof shapes !== "undefined" && Array.isArray(shapes) ? shapes.find((s) => s.shapeName === "image" && s.element === selectedImage) : null;
417
+ if (imageShape) {
418
+ window.currentShape = imageShape;
419
+ }
420
+ if (typeof window.__showSidebarForShape === "function") {
421
+ window.__showSidebarForShape("image");
422
+ }
423
+ }
424
+ function addSelectionOutline() {
425
+ if (!selectedImage) return;
426
+ const x = parseFloat(selectedImage.getAttribute("x"));
427
+ const y = parseFloat(selectedImage.getAttribute("y"));
428
+ const width = parseFloat(selectedImage.getAttribute("width"));
429
+ const height = parseFloat(selectedImage.getAttribute("height"));
430
+ const selectionPadding = 8;
431
+ const expandedX = x - selectionPadding;
432
+ const expandedY = y - selectionPadding;
433
+ const expandedWidth = width + 2 * selectionPadding;
434
+ const expandedHeight = height + 2 * selectionPadding;
435
+ const outlinePoints = [
436
+ [expandedX, expandedY],
437
+ [expandedX + expandedWidth, expandedY],
438
+ [expandedX + expandedWidth, expandedY + expandedHeight],
439
+ [expandedX, expandedY + expandedHeight],
440
+ [expandedX, expandedY]
441
+ ];
442
+ const pointsAttr = outlinePoints.map((p) => p.join(",")).join(" ");
443
+ const outline = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
444
+ outline.setAttribute("points", pointsAttr);
445
+ outline.setAttribute("fill", "none");
446
+ outline.setAttribute("stroke", "#5B57D1");
447
+ outline.setAttribute("stroke-width", 1.5);
448
+ outline.setAttribute("stroke-dasharray", "4 2");
449
+ outline.setAttribute("style", "pointer-events: none;");
450
+ outline.setAttribute("class", "selection-outline");
451
+ const centerX = x + width / 2;
452
+ const centerY = y + height / 2;
453
+ outline.setAttribute("transform", `rotate(${imageRotation}, ${centerX}, ${centerY})`);
454
+ svg.appendChild(outline);
455
+ addResizeAnchors(expandedX, expandedY, expandedWidth, expandedHeight, centerX, centerY);
456
+ addRotationAnchor(expandedX, expandedY, expandedWidth, expandedHeight, centerX, centerY);
457
+ }
458
+ function removeSelectionOutline() {
459
+ svg.querySelectorAll(".selection-outline").forEach((el) => el.remove());
460
+ removeResizeAnchors();
461
+ removeRotationAnchor();
462
+ if (selectedImage) {
463
+ selectedImage.removeEventListener("pointerdown", startDrag);
464
+ selectedImage.removeEventListener("pointerup", stopDrag);
465
+ selectedImage.removeEventListener("pointerleave", stopDrag);
466
+ }
467
+ }
468
+ function addResizeAnchors(x, y, width, height, centerX, centerY) {
469
+ const zoom = window.currentZoom || 1;
470
+ const anchorSize = 10 / zoom;
471
+ const anchorStrokeWidth = 2;
472
+ const positions = [
473
+ { x, y },
474
+ // Top-left
475
+ { x: x + width, y },
476
+ // Top-right
477
+ { x, y: y + height },
478
+ // Bottom-left
479
+ { x: x + width, y: y + height }
480
+ // Bottom-right
481
+ ];
482
+ positions.forEach((pos, i) => {
483
+ const anchor = document.createElementNS("http://www.w3.org/2000/svg", "rect");
484
+ anchor.setAttribute("x", pos.x - anchorSize / 2);
485
+ anchor.setAttribute("y", pos.y - anchorSize / 2);
486
+ anchor.setAttribute("width", anchorSize);
487
+ anchor.setAttribute("height", anchorSize);
488
+ anchor.setAttribute("fill", "#121212");
489
+ anchor.setAttribute("stroke", "#5B57D1");
490
+ anchor.setAttribute("stroke-width", anchorStrokeWidth);
491
+ anchor.setAttribute("class", "resize-anchor");
492
+ anchor.style.cursor = ["nw-resize", "ne-resize", "sw-resize", "se-resize"][i];
493
+ anchor.setAttribute("transform", `rotate(${imageRotation}, ${centerX}, ${centerY})`);
494
+ svg.appendChild(anchor);
495
+ anchor.addEventListener("pointerdown", startResize);
496
+ anchor.addEventListener("pointerup", stopResize);
497
+ });
498
+ }
499
+ function addRotationAnchor(x, y, width, height, centerX, centerY) {
500
+ const anchorStrokeWidth = 2;
501
+ const rotationAnchorPos = { x: x + width / 2, y: y - 30 };
502
+ const rotationAnchor = document.createElementNS("http://www.w3.org/2000/svg", "circle");
503
+ rotationAnchor.setAttribute("cx", rotationAnchorPos.x);
504
+ rotationAnchor.setAttribute("cy", rotationAnchorPos.y);
505
+ rotationAnchor.setAttribute("r", 8);
506
+ rotationAnchor.setAttribute("class", "rotation-anchor");
507
+ rotationAnchor.setAttribute("fill", "#121212");
508
+ rotationAnchor.setAttribute("stroke", "#5B57D1");
509
+ rotationAnchor.setAttribute("stroke-width", anchorStrokeWidth);
510
+ rotationAnchor.setAttribute("style", "pointer-events: all;");
511
+ rotationAnchor.setAttribute("transform", `rotate(${imageRotation}, ${centerX}, ${centerY})`);
512
+ svg.appendChild(rotationAnchor);
513
+ rotationAnchor.addEventListener("pointerdown", startRotation);
514
+ rotationAnchor.addEventListener("pointerup", stopRotation);
515
+ rotationAnchor.addEventListener("mouseover", function() {
516
+ if (!isRotatingImage && !isDragging) {
517
+ rotationAnchor.style.cursor = "grab";
518
+ }
519
+ });
520
+ rotationAnchor.addEventListener("mouseout", function() {
521
+ if (!isRotatingImage && !isDragging) {
522
+ rotationAnchor.style.cursor = "default";
523
+ }
524
+ });
525
+ }
526
+ function removeRotationAnchor() {
527
+ svg.querySelectorAll(".rotation-anchor").forEach((el) => el.remove());
528
+ }
529
+ function removeResizeAnchors() {
530
+ const anchors = svg.querySelectorAll(".resize-anchor");
531
+ anchors.forEach((anchor) => svg.removeChild(anchor));
532
+ }
533
+ function startResize(event) {
534
+ event.preventDefault();
535
+ event.stopPropagation();
536
+ currentAnchor = event.target;
537
+ originalX = parseFloat(selectedImage.getAttribute("x"));
538
+ originalY = parseFloat(selectedImage.getAttribute("y"));
539
+ originalWidth = parseFloat(selectedImage.getAttribute("width"));
540
+ originalHeight = parseFloat(selectedImage.getAttribute("height"));
541
+ const transform = selectedImage.getAttribute("transform");
542
+ if (transform) {
543
+ const rotateMatch = transform.match(/rotate\(([^,]+)/);
544
+ if (rotateMatch) {
545
+ imageRotation = parseFloat(rotateMatch[1]);
546
+ }
547
+ }
548
+ svg.addEventListener("pointermove", resizeImage);
549
+ document.addEventListener("pointerup", stopResize);
550
+ }
551
+ function stopResize(event) {
552
+ stopInteracting();
553
+ document.removeEventListener("pointerup", stopResize);
554
+ }
555
+ function resizeImage(event) {
556
+ if (!selectedImage || !currentAnchor) return;
557
+ const { x: globalX, y: globalY } = getSVGCoordsFromMouse(event);
558
+ const centerX = originalX + originalWidth / 2;
559
+ const centerY = originalY + originalHeight / 2;
560
+ let localX = globalX;
561
+ let localY = globalY;
562
+ if (imageRotation !== 0) {
563
+ const rotationRad = imageRotation * Math.PI / 180;
564
+ const translatedX = globalX - centerX;
565
+ const translatedY = globalY - centerY;
566
+ localX = translatedX * Math.cos(-rotationRad) - translatedY * Math.sin(-rotationRad) + centerX;
567
+ localY = translatedX * Math.sin(-rotationRad) + translatedY * Math.cos(-rotationRad) + centerY;
568
+ }
569
+ let dx = localX - originalX;
570
+ let dy = localY - originalY;
571
+ let newWidth = originalWidth;
572
+ let newHeight = originalHeight;
573
+ let newX = originalX;
574
+ let newY = originalY;
575
+ const aspectRatio = originalHeight / originalWidth;
576
+ let fixedRelX, fixedRelY;
577
+ switch (currentAnchor.style.cursor) {
578
+ case "nw-resize":
579
+ newWidth = originalWidth - dx;
580
+ newHeight = originalHeight - dy;
581
+ if (aspect_ratio_lock) {
582
+ newHeight = newWidth * aspectRatio;
583
+ dy = originalHeight - newHeight;
584
+ }
585
+ newX = originalX + (originalWidth - newWidth);
586
+ newY = originalY + (originalHeight - newHeight);
587
+ fixedRelX = originalWidth;
588
+ fixedRelY = originalHeight;
589
+ break;
590
+ case "ne-resize":
591
+ newWidth = dx;
592
+ newHeight = originalHeight - dy;
593
+ if (aspect_ratio_lock) {
594
+ newHeight = newWidth * aspectRatio;
595
+ dy = originalHeight - newHeight;
596
+ }
597
+ newX = originalX;
598
+ newY = originalY + (originalHeight - newHeight);
599
+ fixedRelX = 0;
600
+ fixedRelY = originalHeight;
601
+ break;
602
+ case "sw-resize":
603
+ newWidth = originalWidth - dx;
604
+ newHeight = dy;
605
+ if (aspect_ratio_lock) {
606
+ newHeight = newWidth * aspectRatio;
607
+ dy = newHeight;
608
+ }
609
+ newX = originalX + (originalWidth - newWidth);
610
+ newY = originalY;
611
+ fixedRelX = originalWidth;
612
+ fixedRelY = 0;
613
+ break;
614
+ case "se-resize":
615
+ newWidth = dx;
616
+ newHeight = dy;
617
+ if (aspect_ratio_lock) {
618
+ newHeight = newWidth * aspectRatio;
619
+ }
620
+ newX = originalX;
621
+ newY = originalY;
622
+ fixedRelX = 0;
623
+ fixedRelY = 0;
624
+ break;
625
+ }
626
+ newWidth = Math.max(minImageSize, newWidth);
627
+ newHeight = Math.max(minImageSize, newHeight);
628
+ if (imageRotation !== 0) {
629
+ const rad = imageRotation * Math.PI / 180;
630
+ const cosR = Math.cos(rad);
631
+ const sinR = Math.sin(rad);
632
+ const oldCX = originalX + originalWidth / 2;
633
+ const oldCY = originalY + originalHeight / 2;
634
+ const odx = originalX + fixedRelX - oldCX;
635
+ const ody = originalY + fixedRelY - oldCY;
636
+ const fixedWorldX = oldCX + odx * cosR - ody * sinR;
637
+ const fixedWorldY = oldCY + odx * sinR + ody * cosR;
638
+ const fixedNewRelX = fixedRelX === 0 ? 0 : newWidth;
639
+ const fixedNewRelY = fixedRelY === 0 ? 0 : newHeight;
640
+ const ncx = newWidth / 2;
641
+ const ncy = newHeight / 2;
642
+ const ndx = fixedNewRelX - ncx;
643
+ const ndy = fixedNewRelY - ncy;
644
+ const rotX = ncx + ndx * cosR - ndy * sinR;
645
+ const rotY = ncy + ndx * sinR + ndy * cosR;
646
+ newX = fixedWorldX - rotX;
647
+ newY = fixedWorldY - rotY;
648
+ }
649
+ selectedImage.setAttribute("width", newWidth);
650
+ selectedImage.setAttribute("height", newHeight);
651
+ selectedImage.setAttribute("x", newX);
652
+ selectedImage.setAttribute("y", newY);
653
+ selectedImage.setAttribute("data-shape-x", newX);
654
+ selectedImage.setAttribute("data-shape-y", newY);
655
+ selectedImage.setAttribute("data-shape-width", newWidth);
656
+ selectedImage.setAttribute("data-shape-height", newHeight);
657
+ const newCenterX = newX + newWidth / 2;
658
+ const newCenterY = newY + newHeight / 2;
659
+ selectedImage.setAttribute("transform", `rotate(${imageRotation}, ${newCenterX}, ${newCenterY})`);
660
+ updateAttachedArrows(selectedImage);
661
+ removeSelectionOutline();
662
+ addSelectionOutline();
663
+ }
664
+ function stopRotation(event) {
665
+ if (!isRotatingImage) return;
666
+ stopInteracting();
667
+ isRotatingImage = false;
668
+ startRotationMouseAngle = null;
669
+ startImageRotation = null;
670
+ svg.removeEventListener("pointermove", rotateImage);
671
+ document.removeEventListener("pointerup", stopRotation);
672
+ svg.style.cursor = "default";
673
+ }
674
+ function startDrag(event) {
675
+ if (!isSelectionToolActive || !selectedImage) return;
676
+ event.preventDefault();
677
+ event.stopPropagation();
678
+ isDragging = true;
679
+ originalX = parseFloat(selectedImage.getAttribute("x"));
680
+ originalY = parseFloat(selectedImage.getAttribute("y"));
681
+ originalWidth = parseFloat(selectedImage.getAttribute("width"));
682
+ originalHeight = parseFloat(selectedImage.getAttribute("height"));
683
+ const transform = selectedImage.getAttribute("transform");
684
+ if (transform) {
685
+ const rotateMatch = transform.match(/rotate\(([^,]+)/);
686
+ if (rotateMatch) {
687
+ imageRotation = parseFloat(rotateMatch[1]);
688
+ }
689
+ }
690
+ let imageShape = null;
691
+ if (typeof shapes !== "undefined" && Array.isArray(shapes)) {
692
+ imageShape = shapes.find((shape) => shape.shapeName === "image" && shape.element === selectedImage);
693
+ }
694
+ if (imageShape) {
695
+ draggedShapeInitialFrameImage = imageShape.parentFrame || null;
696
+ if (imageShape.parentFrame) {
697
+ imageShape.parentFrame.temporarilyRemoveFromFrame(imageShape);
698
+ }
699
+ }
700
+ const { x, y } = getSVGCoordsFromMouse(event);
701
+ dragOffsetX = x - parseFloat(selectedImage.getAttribute("x"));
702
+ dragOffsetY = y - parseFloat(selectedImage.getAttribute("y"));
703
+ svg.addEventListener("pointermove", dragImage);
704
+ document.addEventListener("pointerup", stopDrag);
705
+ }
706
+ function dragImage(event) {
707
+ if (!isDragging || !selectedImage) return;
708
+ const { x, y } = getSVGCoordsFromMouse(event);
709
+ let newX = x - dragOffsetX;
710
+ let newY = y - dragOffsetY;
711
+ selectedImage.setAttribute("x", newX);
712
+ selectedImage.setAttribute("y", newY);
713
+ selectedImage.setAttribute("data-shape-x", newX);
714
+ selectedImage.setAttribute("data-shape-y", newY);
715
+ const newCenterX = newX + parseFloat(selectedImage.getAttribute("width")) / 2;
716
+ const newCenterY = newY + parseFloat(selectedImage.getAttribute("height")) / 2;
717
+ selectedImage.setAttribute("transform", `rotate(${imageRotation}, ${newCenterX}, ${newCenterY})`);
718
+ if (typeof shapes !== "undefined" && Array.isArray(shapes)) {
719
+ const imageShape = shapes.find((shape) => shape.shapeName === "image" && shape.element === selectedImage);
720
+ if (imageShape) {
721
+ imageShape.updateFrameContainment();
722
+ }
723
+ }
724
+ updateAttachedArrows(selectedImage);
725
+ removeSelectionOutline();
726
+ addSelectionOutline();
727
+ }
728
+ function stopDrag(event) {
729
+ stopInteracting();
730
+ document.removeEventListener("pointerup", stopDrag);
731
+ }
732
+ function startRotation(event) {
733
+ event.preventDefault();
734
+ event.stopPropagation();
735
+ if (!selectedImage) return;
736
+ isRotatingImage = true;
737
+ const imgX = parseFloat(selectedImage.getAttribute("x"));
738
+ const imgY = parseFloat(selectedImage.getAttribute("y"));
739
+ const imgWidth = parseFloat(selectedImage.getAttribute("width"));
740
+ const imgHeight = parseFloat(selectedImage.getAttribute("height"));
741
+ const centerX = imgX + imgWidth / 2;
742
+ const centerY = imgY + imgHeight / 2;
743
+ const { x: mouseX, y: mouseY } = getSVGCoordsFromMouse(event);
744
+ startRotationMouseAngle = Math.atan2(mouseY - centerY, mouseX - centerX) * 180 / Math.PI;
745
+ startImageRotation = imageRotation;
746
+ svg.addEventListener("pointermove", rotateImage);
747
+ document.addEventListener("pointerup", stopRotation);
748
+ svg.style.cursor = "grabbing";
749
+ }
750
+ function rotateImage(event) {
751
+ if (!isRotatingImage || !selectedImage) return;
752
+ const imgX = parseFloat(selectedImage.getAttribute("x"));
753
+ const imgY = parseFloat(selectedImage.getAttribute("y"));
754
+ const imgWidth = parseFloat(selectedImage.getAttribute("width"));
755
+ const imgHeight = parseFloat(selectedImage.getAttribute("height"));
756
+ const centerX = imgX + imgWidth / 2;
757
+ const centerY = imgY + imgHeight / 2;
758
+ const { x: mouseX, y: mouseY } = getSVGCoordsFromMouse(event);
759
+ const currentMouseAngle = Math.atan2(mouseY - centerY, mouseX - centerX) * 180 / Math.PI;
760
+ const angleDiff = currentMouseAngle - startRotationMouseAngle;
761
+ imageRotation = startImageRotation + angleDiff;
762
+ imageRotation = imageRotation % 360;
763
+ if (imageRotation < 0) imageRotation += 360;
764
+ selectedImage.setAttribute("transform", `rotate(${imageRotation}, ${centerX}, ${centerY})`);
765
+ selectedImage.setAttribute("data-shape-rotation", imageRotation);
766
+ updateAttachedArrows(selectedImage);
767
+ removeSelectionOutline();
768
+ addSelectionOutline();
769
+ }
770
+ function stopInteracting() {
771
+ if (selectedImage && (isDragging || isRotatingImage || currentAnchor)) {
772
+ const newPos = {
773
+ x: parseFloat(selectedImage.getAttribute("x")),
774
+ y: parseFloat(selectedImage.getAttribute("y")),
775
+ width: parseFloat(selectedImage.getAttribute("width")),
776
+ height: parseFloat(selectedImage.getAttribute("height")),
777
+ rotation: imageRotation
778
+ };
779
+ let originalRotation = imageRotation;
780
+ if (isRotatingImage && startImageRotation !== null) {
781
+ originalRotation = startImageRotation;
782
+ }
783
+ const oldPos = {
784
+ x: originalX,
785
+ y: originalY,
786
+ width: originalWidth,
787
+ height: originalHeight,
788
+ rotation: originalRotation
789
+ };
790
+ let imageShape = null;
791
+ if (typeof shapes !== "undefined" && Array.isArray(shapes)) {
792
+ imageShape = shapes.find((shape) => shape.shapeName === "image" && shape.element === selectedImage);
793
+ }
794
+ const oldPosWithFrame = {
795
+ ...oldPos,
796
+ parentFrame: draggedShapeInitialFrameImage
797
+ };
798
+ const newPosWithFrame = {
799
+ ...newPos,
800
+ parentFrame: isDragging ? hoveredFrameImage || null : imageShape ? imageShape.parentFrame : null
801
+ };
802
+ const stateChanged = newPos.x !== oldPos.x || newPos.y !== oldPos.y || newPos.width !== oldPos.width || newPos.height !== oldPos.height || newPos.rotation !== oldPos.rotation;
803
+ const frameChanged = oldPosWithFrame.parentFrame !== newPosWithFrame.parentFrame;
804
+ if (stateChanged || frameChanged) {
805
+ pushTransformAction({
806
+ type: "image",
807
+ element: selectedImage,
808
+ restore: (pos) => {
809
+ selectedImage.setAttribute("x", pos.x);
810
+ selectedImage.setAttribute("y", pos.y);
811
+ selectedImage.setAttribute("width", pos.width);
812
+ selectedImage.setAttribute("height", pos.height);
813
+ const centerX = pos.x + pos.width / 2;
814
+ const centerY = pos.y + pos.height / 2;
815
+ selectedImage.setAttribute("transform", `rotate(${pos.rotation}, ${centerX}, ${centerY})`);
816
+ imageRotation = pos.rotation;
817
+ if (selectedImage) {
818
+ removeSelectionOutline();
819
+ addSelectionOutline();
820
+ }
821
+ selectedImage.setAttribute("data-shape-x", pos.x);
822
+ selectedImage.setAttribute("data-shape-y", pos.y);
823
+ selectedImage.setAttribute("data-shape-width", pos.width);
824
+ selectedImage.setAttribute("data-shape-height", pos.height);
825
+ selectedImage.setAttribute("data-shape-rotation", pos.rotation);
826
+ updateAttachedArrows(selectedImage);
827
+ }
828
+ }, oldPosWithFrame, newPosWithFrame);
829
+ }
830
+ if (isDragging && imageShape) {
831
+ const finalFrame = hoveredFrameImage;
832
+ if (draggedShapeInitialFrameImage !== finalFrame) {
833
+ if (draggedShapeInitialFrameImage) {
834
+ draggedShapeInitialFrameImage.removeShapeFromFrame(imageShape);
835
+ }
836
+ if (finalFrame) {
837
+ finalFrame.addShapeToFrame(imageShape);
838
+ }
839
+ if (frameChanged) {
840
+ pushFrameAttachmentAction(
841
+ finalFrame || draggedShapeInitialFrameImage,
842
+ imageShape,
843
+ finalFrame ? "attach" : "detach",
844
+ draggedShapeInitialFrameImage
845
+ );
846
+ }
847
+ } else if (draggedShapeInitialFrameImage) {
848
+ draggedShapeInitialFrameImage.restoreToFrame(imageShape);
849
+ }
850
+ }
851
+ draggedShapeInitialFrameImage = null;
852
+ }
853
+ if (hoveredFrameImage) {
854
+ hoveredFrameImage.removeHighlight();
855
+ hoveredFrameImage = null;
856
+ }
857
+ isDragging = false;
858
+ isRotatingImage = false;
859
+ svg.removeEventListener("pointermove", dragImage);
860
+ svg.removeEventListener("pointermove", resizeImage);
861
+ svg.removeEventListener("pointermove", rotateImage);
862
+ currentAnchor = null;
863
+ startRotationMouseAngle = null;
864
+ startImageRotation = null;
865
+ if (selectedImage) {
866
+ originalX = parseFloat(selectedImage.getAttribute("x"));
867
+ originalY = parseFloat(selectedImage.getAttribute("y"));
868
+ originalWidth = parseFloat(selectedImage.getAttribute("width"));
869
+ originalHeight = parseFloat(selectedImage.getAttribute("height"));
870
+ const transform = selectedImage.getAttribute("transform");
871
+ if (transform) {
872
+ const rotateMatch = transform.match(/rotate\(([^,]+)/);
873
+ if (rotateMatch) {
874
+ imageRotation = parseFloat(rotateMatch[1]);
875
+ }
876
+ }
877
+ selectedImage.setAttribute("data-shape-x", originalX);
878
+ selectedImage.setAttribute("data-shape-y", originalY);
879
+ selectedImage.setAttribute("data-shape-width", originalWidth);
880
+ selectedImage.setAttribute("data-shape-height", originalHeight);
881
+ selectedImage.setAttribute("data-shape-rotation", imageRotation);
882
+ updateAttachedArrows(selectedImage);
883
+ }
884
+ }
885
+ function deleteCurrentImage() {
886
+ if (selectedImage) {
887
+ let imageShape = typeof shapes !== "undefined" && Array.isArray(shapes) ? shapes.find((s) => s.shapeName === "image" && s.element === selectedImage) : null;
888
+ if (imageShape?.uploadAbortController) {
889
+ imageShape.uploadAbortController.abort();
890
+ imageShape.removeUploadIndicator();
891
+ }
892
+ const freedBytes = selectedImage.__fileSize || 0;
893
+ window.__roomImageBytesUsed = Math.max(0, (window.__roomImageBytesUsed || 0) - freedBytes);
894
+ const imgHref = selectedImage.getAttribute("href") || selectedImage.getAttributeNS("http://www.w3.org/1999/xlink", "href") || "";
895
+ if (imgHref.includes("cloudinary.com") || imgHref.includes("res.cloudinary")) {
896
+ const match = imgHref.match(/\/upload\/(?:v\d+\/)?(lixsketch\/.+?)(?:\.\w+)?$/);
897
+ if (match) {
898
+ const publicId = match[1];
899
+ const workerUrl = window.__WORKER_URL;
900
+ if (workerUrl) {
901
+ fetch(`${workerUrl}/api/images/delete`, {
902
+ method: "DELETE",
903
+ headers: { "Content-Type": "application/json" },
904
+ body: JSON.stringify({ publicId })
905
+ }).catch((err) => console.warn("[ImageTool] Cloudinary cleanup failed:", err));
906
+ }
907
+ }
908
+ }
909
+ if (imageShape) {
910
+ const idx = shapes.indexOf(imageShape);
911
+ if (idx !== -1) shapes.splice(idx, 1);
912
+ if (imageShape.group && imageShape.group.parentNode) {
913
+ imageShape.group.parentNode.removeChild(imageShape.group);
914
+ }
915
+ }
916
+ if (!imageShape && selectedImage.parentNode) {
917
+ selectedImage.parentNode.removeChild(selectedImage);
918
+ }
919
+ if (typeof cleanupAttachments === "function") {
920
+ cleanupAttachments(selectedImage);
921
+ }
922
+ if (imageShape) {
923
+ pushDeleteAction(imageShape);
924
+ }
925
+ removeSelectionOutline();
926
+ selectedImage = null;
927
+ }
928
+ }
929
+ document.addEventListener("keydown", (e) => {
930
+ if (e.key === "Delete" && selectedImage) {
931
+ deleteCurrentImage();
932
+ }
933
+ });
934
+ window.uploadImageToCloudinary = uploadImageToCloudinary;
935
+ window.openImageFilePicker = function() {
936
+ isImageToolActive = true;
937
+ const fileInput = document.createElement("input");
938
+ fileInput.type = "file";
939
+ fileInput.accept = IMAGE_ACCEPT_ATTR;
940
+ fileInput.style.display = "none";
941
+ document.body.appendChild(fileInput);
942
+ let fileSelected = false;
943
+ fileInput.addEventListener("change", (event) => {
944
+ const file = event.target.files[0];
945
+ if (file) {
946
+ fileSelected = true;
947
+ handleImageUpload(file);
948
+ }
949
+ document.body.removeChild(fileInput);
950
+ });
951
+ const onFocus = () => {
952
+ window.removeEventListener("focus", onFocus);
953
+ setTimeout(() => {
954
+ if (!fileSelected) {
955
+ isImageToolActive = false;
956
+ if (window.__sketchEngine?.setActiveTool) {
957
+ window.__sketchEngine.setActiveTool("select");
958
+ }
959
+ if (fileInput.parentNode) {
960
+ document.body.removeChild(fileInput);
961
+ }
962
+ }
963
+ }, 300);
964
+ };
965
+ window.addEventListener("focus", onFocus);
966
+ fileInput.click();
967
+ };
968
+
969
+ export {
970
+ handleMouseMoveImage,
971
+ handleMouseDownImage,
972
+ handleMouseUpImage
973
+ };
974
+ //# sourceMappingURL=chunk-URB6MY46.js.map