fix(subflows): handle selections correctly

This commit is contained in:
moklick
2021-11-26 19:35:31 +01:00
parent 330eefe695
commit 801d8f2ad4
10 changed files with 62 additions and 53 deletions
+2 -2
View File
@@ -3,7 +3,7 @@ import cc from 'classcat';
import shallow from 'zustand/shallow';
import { useStore } from '../../store';
import { getRectOfNodes } from '../../utils/graph';
import { getRectOfNodeInternals } from '../../utils/graph';
import { getBoundsofRects } from '../../utils';
import { Node, ReactFlowState, Rect } from '../../types';
import MiniMapNode from './MiniMapNode';
@@ -55,7 +55,7 @@ const MiniMap = ({
const hasNodes = nodeInternals && nodeInternals.size > 0;
// @TODO: work with nodeInternals instead of converting it to an array
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
const bb = getRectOfNodes(nodes);
const bb = getRectOfNodeInternals(nodes);
const viewBB: Rect = {
x: -tX / tScale,
y: -tY / tScale,
+2 -2
View File
@@ -9,7 +9,7 @@ import cc from 'classcat';
import { useStore } from '../../store';
import { Node, ReactFlowState } from '../../types';
import { getRectOfNodes } from '../..';
import { getRectOfNodeInternals } from '../../utils/graph';
export interface NodesSelectionProps {
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void;
@@ -52,7 +52,7 @@ function NodesSelection({
[tX, tY, tScale]
);
const selectedNodesBbox = useMemo(() => getRectOfNodes(selectedNodes), [selectedNodes]);
const selectedNodesBbox = useMemo(() => getRectOfNodeInternals(selectedNodes), [selectedNodes]);
const innerStyle = useMemo(
() => ({
+8 -2
View File
@@ -7,9 +7,15 @@ import shallow from 'zustand/shallow';
import { useStore, useStoreApi } from '../../store';
import { getSelectionChanges } from '../../utils/changes';
import { XYPosition, ReactFlowState, SelectionRect, NodeChange, EdgeChange } from '../../types';
import { XYPosition, ReactFlowState, NodeChange, EdgeChange, Rect } from '../../types';
import { getConnectedEdges, getNodesInside } from '../../utils/graph';
type SelectionRect = Rect & {
startX: number;
startY: number;
draw: boolean;
};
type UserSelectionProps = {
selectionKeyPressed: boolean;
};
@@ -98,7 +104,7 @@ export default memo(({ selectionKeyPressed }: UserSelectionProps) => {
const { nodeInternals, edges, transform, onNodesChange, onEdgesChange } = store.getState();
const nodes = Array.from(nodeInternals).map(([_, node]) => node);
const selectedNodes = getNodesInside(nodes, nextUserSelectRect, transform, false, true);
const selectedNodes = getNodesInside(nodeInternals, nextUserSelectRect, transform, false, true);
const selectedEdgeIds = getConnectedEdges(selectedNodes, edges).map((e) => e.id);
const selectedNodeIds = selectedNodes.map((n) => n.id);
+3 -1
View File
@@ -1,5 +1,6 @@
import React, { memo, useMemo, ComponentType, MouseEvent, useEffect, useRef } from 'react';
import shallow from 'zustand/shallow';
import useVisibleNodes from '../../hooks/useVisibleNodes';
import { useStore } from '../../store';
import { Node, NodeTypesType, ReactFlowState, WrapNodeProps } from '../../types';
@@ -43,6 +44,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
snapToGrid,
nodeInternals,
} = useStore(selector, shallow);
const nodes = useVisibleNodes(props.onlyRenderVisibleElements);
const reseizeObserverRef = useRef<ResizeObserver>();
const resizeObserver = useMemo(() => {
@@ -73,7 +75,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
return (
<div className="react-flow__nodes react-flow__container">
{Array.from(nodeInternals).map(([_, node]) => {
{nodes.map((node) => {
const nodeType = node.type || 'default';
const internals = nodeInternals.get(node.id);
+2 -4
View File
@@ -8,11 +8,9 @@ function useVisibleNodes(onlyRenderVisible: boolean) {
const nodes = useStore(
useCallback(
(s: ReactFlowState) => {
// @TODO: work with nodeInternals instead of converting it to an array
const nodes = Array.from(s.nodeInternals).map(([_, node]) => node);
return onlyRenderVisible
? getNodesInside(nodes, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true)
: nodes;
? getNodesInside(s.nodeInternals, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true)
: Array.from(s.nodeInternals).map(([_, node]) => node);
},
[onlyRenderVisible]
)
+4 -2
View File
@@ -3,7 +3,7 @@ import { zoomIdentity } from 'd3-zoom';
import shallow from 'zustand/shallow';
import { useStoreApi, useStore } from '../store';
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph';
import { getRectOfNodeInternals, pointToRendererPoint, getTransformForBounds } from '../utils/graph';
import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, ReactFlowState, Rect, XYPosition } from '../types';
const DEFAULT_PADDING = 0.1;
@@ -48,7 +48,9 @@ const useZoomPanHelper = (): ZoomPanHelperFunctions => {
return;
}
const bounds = getRectOfNodes(options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.hidden));
const bounds = getRectOfNodeInternals(
options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.hidden)
);
const [x, y, zoom] = getTransformForBounds(
bounds,
width,
-9
View File
@@ -56,15 +56,6 @@ const initialState: ReactFlowStore = {
nodeExtent: infiniteExtent,
nodesSelectionActive: false,
userSelectionActive: false,
userSelectionRect: {
startX: 0,
startY: 0,
x: 0,
y: 0,
width: 0,
height: 0,
draw: false,
},
connectionNodeId: null,
connectionHandleId: null,
connectionHandleType: 'source',
-8
View File
@@ -24,12 +24,6 @@ export type OnNodesChange = (nodes: NodeChange[]) => void;
export type OnEdgesChange = (nodes: EdgeChange[]) => void;
export interface SelectionRect extends Rect {
startX: number;
startY: number;
draw: boolean;
}
export type OnLoadParams<T = any> = {
zoomIn: () => void;
zoomOut: () => void;
@@ -151,8 +145,6 @@ export type ReactFlowStore = {
nodesSelectionActive: boolean;
userSelectionActive: boolean;
userSelectionRect: SelectionRect;
connectionNodeId: string | null;
connectionHandleId: string | null;
connectionHandleType: HandleType | null;
+2 -2
View File
@@ -104,9 +104,9 @@ export type NodeDimensionUpdate = {
};
export type NodeInternalsItem = Node & {
positionAbsolute?: XYPosition;
handleBounds?: NodeHandleBounds;
positionAbsolute: XYPosition;
z: number;
handleBounds?: NodeHandleBounds;
isParent?: boolean;
};
+39 -21
View File
@@ -1,6 +1,16 @@
import { boxToRect, clamp, getBoundsOfBoxes, rectToBox } from '../utils';
import { Node, Edge, Connection, EdgeMarkerType, Transform, XYPosition, Rect } from '../types';
import {
Node,
Edge,
Connection,
EdgeMarkerType,
Transform,
XYPosition,
Rect,
NodeInternals,
NodeInternalsItem,
} from '../types';
export const isEdge = (element: Node | Connection | Edge): element is Edge =>
'id' in element && 'source' in element && 'target' in element;
@@ -124,6 +134,7 @@ export const pointToRendererPoint = (
return position;
};
// @TODO: use one function for getRectOfNodes and getRectOfNodeInternals
export const getRectOfNodes = (nodes: Node[]): Rect => {
const box = nodes.reduce(
(currBox, { position, width, height }) =>
@@ -134,14 +145,24 @@ export const getRectOfNodes = (nodes: Node[]): Rect => {
return boxToRect(box);
};
export const getRectOfNodeInternals = (nodes: NodeInternalsItem[]): Rect => {
const box = nodes.reduce(
(currBox, { positionAbsolute, width, height }) =>
getBoundsOfBoxes(currBox, rectToBox({ ...positionAbsolute, width: width || 0, height: height || 0 })),
{ x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity }
);
return boxToRect(box);
};
export const getNodesInside = (
nodes: Node[],
nodeInternals: NodeInternals,
rect: Rect,
[tx, ty, tScale]: Transform = [0, 0, 1],
partially: boolean = false,
// set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute
excludeNonSelectableNodes: boolean = false
): Node[] => {
): NodeInternalsItem[] => {
const rBox = rectToBox({
x: (rect.x - tx) / tScale,
y: (rect.y - ty) / tScale,
@@ -149,35 +170,32 @@ export const getNodesInside = (
height: rect.height / tScale,
});
return nodes.filter(({ selectable = true, position, width, height, dragging }) => {
const visibleNodes: NodeInternalsItem[] = [];
nodeInternals.forEach((node) => {
const { positionAbsolute, width, height, dragging, selectable = true } = node;
if (excludeNonSelectableNodes && !selectable) {
return false;
}
const nBox = rectToBox({ ...position, width: width || 0, height: height || 0 });
const nBox = rectToBox({ ...positionAbsolute, width: width || 0, height: height || 0 });
const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x));
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y));
const overlappingArea = Math.ceil(xOverlap * yOverlap);
const notInitialized =
typeof width === 'undefined' || typeof height === 'undefined' || width === null || height === null || dragging;
if (
typeof width === 'undefined' ||
typeof height === 'undefined' ||
width === null ||
height === null ||
dragging
) {
// nodes are initialized with width and height = null
return true;
}
if (partially) {
return overlappingArea > 0;
}
const partiallyVisible = partially && overlappingArea > 0;
const area = (width || 0) * (height || 0);
const isVisible = notInitialized || partiallyVisible || overlappingArea >= area;
return overlappingArea >= area;
if (isVisible) {
visibleNodes.push(node);
}
});
return visibleNodes;
};
export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => {