feat(nested-nodes): change data structure to flat array of nodes
This commit is contained in:
+32
-36
@@ -13,7 +13,6 @@ import ReactFlow, {
|
|||||||
EdgeChange,
|
EdgeChange,
|
||||||
OnLoadParams,
|
OnLoadParams,
|
||||||
Connection,
|
Connection,
|
||||||
nodeHelper,
|
|
||||||
} from 'react-flow-renderer';
|
} from 'react-flow-renderer';
|
||||||
|
|
||||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||||
@@ -30,38 +29,35 @@ const initialNodes: Node[] = [
|
|||||||
position: { x: 400, y: 200 },
|
position: { x: 400, y: 200 },
|
||||||
className: 'light',
|
className: 'light',
|
||||||
style: { backgroundColor: 'rgba(255, 0, 0, .2)' },
|
style: { backgroundColor: 'rgba(255, 0, 0, .2)' },
|
||||||
childNodes: [
|
},
|
||||||
{
|
{
|
||||||
id: '4a',
|
id: '4a',
|
||||||
draggable: false,
|
data: { label: 'Node 4a', isNested: true },
|
||||||
data: { label: 'Node 4a', isNested: true },
|
position: { x: 400, y: 400 },
|
||||||
position: { x: 400, y: 400 },
|
className: 'light',
|
||||||
className: 'light',
|
parentNode: '4',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '4b',
|
id: '4b',
|
||||||
data: { label: 'Node 4b' },
|
data: { label: 'Node 4b' },
|
||||||
position: { x: 500, y: 500 },
|
position: { x: 500, y: 500 },
|
||||||
className: 'light',
|
className: 'light',
|
||||||
style: { backgroundColor: 'rgba(255, 0, 0, .2)' },
|
style: { backgroundColor: 'rgba(255, 0, 0, .2)' },
|
||||||
childNodes: [
|
parentNode: '4',
|
||||||
{
|
},
|
||||||
id: '4b1',
|
{
|
||||||
draggable: false,
|
id: '4b1',
|
||||||
data: { label: 'Node 4b1', isNested: true },
|
data: { label: 'Node 4b1', isNested: true },
|
||||||
position: { x: 450, y: 450 },
|
position: { x: 450, y: 450 },
|
||||||
className: 'light',
|
className: 'light',
|
||||||
},
|
parentNode: '4b',
|
||||||
{
|
},
|
||||||
id: '4b2',
|
{
|
||||||
draggable: false,
|
id: '4b2',
|
||||||
data: { label: 'Node 4b2', isNested: true },
|
data: { label: 'Node 4b2', isNested: true },
|
||||||
position: { x: 550, y: 550 },
|
position: { x: 550, y: 550 },
|
||||||
className: 'light',
|
className: 'light',
|
||||||
},
|
parentNode: '4b',
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -100,7 +96,7 @@ const BasicFlow = () => {
|
|||||||
|
|
||||||
const toggleClassnames = () => {
|
const toggleClassnames = () => {
|
||||||
setNodes((nds) => {
|
setNodes((nds) => {
|
||||||
return nodeHelper(nds).map((n) => {
|
return nds.map((n) => {
|
||||||
n.className = n.className === 'light' ? 'dark' : 'light';
|
n.className = n.className === 'light' ? 'dark' : 'light';
|
||||||
return n;
|
return n;
|
||||||
});
|
});
|
||||||
@@ -109,8 +105,8 @@ const BasicFlow = () => {
|
|||||||
|
|
||||||
const toggleChildNodes = () => {
|
const toggleChildNodes = () => {
|
||||||
setNodes((nds) => {
|
setNodes((nds) => {
|
||||||
return nodeHelper(nds).map((n) => {
|
return nds.map((n) => {
|
||||||
n.isHidden = n.data.isNested && !n.isHidden;
|
n.isHidden = !!n.parentNode && !n.isHidden;
|
||||||
return n;
|
return n;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import {
|
|||||||
HandleType,
|
HandleType,
|
||||||
ReactFlowState,
|
ReactFlowState,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { nodeHelper } from '../../utils/nodes';
|
|
||||||
|
|
||||||
interface ConnectionLineProps {
|
interface ConnectionLineProps {
|
||||||
connectionNodeId: ElementId;
|
connectionNodeId: ElementId;
|
||||||
connectionHandleId: ElementId | null;
|
connectionHandleId: ElementId | null;
|
||||||
@@ -29,7 +27,7 @@ interface ConnectionLineProps {
|
|||||||
CustomConnectionLineComponent?: ConnectionLineComponent;
|
CustomConnectionLineComponent?: ConnectionLineComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodesSelector = (s: ReactFlowState) => nodeHelper(s.nodes).flatten();
|
const nodesSelector = (s: ReactFlowState) => s.nodes;
|
||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
connectionNodeId,
|
connectionNodeId,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ComponentType } from 'react';
|
|||||||
import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges';
|
import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges';
|
||||||
import wrapEdge from '../../components/Edges/wrapEdge';
|
import wrapEdge from '../../components/Edges/wrapEdge';
|
||||||
import { rectToBox } from '../../utils/graph';
|
import { rectToBox } from '../../utils/graph';
|
||||||
import { nodeHelper } from '../../utils/nodes';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EdgeTypesType,
|
EdgeTypesType,
|
||||||
@@ -172,18 +171,16 @@ type SourceTargetNode = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNode => {
|
export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNode => {
|
||||||
return nodeHelper(nodes)
|
return nodes.reduce(
|
||||||
.flatten()
|
(res, node) => {
|
||||||
.reduce(
|
if (node.id === edge.source) {
|
||||||
(res, node) => {
|
res.sourceNode = node;
|
||||||
if (node.id === edge.source) {
|
}
|
||||||
res.sourceNode = node;
|
if (node.id === edge.target) {
|
||||||
}
|
res.targetNode = node;
|
||||||
if (node.id === edge.target) {
|
}
|
||||||
res.targetNode = node;
|
return res;
|
||||||
}
|
},
|
||||||
return res;
|
{ sourceNode: null, targetNode: null } as SourceTargetNode
|
||||||
},
|
);
|
||||||
{ sourceNode: null, targetNode: null } as SourceTargetNode
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,8 +77,16 @@ function Nodes({
|
|||||||
typeof node.height !== 'undefined';
|
typeof node.height !== 'undefined';
|
||||||
let childRect;
|
let childRect;
|
||||||
|
|
||||||
if (node.childNodes) {
|
const childNodes = nodes.filter((n) => n.parentNode === node.id);
|
||||||
childRect = getRectOfNodes(node.childNodes);
|
|
||||||
|
// if (childNodes.length) {
|
||||||
|
// console.log(node.id, childNodes, getRectOfNodes(childNodes));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// console.log(childNodes);
|
||||||
|
|
||||||
|
if (childNodes.length) {
|
||||||
|
childRect = getRectOfNodes(childNodes);
|
||||||
node.position = node.isDragging
|
node.position = node.isDragging
|
||||||
? node.position
|
? node.position
|
||||||
: { x: Math.round(childRect.x) - 10, y: Math.round(childRect.y) - 10 };
|
: { x: Math.round(childRect.x) - 10, y: Math.round(childRect.y) - 10 };
|
||||||
@@ -126,20 +134,6 @@ function Nodes({
|
|||||||
dragHandle={node.dragHandle}
|
dragHandle={node.dragHandle}
|
||||||
zIndex={3 + recursionDepth}
|
zIndex={3 + recursionDepth}
|
||||||
/>
|
/>
|
||||||
{node.childNodes && (
|
|
||||||
<MemoizedNodes
|
|
||||||
nodes={node.childNodes}
|
|
||||||
snapToGrid={snapToGrid}
|
|
||||||
snapGrid={snapGrid}
|
|
||||||
nodesDraggable={nodesDraggable}
|
|
||||||
nodesConnectable={nodesConnectable}
|
|
||||||
resizeObserver={resizeObserver}
|
|
||||||
elementsSelectable={elementsSelectable}
|
|
||||||
scale={scale}
|
|
||||||
recursionDepth={recursionDepth + 1}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ export {
|
|||||||
applyEdgeChanges,
|
applyEdgeChanges,
|
||||||
} from './utils/graph';
|
} from './utils/graph';
|
||||||
|
|
||||||
export { nodeHelper } from './utils/nodes';
|
|
||||||
|
|
||||||
export { default as useZoomPanHelper } from './hooks/useZoomPanHelper';
|
export { default as useZoomPanHelper } from './hooks/useZoomPanHelper';
|
||||||
export { default as useUpdateNodeInternals } from './hooks/useUpdateNodeInternals';
|
export { default as useUpdateNodeInternals } from './hooks/useUpdateNodeInternals';
|
||||||
|
|
||||||
|
|||||||
+8
-12
@@ -28,7 +28,6 @@ import {
|
|||||||
NodePositionChange,
|
NodePositionChange,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { isNode, isEdge, getRectOfNodes, getNodesInside, getConnectedEdges } from '../utils/graph';
|
import { isNode, isEdge, getRectOfNodes, getNodesInside, getConnectedEdges } from '../utils/graph';
|
||||||
import { nodeHelper } from '../utils/nodes';
|
|
||||||
import { getHandleBounds } from '../components/Nodes/utils';
|
import { getHandleBounds } from '../components/Nodes/utils';
|
||||||
|
|
||||||
const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
|
const { Provider, useStore, useStoreApi } = createContext<ReactFlowState>();
|
||||||
@@ -127,7 +126,7 @@ const createStore = () =>
|
|||||||
const { onNodesChange, nodes, transform } = get();
|
const { onNodesChange, nodes, transform } = get();
|
||||||
|
|
||||||
const nodesToChange: NodeChange[] = updates.reduce<NodeChange[]>((res, update) => {
|
const nodesToChange: NodeChange[] = updates.reduce<NodeChange[]>((res, update) => {
|
||||||
const node = nodeHelper(nodes).find((n) => n.id === update.id);
|
const node = nodes.find((n) => n.id === update.id);
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
const dimensions = getDimensions(update.nodeElement);
|
const dimensions = getDimensions(update.nodeElement);
|
||||||
@@ -181,12 +180,11 @@ const createStore = () =>
|
|||||||
const { onNodesChange, nodes, nodeExtent } = get();
|
const { onNodesChange, nodes, nodeExtent } = get();
|
||||||
|
|
||||||
if (onNodesChange) {
|
if (onNodesChange) {
|
||||||
const matchingNodes = nodeHelper(nodes).filter((n) => n.id === id || !!n.isSelected);
|
const matchingNodes = nodes.filter((n) => n.id === id || n.parentNode === id || !!n.isSelected);
|
||||||
const changingNodes = nodeHelper(matchingNodes).flatten();
|
|
||||||
|
|
||||||
if (changingNodes?.length) {
|
if (matchingNodes?.length) {
|
||||||
onNodesChange(
|
onNodesChange(
|
||||||
changingNodes.map((n) => {
|
matchingNodes.map((n) => {
|
||||||
const change: NodePositionChange = {
|
const change: NodePositionChange = {
|
||||||
id: n.id,
|
id: n.id,
|
||||||
type: 'position',
|
type: 'position',
|
||||||
@@ -304,12 +302,10 @@ const createStore = () =>
|
|||||||
unselectNodesAndEdges: () => {
|
unselectNodesAndEdges: () => {
|
||||||
const { nodes, edges, onNodesChange, onEdgesChange } = get();
|
const { nodes, edges, onNodesChange, onEdgesChange } = get();
|
||||||
|
|
||||||
const nodesToUnselect = nodeHelper(nodes)
|
const nodesToUnselect = nodes.map((n) => {
|
||||||
.flatten()
|
n.isSelected = false;
|
||||||
.map((n) => {
|
return createNodeOrEdgeSelectionChange(false)(n);
|
||||||
n.isSelected = false;
|
}) as NodeChange[];
|
||||||
return createNodeOrEdgeSelectionChange(false)(n);
|
|
||||||
}) as NodeChange[];
|
|
||||||
const edgesToUnselect = edges.map(createNodeOrEdgeSelectionChange(false)) as EdgeChange[];
|
const edgesToUnselect = edges.map(createNodeOrEdgeSelectionChange(false)) as EdgeChange[];
|
||||||
|
|
||||||
if (nodesToUnselect.length) {
|
if (nodesToUnselect.length) {
|
||||||
|
|||||||
+1
-1
@@ -86,7 +86,7 @@ export interface Node<T = any> {
|
|||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
handleBounds?: NodeHandleBounds;
|
handleBounds?: NodeHandleBounds;
|
||||||
childNodes?: Node[];
|
parentNode?: ElementId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ArrowHeadType {
|
export enum ArrowHeadType {
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
import { Node } from '../types';
|
|
||||||
|
|
||||||
function flat(arr: Node[], target: Node[]) {
|
|
||||||
arr.forEach(function (el) {
|
|
||||||
target.push(el);
|
|
||||||
if (el.childNodes) {
|
|
||||||
flat(el.childNodes, target);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterNodes = (condition: (node: Node) => boolean, nodes: Node[]): Node[] => {
|
|
||||||
let res = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
|
||||||
const n = nodes[i];
|
|
||||||
|
|
||||||
if (condition(n)) {
|
|
||||||
res.push(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n.childNodes) {
|
|
||||||
const matches = filterNodes(condition, n.childNodes);
|
|
||||||
|
|
||||||
for (let j = 0; j < matches.length; j++) {
|
|
||||||
res.push(matches[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapNodes = (accessor: (node: Node) => any, nodes: Node[]): Node[] => {
|
|
||||||
let res = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
|
||||||
const n = nodes[i];
|
|
||||||
res.push(accessor(n));
|
|
||||||
|
|
||||||
if (n.childNodes) {
|
|
||||||
n.childNodes = mapNodes(accessor, n.childNodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
const forEachNode = (accessor: (node: Node) => any, nodes: Node[]): void => {
|
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
|
||||||
const n = nodes[i];
|
|
||||||
accessor(n);
|
|
||||||
|
|
||||||
if (n.childNodes) {
|
|
||||||
forEachNode(accessor, n.childNodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function findNode(accessor: (node: Node) => boolean, nodes: Node[]): Node | undefined {
|
|
||||||
let res = undefined;
|
|
||||||
|
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
|
||||||
const n = nodes[i];
|
|
||||||
|
|
||||||
if (accessor(n)) {
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n.childNodes) {
|
|
||||||
res = findNode(accessor, n.childNodes);
|
|
||||||
|
|
||||||
if (res) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeHelper {
|
|
||||||
filter: (accessor: (node: Node) => boolean) => Node[];
|
|
||||||
map: (accessor: (node: Node) => any) => Node[];
|
|
||||||
find: (accessor: (node: Node) => boolean) => Node | undefined;
|
|
||||||
forEach: (accessor: (node: Node) => void) => void;
|
|
||||||
flatten: () => Node[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function nodeHelper(nodes: Node[]): NodeHelper {
|
|
||||||
const flatten: NodeHelper['flatten'] = () => {
|
|
||||||
const flattened: Node[] = [];
|
|
||||||
flat(nodes, flattened);
|
|
||||||
return flattened;
|
|
||||||
};
|
|
||||||
|
|
||||||
const filter: NodeHelper['filter'] = (accessor) => {
|
|
||||||
return filterNodes(accessor, nodes);
|
|
||||||
};
|
|
||||||
|
|
||||||
const forEach: NodeHelper['forEach'] = (accessor) => {
|
|
||||||
return forEachNode(accessor, nodes);
|
|
||||||
};
|
|
||||||
|
|
||||||
const find: NodeHelper['find'] = (accessor) => {
|
|
||||||
return findNode(accessor, nodes);
|
|
||||||
};
|
|
||||||
|
|
||||||
const map: NodeHelper['map'] = (accessor) => {
|
|
||||||
return mapNodes(accessor, nodes);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
filter,
|
|
||||||
forEach,
|
|
||||||
flatten,
|
|
||||||
find,
|
|
||||||
map,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user