editor: group resize handles for multi-select
This commit is contained in:
parent
cdcc9d049b
commit
510d478be3
@ -48,6 +48,25 @@ export function Canvas(props: CanvasProps) {
|
|||||||
|
|
||||||
const bounds = useMemo(() => ({ w: props.screen.width, h: props.screen.height }), [props.screen.width, props.screen.height]);
|
const bounds = useMemo(() => ({ w: props.screen.width, h: props.screen.height }), [props.screen.width, props.screen.height]);
|
||||||
|
|
||||||
|
const resizableSelected = useMemo(() => {
|
||||||
|
return props.screen.nodes.filter(
|
||||||
|
(n) => props.selectionIds.includes(n.id) && !n.locked && !n.hidden,
|
||||||
|
);
|
||||||
|
}, [props.screen.nodes, props.selectionIds]);
|
||||||
|
|
||||||
|
const groupResize = useMemo(() => {
|
||||||
|
if (resizableSelected.length < 2) return null;
|
||||||
|
const rects = resizableSelected.map((n) => n.rect);
|
||||||
|
const x1 = Math.min(...rects.map((r) => r.x));
|
||||||
|
const y1 = Math.min(...rects.map((r) => r.y));
|
||||||
|
const x2 = Math.max(...rects.map((r) => r.x + r.w));
|
||||||
|
const y2 = Math.max(...rects.map((r) => r.y + r.h));
|
||||||
|
return {
|
||||||
|
targetId: resizableSelected[0]!.id,
|
||||||
|
rect: { x: x1, y: y1, w: x2 - x1, h: y2 - y1 },
|
||||||
|
};
|
||||||
|
}, [resizableSelected]);
|
||||||
|
|
||||||
const clientToCanvas = useCallback((clientX: number, clientY: number) => {
|
const clientToCanvas = useCallback((clientX: number, clientY: number) => {
|
||||||
const el = ref.current;
|
const el = ref.current;
|
||||||
if (!el) return null;
|
if (!el) return null;
|
||||||
@ -325,6 +344,7 @@ export function Canvas(props: CanvasProps) {
|
|||||||
key={node.id}
|
key={node.id}
|
||||||
node={node}
|
node={node}
|
||||||
selected={props.selectionIds.includes(node.id)}
|
selected={props.selectionIds.includes(node.id)}
|
||||||
|
selectionCount={props.selectionIds.length}
|
||||||
onPointerDown={(e) => {
|
onPointerDown={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -368,6 +388,17 @@ export function Canvas(props: CanvasProps) {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{groupResize ? (
|
||||||
|
<GroupSelectionResizeBox
|
||||||
|
rect={groupResize.rect}
|
||||||
|
onPointerDown={(e, handle) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
props.onBeginResize(e, groupResize.targetId, handle);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{box && (
|
{box && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -391,6 +422,7 @@ export function Canvas(props: CanvasProps) {
|
|||||||
function NodeView(props: {
|
function NodeView(props: {
|
||||||
node: WidgetNode;
|
node: WidgetNode;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
|
selectionCount: number;
|
||||||
onPointerDown: (e: React.PointerEvent) => void;
|
onPointerDown: (e: React.PointerEvent) => void;
|
||||||
onContextMenu: (e: React.MouseEvent) => void;
|
onContextMenu: (e: React.MouseEvent) => void;
|
||||||
onResizePointerDown: (e: React.PointerEvent, handle: ResizeHandle) => void;
|
onResizePointerDown: (e: React.PointerEvent, handle: ResizeHandle) => void;
|
||||||
@ -565,7 +597,35 @@ function NodeView(props: {
|
|||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
{props.selected && !node.locked && !node.hidden && <ResizeHandles onPointerDown={props.onResizePointerDown} />}
|
{props.selected &&
|
||||||
|
props.selectionCount === 1 &&
|
||||||
|
!node.locked &&
|
||||||
|
!node.hidden && <ResizeHandles onPointerDown={props.onResizePointerDown} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GroupSelectionResizeBox(props: {
|
||||||
|
rect: { x: number; y: number; w: number; h: number };
|
||||||
|
onPointerDown: (e: React.PointerEvent, handle: ResizeHandle) => void;
|
||||||
|
}) {
|
||||||
|
const r = props.rect;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: r.x,
|
||||||
|
top: r.y,
|
||||||
|
width: r.w,
|
||||||
|
height: r.h,
|
||||||
|
border: '1px solid rgba(24,144,255,0.9)',
|
||||||
|
boxShadow: '0 0 0 2px rgba(24,144,255,0.18)',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ position: 'absolute', inset: 0, pointerEvents: 'auto' }}>
|
||||||
|
<ResizeHandles onPointerDown={props.onPointerDown} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user