@elixpo/lixsketch 5.5.19 → 5.5.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/Circle-6POZCUZD.js +9 -0
- package/dist/react/{CopyPaste-JMIXWUUO.js → CopyPaste-FJN7K5LS.js} +43 -14
- package/dist/react/CopyPaste-FJN7K5LS.js.map +7 -0
- package/dist/react/{EventDispatcher-H5EA4KZN.js → EventDispatcher-O7HCQQNM.js} +14 -13
- package/dist/react/{EventDispatcher-H5EA4KZN.js.map → EventDispatcher-O7HCQQNM.js.map} +1 -1
- package/dist/react/Frame-RLSMJJSK.js +11 -0
- package/dist/react/FreehandStroke-U6GYZOM4.js +9 -0
- package/dist/react/{GraphEngine-KQXHNTZQ.js → GraphEngine-MM6ALA2N.js} +105 -148
- package/dist/react/{GraphEngine-KQXHNTZQ.js.map → GraphEngine-MM6ALA2N.js.map} +2 -2
- package/dist/react/IconShape-7OPXAKLK.js +10 -0
- package/dist/react/ImageShape-LRNJQCF3.js +10 -0
- package/dist/react/Line-WWQFBNX5.js +10 -0
- package/dist/react/Rectangle-S7XUKWYD.js +9 -0
- package/dist/react/{SceneSerializer-ARTKPZVR.js → SceneSerializer-GSJR7ZHC.js} +36 -11
- package/dist/react/SceneSerializer-GSJR7ZHC.js.map +7 -0
- package/dist/react/{Selection-YJNZRBON.js → Selection-ZL3G6IAO.js} +5 -4
- package/dist/react/{SketchEngine-ZGYBIZG7.js → SketchEngine-BFLHJPCG.js} +25 -25
- package/dist/react/{UndoRedo-BZ6W4QTV.js → UndoRedo-NL7KLR5B.js} +2 -2
- package/dist/react/{ZoomPan-NYPDJBD5.js → ZoomPan-FAWA5OPS.js} +5 -1
- package/dist/react/ZoomPan-FAWA5OPS.js.map +7 -0
- package/dist/react/{arrowTool-LKFSZKXC.js → arrowTool-CNVGM6RX.js} +3 -3
- package/dist/react/{chunk-RFHMFWVR.js → chunk-2FMFOPGW.js} +5 -6
- package/dist/react/{chunk-RFHMFWVR.js.map → chunk-2FMFOPGW.js.map} +2 -2
- package/dist/react/{chunk-QYNTPZON.js → chunk-2THKDSWW.js} +9 -5
- package/dist/react/chunk-2THKDSWW.js.map +7 -0
- package/dist/react/{chunk-JOPQ27YH.js → chunk-4BLMNGTR.js} +3 -3
- package/dist/react/{chunk-C62SXFBL.js → chunk-4TAS67SL.js} +23 -22
- package/dist/react/chunk-4TAS67SL.js.map +7 -0
- package/dist/react/{chunk-FV3EPR4L.js → chunk-AH4PWNEB.js} +19 -11
- package/dist/react/chunk-AH4PWNEB.js.map +7 -0
- package/dist/react/{chunk-XCJOF6BM.js → chunk-FETTJLCV.js} +5 -1
- package/dist/react/chunk-FETTJLCV.js.map +7 -0
- package/dist/react/{chunk-JJTII2UQ.js → chunk-GC3KTQDA.js} +78 -15
- package/dist/react/chunk-GC3KTQDA.js.map +7 -0
- package/dist/react/{chunk-2V7G2PLX.js → chunk-GPIYXREE.js} +3 -3
- package/dist/react/{chunk-3VSGFP6U.js → chunk-HFQZYCHJ.js} +8 -4
- package/dist/react/chunk-HFQZYCHJ.js.map +7 -0
- package/dist/react/{chunk-C72OHQLE.js → chunk-ILGAFR56.js} +3 -3
- package/dist/react/{chunk-CF7AK4ES.js → chunk-KVAQPCUH.js} +26 -3
- package/dist/react/chunk-KVAQPCUH.js.map +7 -0
- package/dist/react/{chunk-WEWMB35W.js → chunk-OFGJJKPH.js} +3 -3
- package/dist/react/chunk-PAKA6HUX.js +59 -0
- package/dist/react/chunk-PAKA6HUX.js.map +7 -0
- package/dist/react/{chunk-HKYUJUD7.js → chunk-PXFPPO3R.js} +5 -1
- package/dist/react/chunk-PXFPPO3R.js.map +7 -0
- package/dist/react/{chunk-6G3ITAS5.js → chunk-SZD7DKYJ.js} +18 -9
- package/dist/react/chunk-SZD7DKYJ.js.map +7 -0
- package/dist/react/{chunk-N7NZLF6P.js → chunk-TKXKJNTX.js} +5 -1
- package/dist/react/chunk-TKXKJNTX.js.map +7 -0
- package/dist/react/{chunk-MYYBFZD3.js → chunk-U3TB4KRS.js} +45 -6
- package/dist/react/chunk-U3TB4KRS.js.map +7 -0
- package/dist/react/{chunk-UQBT7F4K.js → chunk-V2QTYSBK.js} +2 -2
- package/dist/react/{chunk-73VA5ALZ.js → chunk-XFQECTTF.js} +48 -19
- package/dist/react/chunk-XFQECTTF.js.map +7 -0
- package/dist/react/{chunk-O64N5JPO.js → chunk-YF3TK7SC.js} +2 -2
- package/dist/react/{circleTool-RDOIMKKV.js → circleTool-KICNRQCI.js} +4 -4
- package/dist/react/{codeTool-O2Z7ZXGP.js → codeTool-NCQ73R6M.js} +5 -4
- package/dist/react/{frameTool-Z5ZDCX6H.js → frameTool-Y6KB67DO.js} +4 -4
- package/dist/react/{freehandTool-AAQCTMVM.js → freehandTool-MMULHCQ7.js} +4 -4
- package/dist/react/{iconTool-AUHRLOO2.js → iconTool-LSW2DYGB.js} +5 -4
- package/dist/react/{imageTool-DRKDCPUV.js → imageTool-2DSR32RX.js} +5 -4
- package/dist/react/index.js +141 -66
- package/dist/react/index.js.map +3 -3
- package/dist/react/{lineTool-XLOOZE3E.js → lineTool-N5I7IGT7.js} +4 -4
- package/dist/react/{rectangleTool-AEYDDJCA.js → rectangleTool-YUT7ZK2E.js} +4 -4
- package/dist/react/{textTool-UORBAAJE.js → textTool-IQOYPELP.js} +6 -5
- package/package.json +1 -1
- package/src/core/CopyPaste.js +34 -4
- package/src/core/GraphEngine.js +129 -185
- package/src/core/SceneSerializer.js +26 -0
- package/src/core/ScreenSpaceControls.js +60 -0
- package/src/core/Selection.js +19 -11
- package/src/core/UndoRedo.js +47 -3
- package/src/core/ZoomPan.js +3 -0
- package/src/react/components/canvas/MultiSelectActions.jsx +4 -1
- package/src/react/components/sidebars/IconSidebar.jsx +14 -23
- package/src/react/components/sidebars/ImageSidebar.jsx +47 -24
- package/src/react/store/useUIStore.js +71 -14
- package/src/shapes/Circle.js +2 -0
- package/src/shapes/Frame.js +26 -2
- package/src/shapes/FreehandStroke.js +2 -0
- package/src/shapes/IconShape.js +23 -25
- package/src/shapes/ImageShape.js +6 -2
- package/src/shapes/Rectangle.js +2 -0
- package/src/tools/codeTool.js +3 -2
- package/src/tools/frameTool.js +19 -9
- package/src/tools/iconTool.js +63 -26
- package/src/tools/imageTool.js +80 -14
- package/src/tools/textTool.js +3 -1
- package/dist/react/Circle-QQMKCKFR.js +0 -8
- package/dist/react/CopyPaste-JMIXWUUO.js.map +0 -7
- package/dist/react/Frame-VADA625S.js +0 -10
- package/dist/react/FreehandStroke-BOWDNAVC.js +0 -8
- package/dist/react/IconShape-32EYI2MA.js +0 -10
- package/dist/react/ImageShape-MYP2DVVH.js +0 -10
- package/dist/react/Line-FCCF2BOB.js +0 -10
- package/dist/react/Rectangle-MZ6ZB6KM.js +0 -8
- package/dist/react/SceneSerializer-ARTKPZVR.js.map +0 -7
- package/dist/react/ZoomPan-NYPDJBD5.js.map +0 -7
- package/dist/react/chunk-3VSGFP6U.js.map +0 -7
- package/dist/react/chunk-6G3ITAS5.js.map +0 -7
- package/dist/react/chunk-73VA5ALZ.js.map +0 -7
- package/dist/react/chunk-C62SXFBL.js.map +0 -7
- package/dist/react/chunk-CF7AK4ES.js.map +0 -7
- package/dist/react/chunk-FV3EPR4L.js.map +0 -7
- package/dist/react/chunk-HKYUJUD7.js.map +0 -7
- package/dist/react/chunk-JJTII2UQ.js.map +0 -7
- package/dist/react/chunk-MYYBFZD3.js.map +0 -7
- package/dist/react/chunk-N7NZLF6P.js.map +0 -7
- package/dist/react/chunk-QYNTPZON.js.map +0 -7
- package/dist/react/chunk-XCJOF6BM.js.map +0 -7
- /package/dist/react/{Circle-QQMKCKFR.js.map → Circle-6POZCUZD.js.map} +0 -0
- /package/dist/react/{Frame-VADA625S.js.map → Frame-RLSMJJSK.js.map} +0 -0
- /package/dist/react/{FreehandStroke-BOWDNAVC.js.map → FreehandStroke-U6GYZOM4.js.map} +0 -0
- /package/dist/react/{IconShape-32EYI2MA.js.map → IconShape-7OPXAKLK.js.map} +0 -0
- /package/dist/react/{ImageShape-MYP2DVVH.js.map → ImageShape-LRNJQCF3.js.map} +0 -0
- /package/dist/react/{Line-FCCF2BOB.js.map → Line-WWQFBNX5.js.map} +0 -0
- /package/dist/react/{Rectangle-MZ6ZB6KM.js.map → Rectangle-S7XUKWYD.js.map} +0 -0
- /package/dist/react/{Selection-YJNZRBON.js.map → Selection-ZL3G6IAO.js.map} +0 -0
- /package/dist/react/{SketchEngine-ZGYBIZG7.js.map → SketchEngine-BFLHJPCG.js.map} +0 -0
- /package/dist/react/{UndoRedo-BZ6W4QTV.js.map → UndoRedo-NL7KLR5B.js.map} +0 -0
- /package/dist/react/{arrowTool-LKFSZKXC.js.map → arrowTool-CNVGM6RX.js.map} +0 -0
- /package/dist/react/{chunk-JOPQ27YH.js.map → chunk-4BLMNGTR.js.map} +0 -0
- /package/dist/react/{chunk-2V7G2PLX.js.map → chunk-GPIYXREE.js.map} +0 -0
- /package/dist/react/{chunk-C72OHQLE.js.map → chunk-ILGAFR56.js.map} +0 -0
- /package/dist/react/{chunk-WEWMB35W.js.map → chunk-OFGJJKPH.js.map} +0 -0
- /package/dist/react/{chunk-UQBT7F4K.js.map → chunk-V2QTYSBK.js.map} +0 -0
- /package/dist/react/{chunk-O64N5JPO.js.map → chunk-YF3TK7SC.js.map} +0 -0
- /package/dist/react/{circleTool-RDOIMKKV.js.map → circleTool-KICNRQCI.js.map} +0 -0
- /package/dist/react/{codeTool-O2Z7ZXGP.js.map → codeTool-NCQ73R6M.js.map} +0 -0
- /package/dist/react/{frameTool-Z5ZDCX6H.js.map → frameTool-Y6KB67DO.js.map} +0 -0
- /package/dist/react/{freehandTool-AAQCTMVM.js.map → freehandTool-MMULHCQ7.js.map} +0 -0
- /package/dist/react/{iconTool-AUHRLOO2.js.map → iconTool-LSW2DYGB.js.map} +0 -0
- /package/dist/react/{imageTool-DRKDCPUV.js.map → imageTool-2DSR32RX.js.map} +0 -0
- /package/dist/react/{lineTool-XLOOZE3E.js.map → lineTool-N5I7IGT7.js.map} +0 -0
- /package/dist/react/{rectangleTool-AEYDDJCA.js.map → rectangleTool-YUT7ZK2E.js.map} +0 -0
- /package/dist/react/{textTool-UORBAAJE.js.map → textTool-IQOYPELP.js.map} +0 -0
package/src/shapes/Rectangle.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
|
+
import { registerRotationAnchor } from '../core/ScreenSpaceControls.js';
|
|
2
3
|
// Rectangle shape class - extracted from drawSquare.js
|
|
3
4
|
// Depends on globals: svg, shapes, rough, currentShape, currentZoom, rc
|
|
4
5
|
|
|
@@ -521,6 +522,7 @@ class Rectangle {
|
|
|
521
522
|
this.rotationAnchor.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
522
523
|
this.rotationAnchor.setAttribute('style', 'pointer-events: all;');
|
|
523
524
|
this.group.appendChild(this.rotationAnchor);
|
|
525
|
+
registerRotationAnchor(this.rotationAnchor, { radius: 8, edgeY: expandedY });
|
|
524
526
|
|
|
525
527
|
this.rotationAnchor.addEventListener('mouseover', function () {
|
|
526
528
|
if (!isResizingShapeSquare && !isDraggingShapeSquare && !isRotatingShapeSquare) {
|
package/src/tools/codeTool.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
updateSelectedElement
|
|
11
11
|
} from '../core/UndoRedo.js';
|
|
12
12
|
import { cleanupAttachments, updateAttachedArrows } from './arrowTool.js';
|
|
13
|
+
import { registerRotationAnchor } from '../core/ScreenSpaceControls.js';
|
|
13
14
|
|
|
14
15
|
function getThemeStroke() { if (typeof document === "undefined") return "#fff"; return document.body && document.body.classList.contains("theme-dark") ? "#fff" : "#1a1a2e"; }
|
|
15
16
|
|
|
@@ -1068,6 +1069,7 @@ function createCodeSelectionFeedback(groupElement) {
|
|
|
1068
1069
|
rotationAnchor.style.cursor = 'grab';
|
|
1069
1070
|
rotationAnchor.setAttribute('pointer-events', 'all');
|
|
1070
1071
|
groupElement.appendChild(rotationAnchor);
|
|
1072
|
+
registerRotationAnchor(rotationAnchor, { radius: 8, edgeY: selY });
|
|
1071
1073
|
|
|
1072
1074
|
codeResizeHandles.rotate = rotationAnchor;
|
|
1073
1075
|
|
|
@@ -1150,7 +1152,7 @@ function updateCodeSelectionFeedback() {
|
|
|
1150
1152
|
|
|
1151
1153
|
const rotationAnchor = codeResizeHandles.rotate;
|
|
1152
1154
|
if (rotationAnchor) {
|
|
1153
|
-
const rotationAnchorPos = { x: selX + selWidth / 2, y: selY - 30 };
|
|
1155
|
+
const rotationAnchorPos = { x: selX + selWidth / 2, y: selY - 30 / zoom };
|
|
1154
1156
|
rotationAnchor.setAttribute('cx', rotationAnchorPos.x);
|
|
1155
1157
|
rotationAnchor.setAttribute('cy', rotationAnchorPos.y);
|
|
1156
1158
|
}
|
|
@@ -2103,4 +2105,3 @@ export {
|
|
|
2103
2105
|
getCodeLanguage,
|
|
2104
2106
|
getSelectedCodeBlock
|
|
2105
2107
|
};
|
|
2106
|
-
|
package/src/tools/frameTool.js
CHANGED
|
@@ -143,9 +143,12 @@ const handleMouseDown = (e) => {
|
|
|
143
143
|
currentFrame = null;
|
|
144
144
|
currentShape = null;
|
|
145
145
|
} else {
|
|
146
|
-
pushCreateAction(currentFrame);
|
|
147
146
|
// Check for shapes that should be contained in the new frame
|
|
148
147
|
currentFrame.updateContainedShapes(true); // Apply clipping immediately for new frames
|
|
148
|
+
pushCreateAction(currentFrame, {
|
|
149
|
+
frameCreation: true,
|
|
150
|
+
containedShapes: [...currentFrame.containedShapes]
|
|
151
|
+
});
|
|
149
152
|
|
|
150
153
|
// Auto-select the new frame and switch to selection tool
|
|
151
154
|
const placedFrame = currentFrame;
|
|
@@ -177,17 +180,24 @@ const handleMouseDown = (e) => {
|
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
// Only check frame containment for shapes that aren't already in frames
|
|
183
|
+
// Only check frame containment for shapes that aren't already in frames.
|
|
184
|
+
// Record this fallback attachment too; individual shape tools normally do
|
|
185
|
+
// it themselves, but restored/imported shapes can reach this path.
|
|
181
186
|
if (!isDragging) {
|
|
182
187
|
shapes.forEach(shape => {
|
|
183
188
|
if (shape.shapeName !== 'frame' && !shape.parentFrame) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
let targetFrame = null;
|
|
190
|
+
for (let i = shapes.length - 1; i >= 0; i--) {
|
|
191
|
+
const frame = shapes[i];
|
|
192
|
+
if (frame.shapeName === 'frame' && frame.isShapeInFrame(shape)) {
|
|
193
|
+
targetFrame = frame;
|
|
194
|
+
break;
|
|
189
195
|
}
|
|
190
|
-
}
|
|
196
|
+
}
|
|
197
|
+
if (targetFrame) {
|
|
198
|
+
targetFrame.addShapeToFrame(shape);
|
|
199
|
+
pushFrameAttachmentAction(targetFrame, shape, 'attach', null);
|
|
200
|
+
}
|
|
191
201
|
}
|
|
192
202
|
});
|
|
193
203
|
}
|
|
@@ -278,4 +288,4 @@ export {
|
|
|
278
288
|
handleMouseDown as handleMouseDownFrame,
|
|
279
289
|
handleMouseMove as handleMouseMoveFrame,
|
|
280
290
|
handleMouseUp as handleMouseUpFrame
|
|
281
|
-
};
|
|
291
|
+
};
|
package/src/tools/iconTool.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Icon tool event handlers - extracted from icons.js
|
|
3
3
|
import { pushCreateAction, pushDeleteAction, pushTransformAction, pushFrameAttachmentAction } from '../core/UndoRedo.js';
|
|
4
4
|
import { updateAttachedArrows as updateArrowsForShape, cleanupAttachments } from './arrowTool.js';
|
|
5
|
+
import { registerRotationAnchor } from '../core/ScreenSpaceControls.js';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
let isDraggingIcon = false;
|
|
@@ -257,6 +258,18 @@ const handleMouseDownIcon = async (e) => {
|
|
|
257
258
|
return;
|
|
258
259
|
}
|
|
259
260
|
|
|
261
|
+
// Helper to get inherited attribute from the SVG sub-tree
|
|
262
|
+
function getInheritedAttr(element, attrName) {
|
|
263
|
+
let curr = element;
|
|
264
|
+
while (curr && curr.nodeType === 1) {
|
|
265
|
+
if (curr.getAttribute(attrName)) {
|
|
266
|
+
return curr.getAttribute(attrName);
|
|
267
|
+
}
|
|
268
|
+
curr = curr.parentElement;
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
260
273
|
if (!isDraggingIcon || !iconToPlace || !isIconToolActive) {
|
|
261
274
|
return;
|
|
262
275
|
}
|
|
@@ -321,29 +334,50 @@ const handleMouseDownIcon = async (e) => {
|
|
|
321
334
|
backgroundRect.setAttribute('style', 'pointer-events: all; cursor: pointer;');
|
|
322
335
|
finalIconGroup.appendChild(backgroundRect);
|
|
323
336
|
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
337
|
+
const isDark = document.body && document.body.classList.contains('theme-dark');
|
|
338
|
+
const targetDarkColor = '#1a1a2e';
|
|
339
|
+
const bgDarkColor = '#15111f';
|
|
327
340
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
const applyThemeStyle = (element) => {
|
|
342
|
+
if (element.nodeType === 1) {
|
|
343
|
+
const fill = getInheritedAttr(element, 'fill');
|
|
344
|
+
const stroke = getInheritedAttr(element, 'stroke');
|
|
345
|
+
|
|
346
|
+
const normFill = fill ? fill.toLowerCase().trim() : null;
|
|
347
|
+
const normStroke = stroke ? stroke.toLowerCase().trim() : null;
|
|
348
|
+
|
|
349
|
+
// Handle fill
|
|
350
|
+
if (normFill === 'none' || normFill === 'transparent') {
|
|
351
|
+
element.setAttribute('fill', 'none');
|
|
352
|
+
} else if (!normFill || ['#000', '#000000', 'black', 'currentcolor', '#1a1a2e'].includes(normFill)) {
|
|
353
|
+
element.setAttribute('fill', isDark ? '#ffffff' : targetDarkColor);
|
|
354
|
+
} else if (['#fff', '#ffffff', 'white', '#15111f'].includes(normFill)) {
|
|
355
|
+
element.setAttribute('fill', isDark ? bgDarkColor : '#ffffff');
|
|
343
356
|
}
|
|
344
|
-
};
|
|
345
|
-
applyWhiteStyle(clonedChild);
|
|
346
357
|
|
|
358
|
+
// Handle stroke
|
|
359
|
+
if (normStroke === 'none' || normStroke === 'transparent') {
|
|
360
|
+
element.setAttribute('stroke', 'none');
|
|
361
|
+
} else if (normStroke && ['#000', '#000000', 'black', 'currentcolor', '#1a1a2e'].includes(normStroke)) {
|
|
362
|
+
element.setAttribute('stroke', isDark ? '#ffffff' : targetDarkColor);
|
|
363
|
+
} else if (normStroke && ['#fff', '#ffffff', 'white', '#15111f'].includes(normStroke)) {
|
|
364
|
+
element.setAttribute('stroke', isDark ? bgDarkColor : '#ffffff');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (let j = 0; j < element.children.length; j++) {
|
|
368
|
+
applyThemeStyle(element.children[j]);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// Apply theme styles to original elements while they are still in context
|
|
374
|
+
for (let i = 0; i < originalSvgElement.children.length; i++) {
|
|
375
|
+
applyThemeStyle(originalSvgElement.children[i]);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const allChildren = originalSvgElement.children;
|
|
379
|
+
for (let i = 0; i < allChildren.length; i++) {
|
|
380
|
+
const clonedChild = allChildren[i].cloneNode(true);
|
|
347
381
|
finalIconGroup.appendChild(clonedChild);
|
|
348
382
|
}
|
|
349
383
|
|
|
@@ -646,6 +680,7 @@ function addRotationAnchor(x, y, width, height, centerX, centerY, iconWidth, rot
|
|
|
646
680
|
rotationAnchor.setAttribute('transform', `rotate(${rotation}, ${centerX}, ${centerY})`);
|
|
647
681
|
|
|
648
682
|
svg.appendChild(rotationAnchor);
|
|
683
|
+
registerRotationAnchor(rotationAnchor, { radius: 5, edgeY: y });
|
|
649
684
|
|
|
650
685
|
rotationAnchor.addEventListener('pointerdown', startRotation);
|
|
651
686
|
rotationAnchor.addEventListener('pointerup', stopRotation);
|
|
@@ -1248,8 +1283,8 @@ function normalizeSVGSize(svgContent, fillColor = '#fff', strokeColor = null) {
|
|
|
1248
1283
|
|
|
1249
1284
|
// Only apply to shape elements, not container elements like 'g'
|
|
1250
1285
|
if (['path', 'circle', 'rect', 'polygon', 'ellipse', 'polyline', 'line'].includes(tagName)) {
|
|
1251
|
-
const currentFill = element
|
|
1252
|
-
const currentStroke = element
|
|
1286
|
+
const currentFill = getInheritedAttr(element, 'fill');
|
|
1287
|
+
const currentStroke = getInheritedAttr(element, 'stroke');
|
|
1253
1288
|
|
|
1254
1289
|
// Check if parent <g> has explicit colors - if so, don't override
|
|
1255
1290
|
let parentG = element.parentElement;
|
|
@@ -1264,19 +1299,21 @@ function normalizeSVGSize(svgContent, fillColor = '#fff', strokeColor = null) {
|
|
|
1264
1299
|
}
|
|
1265
1300
|
|
|
1266
1301
|
// Only apply white fill if:
|
|
1267
|
-
// 1. Element doesn't have explicit 'none' fill
|
|
1302
|
+
// 1. Element doesn't have explicit/inherited 'none'/'transparent' fill
|
|
1268
1303
|
// 2. Element doesn't already have a specific color (other than black/default)
|
|
1269
1304
|
// 3. Parent <g> doesn't have explicit colors
|
|
1270
|
-
if (!hasParentColor && currentFill !== 'none') {
|
|
1305
|
+
if (!hasParentColor && currentFill !== 'none' && currentFill !== 'transparent') {
|
|
1271
1306
|
// Only override if it's black, default, or unset
|
|
1272
1307
|
if (!currentFill || currentFill === '#000' || currentFill === '#000000' || currentFill === 'black' || currentFill === 'currentColor') {
|
|
1273
1308
|
element.setAttribute('fill', fillColor);
|
|
1274
1309
|
}
|
|
1310
|
+
} else if (currentFill === 'none' || currentFill === 'transparent') {
|
|
1311
|
+
element.setAttribute('fill', 'none');
|
|
1275
1312
|
}
|
|
1276
1313
|
|
|
1277
1314
|
// Handle stroke similarly
|
|
1278
1315
|
if (strokeColor && !hasParentColor) {
|
|
1279
|
-
if (currentStroke && currentStroke !== 'none') {
|
|
1316
|
+
if (currentStroke && currentStroke !== 'none' && currentStroke !== 'transparent') {
|
|
1280
1317
|
if (!currentStroke || currentStroke === '#000' || currentStroke === '#000000' || currentStroke === 'black' || currentStroke === 'currentColor') {
|
|
1281
1318
|
element.setAttribute('stroke', strokeColor);
|
|
1282
1319
|
}
|
|
@@ -1364,4 +1401,4 @@ window.__iconToolRemoveSelection = removeSelection;
|
|
|
1364
1401
|
|
|
1365
1402
|
// Legacy icon rendering disabled — React IconSidebar handles icon UI
|
|
1366
1403
|
|
|
1367
|
-
export { handleMouseDownIcon, handleMouseMoveIcon, handleMouseUpIcon, startDrag, stopDrag}
|
|
1404
|
+
export { handleMouseDownIcon, handleMouseMoveIcon, handleMouseUpIcon, startDrag, stopDrag}
|
package/src/tools/imageTool.js
CHANGED
|
@@ -4,6 +4,7 @@ import { pushCreateAction, pushDeleteAction, pushTransformAction, pushFrameAttac
|
|
|
4
4
|
import { updateAttachedArrows as updateArrowsForShape, cleanupAttachments } from './arrowTool.js';
|
|
5
5
|
import { compressImage } from '../utils/imageCompressor.js';
|
|
6
6
|
import { isAllowedImage, IMAGE_ACCEPT_ATTR } from '../utils/allowedImageTypes.js';
|
|
7
|
+
import { registerRotationAnchor } from '../core/ScreenSpaceControls.js';
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
let isDraggingImage = false;
|
|
@@ -29,10 +30,16 @@ const minImageSize = 20;
|
|
|
29
30
|
let draggedShapeInitialFrameImage = null;
|
|
30
31
|
let hoveredFrameImage = null;
|
|
31
32
|
|
|
32
|
-
// Per-room image size limit: 5MB total
|
|
33
|
-
const ROOM_IMAGE_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
34
33
|
if (!window.__roomImageBytesUsed) window.__roomImageBytesUsed = 0;
|
|
35
34
|
|
|
35
|
+
function getRoomImageLimitBytes() {
|
|
36
|
+
return Number(window.__roomImageLimitBytes) || 2 * 1024 * 1024;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function usesPersonalCloudinary() {
|
|
40
|
+
return Boolean(window.__personalCloudinary?.connected && window.__personalCloudinary?.useForUploads);
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
|
|
37
44
|
// Convert SVG element to our ImageShape class
|
|
38
45
|
function wrapImageElement(element) {
|
|
@@ -68,6 +75,37 @@ async function uploadImageToCloudinary(imageShape) {
|
|
|
68
75
|
const compressed = await compressImage(href);
|
|
69
76
|
if (signal.aborted) return;
|
|
70
77
|
|
|
78
|
+
const roomLimit = getRoomImageLimitBytes();
|
|
79
|
+
const oldSize = imageShape.element.__fileSize || 0;
|
|
80
|
+
if (!usesPersonalCloudinary() && (window.__roomImageBytesUsed || 0) - oldSize + compressed.compressedSize > roomLimit) {
|
|
81
|
+
throw new Error(`Room image limit reached (${Math.round(roomLimit / (1024 * 1024))} MB)`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (usesPersonalCloudinary()) {
|
|
85
|
+
const personalForm = new FormData();
|
|
86
|
+
personalForm.append('file', compressed.blob, `image_${Date.now()}`);
|
|
87
|
+
personalForm.append('sessionId', sessionId);
|
|
88
|
+
const personalResponse = await fetch('/api/images/personal-upload', {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
body: personalForm,
|
|
91
|
+
signal,
|
|
92
|
+
});
|
|
93
|
+
const personalData = await personalResponse.json().catch(() => ({}));
|
|
94
|
+
if (!personalResponse.ok || !personalData.url) {
|
|
95
|
+
throw new Error(personalData.error || 'Personal Cloudinary upload failed');
|
|
96
|
+
}
|
|
97
|
+
imageShape.element.setAttribute('href', personalData.url);
|
|
98
|
+
imageShape.element.setAttribute('data-href', personalData.url);
|
|
99
|
+
imageShape.element.setAttribute('data-cloudinary-id', personalData.publicId);
|
|
100
|
+
imageShape.element.setAttribute('data-storage-provider', 'user_cloudinary');
|
|
101
|
+
imageShape.element.setAttribute('data-storage-cloud-name', personalData.cloudName || '');
|
|
102
|
+
imageShape.element.__fileSize = 0;
|
|
103
|
+
imageShape.element.setAttribute('data-file-size', '0');
|
|
104
|
+
window.__roomImageBytesUsed = Math.max(0, (window.__roomImageBytesUsed || 0) - oldSize);
|
|
105
|
+
imageShape.uploadStatus = 'done';
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
71
109
|
// Step 2: Get signed upload params
|
|
72
110
|
const signRes = await fetch(`${workerUrl}/api/images/sign`, {
|
|
73
111
|
method: 'POST',
|
|
@@ -75,10 +113,14 @@ async function uploadImageToCloudinary(imageShape) {
|
|
|
75
113
|
body: JSON.stringify({
|
|
76
114
|
sessionId,
|
|
77
115
|
filename: `img_${Date.now()}`,
|
|
116
|
+
sizeBytes: compressed.compressedSize,
|
|
78
117
|
}),
|
|
79
118
|
signal,
|
|
80
119
|
});
|
|
81
|
-
if (!signRes.ok)
|
|
120
|
+
if (!signRes.ok) {
|
|
121
|
+
const errorBody = await signRes.json().catch(() => ({}));
|
|
122
|
+
throw new Error(errorBody.message || 'Failed to get upload signature');
|
|
123
|
+
}
|
|
82
124
|
const signData = await signRes.json();
|
|
83
125
|
if (signal.aborted) return;
|
|
84
126
|
|
|
@@ -99,6 +141,17 @@ async function uploadImageToCloudinary(imageShape) {
|
|
|
99
141
|
const uploadData = await uploadRes.json();
|
|
100
142
|
if (signal.aborted) return;
|
|
101
143
|
|
|
144
|
+
await fetch(`${workerUrl}/api/images/complete`, {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: { 'Content-Type': 'application/json' },
|
|
147
|
+
body: JSON.stringify({
|
|
148
|
+
sessionId,
|
|
149
|
+
publicId: uploadData.public_id || signData.publicId,
|
|
150
|
+
sizeBytes: uploadData.bytes || compressed.compressedSize,
|
|
151
|
+
}),
|
|
152
|
+
signal,
|
|
153
|
+
}).catch(() => {});
|
|
154
|
+
|
|
102
155
|
// Step 4: Replace base64 with Cloudinary URL
|
|
103
156
|
const cloudUrl = uploadData.secure_url || uploadData.url;
|
|
104
157
|
imageShape.element.setAttribute('href', cloudUrl);
|
|
@@ -106,9 +159,9 @@ async function uploadImageToCloudinary(imageShape) {
|
|
|
106
159
|
imageShape.element.setAttribute('data-cloudinary-id', uploadData.public_id);
|
|
107
160
|
|
|
108
161
|
// Update file size tracking with actual compressed size
|
|
109
|
-
const oldSize = imageShape.element.__fileSize || 0;
|
|
110
162
|
const newSize = uploadData.bytes || compressed.compressedSize;
|
|
111
163
|
imageShape.element.__fileSize = newSize;
|
|
164
|
+
imageShape.element.setAttribute('data-file-size', String(newSize));
|
|
112
165
|
window.__roomImageBytesUsed = Math.max(0, (window.__roomImageBytesUsed || 0) - oldSize + newSize);
|
|
113
166
|
|
|
114
167
|
imageShape.uploadStatus = 'done';
|
|
@@ -208,19 +261,20 @@ const handleImageUpload = async (file) => {
|
|
|
208
261
|
return;
|
|
209
262
|
}
|
|
210
263
|
|
|
211
|
-
|
|
212
|
-
|
|
264
|
+
const roomImageLimitBytes = getRoomImageLimitBytes();
|
|
265
|
+
const personalStorage = usesPersonalCloudinary();
|
|
266
|
+
if (!personalStorage && window.__roomImageBytesUsed + file.size > roomImageLimitBytes) {
|
|
213
267
|
const usedMB = (window.__roomImageBytesUsed / (1024 * 1024)).toFixed(2);
|
|
214
268
|
const fileMB = (file.size / (1024 * 1024)).toFixed(2);
|
|
215
|
-
alert(`Room image limit reached (
|
|
269
|
+
alert(`Room image limit reached (${Math.round(roomImageLimitBytes / (1024 * 1024))} MB). Used: ${usedMB} MB, this file: ${fileMB} MB. Delete some images to free space.`);
|
|
216
270
|
isImageToolActive = false;
|
|
217
271
|
return;
|
|
218
272
|
}
|
|
219
273
|
|
|
220
|
-
const maxSize =
|
|
274
|
+
const maxSize = personalStorage ? 20 * 1024 * 1024 : roomImageLimitBytes;
|
|
221
275
|
if (file.size > maxSize) {
|
|
222
276
|
console.error('File size too large');
|
|
223
|
-
alert(
|
|
277
|
+
alert(`Image file is too large. Please select an image smaller than ${Math.round(maxSize / (1024 * 1024))} MB.`);
|
|
224
278
|
return;
|
|
225
279
|
}
|
|
226
280
|
|
|
@@ -470,6 +524,7 @@ const handleMouseDownImage = async (e) => {
|
|
|
470
524
|
// Track image size for room limit
|
|
471
525
|
const placedFileSize = window.__pendingImageFileSize || 0;
|
|
472
526
|
finalImage.__fileSize = placedFileSize;
|
|
527
|
+
finalImage.setAttribute('data-file-size', String(placedFileSize));
|
|
473
528
|
window.__roomImageBytesUsed = (window.__roomImageBytesUsed || 0) + placedFileSize;
|
|
474
529
|
window.__pendingImageFileSize = 0;
|
|
475
530
|
|
|
@@ -537,8 +592,8 @@ const handleMouseUpImage = (e) => {
|
|
|
537
592
|
}
|
|
538
593
|
};
|
|
539
594
|
|
|
540
|
-
function selectImage(event) {
|
|
541
|
-
if (!isSelectionToolActive) return;
|
|
595
|
+
function selectImage(event, force = false) {
|
|
596
|
+
if (!isSelectionToolActive && !force) return;
|
|
542
597
|
|
|
543
598
|
event.stopPropagation(); // Prevent click from propagating to the SVG
|
|
544
599
|
|
|
@@ -705,6 +760,7 @@ function addRotationAnchor(x, y, width, height, centerX, centerY) {
|
|
|
705
760
|
rotationAnchor.setAttribute('transform', `rotate(${imageRotation}, ${centerX}, ${centerY})`);
|
|
706
761
|
|
|
707
762
|
svg.appendChild(rotationAnchor);
|
|
763
|
+
registerRotationAnchor(rotationAnchor, { radius: 5, edgeY: y });
|
|
708
764
|
|
|
709
765
|
// Add event listeners for rotation
|
|
710
766
|
rotationAnchor.addEventListener('pointerdown', startRotation);
|
|
@@ -1268,9 +1324,11 @@ function deleteCurrentImage() {
|
|
|
1268
1324
|
const match = imgHref.match(/\/upload\/(?:v\d+\/)?(lixsketch\/.+?)(?:\.\w+)?$/);
|
|
1269
1325
|
if (match) {
|
|
1270
1326
|
const publicId = match[1];
|
|
1327
|
+
const isPersonal = selectedImage.getAttribute('data-storage-provider') === 'user_cloudinary';
|
|
1271
1328
|
const workerUrl = window.__WORKER_URL;
|
|
1272
|
-
|
|
1273
|
-
|
|
1329
|
+
const deleteUrl = isPersonal ? '/api/images/personal-delete' : (workerUrl ? `${workerUrl}/api/images/delete` : null);
|
|
1330
|
+
if (deleteUrl) {
|
|
1331
|
+
fetch(deleteUrl, {
|
|
1274
1332
|
method: 'DELETE',
|
|
1275
1333
|
headers: { 'Content-Type': 'application/json' },
|
|
1276
1334
|
body: JSON.stringify({ publicId }),
|
|
@@ -1319,6 +1377,14 @@ document.addEventListener('keydown', (e) => {
|
|
|
1319
1377
|
// Expose upload pipeline globally so generated/pasted images can use it
|
|
1320
1378
|
window.uploadImageToCloudinary = uploadImageToCloudinary;
|
|
1321
1379
|
|
|
1380
|
+
// Programmatic selection bridge used by generated vector-image shapes.
|
|
1381
|
+
// Keeping the selection state in this module prevents a second, divergent
|
|
1382
|
+
// implementation of image dragging and screen-space resize controls.
|
|
1383
|
+
window.__selectImageElement = function(element, { force = false } = {}) {
|
|
1384
|
+
if (!element) return;
|
|
1385
|
+
selectImage({ target: element, stopPropagation: () => {} }, force);
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1322
1388
|
// Window bridge: allow React UI to trigger the file picker
|
|
1323
1389
|
window.openImageFilePicker = function() {
|
|
1324
1390
|
isImageToolActive = true;
|
|
@@ -1361,4 +1427,4 @@ window.openImageFilePicker = function() {
|
|
|
1361
1427
|
fileInput.click();
|
|
1362
1428
|
};
|
|
1363
1429
|
|
|
1364
|
-
export { handleMouseDownImage, handleMouseMoveImage, handleMouseUpImage };
|
|
1430
|
+
export { handleMouseDownImage, handleMouseMoveImage, handleMouseUpImage };
|
package/src/tools/textTool.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
updateSelectedElement
|
|
12
12
|
} from '../core/UndoRedo.js';
|
|
13
13
|
import { cleanupAttachments, updateAttachedArrows } from './arrowTool.js';
|
|
14
|
+
import { registerRotationAnchor } from '../core/ScreenSpaceControls.js';
|
|
14
15
|
|
|
15
16
|
function getThemeStroke() { if (typeof document === "undefined") return "#fff"; return document.body && document.body.classList.contains("theme-dark") ? "#fff" : "#1a1a2e"; }
|
|
16
17
|
import {
|
|
@@ -516,6 +517,7 @@ function createSelectionFeedback(groupElement) {
|
|
|
516
517
|
rotationAnchor.style.cursor = 'grab';
|
|
517
518
|
rotationAnchor.setAttribute('pointer-events', 'all');
|
|
518
519
|
groupElement.appendChild(rotationAnchor);
|
|
520
|
+
registerRotationAnchor(rotationAnchor, { radius: 8, edgeY: selY });
|
|
519
521
|
|
|
520
522
|
resizeHandles.rotate = rotationAnchor;
|
|
521
523
|
|
|
@@ -593,7 +595,7 @@ function updateSelectionFeedback() {
|
|
|
593
595
|
|
|
594
596
|
const rotationAnchor = resizeHandles.rotate;
|
|
595
597
|
if (rotationAnchor) {
|
|
596
|
-
const rotationAnchorPos = { x: selX + selWidth / 2, y: selY - 30 };
|
|
598
|
+
const rotationAnchorPos = { x: selX + selWidth / 2, y: selY - 30 / zoom2 };
|
|
597
599
|
rotationAnchor.setAttribute('cx', rotationAnchorPos.x);
|
|
598
600
|
rotationAnchor.setAttribute('cy', rotationAnchorPos.y);
|
|
599
601
|
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/core/CopyPaste.js"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable */\n// Centralized Copy/Paste system for ALL shape types\n// Supports single and multi-selection copy/paste\n// Pastes at mouse pointer position with offset to prevent stacking\n\nimport { pushCreateAction } from './UndoRedo.js';\nimport { Rectangle } from '../shapes/Rectangle.js';\nimport { Circle } from '../shapes/Circle.js';\nimport { Line } from '../shapes/Line.js';\nimport { Arrow } from '../shapes/Arrow.js';\nimport { FreehandStroke } from '../shapes/FreehandStroke.js';\nimport { Frame } from '../shapes/Frame.js';\nimport { TextShape } from '../shapes/TextShape.js';\nimport { CodeShape } from '../shapes/CodeShape.js';\nimport { ImageShape } from '../shapes/ImageShape.js';\nimport { isAllowedImage, isAllowedImageDataUrl } from '../utils/allowedImageTypes.js';\nimport { compressImage } from '../utils/imageCompressor.js';\nimport { IconShape } from '../shapes/IconShape.js';\n\n// Clipboard storage\nlet clipboard = null; // { shapes: [...clonedData], centerX, centerY }\nlet pasteCount = 0; // Increments per paste to offset repeated pastes\n\n// Last known mouse position in SVG coordinates\nlet lastMouseSVG = { x: 0, y: 0 };\n\nfunction getSVGElement() {\n return document.getElementById('freehand-canvas');\n}\n\nfunction getSVGCoordsFromMouse(e) {\n const svgEl = getSVGElement();\n if (!svgEl) return { x: 0, y: 0 };\n const viewBox = svgEl.viewBox.baseVal;\n const rect = svgEl.getBoundingClientRect();\n const mouseX = e.clientX - rect.left;\n const mouseY = e.clientY - rect.top;\n const svgX = viewBox.x + (mouseX / rect.width) * viewBox.width;\n const svgY = viewBox.y + (mouseY / rect.height) * viewBox.height;\n return { x: svgX, y: svgY };\n}\n\nfunction cloneOptions(options) {\n return JSON.parse(JSON.stringify(options));\n}\n\n// ============================================================\n// SERIALIZE: Extract copyable data from a shape\n// ============================================================\nfunction serializeShape(shape) {\n switch (shape.shapeName) {\n case 'rectangle':\n return {\n type: 'rectangle',\n x: shape.x,\n y: shape.y,\n width: shape.width,\n height: shape.height,\n rotation: shape.rotation,\n options: cloneOptions(shape.options)\n };\n\n case 'circle':\n return {\n type: 'circle',\n x: shape.x,\n y: shape.y,\n rx: shape.rx,\n ry: shape.ry,\n rotation: shape.rotation,\n options: cloneOptions(shape.options)\n };\n\n case 'line':\n return {\n type: 'line',\n startPoint: { x: shape.startPoint.x, y: shape.startPoint.y },\n endPoint: { x: shape.endPoint.x, y: shape.endPoint.y },\n controlPoint: shape.controlPoint ? { x: shape.controlPoint.x, y: shape.controlPoint.y } : null,\n isCurved: shape.isCurved || false,\n options: cloneOptions(shape.options)\n };\n\n case 'arrow':\n return {\n type: 'arrow',\n startPoint: { x: shape.startPoint.x, y: shape.startPoint.y },\n endPoint: { x: shape.endPoint.x, y: shape.endPoint.y },\n controlPoint1: shape.controlPoint1 ? { x: shape.controlPoint1.x, y: shape.controlPoint1.y } : null,\n controlPoint2: shape.controlPoint2 ? { x: shape.controlPoint2.x, y: shape.controlPoint2.y } : null,\n options: cloneOptions(shape.options),\n arrowOutlineStyle: shape.arrowOutlineStyle,\n arrowHeadStyle: shape.arrowHeadStyle,\n arrowCurved: shape.arrowCurved,\n arrowCurveAmount: shape.arrowCurveAmount\n };\n\n case 'freehandStroke':\n return {\n type: 'freehandStroke',\n points: JSON.parse(JSON.stringify(shape.points)),\n rotation: shape.rotation,\n options: cloneOptions(shape.options)\n };\n\n case 'frame':\n return {\n type: 'frame',\n x: shape.x,\n y: shape.y,\n width: shape.width,\n height: shape.height,\n rotation: shape.rotation,\n frameName: shape.frameName,\n options: cloneOptions(shape.options)\n };\n\n case 'text': {\n const group = shape.group;\n const textEl = group.querySelector('text');\n return {\n type: 'text',\n x: shape.x,\n y: shape.y,\n rotation: shape.rotation,\n groupHTML: group.cloneNode(true).outerHTML\n };\n }\n\n case 'code': {\n const group = shape.group;\n return {\n type: 'code',\n x: shape.x,\n y: shape.y,\n rotation: shape.rotation,\n groupHTML: group.cloneNode(true).outerHTML\n };\n }\n\n case 'image': {\n const el = shape.element;\n return {\n type: 'image',\n x: shape.x,\n y: shape.y,\n width: shape.width,\n height: shape.height,\n rotation: shape.rotation,\n href: el.getAttribute('href') || el.getAttributeNS('http://www.w3.org/1999/xlink', 'href') || ''\n };\n }\n\n case 'icon': {\n const el = shape.element;\n return {\n type: 'icon',\n x: shape.x,\n y: shape.y,\n width: shape.width,\n height: shape.height,\n rotation: shape.rotation,\n elementHTML: el.cloneNode(true).outerHTML,\n viewboxWidth: parseFloat(el.getAttribute('data-viewbox-width')) || 24,\n viewboxHeight: parseFloat(el.getAttribute('data-viewbox-height')) || 24\n };\n }\n\n default:\n console.warn('Copy not supported for shape type:', shape.shapeName);\n return null;\n }\n}\n\n// ============================================================\n// Get bounding center of a serialized shape\n// ============================================================\nfunction getShapeCenter(data) {\n switch (data.type) {\n case 'rectangle':\n case 'frame':\n return { x: data.x + data.width / 2, y: data.y + data.height / 2 };\n case 'circle':\n return { x: data.x, y: data.y };\n case 'line':\n return {\n x: (data.startPoint.x + data.endPoint.x) / 2,\n y: (data.startPoint.y + data.endPoint.y) / 2\n };\n case 'arrow':\n return {\n x: (data.startPoint.x + data.endPoint.x) / 2,\n y: (data.startPoint.y + data.endPoint.y) / 2\n };\n case 'freehandStroke': {\n if (!data.points.length) return { x: 0, y: 0 };\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n data.points.forEach(p => {\n if (p[0] < minX) minX = p[0];\n if (p[1] < minY) minY = p[1];\n if (p[0] > maxX) maxX = p[0];\n if (p[1] > maxY) maxY = p[1];\n });\n return { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };\n }\n case 'text':\n case 'code':\n return { x: data.x, y: data.y };\n case 'image':\n case 'icon':\n return { x: data.x + data.width / 2, y: data.y + data.height / 2 };\n default:\n return { x: 0, y: 0 };\n }\n}\n\n// ============================================================\n// DESERIALIZE: Create a new shape from copied data at offset\n// ============================================================\nfunction createShapeFromData(data, offsetX, offsetY) {\n const svgEl = getSVGElement();\n if (!svgEl) return null;\n\n switch (data.type) {\n case 'rectangle': {\n const newRect = new Rectangle(\n data.x + offsetX,\n data.y + offsetY,\n data.width,\n data.height,\n cloneOptions(data.options)\n );\n newRect.rotation = data.rotation;\n newRect.draw();\n return newRect;\n }\n\n case 'circle': {\n const newCircle = new Circle(\n data.x + offsetX,\n data.y + offsetY,\n data.rx,\n data.ry,\n cloneOptions(data.options)\n );\n newCircle.rotation = data.rotation;\n newCircle.draw();\n return newCircle;\n }\n\n case 'line': {\n const newLine = new Line(\n { x: data.startPoint.x + offsetX, y: data.startPoint.y + offsetY },\n { x: data.endPoint.x + offsetX, y: data.endPoint.y + offsetY },\n cloneOptions(data.options)\n );\n if (data.isCurved && data.controlPoint) {\n newLine.isCurved = true;\n newLine.controlPoint = { x: data.controlPoint.x + offsetX, y: data.controlPoint.y + offsetY };\n }\n newLine.draw();\n return newLine;\n }\n\n case 'arrow': {\n const opts = {\n ...cloneOptions(data.options),\n arrowOutlineStyle: data.arrowOutlineStyle,\n arrowHeadStyle: data.arrowHeadStyle,\n arrowCurved: data.arrowCurved,\n arrowCurveAmount: data.arrowCurveAmount,\n controlPoint1: data.controlPoint1 ? { x: data.controlPoint1.x + offsetX, y: data.controlPoint1.y + offsetY } : null,\n controlPoint2: data.controlPoint2 ? { x: data.controlPoint2.x + offsetX, y: data.controlPoint2.y + offsetY } : null\n };\n const newArrow = new Arrow(\n { x: data.startPoint.x + offsetX, y: data.startPoint.y + offsetY },\n { x: data.endPoint.x + offsetX, y: data.endPoint.y + offsetY },\n opts\n );\n return newArrow;\n }\n\n case 'freehandStroke': {\n const offsetPoints = data.points.map(p => [\n p[0] + offsetX,\n p[1] + offsetY,\n p[2] || 0.5\n ]);\n const newStroke = new FreehandStroke(offsetPoints, cloneOptions(data.options));\n newStroke.rotation = data.rotation;\n newStroke.draw();\n return newStroke;\n }\n\n case 'frame': {\n const newFrame = new Frame(\n data.x + offsetX,\n data.y + offsetY,\n data.width,\n data.height,\n { ...cloneOptions(data.options), frameName: data.frameName, rotation: data.rotation }\n );\n newFrame.rotation = data.rotation;\n return newFrame;\n }\n\n case 'text': {\n // Parse the cloned HTML and insert into SVG\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = data.groupHTML;\n const clonedGroup = tempDiv.firstElementChild;\n if (!clonedGroup) return null;\n\n // Generate new unique ID\n const newID = `text-${String(Date.now()).slice(0, 8)}-${Math.floor(Math.random() * 10000)}`;\n clonedGroup.setAttribute('id', newID);\n const textChild = clonedGroup.querySelector('text');\n if (textChild) textChild.setAttribute('id', `${newID}-text`);\n\n // Update position\n const newX = data.x + offsetX;\n const newY = data.y + offsetY;\n const rotation = data.rotation || 0;\n if (textChild && rotation) {\n try {\n const bbox = { x: 0, y: 0, width: 100, height: 30 }; // approximate until appended\n clonedGroup.setAttribute('transform', `translate(${newX}, ${newY}) rotate(${rotation}, ${bbox.width / 2}, ${bbox.height / 2})`);\n } catch (e) {\n clonedGroup.setAttribute('transform', `translate(${newX}, ${newY})`);\n }\n } else {\n clonedGroup.setAttribute('transform', `translate(${newX}, ${newY})`);\n }\n clonedGroup.setAttribute('data-x', newX);\n clonedGroup.setAttribute('data-y', newY);\n\n svgEl.appendChild(clonedGroup);\n\n const textShape = new TextShape(clonedGroup);\n return textShape;\n }\n\n case 'code': {\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = data.groupHTML;\n const clonedGroup = tempDiv.firstElementChild;\n if (!clonedGroup) return null;\n\n const newID = `code-${String(Date.now()).slice(0, 8)}-${Math.floor(Math.random() * 10000)}`;\n clonedGroup.setAttribute('id', newID);\n const codeChild = clonedGroup.querySelector('text');\n if (codeChild) codeChild.setAttribute('id', `${newID}-code`);\n\n const newX = data.x + offsetX;\n const newY = data.y + offsetY;\n const rotation = data.rotation || 0;\n if (rotation) {\n clonedGroup.setAttribute('transform', `translate(${newX}, ${newY}) rotate(${rotation}, 0, 0)`);\n } else {\n clonedGroup.setAttribute('transform', `translate(${newX}, ${newY})`);\n }\n clonedGroup.setAttribute('data-x', newX);\n clonedGroup.setAttribute('data-y', newY);\n\n svgEl.appendChild(clonedGroup);\n\n const codeShape = new CodeShape(clonedGroup);\n return codeShape;\n }\n\n case 'image': {\n const imgEl = document.createElementNS('http://www.w3.org/2000/svg', 'image');\n const newX = data.x + offsetX;\n const newY = data.y + offsetY;\n imgEl.setAttribute('x', newX);\n imgEl.setAttribute('y', newY);\n imgEl.setAttribute('width', data.width);\n imgEl.setAttribute('height', data.height);\n imgEl.setAttribute('href', data.href);\n imgEl.setAttribute('data-shape-x', newX);\n imgEl.setAttribute('data-shape-y', newY);\n imgEl.setAttribute('data-shape-width', data.width);\n imgEl.setAttribute('data-shape-height', data.height);\n imgEl.setAttribute('preserveAspectRatio', 'none');\n imgEl.setAttribute('style', 'cursor: pointer;');\n\n // ImageShape constructor moves element into a group and appends to svg\n svgEl.appendChild(imgEl);\n const imageShape = new ImageShape(imgEl);\n imageShape.rotation = data.rotation;\n return imageShape;\n }\n\n case 'icon': {\n // Parse inside an SVG document. Parsing a copied <g> through an\n // HTML <div> creates it in the HTML namespace, so it can be added\n // to shapes[] but the SVG renderer will not paint it.\n const parser = new DOMParser();\n const parsed = parser.parseFromString(\n `<svg xmlns=\"http://www.w3.org/2000/svg\">${data.elementHTML}</svg>`,\n 'image/svg+xml'\n );\n const parsedGroup = parsed.querySelector('g');\n if (!parsedGroup || parsed.querySelector('parsererror')) return null;\n const clonedEl = svgEl.ownerDocument.importNode(parsedGroup, true);\n\n const newX = data.x + offsetX;\n const newY = data.y + offsetY;\n const newID = `icon-${String(Date.now()).slice(0, 8)}-${Math.floor(Math.random() * 10000)}`;\n clonedEl.setAttribute('id', newID);\n clonedEl.setAttribute('x', newX);\n clonedEl.setAttribute('y', newY);\n clonedEl.setAttribute('data-shape-x', newX);\n clonedEl.setAttribute('data-shape-y', newY);\n clonedEl.setAttribute('data-shape-width', data.width);\n clonedEl.setAttribute('data-shape-height', data.height);\n clonedEl.setAttribute('data-shape-rotation', data.rotation);\n clonedEl.setAttribute('data-viewbox-width', data.viewboxWidth);\n clonedEl.setAttribute('data-viewbox-height', data.viewboxHeight);\n\n // Recalculate transform\n const scale = data.width / Math.max(data.viewboxWidth, data.viewboxHeight);\n const localCenterX = data.width / 2 / scale;\n const localCenterY = data.height / 2 / scale;\n clonedEl.setAttribute('transform', `translate(${newX}, ${newY}) scale(${scale}) rotate(${data.rotation}, ${localCenterX}, ${localCenterY})`);\n\n svgEl.appendChild(clonedEl);\n\n const iconShape = new IconShape(clonedEl);\n return iconShape;\n }\n\n default:\n return null;\n }\n}\n\n// ============================================================\n// COPY: Serialize currently selected shapes\n// ============================================================\nfunction copySelected() {\n const shapesToCopy = [];\n\n // Check multi-selection first\n if (window.multiSelection && window.multiSelection.selectedShapes && window.multiSelection.selectedShapes.size > 0) {\n window.multiSelection.selectedShapes.forEach(shape => {\n const data = serializeShape(shape);\n if (data) shapesToCopy.push(data);\n });\n }\n // Fall back to single currentShape\n else if (typeof currentShape !== 'undefined' && currentShape && currentShape.isSelected) {\n const data = serializeShape(currentShape);\n if (data) shapesToCopy.push(data);\n }\n\n if (shapesToCopy.length === 0) return false;\n\n // Calculate center of all copied shapes\n let totalX = 0, totalY = 0;\n shapesToCopy.forEach(d => {\n const c = getShapeCenter(d);\n totalX += c.x;\n totalY += c.y;\n });\n const centerX = totalX / shapesToCopy.length;\n const centerY = totalY / shapesToCopy.length;\n\n clipboard = {\n shapes: shapesToCopy,\n centerX,\n centerY\n };\n pasteCount = 0;\n\n return true;\n}\n\n// ============================================================\n// PASTE: Create new shapes from clipboard at mouse position\n// ============================================================\nfunction pasteClipboard() {\n if (!clipboard || clipboard.shapes.length === 0) return false;\n\n pasteCount++;\n const baseOffset = 20 * pasteCount;\n\n // Calculate paste position: center the group on the mouse pointer\n const targetX = lastMouseSVG.x;\n const targetY = lastMouseSVG.y;\n const groupOffsetX = targetX - clipboard.centerX + baseOffset;\n const groupOffsetY = targetY - clipboard.centerY + baseOffset;\n\n // Deselect all currently selected shapes\n if (typeof shapes !== 'undefined' && Array.isArray(shapes)) {\n shapes.forEach(s => {\n if (s.isSelected && typeof s.removeSelection === 'function') {\n s.removeSelection();\n s.isSelected = false;\n }\n });\n }\n if (window.multiSelection) {\n window.multiSelection.clearSelection();\n }\n if (typeof currentShape !== 'undefined') {\n currentShape = null;\n }\n if (typeof disableAllSideBars === 'function') {\n disableAllSideBars();\n }\n\n const pastedShapes = [];\n\n clipboard.shapes.forEach(data => {\n const newShape = createShapeFromData(data, groupOffsetX, groupOffsetY);\n if (newShape) {\n if (typeof shapes !== 'undefined' && Array.isArray(shapes)) {\n shapes.push(newShape);\n }\n pushCreateAction(newShape);\n pastedShapes.push(newShape);\n }\n });\n\n // Select the pasted shapes\n if (pastedShapes.length === 1) {\n const s = pastedShapes[0];\n s.isSelected = true;\n currentShape = s;\n if (typeof s.addAnchors === 'function') {\n s.addAnchors();\n } else if (typeof s.selectShape === 'function') {\n s.selectShape();\n }\n } else if (pastedShapes.length > 1 && window.multiSelection) {\n pastedShapes.forEach(s => {\n window.multiSelection.addShape(s);\n });\n window.multiSelection.updateControls();\n }\n\n return true;\n}\n\n// ============================================================\n// KEYBOARD HANDLER: Ctrl+C / Ctrl+V\n// ============================================================\nfunction handleCopyPasteKeydown(e) {\n // Don't intercept if user is typing in an input/textarea/contenteditable\n const tag = (e.target.tagName || '').toLowerCase();\n if (tag === 'input' || tag === 'textarea' || e.target.isContentEditable) return;\n\n if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'c') {\n if (copySelected()) {\n e.preventDefault();\n }\n }\n\n if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'v') {\n if (pasteClipboard()) {\n e.preventDefault();\n }\n }\n}\n\n// Track mouse position for paste placement\nfunction handleMouseMoveForPaste(e) {\n const svgEl = getSVGElement();\n if (!svgEl) return;\n const viewBox = svgEl.viewBox.baseVal;\n const rect = svgEl.getBoundingClientRect();\n const mouseX = e.clientX - rect.left;\n const mouseY = e.clientY - rect.top;\n lastMouseSVG.x = viewBox.x + (mouseX / rect.width) * viewBox.width;\n lastMouseSVG.y = viewBox.y + (mouseY / rect.height) * viewBox.height;\n}\n\n// ============================================================\n// PASTE IMAGE FROM CLIPBOARD (external images, screenshots)\n// ============================================================\nfunction handlePasteEvent(e) {\n // Don't intercept if user is typing\n const tag = (e.target.tagName || '').toLowerCase();\n if (tag === 'input' || tag === 'textarea' || e.target.isContentEditable) return;\n\n const items = e.clipboardData?.items;\n if (!items) return;\n\n for (const item of items) {\n if (item.type.startsWith('image/')) {\n e.preventDefault();\n const blob = item.getAsFile();\n if (!blob) return;\n\n // Validate against the canonical allowlist \u2014 clipboard sources\n // can include animated GIFs / HEIC / TIFF / arbitrary mime types.\n if (!isAllowedImage(blob)) {\n console.warn('[CopyPaste] Rejected pasted image type:', blob.type);\n return;\n }\n\n const reader = new FileReader();\n reader.onload = async (ev) => {\n const rawDataUrl = ev.target.result;\n const isSvg = (blob.type || '').toLowerCase() === 'image/svg+xml';\n let placedDataUrl = rawDataUrl;\n if (!isSvg) {\n try {\n const compressed = await compressImage(rawDataUrl);\n if (compressed?.dataUrl) placedDataUrl = compressed.dataUrl;\n } catch (err) {\n console.warn('[CopyPaste] Pre-placement compression failed, using raw:', err);\n }\n }\n placeImageFromDataUrl(placedDataUrl);\n };\n reader.readAsDataURL(blob);\n return; // only handle first image\n }\n }\n}\n\nfunction placeImageFromDataUrl(dataUrl) {\n const svgEl = getSVGElement();\n if (!svgEl || !window.ImageShape) return;\n\n const img = new Image();\n img.onload = () => {\n const aspectRatio = img.height / img.width;\n const displayW = Math.min(400, img.width);\n const displayH = displayW * aspectRatio;\n\n // Place at center of current viewport\n const vb = svgEl.viewBox.baseVal;\n const cx = vb.x + vb.width / 2;\n const cy = vb.y + vb.height / 2;\n\n const imgEl = document.createElementNS('http://www.w3.org/2000/svg', 'image');\n imgEl.setAttribute('href', dataUrl);\n imgEl.setAttribute('x', cx - displayW / 2);\n imgEl.setAttribute('y', cy - displayH / 2);\n imgEl.setAttribute('width', displayW);\n imgEl.setAttribute('height', displayH);\n imgEl.setAttribute('data-shape-x', cx - displayW / 2);\n imgEl.setAttribute('data-shape-y', cy - displayH / 2);\n imgEl.setAttribute('data-shape-width', displayW);\n imgEl.setAttribute('data-shape-height', displayH);\n imgEl.setAttribute('type', 'image');\n imgEl.setAttribute('preserveAspectRatio', 'none');\n\n svgEl.appendChild(imgEl);\n const imageShape = new ImageShape(imgEl);\n if (window.shapes) window.shapes.push(imageShape);\n if (window.pushCreateAction) window.pushCreateAction(imageShape);\n\n // Compress and upload to Cloudinary\n if (window.uploadImageToCloudinary) {\n window.uploadImageToCloudinary(imageShape);\n }\n\n console.log('[CopyPaste] Pasted image from clipboard');\n };\n img.src = dataUrl;\n}\n\n// ============================================================\n// INIT: Set up event listeners\n// ============================================================\nfunction initCopyPaste() {\n document.addEventListener('keydown', handleCopyPasteKeydown);\n document.addEventListener('pointermove', handleMouseMoveForPaste);\n document.addEventListener('paste', handlePasteEvent);\n\n // Expose for context menu\n window.copySelected = copySelected;\n window.pasteClipboard = pasteClipboard;\n}\n\nexport { initCopyPaste, copySelected, pasteClipboard };\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|