refactor(connectionlookup): use maps instead of arrays
This commit is contained in:
@@ -23,7 +23,7 @@ function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeI
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log('useEffect, node id:', nodeId, handleProps.type, status);
|
console.log('useEffect, node id:', nodeId, handleProps.type, status);
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
return <Handle {...handleProps} />;
|
return <Handle {...handleProps} />;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Connection, HandleType } from '@xyflow/system';
|
|||||||
|
|
||||||
import { useStore } from './useStore';
|
import { useStore } from './useStore';
|
||||||
import { useNodeId } from '../contexts/NodeIdContext';
|
import { useNodeId } from '../contexts/NodeIdContext';
|
||||||
import { areConnectionsEqual, isSameConnection } from '../utils/general';
|
|
||||||
|
|
||||||
type useHandleConnectionStatusParams = {
|
type useHandleConnectionStatusParams = {
|
||||||
handleType: HandleType;
|
handleType: HandleType;
|
||||||
@@ -13,10 +12,69 @@ type useHandleConnectionStatusParams = {
|
|||||||
onDisconnect?: (connections: Connection[]) => void;
|
onDisconnect?: (connections: Connection[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function areConnectionMapsEqual(a?: Map<string, Connection>, b?: Map<string, Connection>) {
|
||||||
|
if (!a && !b) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a || !b || a.size !== b.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a.size && !b.size) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of a.keys()) {
|
||||||
|
if (!b.has(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We call the callback for all connections in a that are not in b
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function handleConnectionChange(
|
||||||
|
a: Map<string, Connection>,
|
||||||
|
b: Map<string, Connection>,
|
||||||
|
cb?: (diff: Connection[]) => void
|
||||||
|
) {
|
||||||
|
if (!cb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff: Connection[] = [];
|
||||||
|
|
||||||
|
a.forEach((connection, key) => {
|
||||||
|
if (!b?.has(key)) {
|
||||||
|
diff.push(connection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (diff.length) {
|
||||||
|
cb(diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to check if a <Handle /> is connected to another <Handle /> and get the connections.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param param.handleType - 'source' or 'target'
|
||||||
|
* @param param.handleId - the handle id (this is only needed if the node has multiple handles of the same type)
|
||||||
|
* @param param.nodeId - node id - if not provided, the node id from the NodeIdContext is used
|
||||||
|
* @param param.onConnect - gets called when a connection is established
|
||||||
|
* @param param.onDisconnect - gets called when a connection is removed
|
||||||
|
* @returns a `connected` boolean and a connections array
|
||||||
|
*/
|
||||||
export function useHandleConnectionStatus({
|
export function useHandleConnectionStatus({
|
||||||
handleType,
|
handleType,
|
||||||
nodeId,
|
|
||||||
handleId = null,
|
handleId = null,
|
||||||
|
nodeId,
|
||||||
onConnect,
|
onConnect,
|
||||||
onDisconnect,
|
onDisconnect,
|
||||||
}: useHandleConnectionStatusParams): {
|
}: useHandleConnectionStatusParams): {
|
||||||
@@ -24,41 +82,29 @@ export function useHandleConnectionStatus({
|
|||||||
connections: Connection[] | null;
|
connections: Connection[] | null;
|
||||||
} {
|
} {
|
||||||
const _nodeId = useNodeId();
|
const _nodeId = useNodeId();
|
||||||
const prevConnections = useRef<Connection[] | null>(null);
|
const prevConnections = useRef<Map<string, Connection> | null>(null);
|
||||||
const currentNodeId = nodeId || _nodeId;
|
const currentNodeId = nodeId || _nodeId;
|
||||||
|
|
||||||
const connections = useStore(
|
const connections = useStore(
|
||||||
(state) => state.connectionLookup.get(`${currentNodeId}-${handleType}-${handleId}`) || null,
|
(state) => state.connectionLookup.get(`${currentNodeId}-${handleType}-${handleId}`),
|
||||||
areConnectionsEqual
|
areConnectionMapsEqual
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts
|
// @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts
|
||||||
if (prevConnections.current && prevConnections.current !== connections) {
|
if (prevConnections.current && prevConnections.current !== connections) {
|
||||||
const disconnectedConnections = prevConnections.current.filter(
|
const _connections = connections ?? new Map();
|
||||||
(prevConnection) => !connections?.find((connection) => isSameConnection(connection, prevConnection))
|
handleConnectionChange(prevConnections.current, _connections, onDisconnect);
|
||||||
);
|
handleConnectionChange(_connections, prevConnections.current, onConnect);
|
||||||
|
|
||||||
const newConnections = connections?.filter(
|
|
||||||
(connection) => !prevConnections.current?.find((prevConnection) => isSameConnection(prevConnection, connection))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (disconnectedConnections.length) {
|
|
||||||
onDisconnect?.(disconnectedConnections);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newConnections?.length) {
|
|
||||||
onConnect?.(newConnections);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prevConnections.current = connections ?? [];
|
prevConnections.current = connections ?? new Map();
|
||||||
}, [connections, onConnect, onDisconnect]);
|
}, [connections, onConnect, onDisconnect]);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
connected: !!connections,
|
connected: !!connections,
|
||||||
connections,
|
connections: Array.from(connections?.values() ?? []),
|
||||||
}),
|
}),
|
||||||
[connections]
|
[connections]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const getInitialState = ({
|
|||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
} = {}): ReactFlowStore => {
|
} = {}): ReactFlowStore => {
|
||||||
const nodeLookup = new Map<string, Node>();
|
const nodeLookup = new Map<string, Node>();
|
||||||
const connectionLookup = updateConnectionLookup(new Map<string, Connection[]>(), edges);
|
const connectionLookup = updateConnectionLookup(new Map<string, Map<string, Connection>>(), edges);
|
||||||
const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
||||||
|
|
||||||
let transform: Transform = [0, 0, 1];
|
let transform: Transform = [0, 0, 1];
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function updateNodesAndEdgesSelections({ changedNodes, changedEdges, get,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateConnectionLookup(lookup: Map<string, Connection[]>, edges: Edge[]) {
|
export function updateConnectionLookup(lookup: Map<string, Map<string, Connection>>, edges: Edge[]) {
|
||||||
lookup.clear();
|
lookup.clear();
|
||||||
|
|
||||||
edges.forEach(({ source, target, sourceHandle = null, targetHandle = null }) => {
|
edges.forEach(({ source, target, sourceHandle = null, targetHandle = null }) => {
|
||||||
@@ -52,13 +52,12 @@ export function updateConnectionLookup(lookup: Map<string, Connection[]>, edges:
|
|||||||
const sourceKey = `${source}-source-${sourceHandle}`;
|
const sourceKey = `${source}-source-${sourceHandle}`;
|
||||||
const targetKey = `${target}-target-${targetHandle}`;
|
const targetKey = `${target}-target-${targetHandle}`;
|
||||||
|
|
||||||
const prevSource = lookup.get(sourceKey);
|
const prevSource = lookup.get(sourceKey) || new Map();
|
||||||
const prevTarget = lookup.get(targetKey);
|
const prevTarget = lookup.get(targetKey) || new Map();
|
||||||
|
|
||||||
const connection = { source, target, sourceHandle, targetHandle };
|
const connection = { source, target, sourceHandle, targetHandle };
|
||||||
|
|
||||||
lookup.set(sourceKey, prevSource ? [...prevSource, connection] : [connection]);
|
lookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection));
|
||||||
lookup.set(targetKey, prevTarget ? [...prevTarget, connection] : [connection]);
|
lookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export type ReactFlowStore = {
|
|||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
nodeLookup: Map<string, Node>;
|
nodeLookup: Map<string, Node>;
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
connectionLookup: Map<string, Connection[]>;
|
connectionLookup: Map<string, Map<string, Connection>>;
|
||||||
|
|
||||||
onNodesChange: OnNodesChange | null;
|
onNodesChange: OnNodesChange | null;
|
||||||
onEdgesChange: OnEdgesChange | null;
|
onEdgesChange: OnEdgesChange | null;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
getIncomersBase,
|
getIncomersBase,
|
||||||
updateEdgeBase,
|
updateEdgeBase,
|
||||||
getConnectedEdgesBase,
|
getConnectedEdgesBase,
|
||||||
Connection,
|
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { Edge, Node } from '../types';
|
import type { Edge, Node } from '../types';
|
||||||
@@ -18,32 +17,3 @@ export const getIncomers = getIncomersBase<Node, Edge>;
|
|||||||
export const addEdge = addEdgeBase<Edge>;
|
export const addEdge = addEdgeBase<Edge>;
|
||||||
export const updateEdge = updateEdgeBase<Edge>;
|
export const updateEdge = updateEdgeBase<Edge>;
|
||||||
export const getConnectedEdges = getConnectedEdgesBase<Node, Edge>;
|
export const getConnectedEdges = getConnectedEdgesBase<Node, Edge>;
|
||||||
|
|
||||||
export function isSameConnection(a: Connection, b: Connection) {
|
|
||||||
return (
|
|
||||||
a.source === b.source &&
|
|
||||||
a.target === b.target &&
|
|
||||||
a.sourceHandle === b.sourceHandle &&
|
|
||||||
a.targetHandle === b.targetHandle
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function areConnectionsEqual(a: Connection[] | null | undefined, b: Connection[] | null | undefined) {
|
|
||||||
if (!a && !b) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!a || !b) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.length !== b.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!a.length && !b.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !a.some((connA) => !b.find((connB) => isSameConnection(connA, connB)));
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user