feat(utils): add snapPosition function

This commit is contained in:
moklick
2023-06-12 22:32:57 +02:00
parent a41ec8f1bc
commit d6b8439f6e
5 changed files with 60 additions and 57 deletions
@@ -6,7 +6,7 @@ import useKeyPress from './useKeyPress';
import useReactFlow from './useReactFlow'; import useReactFlow from './useReactFlow';
import { Edge, Node } from '../types'; import { Edge, Node } from '../types';
const getSelected = (item: Node | Edge) => item.selected; const selected = (item: Node | Edge) => item.selected;
export default ({ export default ({
deleteKeyCode, deleteKeyCode,
@@ -24,7 +24,7 @@ export default ({
useEffect(() => { useEffect(() => {
if (deleteKeyPressed) { if (deleteKeyPressed) {
const { edges, nodes } = store.getState(); const { edges, nodes } = store.getState();
deleteElements({ nodes: nodes.filter(getSelected), edges: edges.filter(getSelected) }); deleteElements({ nodes: nodes.filter(selected), edges: edges.filter(selected) });
store.setState({ nodesSelectionActive: false }); store.setState({ nodesSelectionActive: false });
} }
}, [deleteKeyPressed]); }, [deleteKeyPressed]);
+15 -21
View File
@@ -3,18 +3,12 @@ import { errorMessages, getDimensions } from '@xyflow/system';
import { useStoreApi } from '../hooks/useStore'; import { useStoreApi } from '../hooks/useStore';
function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>): void { function useResizeHandler(domNode: MutableRefObject<HTMLDivElement | null>): void {
const store = useStoreApi(); const store = useStoreApi();
useEffect(() => { useEffect(() => {
let resizeObserver: ResizeObserver;
const updateDimensions = () => { const updateDimensions = () => {
if (!rendererNode.current) { const size = getDimensions(domNode.current!);
return;
}
const size = getDimensions(rendererNode.current);
if (size.height === 0 || size.width === 0) { if (size.height === 0 || size.width === 0) {
store.getState().onError?.('004', errorMessages['error004']()); store.getState().onError?.('004', errorMessages['error004']());
@@ -23,21 +17,21 @@ function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>)
store.setState({ width: size.width || 500, height: size.height || 500 }); store.setState({ width: size.width || 500, height: size.height || 500 });
}; };
updateDimensions(); if (domNode.current) {
window.addEventListener('resize', updateDimensions); updateDimensions();
window.addEventListener('resize', updateDimensions);
if (rendererNode.current) { const resizeObserver = new ResizeObserver(() => updateDimensions());
resizeObserver = new ResizeObserver(() => updateDimensions()); resizeObserver.observe(domNode.current);
resizeObserver.observe(rendererNode.current);
return () => {
window.removeEventListener('resize', updateDimensions);
if (resizeObserver && domNode.current) {
resizeObserver.unobserve(domNode.current);
}
};
} }
return () => {
window.removeEventListener('resize', updateDimensions);
if (resizeObserver && rendererNode.current) {
resizeObserver.unobserve(rendererNode.current!);
}
};
}, []); }, []);
} }
@@ -1,41 +1,49 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { calcNextPosition } from '@xyflow/system'; import { calcNextPosition, snapPosition } from '@xyflow/system';
import { Node } from '../types';
import { useStoreApi } from '../hooks/useStore'; import { useStoreApi } from '../hooks/useStore';
const selectedAndDraggable = (nodesDraggable: boolean) => (n: Node) =>
n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined'));
function useUpdateNodePositions() { function useUpdateNodePositions() {
const store = useStoreApi(); const store = useStoreApi();
const updatePositions = useCallback((params: { x: number; y: number; isShiftPressed: boolean }) => { const updatePositions = useCallback((params: { x: number; y: number; isShiftPressed: boolean }) => {
const { nodeExtent, updateNodePositions, nodes, snapToGrid, snapGrid, onError, nodesDraggable } = store.getState(); const { nodeExtent, nodes, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions } = store.getState();
const selectedNodes = nodes.filter( const selectedNodes = nodes.filter(selectedAndDraggable(nodesDraggable));
(n) => n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined'))
);
// by default a node moves 5px on each key press, or 20px if shift is pressed // by default a node moves 5px on each key press, or 20px if shift is pressed
// if snap grid is enabled, we use that for the velocity. // if snap grid is enabled, we use that for the velocity.
const xVelo = snapToGrid ? snapGrid[0] : 5; const xVelo = snapToGrid ? snapGrid[0] : 5;
const yVelo = snapToGrid ? snapGrid[1] : 5; const yVelo = snapToGrid ? snapGrid[1] : 5;
const factor = params.isShiftPressed ? 4 : 1; const factor = params.isShiftPressed ? 4 : 1;
const positionDiffX = params.x * xVelo * factor; const xDiff = params.x * xVelo * factor;
const positionDiffY = params.y * yVelo * factor; const yDiff = params.y * yVelo * factor;
const nodeUpdates = selectedNodes.map((n) => { const nodeUpdates = selectedNodes.map((node) => {
if (n.positionAbsolute) { if (node.positionAbsolute) {
const nextPosition = { x: n.positionAbsolute.x + positionDiffX, y: n.positionAbsolute.y + positionDiffY }; let nextPosition = { x: node.positionAbsolute.x + xDiff, y: node.positionAbsolute.y + yDiff };
if (snapToGrid) { if (snapToGrid) {
nextPosition.x = snapGrid[0] * Math.round(nextPosition.x / snapGrid[0]); nextPosition = snapPosition(nextPosition, snapGrid);
nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]);
} }
const { positionAbsolute, position } = calcNextPosition(n, nextPosition, nodes, nodeExtent, undefined, onError); const { positionAbsolute, position } = calcNextPosition(
node,
nextPosition,
nodes,
nodeExtent,
undefined,
onError
);
n.position = position; node.position = position;
n.positionAbsolute = positionAbsolute; node.positionAbsolute = positionAbsolute;
} }
return n; return node;
}); });
updateNodePositions(nodeUpdates, true, false); updateNodePositions(nodeUpdates, true, false);
+15 -13
View File
@@ -144,21 +144,14 @@ export const pointToRendererPoint = (
{ x, y }: XYPosition, { x, y }: XYPosition,
[tx, ty, tScale]: Transform, [tx, ty, tScale]: Transform,
snapToGrid: boolean, snapToGrid: boolean,
[snapX, snapY]: [number, number] snapGrid: SnapGrid
): XYPosition => { ): XYPosition => {
const position: XYPosition = { const position: XYPosition = {
x: (x - tx) / tScale, x: (x - tx) / tScale,
y: (y - ty) / tScale, y: (y - ty) / tScale,
}; };
if (snapToGrid) { return snapToGrid ? snapPosition(position, snapGrid) : position;
return {
x: snapX * Math.round(position.x / snapX),
y: snapY * Math.round(position.y / snapY),
};
}
return position;
}; };
export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Transform): XYPosition => { export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Transform): XYPosition => {
@@ -349,10 +342,12 @@ export function getPointerPosition(
y: (y - transform[1]) / transform[2], y: (y - transform[1]) / transform[2],
}; };
const { x: xSnapped, y: ySnapped } = snapToGrid ? snapPosition(pointerPos, snapGrid) : pointerPos;
// we need the snapped position in order to be able to skip unnecessary drag events // we need the snapped position in order to be able to skip unnecessary drag events
return { return {
xSnapped: snapToGrid ? snapGrid[0] * Math.round(pointerPos.x / snapGrid[0]) : pointerPos.x, xSnapped,
ySnapped: snapToGrid ? snapGrid[1] * Math.round(pointerPos.y / snapGrid[1]) : pointerPos.y, ySnapped,
...pointerPos, ...pointerPos,
}; };
} }
@@ -366,11 +361,11 @@ export function calcNextPosition<NodeType extends BaseNode>(
onError?: OnError onError?: OnError
): { position: XYPosition; positionAbsolute: XYPosition } { ): { position: XYPosition; positionAbsolute: XYPosition } {
let currentExtent = node.extent || nodeExtent; let currentExtent = node.extent || nodeExtent;
let parentNode: NodeType; let parentNode: NodeType | null = null;
let parentPos = { x: 0, y: 0 }; let parentPos = { x: 0, y: 0 };
if (node.parentNode) { if (node.parentNode) {
parentNode = nodes.find((n) => n.id === node.parentNode); parentNode = nodes.find((n) => n.id === node.parentNode) || null;
parentPos = parentNode parentPos = parentNode
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute ? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
: parentPos; : parentPos;
@@ -413,3 +408,10 @@ export function calcNextPosition<NodeType extends BaseNode>(
positionAbsolute, positionAbsolute,
}; };
} }
export function snapPosition(position: XYPosition, snapGrid: SnapGrid = [1, 1]) {
return {
x: snapGrid[0] * Math.round(position.x / snapGrid[0]),
y: snapGrid[1] * Math.round(position.y / snapGrid[1]),
};
}
+4 -5
View File
@@ -1,7 +1,7 @@
import { drag } from 'd3-drag'; import { drag } from 'd3-drag';
import { select } from 'd3-selection'; import { select } from 'd3-selection';
import { calcAutoPan, getEventPosition, getPointerPosition, calcNextPosition } from '../utils'; import { calcAutoPan, getEventPosition, getPointerPosition, calcNextPosition, snapPosition } from '../utils';
import { getDragItems, getEventHandlerParams, hasSelector, wrapSelectionDragFunc } from './utils'; import { getDragItems, getEventHandlerParams, hasSelector, wrapSelectionDragFunc } from './utils';
import type { import type {
BaseNode, BaseNode,
@@ -107,11 +107,10 @@ export function XYDrag({
let hasChange = false; let hasChange = false;
dragItems = dragItems.map((n) => { dragItems = dragItems.map((n) => {
const nextPosition = { x: x - n.distance.x, y: y - n.distance.y }; let nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
if (snapToGrid) { if (snapToGrid) {
nextPosition.x = snapGrid[0] * Math.round(nextPosition.x / snapGrid[0]); nextPosition = snapPosition(nextPosition, snapGrid);
nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]);
} }
const updatedPos = calcNextPosition(n, nextPosition, nodes, nodeExtent, nodeOrigin, onError); const updatedPos = calcNextPosition(n, nextPosition, nodes, nodeExtent, nodeOrigin, onError);
@@ -173,10 +172,10 @@ export function XYDrag({
transform, transform,
snapGrid, snapGrid,
snapToGrid, snapToGrid,
selectNodesOnDrag,
onNodeDragStart, onNodeDragStart,
onSelectionDragStart, onSelectionDragStart,
unselectNodesAndEdges, unselectNodesAndEdges,
selectNodesOnDrag,
} = getStoreItems(); } = getStoreItems();
if (!selectNodesOnDrag && !multiSelectionActive && nodeId) { if (!selectNodesOnDrag && !multiSelectionActive && nodeId) {