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 [isLoading, setIsLoading] = useState(false);
const [flows, setFlows] = useState<Array<FlowType>>([]);
const [id, setId] = useState(uid());
const { reactFlowInstance, setData } = useContext(typesContext);
const [lastCopiedSelection, setLastCopiedSelection] = useState<{
nodes: any;

View file

@ -31,6 +31,7 @@ import { FlowType, NodeType, targetHandleType } from "../../../../types/flow";
import {
generateFlow,
generateNodeFromFlow,
getNodeId,
isValidConnection,
scapeJSONParse,
validateSelection,
@ -39,6 +40,7 @@ import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils";
import ConnectionLineComponent from "../ConnectionLineComponent";
import SelectionMenu from "../SelectionMenuComponent";
import ExtraSidebar from "../extraSidebarComponent";
import useFlow from "../../../../stores/flowManagerStore";
const nodeTypes = {
genericNode: GenericNode,
@ -53,34 +55,23 @@ export default function Page({
}): JSX.Element {
let {
uploadFlow,
getNodeId,
paste,
lastCopiedSelection,
setLastCopiedSelection,
deleteNode,
deleteEdge,
saveFlow,
} = useContext(FlowsContext);
const {
types,
reactFlowInstance,
setReactFlowInstance,
templates,
setFilterEdge,
} = useContext(typesContext);
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const [lastCopiedSelection, setLastCopiedSelection] = useState<{
nodes: any;
edges: any;
} | null>(null);
const { takeSnapshot } = useContext(undoRedoContext);
const {
nodes,
edges,
setNodes,
setEdges,
onNodesChange,
onEdgesChange,
setPending,
saveFlow,
isPending,
} = useContext(FlowsContext);
const { reactFlowInstance, setReactFlowInstance, nodes, edges, onNodesChange, onEdgesChange, onConnect, setNodes, setEdges, deleteNode, deleteEdge, setPending, isPending, paste } = useFlow();
const position = useRef({ x: 0, y: 0 });
const [lastSelection, setLastSelection] =
@ -183,30 +174,10 @@ export default function Page({
};
}, [flow, reactFlowInstance]);
const onConnect = useCallback(
const onConnectMod = useCallback(
(params: Connection) => {
takeSnapshot();
setEdges((eds) =>
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
)
);
onConnect(params);
},
[setEdges, takeSnapshot, addEdge]
);
@ -404,7 +375,7 @@ export default function Page({
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onConnect={onConnectMod}
disableKeyboardA11y={true}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}

View file

@ -1,3 +1,4 @@
import { cloneDeep } from "lodash";
import {
Connection,
Edge,
@ -7,72 +8,217 @@ import {
OnConnect,
OnEdgesChange,
OnNodesChange,
ReactFlowInstance,
addEdge,
applyEdgeChanges,
applyNodeChanges,
} from "reactflow";
import ShortUniqueId from "short-unique-id";
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 = {
reactFlowInstance: ReactFlowInstance | null;
setReactFlowInstance: (newState: ReactFlowInstance) => void;
nodes: Node[];
edges: Edge[];
onNodesChange: OnNodesChange;
onEdgesChange: OnEdgesChange;
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void;
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void;
onConnect: OnConnect;
deleteNode: (nodeId: string) => void;
deleteEdge: (edgeId: string) => void;
deleteNode: (nodeId: string | Array<string>) => void;
deleteEdge: (edgeId: string | Array<string>) => void;
isBuilt: boolean;
paste: (
selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number }
) => void;
lastCopiedSelection: { nodes: any; edges: any };
nodeId: string;
incrementNodeId: () => void;
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
const useFlow = create<RFState>((set, get) => ({
reactFlowInstance: null,
setReactFlowInstance: (newState) => {
set({ reactFlowInstance: newState });
},
nodes: [],
edges: [],
isBuilt: false,
copiedSelection: { nodes: [], edges: [] },
onNodesChange: (changes: NodeChange[]) => {
set({
nodes: applyNodeChanges(changes, get().nodes),
});
if (!get().isPending) set({ isPending: true });
},
onEdgesChange: (changes: EdgeChange[]) => {
set({
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) => {
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) => {
set({
nodes: get().nodes.filter((node) => node.id !== nodeId),
edges: get().edges.filter((edge) => edge.source !== nodeId),
});
get().setNodes(
get().nodes.filter((node) =>
typeof nodeId === "string"
? node.id !== nodeId
: !nodeId.includes(node.id)
)
);
},
deleteEdge: (edgeId) => {
set({
edges: get().edges.filter((edge) => edge.id !== edgeId),
});
get().setEdges(
get().edges.filter((edge) =>
typeof edgeId === "string"
? edge.id !== edgeId
: !edgeId.includes(edge.id)
)
);
},
paste: (selection, position) => {},
lastCopiedSelection: { nodes: [], edges: [] },
nodeId: uid(),
incrementNodeId: () => {
set((state) => ({ nodeId: uid() }));
paste: (selection, position) => {
let minimumX = Infinity;
let minimumY = Infinity;
let idsMap = {};
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,
setPending: (pending: boolean) => {
set({ isPending: pending });
},
}));
export default useFlow;

View file

@ -27,6 +27,7 @@ import {
updateEdgesHandleIdsType,
} from "../types/utils/reactflowUtils";
import { getFieldTitle, toTitleCase } from "./utils";
const uid = new ShortUniqueId({ length: 5 });
export function cleanEdges(nodes: Node[], edges: Edge[]) {
let newEdges = _.cloneDeep(edges);
@ -495,6 +496,19 @@ export function getMiddlePoint(nodes: Node[]) {
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(
selection: OnSelectionChangeParams,
nodes: Node[],