multiselect and line node resizing/moving fix

This commit is contained in:
bflatastic committed 2025-07-11 22:32:09 +07:00
1 parent 212283e8d2
commit 590c2fbf96
2 files changed
+135 -13

No files matched your search

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useLayoutEffect } from "react";
import React, { useState, useEffect, useLayoutEffect, useMemo } from "react";
import {
Stage,
Layer,
@@ -94,6 +94,30 @@ export const PaintSurface = ({
} | null>(null);
const [isMiddleMousePressed, setIsMiddleMousePressed] = useState(false);
const boundingBox = useMemo(() => {
if (selectedNodeIds.length < 2 || !stageRef.current) return null;
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
selectedNodeIds.forEach((id) => {
const konvaNode = stageRef.current!.findOne<Konva.Node>(`#${id}`);
if (!konvaNode) return;
const rect = konvaNode.getClientRect({ relativeTo: stageRef.current });
minX = Math.min(minX, rect.x);
minY = Math.min(minY, rect.y);
maxX = Math.max(maxX, rect.x + rect.width);
maxY = Math.max(maxY, rect.y + rect.height);
});
if (minX === Infinity) return null;
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedNodeIds, store.nodes, store.lineNodes, stageRef]);
useLayoutEffect(() => {
const updateDimensions = () => {
if (containerRef.current) {
@@ -240,6 +264,8 @@ export const PaintSurface = ({
opacity: opacity,
locked: false,
zIndex: store.lineNodes.length,
x: 0,
y: 0,
};
store.selectNode(null);
store.addLineNode(newLineNode, false);
@@ -1300,9 +1326,21 @@ export const PaintSurface = ({
const isLineNode = store.lineNodes.find((ln) => ln.id === nodeId);
if (isLineNode) {
store.updateLineNode(nodeId, commonNodeUpdates, true);
store.updateLineNode(
nodeId,
{
x: finalX,
y: finalY,
rotation: finalRotation,
scaleX: finalScaleX,
scaleY: finalScaleY,
offsetX: finalOffsetX,
offsetY: finalOffsetY,
},
false,
);
} else {
store.updateNode(nodeId, commonNodeUpdates, true);
store.updateNode(nodeId, { ...commonNodeUpdates }, false);
}
}}
/>
@@ -1526,10 +1564,25 @@ export const PaintSurface = ({
);
if (isLineNode) {
// Lines keep their points; we don't change their width/height semantics
store.updateLineNode(nodeId, commonNodeUpdates, true);
store.updateLineNode(
nodeId,
{
x: finalX,
y: finalY,
rotation: finalRotation,
scaleX: finalScaleX,
scaleY: finalScaleY,
offsetX: finalOffsetX,
offsetY: finalOffsetY,
},
false,
);
} else {
store.updateNode(nodeId, commonNodeUpdates, true);
store.updateNode(
nodeId,
{ ...commonNodeUpdates },
false,
);
}
}
});
@@ -1537,6 +1590,65 @@ export const PaintSurface = ({
}}
/>
)}
{/* Draggable transparent rect to move the whole selection together */}
{selectedNodeIds.length > 1 && boundingBox && (
<Rect
x={boundingBox.x}
y={boundingBox.y}
width={boundingBox.width}
height={boundingBox.height}
fill="rgba(0,0,0,0)"
draggable
onDragStart={(e) => {
const tgt = e.target as Konva.Rect & {
lastX?: number;
lastY?: number;
};
tgt.lastX = tgt.x();
tgt.lastY = tgt.y();
}}
onDragMove={(e) => {
const tgt = e.target as Konva.Rect & {
lastX?: number;
lastY?: number;
};
const dx = tgt.x() - (tgt.lastX ?? tgt.x());
const dy = tgt.y() - (tgt.lastY ?? tgt.y());
tgt.lastX = tgt.x();
tgt.lastY = tgt.y();
selectedNodeIds.forEach((id) => {
const lineNode = store.lineNodes.find(
(ln) => ln.id === id,
);
if (lineNode) {
store.moveLineNode(id, dx, dy);
} else {
const currentNode = store.nodes.find(
(n) => n.id === id,
);
if (currentNode) {
// Move each shape individually without triggering extra multi-select propagation
store.moveNode(
id,
currentNode.x + dx,
currentNode.y + dy,
undefined,
undefined,
false,
);
}
}
});
}}
onDragEnd={() => {
store.saveState();
}}
listening={true}
strokeWidth={0}
/>
)}
</Layer>
</Stage>
</div>
@@ -165,7 +165,12 @@ interface SceneState {
updates: Partial<LineNode>,
shouldSaveState: boolean,
) => void;
moveLineNode: (id: string, dx: number, dy: number) => void;
moveLineNode: (
id: string,
dx: number,
dy: number,
shouldSaveState?: boolean,
) => void;
// Add a specific method for file uploads
createImageFromFile: (
@@ -683,23 +688,28 @@ export const useSceneStore = create<SceneState>((set, get) => ({
}
},
moveLineNode: (id: string, dx: number, dy: number) => {
moveLineNode: (
id: string,
dx: number,
dy: number,
shouldSaveState: boolean = false,
) => {
set((state) => {
const newLineNodes = state.lineNodes.map((node) => {
if (node.id === id) {
return {
...node,
points: node.points.map((point, index) => {
// Even indices are x coordinates, odd are y
return index % 2 === 0 ? point + dx : point + dy;
}),
x: (node.x ?? 0) + dx,
y: (node.y ?? 0) + dy,
};
}
return node;
});
return { lineNodes: newLineNodes };
});
get().saveState();
if (shouldSaveState) {
get().saveState();
}
},
// Add a specific method for file uploads