Implemented Zustand State of flow manager

This commit is contained in:
Lucas Oliveira 2024-01-05 10:38:33 -03:00
commit df8912055d
4 changed files with 194 additions and 64 deletions

View file

@ -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;

View file

@ -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}

View file

@ -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;

View file

@ -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[],