initial version of middlewares
This commit is contained in:
@@ -17,6 +17,8 @@ import {
|
|||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
OnBeforeDelete,
|
OnBeforeDelete,
|
||||||
OnDelete,
|
OnDelete,
|
||||||
|
useNodeChangeMiddleware,
|
||||||
|
NodeChange,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
|
|
||||||
const onNodeDragStart = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes);
|
const onNodeDragStart = (_: ReactMouseEvent, node: Node, nodes: Node[]) => console.log('drag start', node, nodes);
|
||||||
@@ -191,6 +193,37 @@ const nodeColor = (n: Node): string => {
|
|||||||
return '#fff';
|
return '#fff';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function RestrictExtent({
|
||||||
|
minX = -Infinity,
|
||||||
|
minY = -Infinity,
|
||||||
|
maxX = Infinity,
|
||||||
|
maxY = Infinity,
|
||||||
|
}: {
|
||||||
|
minX?: number;
|
||||||
|
minY?: number;
|
||||||
|
maxX?: number;
|
||||||
|
maxY?: number;
|
||||||
|
}) {
|
||||||
|
useNodeChangeMiddleware(
|
||||||
|
useCallback(
|
||||||
|
(changes: NodeChange[]) => {
|
||||||
|
return changes.map((change) => {
|
||||||
|
if (change.type === 'position' && change.position) {
|
||||||
|
const { position } = change;
|
||||||
|
position.x = Math.min(Math.max(position.x, minX), maxX);
|
||||||
|
position.y = Math.min(Math.max(position.y, minY), maxY);
|
||||||
|
change.position = position;
|
||||||
|
}
|
||||||
|
return change;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[minX, minY, maxX, maxY]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const OverviewFlow = () => {
|
const OverviewFlow = () => {
|
||||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
@@ -234,8 +267,9 @@ const OverviewFlow = () => {
|
|||||||
maxZoom={Infinity}
|
maxZoom={Infinity}
|
||||||
onBeforeDelete={onBeforeDelete}
|
onBeforeDelete={onBeforeDelete}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onPaneMouseMove={onPaneMouseMove}
|
// onPaneMouseMove={onPaneMouseMove}
|
||||||
>
|
>
|
||||||
|
<RestrictExtent minX={-100} maxX={500} />
|
||||||
<MiniMap nodeBorderRadius={2} />
|
<MiniMap nodeBorderRadius={2} />
|
||||||
<Controls orientation="horizontal" />
|
<Controls orientation="horizontal" />
|
||||||
<Background gap={25} />
|
<Background gap={25} />
|
||||||
|
|||||||
@@ -28,7 +28,15 @@ export function BatchProvider<NodeType extends Node = Node, EdgeType extends Edg
|
|||||||
const store = useStoreApi<NodeType, EdgeType>();
|
const store = useStoreApi<NodeType, EdgeType>();
|
||||||
|
|
||||||
const nodeQueueHandler = useCallback((queueItems: QueueItem<NodeType>[]) => {
|
const nodeQueueHandler = useCallback((queueItems: QueueItem<NodeType>[]) => {
|
||||||
const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup, fitViewQueued } = store.getState();
|
const {
|
||||||
|
nodes = [],
|
||||||
|
setNodes,
|
||||||
|
hasDefaultNodes,
|
||||||
|
onNodesChange,
|
||||||
|
nodeLookup,
|
||||||
|
fitViewQueued,
|
||||||
|
nodeChangeMiddleware,
|
||||||
|
} = store.getState();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is essentially an `Array.reduce` in imperative clothing. Processing
|
* This is essentially an `Array.reduce` in imperative clothing. Processing
|
||||||
@@ -40,11 +48,23 @@ export function BatchProvider<NodeType extends Node = Node, EdgeType extends Edg
|
|||||||
next = typeof payload === 'function' ? payload(next) : payload;
|
next = typeof payload === 'function' ? payload(next) : payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
const changes = getElementsDiffChanges({
|
let changes = getElementsDiffChanges({
|
||||||
items: next,
|
items: next,
|
||||||
lookup: nodeLookup,
|
lookup: nodeLookup,
|
||||||
}) as NodeChange<NodeType>[];
|
}) as NodeChange<NodeType>[];
|
||||||
|
|
||||||
|
console.log('this workds');
|
||||||
|
|
||||||
|
for (const middleware of nodeChangeMiddleware.values()) {
|
||||||
|
changes = middleware(changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// for (const middleware of nodeSyncMiddleware.values()) {
|
||||||
|
// changes = middleware(changes);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Do we always want to setNodes(next) or only when there are changes?
|
||||||
|
|
||||||
if (hasDefaultNodes) {
|
if (hasDefaultNodes) {
|
||||||
setNodes(next);
|
setNodes(next);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import { NodeChange } from '@xyflow/system';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { useStoreApi } from './useStore';
|
||||||
|
import { Edge, Node } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We identify each middleware with a symbol that is unique for each hook instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function useSymbol() {
|
||||||
|
const symbol = useRef<symbol>();
|
||||||
|
if (!symbol.current) {
|
||||||
|
symbol.current = Symbol();
|
||||||
|
}
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNodeChangeMiddleware<NodeType extends Node = Node>(
|
||||||
|
fn: (changes: NodeChange<NodeType>[]) => NodeChange<NodeType>[]
|
||||||
|
) {
|
||||||
|
const store = useStoreApi<NodeType, Edge>();
|
||||||
|
const symbol = useSymbol();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { nodeChangeMiddleware } = store.getState();
|
||||||
|
nodeChangeMiddleware.set(symbol.current!, fn);
|
||||||
|
}, [fn]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { nodeChangeMiddleware } = store.getState();
|
||||||
|
return () => {
|
||||||
|
nodeChangeMiddleware.delete(symbol.current!);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNodeChangeMiddlewareDynamic<NodeType extends Node = Node>(
|
||||||
|
fn: (changes: NodeChange<NodeType>[]) => NodeChange<NodeType>[]
|
||||||
|
) {
|
||||||
|
const store = useStoreApi<NodeType, Edge>();
|
||||||
|
const symbol = useSymbol();
|
||||||
|
|
||||||
|
const { nodeChangeMiddleware } = store.getState();
|
||||||
|
nodeChangeMiddleware.delete(symbol.current!);
|
||||||
|
nodeChangeMiddleware.set(symbol.current!, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function useEdgeChangeMiddleware(fn: (changes: EdgeChange[]) => EdgeChange[]) {
|
||||||
|
// const store = useStoreApi();
|
||||||
|
// const symbol = useSymbol();
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const { edgeChangeMiddleware } = store.getState();
|
||||||
|
// edgeChangeMiddleware.set(symbol.current!, fn);
|
||||||
|
// }, [fn]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const { edgeChangeMiddleware } = store.getState();
|
||||||
|
// return () => {
|
||||||
|
// edgeChangeMiddleware.delete(symbol.current!);
|
||||||
|
// };
|
||||||
|
// }, []);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function useNodeSyncMiddleware(fn: (changes: NodeChange[]) => NodeChange[]) {
|
||||||
|
// const store = useStoreApi();
|
||||||
|
// const symbol = useSymbol();
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const { nodeSyncMiddleware } = store.getState();
|
||||||
|
// nodeSyncMiddleware.set(symbol.current!, fn);
|
||||||
|
// }, [fn]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const { nodeSyncMiddleware } = store.getState();
|
||||||
|
// return () => {
|
||||||
|
// nodeSyncMiddleware.delete(symbol.current!);
|
||||||
|
// };
|
||||||
|
// }, []);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function useEdgeSyncMiddleware(fn: (changes: EdgeChange[]) => EdgeChange[]) {
|
||||||
|
// const store = useStoreApi();
|
||||||
|
// const symbol = useSymbol();
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const { edgeSyncMiddleware } = store.getState();
|
||||||
|
// edgeSyncMiddleware.set(symbol.current!, fn);
|
||||||
|
// }, [fn]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const { edgeSyncMiddleware } = store.getState();
|
||||||
|
// return () => {
|
||||||
|
// edgeSyncMiddleware.delete(symbol.current!);
|
||||||
|
// };
|
||||||
|
// }, []);
|
||||||
|
// }
|
||||||
@@ -30,6 +30,8 @@ export { useConnection } from './hooks/useConnection';
|
|||||||
export { useInternalNode } from './hooks/useInternalNode';
|
export { useInternalNode } from './hooks/useInternalNode';
|
||||||
export { useNodeId } from './contexts/NodeIdContext';
|
export { useNodeId } from './contexts/NodeIdContext';
|
||||||
|
|
||||||
|
export { useNodeChangeMiddleware, useNodeChangeMiddlewareDynamic } from './hooks/useMiddleware';
|
||||||
|
|
||||||
export { applyNodeChanges, applyEdgeChanges } from './utils/changes';
|
export { applyNodeChanges, applyEdgeChanges } from './utils/changes';
|
||||||
export { isNode, isEdge } from './utils/general';
|
export { isNode, isEdge } from './utils/general';
|
||||||
|
|
||||||
|
|||||||
@@ -175,8 +175,8 @@ const createStore = ({
|
|||||||
},
|
},
|
||||||
updateNodePositions: (nodeDragItems, dragging = false) => {
|
updateNodePositions: (nodeDragItems, dragging = false) => {
|
||||||
const parentExpandChildren: ParentExpandChild[] = [];
|
const parentExpandChildren: ParentExpandChild[] = [];
|
||||||
const changes = [];
|
let changes = [];
|
||||||
const { nodeLookup, triggerNodeChanges } = get();
|
const { nodeLookup, triggerNodeChanges, nodeChangeMiddleware } = get();
|
||||||
|
|
||||||
for (const [id, dragItem] of nodeDragItems) {
|
for (const [id, dragItem] of nodeDragItems) {
|
||||||
// we are using the nodelookup to be sure to use the current expandParent and parentId value
|
// we are using the nodelookup to be sure to use the current expandParent and parentId value
|
||||||
@@ -216,6 +216,10 @@ const createStore = ({
|
|||||||
changes.push(...parentExpandChanges);
|
changes.push(...parentExpandChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const middleware of nodeChangeMiddleware.values()) {
|
||||||
|
changes = middleware(changes);
|
||||||
|
}
|
||||||
|
|
||||||
triggerNodeChanges(changes);
|
triggerNodeChanges(changes);
|
||||||
},
|
},
|
||||||
triggerNodeChanges: (changes) => {
|
triggerNodeChanges: (changes) => {
|
||||||
|
|||||||
@@ -146,6 +146,11 @@ const getInitialState = ({
|
|||||||
lib: 'react',
|
lib: 'react',
|
||||||
debug: false,
|
debug: false,
|
||||||
ariaLabelConfig: defaultAriaLabelConfig,
|
ariaLabelConfig: defaultAriaLabelConfig,
|
||||||
|
|
||||||
|
nodeChangeMiddleware: new Map(),
|
||||||
|
edgeChangeMiddleware: new Map(),
|
||||||
|
nodeSyncMiddleware: new Map(),
|
||||||
|
edgeSyncMiddleware: new Map(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -152,6 +152,11 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
|||||||
lib: string;
|
lib: string;
|
||||||
debug: boolean;
|
debug: boolean;
|
||||||
ariaLabelConfig: AriaLabelConfig;
|
ariaLabelConfig: AriaLabelConfig;
|
||||||
|
|
||||||
|
nodeChangeMiddleware: Map<symbol, (changes: NodeChange<NodeType>[]) => NodeChange<NodeType>[]>;
|
||||||
|
edgeChangeMiddleware: Map<symbol, (changes: EdgeChange<EdgeType>[]) => EdgeChange<EdgeType>[]>;
|
||||||
|
nodeSyncMiddleware: Map<symbol, (changes: NodeChange<NodeType>[]) => NodeChange<NodeType>[]>;
|
||||||
|
edgeSyncMiddleware: Map<symbol, (changes: EdgeChange<EdgeType>[]) => EdgeChange<EdgeType>[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
||||||
|
|||||||
Reference in New Issue
Block a user