feat(nodes): add helper functions to iterate over child nodes

This commit is contained in:
Christopher Möller
2021-10-21 11:53:44 +02:00
parent 9350884836
commit 9ad894d2df
7 changed files with 157 additions and 96 deletions
+2 -2
View File
@@ -13,6 +13,7 @@ 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);
@@ -79,9 +80,8 @@ const BasicFlow = () => {
const toggleClassnames = () => { const toggleClassnames = () => {
setNodes((nds) => { setNodes((nds) => {
return nds.map((n) => { return nodeHelper(nds).map((n) => {
n.className = n.className === 'light' ? 'dark' : 'light'; n.className = n.className === 'light' ? 'dark' : 'light';
return n; return n;
}); });
}); });
+2 -2
View File
@@ -14,7 +14,7 @@ import {
HandleType, HandleType,
ReactFlowState, ReactFlowState,
} from '../../types'; } from '../../types';
import { flattenNodes } from '../../utils/graph'; import { nodeHelper } from '../../utils/nodes';
interface ConnectionLineProps { interface ConnectionLineProps {
connectionNodeId: ElementId; connectionNodeId: ElementId;
@@ -29,7 +29,7 @@ interface ConnectionLineProps {
CustomConnectionLineComponent?: ConnectionLineComponent; CustomConnectionLineComponent?: ConnectionLineComponent;
} }
const nodesSelector = (s: ReactFlowState) => flattenNodes(s.nodes); const nodesSelector = (s: ReactFlowState) => nodeHelper(s.nodes).flatten();
export default ({ export default ({
connectionNodeId, connectionNodeId,
+16 -13
View File
@@ -2,7 +2,8 @@ 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, flattenNodes } from '../../utils/graph'; import { rectToBox } from '../../utils/graph';
import { nodeHelper } from '../../utils/nodes';
import { import {
EdgeTypesType, EdgeTypesType,
@@ -171,16 +172,18 @@ type SourceTargetNode = {
}; };
export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNode => { export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNode => {
return flattenNodes(nodes).reduce( return nodeHelper(nodes)
(res, node) => { .flatten()
if (node.id === edge.source) { .reduce(
res.sourceNode = node; (res, node) => {
} if (node.id === edge.source) {
if (node.id === edge.target) { res.sourceNode = node;
res.targetNode = node; }
} if (node.id === edge.target) {
return res; res.targetNode = node;
}, }
{ sourceNode: null, targetNode: null } as SourceTargetNode return res;
); },
{ sourceNode: null, targetNode: null } as SourceTargetNode
);
}; };
+3
View File
@@ -25,6 +25,9 @@ export {
applyNodeChanges, applyNodeChanges,
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';
+13 -53
View File
@@ -27,7 +27,8 @@ import {
EdgeChange, EdgeChange,
NodePositionChange, NodePositionChange,
} from '../types'; } from '../types';
import { isNode, isEdge, getRectOfNodes, getNodesInside, getConnectedEdges, flattenNodes } 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>();
@@ -38,50 +39,6 @@ const createNodeOrEdgeSelectionChange = (isSelected: boolean) => (item: Node | E
isSelected, isSelected,
}); });
const findNodeById = (id: string, nodes: Node[]): Node | null => {
let res = null;
for (let i = 0; i < nodes.length; i++) {
const n = nodes[i];
if (n.id === id) {
return n;
}
if (n.childNodes) {
res = findNodeById(id, n.childNodes);
if (res) {
return res;
}
}
}
return res;
};
const findNodes = (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 = findNodes(condition, n.childNodes);
for (let j = 0; j < matches.length; j++) {
res.push(matches[j]);
}
}
}
return res;
};
const createStore = () => const createStore = () =>
create<ReactFlowState>((set, get) => ({ create<ReactFlowState>((set, get) => ({
width: 0, width: 0,
@@ -170,7 +127,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 = findNodeById(update.id, nodes); const node = nodeHelper(nodes).find((n) => n.id === update.id);
if (node) { if (node) {
const dimensions = getDimensions(update.nodeElement); const dimensions = getDimensions(update.nodeElement);
@@ -224,11 +181,12 @@ const createStore = () =>
const { onNodesChange, nodes, nodeExtent } = get(); const { onNodesChange, nodes, nodeExtent } = get();
if (onNodesChange) { if (onNodesChange) {
const matchingNodes = flattenNodes(findNodes((n) => n.id === id || !!n.isSelected, nodes)); const matchingNodes = nodeHelper(nodes).filter((n) => n.id === id || !!n.isSelected);
const changingNodes = nodeHelper(matchingNodes).flatten();
if (matchingNodes?.length) { if (changingNodes?.length) {
onNodesChange( onNodesChange(
matchingNodes.map((n) => { changingNodes.map((n) => {
const change: NodePositionChange = { const change: NodePositionChange = {
id: n.id, id: n.id,
type: 'position', type: 'position',
@@ -346,10 +304,12 @@ const createStore = () =>
unselectNodesAndEdges: () => { unselectNodesAndEdges: () => {
const { nodes, edges, onNodesChange, onEdgesChange } = get(); const { nodes, edges, onNodesChange, onEdgesChange } = get();
const nodesToUnselect = flattenNodes(nodes).map((n) => { const nodesToUnselect = nodeHelper(nodes)
n.isSelected = false; .flatten()
return createNodeOrEdgeSelectionChange(false)(n); .map((n) => {
}) as NodeChange[]; n.isSelected = false;
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) {
-26
View File
@@ -342,29 +342,3 @@ export function applyNodeChanges(changes: NodeChange[], nodes: Node[]): Node[] {
export function applyEdgeChanges(changes: EdgeChange[], edges: Edge[]): Edge[] { export function applyEdgeChanges(changes: EdgeChange[], edges: Edge[]): Edge[] {
return applyChanges(changes, edges) as Edge[]; return applyChanges(changes, edges) as Edge[];
} }
function flat(arr: Node[], target: Node[]) {
arr.forEach(function (el) {
if (el.childNodes) {
flat(el.childNodes, target);
} else {
target.push(el);
}
});
}
export function flattenNodes(nodes: Node[]): Node[] {
const flattened: Node[] = [];
flat(nodes, flattened);
return flattened;
// return nodes.reduce<Node[]>((result, node) => {
// result.push(node);
// if (node.childNodes) {
// result.push(...flattenNodes(node.childNodes));
// }
// return result;
// }, []);
}
+121
View File
@@ -0,0 +1,121 @@
import { Node } from '../types';
function flat(arr: Node[], target: Node[]) {
arr.forEach(function (el) {
if (el.childNodes) {
flat(el.childNodes, target);
} else {
target.push(el);
}
});
}
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,
};
}