Implemented Zustand State of flow manager
This commit is contained in:
parent
17fd965231
commit
df8912055d
4 changed files with 194 additions and 64 deletions
|
|
@ -122,7 +122,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
|
||||||
const [tabId, setTabId] = useState("");
|
const [tabId, setTabId] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||||
const [id, setId] = useState(uid());
|
|
||||||
const { reactFlowInstance, setData } = useContext(typesContext);
|
const { reactFlowInstance, setData } = useContext(typesContext);
|
||||||
const [lastCopiedSelection, setLastCopiedSelection] = useState<{
|
const [lastCopiedSelection, setLastCopiedSelection] = useState<{
|
||||||
nodes: any;
|
nodes: any;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import { FlowType, NodeType, targetHandleType } from "../../../../types/flow";
|
||||||
import {
|
import {
|
||||||
generateFlow,
|
generateFlow,
|
||||||
generateNodeFromFlow,
|
generateNodeFromFlow,
|
||||||
|
getNodeId,
|
||||||
isValidConnection,
|
isValidConnection,
|
||||||
scapeJSONParse,
|
scapeJSONParse,
|
||||||
validateSelection,
|
validateSelection,
|
||||||
|
|
@ -39,6 +40,7 @@ import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils";
|
||||||
import ConnectionLineComponent from "../ConnectionLineComponent";
|
import ConnectionLineComponent from "../ConnectionLineComponent";
|
||||||
import SelectionMenu from "../SelectionMenuComponent";
|
import SelectionMenu from "../SelectionMenuComponent";
|
||||||
import ExtraSidebar from "../extraSidebarComponent";
|
import ExtraSidebar from "../extraSidebarComponent";
|
||||||
|
import useFlow from "../../../../stores/flowManagerStore";
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
genericNode: GenericNode,
|
genericNode: GenericNode,
|
||||||
|
|
@ -53,34 +55,23 @@ export default function Page({
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
let {
|
let {
|
||||||
uploadFlow,
|
uploadFlow,
|
||||||
getNodeId,
|
saveFlow,
|
||||||
paste,
|
|
||||||
lastCopiedSelection,
|
|
||||||
setLastCopiedSelection,
|
|
||||||
deleteNode,
|
|
||||||
deleteEdge,
|
|
||||||
} = useContext(FlowsContext);
|
} = useContext(FlowsContext);
|
||||||
const {
|
const {
|
||||||
types,
|
types,
|
||||||
reactFlowInstance,
|
|
||||||
setReactFlowInstance,
|
|
||||||
templates,
|
templates,
|
||||||
setFilterEdge,
|
setFilterEdge,
|
||||||
} = useContext(typesContext);
|
} = useContext(typesContext);
|
||||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [lastCopiedSelection, setLastCopiedSelection] = useState<{
|
||||||
|
nodes: any;
|
||||||
|
edges: any;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
const { takeSnapshot } = useContext(undoRedoContext);
|
const { takeSnapshot } = useContext(undoRedoContext);
|
||||||
const {
|
|
||||||
nodes,
|
const { reactFlowInstance, setReactFlowInstance, nodes, edges, onNodesChange, onEdgesChange, onConnect, setNodes, setEdges, deleteNode, deleteEdge, setPending, isPending, paste } = useFlow();
|
||||||
edges,
|
|
||||||
setNodes,
|
|
||||||
setEdges,
|
|
||||||
onNodesChange,
|
|
||||||
onEdgesChange,
|
|
||||||
setPending,
|
|
||||||
saveFlow,
|
|
||||||
isPending,
|
|
||||||
} = useContext(FlowsContext);
|
|
||||||
|
|
||||||
const position = useRef({ x: 0, y: 0 });
|
const position = useRef({ x: 0, y: 0 });
|
||||||
const [lastSelection, setLastSelection] =
|
const [lastSelection, setLastSelection] =
|
||||||
|
|
@ -183,30 +174,10 @@ export default function Page({
|
||||||
};
|
};
|
||||||
}, [flow, reactFlowInstance]);
|
}, [flow, reactFlowInstance]);
|
||||||
|
|
||||||
const onConnect = useCallback(
|
const onConnectMod = useCallback(
|
||||||
(params: Connection) => {
|
(params: Connection) => {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
setEdges((eds) =>
|
onConnect(params);
|
||||||
addEdge(
|
|
||||||
{
|
|
||||||
...params,
|
|
||||||
data: {
|
|
||||||
targetHandle: scapeJSONParse(params.targetHandle!),
|
|
||||||
sourceHandle: scapeJSONParse(params.sourceHandle!),
|
|
||||||
},
|
|
||||||
style: { stroke: "#555" },
|
|
||||||
className:
|
|
||||||
((scapeJSONParse(params.targetHandle!) as targetHandleType)
|
|
||||||
.type === "Text"
|
|
||||||
? "stroke-foreground "
|
|
||||||
: "stroke-foreground ") + " stroke-connection",
|
|
||||||
animated:
|
|
||||||
(scapeJSONParse(params.targetHandle!) as targetHandleType)
|
|
||||||
.type === "Text",
|
|
||||||
},
|
|
||||||
eds
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[setEdges, takeSnapshot, addEdge]
|
[setEdges, takeSnapshot, addEdge]
|
||||||
);
|
);
|
||||||
|
|
@ -404,7 +375,7 @@ export default function Page({
|
||||||
edges={edges}
|
edges={edges}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onConnect={onConnect}
|
onConnect={onConnectMod}
|
||||||
disableKeyboardA11y={true}
|
disableKeyboardA11y={true}
|
||||||
onInit={setReactFlowInstance}
|
onInit={setReactFlowInstance}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
import {
|
import {
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
|
|
@ -7,72 +8,217 @@ import {
|
||||||
OnConnect,
|
OnConnect,
|
||||||
OnEdgesChange,
|
OnEdgesChange,
|
||||||
OnNodesChange,
|
OnNodesChange,
|
||||||
|
ReactFlowInstance,
|
||||||
addEdge,
|
addEdge,
|
||||||
applyEdgeChanges,
|
applyEdgeChanges,
|
||||||
applyNodeChanges,
|
applyNodeChanges,
|
||||||
} from "reactflow";
|
} from "reactflow";
|
||||||
import ShortUniqueId from "short-unique-id";
|
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
const uid = new ShortUniqueId({ length: 5 });
|
import {
|
||||||
|
NodeDataType,
|
||||||
|
NodeType,
|
||||||
|
sourceHandleType,
|
||||||
|
targetHandleType,
|
||||||
|
} from "../types/flow";
|
||||||
|
import {
|
||||||
|
cleanEdges,
|
||||||
|
getHandleId,
|
||||||
|
getNodeId,
|
||||||
|
scapeJSONParse,
|
||||||
|
scapedJSONStringfy,
|
||||||
|
} from "../utils/reactflowUtils";
|
||||||
|
|
||||||
type RFState = {
|
type RFState = {
|
||||||
|
reactFlowInstance: ReactFlowInstance | null;
|
||||||
|
setReactFlowInstance: (newState: ReactFlowInstance) => void;
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
onNodesChange: OnNodesChange;
|
onNodesChange: OnNodesChange;
|
||||||
onEdgesChange: OnEdgesChange;
|
onEdgesChange: OnEdgesChange;
|
||||||
|
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void;
|
||||||
|
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void;
|
||||||
onConnect: OnConnect;
|
onConnect: OnConnect;
|
||||||
deleteNode: (nodeId: string) => void;
|
deleteNode: (nodeId: string | Array<string>) => void;
|
||||||
deleteEdge: (edgeId: string) => void;
|
deleteEdge: (edgeId: string | Array<string>) => void;
|
||||||
isBuilt: boolean;
|
isBuilt: boolean;
|
||||||
paste: (
|
paste: (
|
||||||
selection: { nodes: any; edges: any },
|
selection: { nodes: any; edges: any },
|
||||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
position: { x: number; y: number; paneX?: number; paneY?: number }
|
||||||
) => void;
|
) => void;
|
||||||
lastCopiedSelection: { nodes: any; edges: any };
|
|
||||||
nodeId: string;
|
|
||||||
incrementNodeId: () => void;
|
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
|
setPending: (pending: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is our useStore hook that we can use in our components to get parts of the store and call actions
|
// this is our useStore hook that we can use in our components to get parts of the store and call actions
|
||||||
const useFlow = create<RFState>((set, get) => ({
|
const useFlow = create<RFState>((set, get) => ({
|
||||||
|
reactFlowInstance: null,
|
||||||
|
setReactFlowInstance: (newState) => {
|
||||||
|
set({ reactFlowInstance: newState });
|
||||||
|
},
|
||||||
nodes: [],
|
nodes: [],
|
||||||
edges: [],
|
edges: [],
|
||||||
isBuilt: false,
|
isBuilt: false,
|
||||||
copiedSelection: { nodes: [], edges: [] },
|
|
||||||
onNodesChange: (changes: NodeChange[]) => {
|
onNodesChange: (changes: NodeChange[]) => {
|
||||||
set({
|
set({
|
||||||
nodes: applyNodeChanges(changes, get().nodes),
|
nodes: applyNodeChanges(changes, get().nodes),
|
||||||
});
|
});
|
||||||
|
if (!get().isPending) set({ isPending: true });
|
||||||
},
|
},
|
||||||
onEdgesChange: (changes: EdgeChange[]) => {
|
onEdgesChange: (changes: EdgeChange[]) => {
|
||||||
set({
|
set({
|
||||||
edges: applyEdgeChanges(changes, get().edges),
|
edges: applyEdgeChanges(changes, get().edges),
|
||||||
});
|
});
|
||||||
|
if (!get().isPending) set({ isPending: true });
|
||||||
|
},
|
||||||
|
setNodes: (change) => {
|
||||||
|
let newChange = typeof change === "function" ? change(get().nodes) : change;
|
||||||
|
let newEdges = cleanEdges(newChange, get().edges);
|
||||||
|
|
||||||
|
set({ edges: newEdges });
|
||||||
|
set({ nodes: newChange });
|
||||||
|
},
|
||||||
|
setEdges: (change) => {
|
||||||
|
let newChange = typeof change === "function" ? change(get().edges) : change;
|
||||||
|
|
||||||
|
set({ edges: newChange });
|
||||||
},
|
},
|
||||||
onConnect: (connection: Connection) => {
|
onConnect: (connection: Connection) => {
|
||||||
set({
|
set({
|
||||||
edges: addEdge(connection, get().edges),
|
edges: addEdge(
|
||||||
|
{
|
||||||
|
...connection,
|
||||||
|
data: {
|
||||||
|
targetHandle: scapeJSONParse(connection.targetHandle!),
|
||||||
|
sourceHandle: scapeJSONParse(connection.sourceHandle!),
|
||||||
|
},
|
||||||
|
style: { stroke: "#555" },
|
||||||
|
className:
|
||||||
|
((scapeJSONParse(connection.targetHandle!) as targetHandleType)
|
||||||
|
.type === "Text"
|
||||||
|
? "stroke-foreground "
|
||||||
|
: "stroke-foreground ") + " stroke-connection",
|
||||||
|
animated:
|
||||||
|
(scapeJSONParse(connection.targetHandle!) as targetHandleType)
|
||||||
|
.type === "Text",
|
||||||
|
},
|
||||||
|
get().edges
|
||||||
|
),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteNode: (nodeId) => {
|
deleteNode: (nodeId) => {
|
||||||
set({
|
get().setNodes(
|
||||||
nodes: get().nodes.filter((node) => node.id !== nodeId),
|
get().nodes.filter((node) =>
|
||||||
edges: get().edges.filter((edge) => edge.source !== nodeId),
|
typeof nodeId === "string"
|
||||||
});
|
? node.id !== nodeId
|
||||||
|
: !nodeId.includes(node.id)
|
||||||
|
)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
deleteEdge: (edgeId) => {
|
deleteEdge: (edgeId) => {
|
||||||
set({
|
get().setEdges(
|
||||||
edges: get().edges.filter((edge) => edge.id !== edgeId),
|
get().edges.filter((edge) =>
|
||||||
});
|
typeof edgeId === "string"
|
||||||
|
? edge.id !== edgeId
|
||||||
|
: !edgeId.includes(edge.id)
|
||||||
|
)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
paste: (selection, position) => {},
|
paste: (selection, position) => {
|
||||||
lastCopiedSelection: { nodes: [], edges: [] },
|
let minimumX = Infinity;
|
||||||
nodeId: uid(),
|
let minimumY = Infinity;
|
||||||
incrementNodeId: () => {
|
let idsMap = {};
|
||||||
set((state) => ({ nodeId: uid() }));
|
let newNodes: Node<NodeDataType>[] = get().nodes;
|
||||||
|
let newEdges = get().edges;
|
||||||
|
selection.nodes.forEach((node: Node) => {
|
||||||
|
if (node.position.y < minimumY) {
|
||||||
|
minimumY = node.position.y;
|
||||||
|
}
|
||||||
|
if (node.position.x < minimumX) {
|
||||||
|
minimumX = node.position.x;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const insidePosition = position.paneX
|
||||||
|
? { x: position.paneX + position.x, y: position.paneY! + position.y }
|
||||||
|
: get().reactFlowInstance!.screenToFlowPosition({
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
});
|
||||||
|
|
||||||
|
selection.nodes.forEach((node: NodeType) => {
|
||||||
|
// Generate a unique node ID
|
||||||
|
let newId = getNodeId(node.data.type);
|
||||||
|
idsMap[node.id] = newId;
|
||||||
|
|
||||||
|
// Create a new node object
|
||||||
|
const newNode: NodeType = {
|
||||||
|
id: newId,
|
||||||
|
type: "genericNode",
|
||||||
|
position: {
|
||||||
|
x: insidePosition.x + node.position!.x - minimumX,
|
||||||
|
y: insidePosition.y + node.position!.y - minimumY,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...cloneDeep(node.data),
|
||||||
|
id: newId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the new node to the list of nodes in state
|
||||||
|
newNodes = newNodes
|
||||||
|
.map((node) => ({ ...node, selected: false }))
|
||||||
|
.concat({ ...newNode, selected: false });
|
||||||
|
});
|
||||||
|
set({ nodes: newNodes });
|
||||||
|
|
||||||
|
selection.edges.forEach((edge: Edge) => {
|
||||||
|
let source = idsMap[edge.source];
|
||||||
|
let target = idsMap[edge.target];
|
||||||
|
const sourceHandleObject: sourceHandleType = scapeJSONParse(
|
||||||
|
edge.sourceHandle!
|
||||||
|
);
|
||||||
|
let sourceHandle = scapedJSONStringfy({
|
||||||
|
...sourceHandleObject,
|
||||||
|
id: source,
|
||||||
|
});
|
||||||
|
sourceHandleObject.id = source;
|
||||||
|
|
||||||
|
edge.data.sourceHandle = sourceHandleObject;
|
||||||
|
const targetHandleObject: targetHandleType = scapeJSONParse(
|
||||||
|
edge.targetHandle!
|
||||||
|
);
|
||||||
|
let targetHandle = scapedJSONStringfy({
|
||||||
|
...targetHandleObject,
|
||||||
|
id: target,
|
||||||
|
});
|
||||||
|
targetHandleObject.id = target;
|
||||||
|
edge.data.targetHandle = targetHandleObject;
|
||||||
|
let id = getHandleId(source, sourceHandle, target, targetHandle);
|
||||||
|
newEdges = addEdge(
|
||||||
|
{
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
sourceHandle,
|
||||||
|
targetHandle,
|
||||||
|
id,
|
||||||
|
data: cloneDeep(edge.data),
|
||||||
|
style: { stroke: "#555" },
|
||||||
|
className:
|
||||||
|
targetHandleObject.type === "Text"
|
||||||
|
? "stroke-gray-800 "
|
||||||
|
: "stroke-gray-900 ",
|
||||||
|
animated: targetHandleObject.type === "Text",
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
newEdges.map((edge) => ({ ...edge, selected: false }))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
set({ edges: newEdges });
|
||||||
},
|
},
|
||||||
isPending: false,
|
isPending: false,
|
||||||
|
setPending: (pending: boolean) => {
|
||||||
|
set({ isPending: pending });
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useFlow;
|
export default useFlow;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
updateEdgesHandleIdsType,
|
updateEdgesHandleIdsType,
|
||||||
} from "../types/utils/reactflowUtils";
|
} from "../types/utils/reactflowUtils";
|
||||||
import { getFieldTitle, toTitleCase } from "./utils";
|
import { getFieldTitle, toTitleCase } from "./utils";
|
||||||
|
const uid = new ShortUniqueId({ length: 5 });
|
||||||
|
|
||||||
export function cleanEdges(nodes: Node[], edges: Edge[]) {
|
export function cleanEdges(nodes: Node[], edges: Edge[]) {
|
||||||
let newEdges = _.cloneDeep(edges);
|
let newEdges = _.cloneDeep(edges);
|
||||||
|
|
@ -495,6 +496,19 @@ export function getMiddlePoint(nodes: Node[]) {
|
||||||
return { x: averageX, y: averageY };
|
return { x: averageX, y: averageY };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNodeId(nodeType: string) {
|
||||||
|
return nodeType + "-" + uid();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHandleId(source: string, sourceHandle: string, target: string, targetHandle: string){
|
||||||
|
return "reactflow__edge-" +
|
||||||
|
source +
|
||||||
|
sourceHandle +
|
||||||
|
"-" +
|
||||||
|
target +
|
||||||
|
targetHandle;
|
||||||
|
}
|
||||||
|
|
||||||
export function generateFlow(
|
export function generateFlow(
|
||||||
selection: OnSelectionChangeParams,
|
selection: OnSelectionChangeParams,
|
||||||
nodes: Node[],
|
nodes: Node[],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue