@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/core/Selection.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// Multi-selection system - copied from selection.js
|
|
3
3
|
|
|
4
4
|
import { cleanupAttachments } from '../tools/arrowTool.js';
|
|
5
|
-
import { pushTransformAction, pushFrameAttachmentAction, pushDeleteAction } from './UndoRedo.js';
|
|
5
|
+
import { pushCreateAction, pushTransformAction, pushFrameAttachmentAction, pushDeleteAction } from './UndoRedo.js';
|
|
6
6
|
import { calculateSnap, clearSnapGuides } from './SnapGuides.js';
|
|
7
|
+
import { registerRotationAnchor } from './ScreenSpaceControls.js';
|
|
7
8
|
|
|
8
9
|
let isMultiSelecting = false;
|
|
9
10
|
let multiSelectionStart = { x: 0, y: 0 };
|
|
@@ -545,6 +546,12 @@ class MultiSelection {
|
|
|
545
546
|
|
|
546
547
|
this.group.appendChild(this.rotationLine);
|
|
547
548
|
this.group.appendChild(this.rotationAnchor);
|
|
549
|
+
registerRotationAnchor(this.rotationAnchor, {
|
|
550
|
+
radius: 8,
|
|
551
|
+
edgeY: y,
|
|
552
|
+
line: this.rotationLine,
|
|
553
|
+
lineEnd: '1',
|
|
554
|
+
});
|
|
548
555
|
}
|
|
549
556
|
|
|
550
557
|
removeControls() {
|
|
@@ -1936,6 +1943,11 @@ function deleteSelectedShapes() {
|
|
|
1936
1943
|
function frameSelectedShapes() {
|
|
1937
1944
|
if (multiSelection.selectedShapes.size < 2) return;
|
|
1938
1945
|
|
|
1946
|
+
const shapesToFrame = Array.from(multiSelection.selectedShapes);
|
|
1947
|
+
// Frames cannot be nested through the Frame it action, and objects that
|
|
1948
|
+
// already belong to a frame must first be detached explicitly.
|
|
1949
|
+
if (shapesToFrame.some(shape => shape?.shapeName === 'frame' || shape?.parentFrame)) return;
|
|
1950
|
+
|
|
1939
1951
|
const bounds = multiSelection.getBounds();
|
|
1940
1952
|
if (!bounds) return;
|
|
1941
1953
|
|
|
@@ -1955,17 +1967,7 @@ function frameSelectedShapes() {
|
|
|
1955
1967
|
const frame = new FrameClass(fx, fy, fw, fh);
|
|
1956
1968
|
shapes.push(frame);
|
|
1957
1969
|
|
|
1958
|
-
// Push undo action for frame creation
|
|
1959
|
-
if (window.historyStack) {
|
|
1960
|
-
window.historyStack.push({
|
|
1961
|
-
type: window.ACTION_CREATE || 'create',
|
|
1962
|
-
shape: frame,
|
|
1963
|
-
shapeName: 'frame'
|
|
1964
|
-
});
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
1970
|
// Add each selected shape into the frame
|
|
1968
|
-
const shapesToFrame = Array.from(multiSelection.selectedShapes);
|
|
1969
1971
|
multiSelection.clearSelection();
|
|
1970
1972
|
|
|
1971
1973
|
for (const shape of shapesToFrame) {
|
|
@@ -1989,6 +1991,12 @@ function frameSelectedShapes() {
|
|
|
1989
1991
|
}
|
|
1990
1992
|
}
|
|
1991
1993
|
|
|
1994
|
+
// Record after reordering so redo restores the same hit-test/layer index.
|
|
1995
|
+
pushCreateAction(frame, {
|
|
1996
|
+
frameCreation: true,
|
|
1997
|
+
containedShapes: shapesToFrame
|
|
1998
|
+
});
|
|
1999
|
+
|
|
1992
2000
|
// Select the new frame
|
|
1993
2001
|
if (typeof frame.selectFrame === 'function') {
|
|
1994
2002
|
frame.selectFrame();
|
package/src/core/UndoRedo.js
CHANGED
|
@@ -34,11 +34,17 @@ export function updateSelectedElement(element) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// Call this after creating a new shape (e.g., Rectangle)
|
|
37
|
-
export function pushCreateAction(shape) {
|
|
38
|
-
|
|
37
|
+
export function pushCreateAction(shape, meta = null) {
|
|
38
|
+
const action = {
|
|
39
39
|
type: 'create',
|
|
40
40
|
shape: shape
|
|
41
|
-
}
|
|
41
|
+
};
|
|
42
|
+
if (meta?.frameCreation && shape?.shapeName === 'frame') {
|
|
43
|
+
action.frameCreation = true;
|
|
44
|
+
action.containedShapes = [...(meta.containedShapes || shape.containedShapes || [])];
|
|
45
|
+
action.shapeIndex = Array.isArray(shapes) ? shapes.indexOf(shape) : -1;
|
|
46
|
+
}
|
|
47
|
+
undoStack.push(action);
|
|
42
48
|
|
|
43
49
|
// Clear redo stack when new action is performed
|
|
44
50
|
redoStack.length = 0;
|
|
@@ -567,6 +573,23 @@ export function undo() {
|
|
|
567
573
|
}
|
|
568
574
|
|
|
569
575
|
if (action.type === 'create') {
|
|
576
|
+
if (action.frameCreation && action.shape.shapeName === 'frame') {
|
|
577
|
+
// Treat a manually-created frame and its initial membership as one
|
|
578
|
+
// atomic action. Children remain on the canvas when the frame is
|
|
579
|
+
// undone and are clipped back into it on redo.
|
|
580
|
+
for (const child of action.containedShapes || []) {
|
|
581
|
+
if (child?.parentFrame === action.shape) action.shape.removeShapeFromFrame(child);
|
|
582
|
+
}
|
|
583
|
+
if (currentShape === action.shape) {
|
|
584
|
+
action.shape.removeSelection?.();
|
|
585
|
+
currentShape = null;
|
|
586
|
+
}
|
|
587
|
+
const idx = shapes.indexOf(action.shape);
|
|
588
|
+
if (idx !== -1) shapes.splice(idx, 1);
|
|
589
|
+
action.shape.group?.remove();
|
|
590
|
+
action.shape.clipGroup?.remove();
|
|
591
|
+
action.shape.clipPath?.remove();
|
|
592
|
+
} else
|
|
570
593
|
if (action.shape.type === 'text') {
|
|
571
594
|
// Handle text creation undo
|
|
572
595
|
if (action.shape.element && action.shape.element.parentNode) {
|
|
@@ -1049,6 +1072,27 @@ export function redo() {
|
|
|
1049
1072
|
}
|
|
1050
1073
|
|
|
1051
1074
|
if (action.type === 'create') {
|
|
1075
|
+
if (action.frameCreation && action.shape.shapeName === 'frame') {
|
|
1076
|
+
const frame = action.shape;
|
|
1077
|
+
let defs = svg?.querySelector('defs');
|
|
1078
|
+
if (!defs && svg) {
|
|
1079
|
+
defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
1080
|
+
svg.appendChild(defs);
|
|
1081
|
+
}
|
|
1082
|
+
if (defs && frame.clipPath && !frame.clipPath.parentNode) defs.appendChild(frame.clipPath);
|
|
1083
|
+
if (svg && frame.group && !frame.group.parentNode) svg.appendChild(frame.group);
|
|
1084
|
+
if (svg && frame.clipGroup && !frame.clipGroup.parentNode) svg.appendChild(frame.clipGroup);
|
|
1085
|
+
if (shapes.indexOf(frame) === -1) {
|
|
1086
|
+
const index = action.shapeIndex >= 0 ? Math.min(action.shapeIndex, shapes.length) : shapes.length;
|
|
1087
|
+
shapes.splice(index, 0, frame);
|
|
1088
|
+
}
|
|
1089
|
+
frame.containedShapes = [];
|
|
1090
|
+
frame.draw();
|
|
1091
|
+
frame.updateClipPath();
|
|
1092
|
+
for (const child of action.containedShapes || []) {
|
|
1093
|
+
if (child && shapes.includes(child)) frame.addShapeToFrame(child);
|
|
1094
|
+
}
|
|
1095
|
+
} else
|
|
1052
1096
|
if (action.shape.type === 'text') {
|
|
1053
1097
|
// Handle text creation redo
|
|
1054
1098
|
if (svg) {
|
package/src/core/ZoomPan.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
|
+
import { installScreenSpaceControlSync } from './ScreenSpaceControls.js';
|
|
2
3
|
// Zoom and Pan controls - combined from zoomFunction.js + panCanvas.js
|
|
3
4
|
// Depends on globals: svg, freehandCanvas, currentZoom, currentViewBox, isPanningToolActive, isPanning
|
|
4
5
|
// Depends on globals: minScale, maxScale, zoomInBtn, zoomOutBtn, zoomPercentSpan, panStart, startCanvasX, startCanvasY
|
|
5
6
|
|
|
7
|
+
installScreenSpaceControlSync(freehandCanvas);
|
|
8
|
+
|
|
6
9
|
// === Zoom Functions ===
|
|
7
10
|
let currentY = 0;
|
|
8
11
|
const scrollRate = 50;
|
|
@@ -6,6 +6,7 @@ import { showToast } from '../../utils/toast'
|
|
|
6
6
|
export default function MultiSelectActions() {
|
|
7
7
|
const [count, setCount] = useState(0)
|
|
8
8
|
const [groupState, setGroupState] = useState('none') // 'none' | 'partial' | 'all'
|
|
9
|
+
const [frameEligible, setFrameEligible] = useState(false)
|
|
9
10
|
|
|
10
11
|
useEffect(() => {
|
|
11
12
|
// Poll multi-selection state. Cheap — Set size + a single
|
|
@@ -18,6 +19,7 @@ export default function MultiSelectActions() {
|
|
|
18
19
|
|
|
19
20
|
if (n < 2) {
|
|
20
21
|
setGroupState('none')
|
|
22
|
+
setFrameEligible(false)
|
|
21
23
|
return
|
|
22
24
|
}
|
|
23
25
|
let withId = 0
|
|
@@ -28,6 +30,7 @@ export default function MultiSelectActions() {
|
|
|
28
30
|
groupIds.add(s.groupId)
|
|
29
31
|
}
|
|
30
32
|
})
|
|
33
|
+
setFrameEligible(Array.from(sel).every((s) => s.shapeName !== 'frame' && !s.parentFrame))
|
|
31
34
|
// "all" = every shape has a groupId AND they all share the same one
|
|
32
35
|
if (withId === n && groupIds.size === 1) setGroupState('all')
|
|
33
36
|
else if (withId > 0) setGroupState('partial')
|
|
@@ -82,7 +85,7 @@ export default function MultiSelectActions() {
|
|
|
82
85
|
|
|
83
86
|
{/* Frame is only useful when shapes aren't already grouped — a
|
|
84
87
|
grouped set is conceptually a "frameless frame" already. */}
|
|
85
|
-
{!grouped && (
|
|
88
|
+
{!grouped && frameEligible && (
|
|
86
89
|
<button
|
|
87
90
|
onClick={handleFrame}
|
|
88
91
|
className="flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs text-accent-blue hover:bg-accent-blue/10 transition-all duration-150"
|
|
@@ -90,6 +90,14 @@ export default function IconSidebar() {
|
|
|
90
90
|
const [icons, setIcons] = useState([])
|
|
91
91
|
const [loading, setLoading] = useState(false)
|
|
92
92
|
const debounceRef = useRef(null)
|
|
93
|
+
const searchInputRef = useRef(null)
|
|
94
|
+
const requestSequenceRef = useRef(0)
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!visible) return
|
|
98
|
+
const frame = requestAnimationFrame(() => searchInputRef.current?.focus())
|
|
99
|
+
return () => cancelAnimationFrame(frame)
|
|
100
|
+
}, [visible])
|
|
93
101
|
|
|
94
102
|
// Close on Escape
|
|
95
103
|
useEffect(() => {
|
|
@@ -115,6 +123,7 @@ export default function IconSidebar() {
|
|
|
115
123
|
}, [visible, setActiveTool])
|
|
116
124
|
|
|
117
125
|
const fetchIcons = useCallback(async (searchQuery, cat) => {
|
|
126
|
+
const requestSequence = ++requestSequenceRef.current
|
|
118
127
|
const params = new URLSearchParams()
|
|
119
128
|
if (searchQuery) params.set('q', searchQuery)
|
|
120
129
|
if (cat) params.set('category', cat)
|
|
@@ -124,6 +133,7 @@ export default function IconSidebar() {
|
|
|
124
133
|
// Return cached results instantly if available
|
|
125
134
|
if (iconResultCache.has(cacheKey)) {
|
|
126
135
|
setIcons(iconResultCache.get(cacheKey))
|
|
136
|
+
setLoading(false)
|
|
127
137
|
return
|
|
128
138
|
}
|
|
129
139
|
|
|
@@ -134,12 +144,12 @@ export default function IconSidebar() {
|
|
|
134
144
|
const data = await res.json()
|
|
135
145
|
const results = data.results || []
|
|
136
146
|
iconResultCache.set(cacheKey, results)
|
|
137
|
-
setIcons(results)
|
|
147
|
+
if (requestSequence === requestSequenceRef.current) setIcons(results)
|
|
138
148
|
}
|
|
139
149
|
} catch (err) {
|
|
140
150
|
console.error('Icon fetch failed:', err)
|
|
141
151
|
}
|
|
142
|
-
setLoading(false)
|
|
152
|
+
if (requestSequence === requestSequenceRef.current) setLoading(false)
|
|
143
153
|
}, [])
|
|
144
154
|
|
|
145
155
|
// Fetch icons when visibility, query, or category changes (debounced for query typing)
|
|
@@ -148,30 +158,10 @@ export default function IconSidebar() {
|
|
|
148
158
|
if (debounceRef.current) clearTimeout(debounceRef.current)
|
|
149
159
|
debounceRef.current = setTimeout(() => {
|
|
150
160
|
fetchIcons(query, category)
|
|
151
|
-
}, query ?
|
|
161
|
+
}, query ? 80 : 0)
|
|
152
162
|
return () => clearTimeout(debounceRef.current)
|
|
153
163
|
}, [query, visible, category, fetchIcons])
|
|
154
164
|
|
|
155
|
-
// Preload all categories in background on first sidebar open
|
|
156
|
-
const hasPreloaded = useRef(false)
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
if (!visible || hasPreloaded.current) return
|
|
159
|
-
hasPreloaded.current = true
|
|
160
|
-
// Fire-and-forget: preload each category so switching is instant
|
|
161
|
-
CATEGORIES.forEach((cat) => {
|
|
162
|
-
const params = new URLSearchParams()
|
|
163
|
-
if (cat.value) params.set('category', cat.value)
|
|
164
|
-
params.set('inline', '1')
|
|
165
|
-
const key = params.toString()
|
|
166
|
-
if (!iconResultCache.has(key)) {
|
|
167
|
-
fetch(`${getIconsBaseUrl()}/api/icons/search?${key}`)
|
|
168
|
-
.then((r) => r.ok ? r.json() : null)
|
|
169
|
-
.then((data) => { if (data?.results) iconResultCache.set(key, data.results) })
|
|
170
|
-
.catch(() => {})
|
|
171
|
-
}
|
|
172
|
-
})
|
|
173
|
-
}, [visible])
|
|
174
|
-
|
|
175
165
|
const handleIconClick = useCallback((icon) => {
|
|
176
166
|
if (typeof window === 'undefined') return
|
|
177
167
|
const place = (svgContent) => {
|
|
@@ -214,6 +204,7 @@ export default function IconSidebar() {
|
|
|
214
204
|
<div className="flex items-center gap-2 bg-surface-hover border border-border-light rounded-lg px-2.5 py-2">
|
|
215
205
|
<i className="bx bxs-search text-text-muted text-sm" />
|
|
216
206
|
<input
|
|
207
|
+
ref={searchInputRef}
|
|
217
208
|
id="iconSearchInput"
|
|
218
209
|
type="text"
|
|
219
210
|
value={query}
|
|
@@ -10,6 +10,15 @@ export default function ImageSidebar() {
|
|
|
10
10
|
const { t } = useTranslation()
|
|
11
11
|
const selectedShapeSidebar = useSketchStore((s) => s.selectedShapeSidebar)
|
|
12
12
|
const toggleImageGenerateModal = useUIStore((s) => s.toggleImageGenerateModal)
|
|
13
|
+
const toggleAIModal = useUIStore((s) => s.toggleAIModal)
|
|
14
|
+
const isGraph = typeof window !== 'undefined' && window.currentShape?._frameType === 'graph'
|
|
15
|
+
|
|
16
|
+
const handleEditGraph = useCallback(() => {
|
|
17
|
+
const shape = window.currentShape
|
|
18
|
+
if (!shape || shape._frameType !== 'graph') return
|
|
19
|
+
window.__aiEditTargetFrame = shape
|
|
20
|
+
toggleAIModal()
|
|
21
|
+
}, [toggleAIModal])
|
|
13
22
|
|
|
14
23
|
const handleEditWithAI = useCallback(() => {
|
|
15
24
|
const shape = window.currentShape
|
|
@@ -36,31 +45,45 @@ export default function ImageSidebar() {
|
|
|
36
45
|
|
|
37
46
|
return (
|
|
38
47
|
<ShapeSidebar visible={selectedShapeSidebar === 'image'}>
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
{isGraph && (
|
|
49
|
+
<>
|
|
50
|
+
<button
|
|
51
|
+
onClick={handleEditGraph}
|
|
52
|
+
title="Edit graph"
|
|
53
|
+
className="h-9 flex cursor-pointer items-center gap-1.5 px-3 rounded-lg text-text-muted hover:text-accent hover:bg-accent/10 transition-all duration-100"
|
|
54
|
+
>
|
|
55
|
+
<i className="bx bx-line-chart text-base" />
|
|
56
|
+
<span className="text-xs">Edit graph</span>
|
|
57
|
+
</button>
|
|
58
|
+
<Divider />
|
|
59
|
+
</>
|
|
60
|
+
)}
|
|
50
61
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
{/* Edit with AI */}
|
|
63
|
+
{!isGraph && (
|
|
64
|
+
<>
|
|
65
|
+
<button
|
|
66
|
+
onClick={handleEditWithAI}
|
|
67
|
+
title="Edit with AI"
|
|
68
|
+
className="h-9 flex items-center gap-1.5 px-3 rounded-lg text-text-muted hover:text-purple-400 hover:bg-purple-500/10 transition-all duration-100"
|
|
69
|
+
>
|
|
70
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
|
71
|
+
<path d="M12 2l2.4 7.2L22 12l-7.6 2.8L12 22l-2.4-7.2L2 12l7.6-2.8L12 2z" />
|
|
72
|
+
</svg>
|
|
73
|
+
<span className="text-xs">AI Edit</span>
|
|
74
|
+
</button>
|
|
75
|
+
<Divider />
|
|
76
|
+
<button
|
|
77
|
+
onClick={handleReplace}
|
|
78
|
+
title="Replace image"
|
|
79
|
+
className="h-9 flex items-center gap-1.5 px-3 rounded-lg text-text-muted hover:text-white hover:bg-white/[0.06] transition-all duration-100"
|
|
80
|
+
>
|
|
81
|
+
<i className="bx bx-upload text-base" />
|
|
82
|
+
<span className="text-xs">Replace</span>
|
|
83
|
+
</button>
|
|
84
|
+
<Divider />
|
|
85
|
+
</>
|
|
86
|
+
)}
|
|
64
87
|
|
|
65
88
|
{/* Layer controls */}
|
|
66
89
|
<LayerControls />
|
|
@@ -29,25 +29,82 @@ function invertShapeColors(prevResolved, nextResolved) {
|
|
|
29
29
|
|
|
30
30
|
for (const shape of shapes) {
|
|
31
31
|
let changed = false
|
|
32
|
-
if (shape.
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
if (shape.shapeName === 'icon' || shape.type === 'icon') {
|
|
33
|
+
const isDark = nextResolved === 'dark'
|
|
34
|
+
const targetDarkColor = '#1a1a2e'
|
|
35
|
+
const bgDarkColor = '#15111f'
|
|
36
|
+
|
|
37
|
+
if (shape.group) {
|
|
38
|
+
shape.group.querySelectorAll('*').forEach((element) => {
|
|
39
|
+
if (element.nodeType === 1) {
|
|
40
|
+
const fill = element.getAttribute('fill')
|
|
41
|
+
const stroke = element.getAttribute('stroke')
|
|
42
|
+
|
|
43
|
+
if (fill && fill !== 'none' && fill !== 'transparent') {
|
|
44
|
+
const normFill = fill.toLowerCase().trim()
|
|
45
|
+
if (isDark) {
|
|
46
|
+
if (normFill === targetDarkColor || normFill === '#000000' || normFill === '#000') {
|
|
47
|
+
element.setAttribute('fill', '#ffffff')
|
|
48
|
+
changed = true
|
|
49
|
+
} else if (normFill === '#ffffff' || normFill === '#fff') {
|
|
50
|
+
element.setAttribute('fill', bgDarkColor)
|
|
51
|
+
changed = true
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
if (normFill === '#ffffff' || normFill === '#fff') {
|
|
55
|
+
element.setAttribute('fill', targetDarkColor)
|
|
56
|
+
changed = true
|
|
57
|
+
} else if (normFill === bgDarkColor) {
|
|
58
|
+
element.setAttribute('fill', '#ffffff')
|
|
59
|
+
changed = true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (stroke && stroke !== 'none' && stroke !== 'transparent') {
|
|
65
|
+
const normStroke = stroke.toLowerCase().trim()
|
|
66
|
+
if (isDark) {
|
|
67
|
+
if (normStroke === targetDarkColor || normStroke === '#000000' || normStroke === '#000') {
|
|
68
|
+
element.setAttribute('stroke', '#ffffff')
|
|
69
|
+
changed = true
|
|
70
|
+
} else if (normStroke === '#ffffff' || normStroke === '#fff') {
|
|
71
|
+
element.setAttribute('stroke', bgDarkColor)
|
|
72
|
+
changed = true
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
if (normStroke === '#ffffff' || normStroke === '#fff') {
|
|
76
|
+
element.setAttribute('stroke', targetDarkColor)
|
|
77
|
+
changed = true
|
|
78
|
+
} else if (normStroke === bgDarkColor) {
|
|
79
|
+
element.setAttribute('stroke', '#ffffff')
|
|
80
|
+
changed = true
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
if (shape.options) {
|
|
89
|
+
if (normalize(shape.options.stroke) === from) {
|
|
90
|
+
shape.options.stroke = to
|
|
91
|
+
changed = true
|
|
92
|
+
}
|
|
93
|
+
if (normalize(shape.options.fill) === from) {
|
|
94
|
+
shape.options.fill = to
|
|
95
|
+
changed = true
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Text shapes store color directly
|
|
99
|
+
if (shape.color !== undefined && normalize(shape.color) === from) {
|
|
100
|
+
shape.color = to
|
|
35
101
|
changed = true
|
|
36
102
|
}
|
|
37
|
-
if (normalize(shape.
|
|
38
|
-
shape.
|
|
103
|
+
if (shape.strokeColor !== undefined && normalize(shape.strokeColor) === from) {
|
|
104
|
+
shape.strokeColor = to
|
|
39
105
|
changed = true
|
|
40
106
|
}
|
|
41
107
|
}
|
|
42
|
-
// Text shapes store color directly
|
|
43
|
-
if (shape.color !== undefined && normalize(shape.color) === from) {
|
|
44
|
-
shape.color = to
|
|
45
|
-
changed = true
|
|
46
|
-
}
|
|
47
|
-
if (shape.strokeColor !== undefined && normalize(shape.strokeColor) === from) {
|
|
48
|
-
shape.strokeColor = to
|
|
49
|
-
changed = true
|
|
50
|
-
}
|
|
51
108
|
if (changed && typeof shape.draw === 'function') {
|
|
52
109
|
shape.draw()
|
|
53
110
|
}
|
package/src/shapes/Circle.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
|
+
import { registerRotationAnchor } from '../core/ScreenSpaceControls.js';
|
|
2
3
|
// Circle shape class - extracted from drawCircle.js
|
|
3
4
|
// Depends on globals: svg, shapes, rough, currentShape, currentZoom, rc
|
|
4
5
|
|
|
@@ -325,6 +326,7 @@ class Circle {
|
|
|
325
326
|
this.rotationAnchor.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
326
327
|
this.rotationAnchor.setAttribute('style', 'pointer-events: all;');
|
|
327
328
|
this.group.appendChild(this.rotationAnchor);
|
|
329
|
+
registerRotationAnchor(this.rotationAnchor, { radius: 8, edgeY: expandedY });
|
|
328
330
|
|
|
329
331
|
this.rotationAnchor.addEventListener('mouseover', function () {
|
|
330
332
|
if (!isResizingShapeCircle && !isDraggingShapeCircle && !isRotatingShapeCircle) {
|
package/src/shapes/Frame.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Depends on globals: svg, shapes, currentShape, currentZoom
|
|
4
4
|
|
|
5
5
|
import { cleanupAttachments } from '../tools/arrowTool.js';
|
|
6
|
+
import { registerRotationAnchor } from '../core/ScreenSpaceControls.js';
|
|
6
7
|
|
|
7
8
|
function getSVGCoordsFromMouse(e) {
|
|
8
9
|
const viewBox = svg.viewBox.baseVal;
|
|
@@ -479,6 +480,7 @@ move(dx, dy) {
|
|
|
479
480
|
labelText.textContent = this.frameName || "Frame";
|
|
480
481
|
labelText.style.cursor = "pointer";
|
|
481
482
|
labelText.style.userSelect = "none";
|
|
483
|
+
labelText.style.pointerEvents = "all";
|
|
482
484
|
|
|
483
485
|
if (this.rotation !== 0) {
|
|
484
486
|
const centerX = this.x + this.width / 2;
|
|
@@ -486,6 +488,11 @@ move(dx, dy) {
|
|
|
486
488
|
labelText.setAttribute("transform", `rotate(${this.rotation}, ${centerX}, ${centerY})`);
|
|
487
489
|
}
|
|
488
490
|
|
|
491
|
+
// Do not let the first click select/redraw the frame: replacing the label
|
|
492
|
+
// between clicks prevents the browser from ever emitting `dblclick`.
|
|
493
|
+
labelText.addEventListener('pointerdown', (e) => e.stopPropagation());
|
|
494
|
+
labelText.addEventListener('click', (e) => e.stopPropagation());
|
|
495
|
+
|
|
489
496
|
// Add double-click event for renaming
|
|
490
497
|
labelText.addEventListener('dblclick', (e) => {
|
|
491
498
|
e.stopPropagation();
|
|
@@ -497,6 +504,10 @@ move(dx, dy) {
|
|
|
497
504
|
}
|
|
498
505
|
|
|
499
506
|
startLabelEdit(labelElement) {
|
|
507
|
+
if (this._labelEditor) {
|
|
508
|
+
this._labelEditor.querySelector('input')?.focus();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
500
511
|
// Create a foreignObject to hold an HTML input
|
|
501
512
|
const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
|
|
502
513
|
foreignObject.setAttribute("x", this.x + 5);
|
|
@@ -534,6 +545,7 @@ startLabelEdit(labelElement) {
|
|
|
534
545
|
|
|
535
546
|
foreignObject.appendChild(input);
|
|
536
547
|
this.group.appendChild(foreignObject);
|
|
548
|
+
this._labelEditor = foreignObject;
|
|
537
549
|
|
|
538
550
|
// Hide the original label
|
|
539
551
|
labelElement.style.display = "none";
|
|
@@ -544,7 +556,10 @@ startLabelEdit(labelElement) {
|
|
|
544
556
|
input.select();
|
|
545
557
|
}, 10);
|
|
546
558
|
|
|
559
|
+
let finished = false;
|
|
547
560
|
const finishEdit = () => {
|
|
561
|
+
if (finished) return;
|
|
562
|
+
finished = true;
|
|
548
563
|
const newName = input.value.trim() || "Frame";
|
|
549
564
|
|
|
550
565
|
// Track name change in undo system if it actually changed
|
|
@@ -572,7 +587,8 @@ startLabelEdit(labelElement) {
|
|
|
572
587
|
this.frameName = newName;
|
|
573
588
|
|
|
574
589
|
// Remove the input
|
|
575
|
-
|
|
590
|
+
foreignObject.remove();
|
|
591
|
+
this._labelEditor = null;
|
|
576
592
|
|
|
577
593
|
// Show and update the label
|
|
578
594
|
labelElement.style.display = "block";
|
|
@@ -589,8 +605,10 @@ startLabelEdit(labelElement) {
|
|
|
589
605
|
finishEdit();
|
|
590
606
|
} else if (e.key === 'Escape') {
|
|
591
607
|
e.preventDefault();
|
|
608
|
+
finished = true;
|
|
592
609
|
// Cancel edit - restore original name
|
|
593
|
-
|
|
610
|
+
foreignObject.remove();
|
|
611
|
+
this._labelEditor = null;
|
|
594
612
|
labelElement.style.display = "block";
|
|
595
613
|
}
|
|
596
614
|
});
|
|
@@ -775,6 +793,12 @@ startLabelEdit(labelElement) {
|
|
|
775
793
|
}
|
|
776
794
|
|
|
777
795
|
this.group.appendChild(rotationLine);
|
|
796
|
+
registerRotationAnchor(anchor, {
|
|
797
|
+
radius: 8,
|
|
798
|
+
edgeY: this.y,
|
|
799
|
+
line: rotationLine,
|
|
800
|
+
lineEnd: '2',
|
|
801
|
+
});
|
|
778
802
|
} else {
|
|
779
803
|
// Create resize handle (rectangle)
|
|
780
804
|
anchor = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
2
|
import { getStroke } from "perfect-freehand";
|
|
3
|
+
import { registerRotationAnchor } from '../core/ScreenSpaceControls.js';
|
|
3
4
|
// FreehandStroke shape class - extracted from canvasStroke.js
|
|
4
5
|
// Depends on globals: svg, shapes, currentShape, currentZoom, currentViewBox
|
|
5
6
|
|
|
@@ -608,6 +609,7 @@ class FreehandStroke {
|
|
|
608
609
|
rotationAnchor.style.pointerEvents = 'all';
|
|
609
610
|
|
|
610
611
|
this.group.appendChild(rotationAnchor);
|
|
612
|
+
registerRotationAnchor(rotationAnchor, { radius: 5, edgeY: expandedY });
|
|
611
613
|
this.rotationAnchor = rotationAnchor;
|
|
612
614
|
|
|
613
615
|
// Create selection outline
|
package/src/shapes/IconShape.js
CHANGED
|
@@ -79,6 +79,25 @@ class IconShape {
|
|
|
79
79
|
this.element.setAttribute('data-shape-height', value);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
_transformMetrics() {
|
|
83
|
+
const vbWidth = parseFloat(this.element.getAttribute('data-viewbox-width')) || 24;
|
|
84
|
+
const vbHeight = parseFloat(this.element.getAttribute('data-viewbox-height')) || 24;
|
|
85
|
+
const scale = Math.min(this.width / vbWidth, this.height / vbHeight);
|
|
86
|
+
return {
|
|
87
|
+
scale,
|
|
88
|
+
localCenterX: this.width / 2 / scale,
|
|
89
|
+
localCenterY: this.height / 2 / scale,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_applyTransform() {
|
|
94
|
+
const { scale, localCenterX, localCenterY } = this._transformMetrics();
|
|
95
|
+
this.element.setAttribute(
|
|
96
|
+
'transform',
|
|
97
|
+
`translate(${this.x}, ${this.y}) scale(${scale}) rotate(${this.rotation}, ${localCenterX}, ${localCenterY})`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
82
101
|
get rotation() {
|
|
83
102
|
const dataRotation = this.element.getAttribute('data-shape-rotation');
|
|
84
103
|
if (dataRotation) {
|
|
@@ -97,27 +116,14 @@ class IconShape {
|
|
|
97
116
|
|
|
98
117
|
set rotation(value) {
|
|
99
118
|
this.element.setAttribute('data-shape-rotation', value);
|
|
100
|
-
|
|
101
|
-
const vbWidth = parseFloat(this.element.getAttribute('data-viewbox-width')) || 24;
|
|
102
|
-
const vbHeight = parseFloat(this.element.getAttribute('data-viewbox-height')) || 24;
|
|
103
|
-
const scale = this.width / Math.max(vbWidth, vbHeight);
|
|
104
|
-
const localCenterX = this.width / 2 / scale;
|
|
105
|
-
const localCenterY = this.height / 2 / scale;
|
|
106
|
-
|
|
107
|
-
this.element.setAttribute('transform', `translate(${this.x}, ${this.y}) scale(${scale}) rotate(${value}, ${localCenterX}, ${localCenterY})`);
|
|
119
|
+
this._applyTransform();
|
|
108
120
|
}
|
|
109
121
|
|
|
110
122
|
move(dx, dy) {
|
|
111
123
|
this.x += dx;
|
|
112
124
|
this.y += dy;
|
|
113
125
|
|
|
114
|
-
|
|
115
|
-
const vbHeight = parseFloat(this.element.getAttribute('data-viewbox-height')) || 24;
|
|
116
|
-
const scale = this.width / Math.max(vbWidth, vbHeight);
|
|
117
|
-
const localCenterX = this.width / 2 / scale;
|
|
118
|
-
const localCenterY = this.height / 2 / scale;
|
|
119
|
-
|
|
120
|
-
this.element.setAttribute('transform', `translate(${this.x}, ${this.y}) scale(${scale}) rotate(${this.rotation}, ${localCenterX}, ${localCenterY})`);
|
|
126
|
+
this._applyTransform();
|
|
121
127
|
|
|
122
128
|
this.element.setAttribute('x', this.x);
|
|
123
129
|
this.element.setAttribute('y', this.y);
|
|
@@ -173,11 +179,7 @@ class IconShape {
|
|
|
173
179
|
}
|
|
174
180
|
|
|
175
181
|
draw() {
|
|
176
|
-
|
|
177
|
-
const localCenterX = this.width / 2 / scale;
|
|
178
|
-
const localCenterY = this.height / 2 / scale;
|
|
179
|
-
|
|
180
|
-
this.element.setAttribute('transform', `translate(${this.x}, ${this.y}) scale(${scale}) rotate(${this.rotation}, ${localCenterX}, ${localCenterY})`);
|
|
182
|
+
this._applyTransform();
|
|
181
183
|
|
|
182
184
|
this.element.setAttribute('data-shape-x', this.x);
|
|
183
185
|
this.element.setAttribute('data-shape-y', this.y);
|
|
@@ -246,11 +248,7 @@ class IconShape {
|
|
|
246
248
|
pos.parentFrame.addShapeToFrame(this);
|
|
247
249
|
}
|
|
248
250
|
|
|
249
|
-
|
|
250
|
-
const localCenterX = pos.width / 2 / scale;
|
|
251
|
-
const localCenterY = pos.height / 2 / scale;
|
|
252
|
-
|
|
253
|
-
this.element.setAttribute('transform', `translate(${pos.x}, ${pos.y}) scale(${scale}) rotate(${pos.rotation}, ${localCenterX}, ${localCenterY})`);
|
|
251
|
+
this._applyTransform();
|
|
254
252
|
|
|
255
253
|
this.element.setAttribute('data-shape-x', pos.x);
|
|
256
254
|
this.element.setAttribute('data-shape-y', pos.y);
|
package/src/shapes/ImageShape.js
CHANGED
|
@@ -262,8 +262,12 @@ class ImageShape {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
selectShape() {
|
|
265
|
-
//
|
|
266
|
-
|
|
265
|
+
// Image interaction state lives in imageTool. Use its bridge so
|
|
266
|
+
// programmatically-created images (graphs, restored scenes, paste)
|
|
267
|
+
// receive the same drag/resize controls as pointer-selected images.
|
|
268
|
+
if (typeof window.__selectImageElement === 'function') {
|
|
269
|
+
window.__selectImageElement(this.element, { force: true });
|
|
270
|
+
}
|
|
267
271
|
}
|
|
268
272
|
}
|
|
269
273
|
|