Removed all reactFlowInstance nodes and edges settings. Centralized flow saving.

This commit is contained in:
Lucas Oliveira 2023-12-30 20:50:51 -03:00
commit f6bd5a4e4a
12 changed files with 96 additions and 167 deletions

View file

@ -73,10 +73,8 @@ export default function ParameterComponent({
const {
tabId,
flows,
updateFlow,
nodes,
edges,
setEdges,
setNode,
} = useContext(FlowsContext);
@ -154,7 +152,7 @@ export default function ParameterComponent({
newData.node!.template[name].value = newValue;
return newData;
});
renderTooltips();
};

View file

@ -30,11 +30,9 @@ export default function GenericNode({
xPos: number;
yPos: number;
}): JSX.Element {
const { updateFlow, flows, tabId, saveCurrentFlow } =
useContext(FlowsContext);
const updateNodeInternals = useUpdateNodeInternals();
const { types, deleteNode } =
useContext(typesContext);
const { types } = useContext(typesContext);
const { deleteNode } = useContext(FlowsContext);
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
const [inputName, setInputName] = useState(false);
const [nodeName, setNodeName] = useState(data.node!.display_name);
@ -46,7 +44,6 @@ export default function GenericNode({
useState<validationStatusType | null>(null);
const [handles, setHandles] = useState<boolean[] | []>([]);
let numberOfInputs: boolean[] = [];
const { modalContextOpen } = useContext(alertContext);
const { takeSnapshot } = useContext(undoRedoContext);
@ -118,7 +115,6 @@ export default function GenericNode({
deleteNode={(id) => {
takeSnapshot();
deleteNode(id);
saveCurrentFlow();
}}
setShowNode={(show: boolean) => {
data.showNode = show;

View file

@ -13,7 +13,7 @@ import { NodeType } from "../../types/flow";
export default function Chat({ flow }: ChatType): JSX.Element {
const [open, setOpen] = useState(false);
const [canOpen, setCanOpen] = useState(false);
const { tabsState, isBuilt, setIsBuilt } = useContext(FlowsContext);
const { tabsState, isBuilt, setIsBuilt, isPending } = useContext(FlowsContext);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@ -51,9 +51,7 @@ export default function Chat({ flow }: ChatType): JSX.Element {
_.cloneDeep(node.data.node?.template)
);
if (
tabsState &&
tabsState[flow.id] &&
tabsState[flow.id].isPending &&
isPending &&
JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)
) {
setIsBuilt(false);

View file

@ -15,6 +15,7 @@ import {
Node,
NodeChange,
ReactFlowJsonObject,
Viewport,
XYPosition,
addEdge,
useEdgesState,
@ -74,7 +75,8 @@ const FlowsContextInitialValue: FlowsContextType = {
flowData?: FlowType,
override?: boolean
) => "",
updateFlow: (newFlow: FlowType) => {},
deleteNode: () => {},
deleteEdge: () => {},
incrementNodeId: () => uid(),
downloadFlow: (flow: FlowType) => {},
downloadFlows: () => {},
@ -83,14 +85,13 @@ const FlowsContextInitialValue: FlowsContextType = {
isBuilt: false,
setIsBuilt: (state: boolean) => {},
hardReset: () => {},
saveFlow: async (flow: FlowType, silent?: boolean) => {},
saveFlow: async (flow?: FlowType, silent?: boolean) => {},
lastCopiedSelection: null,
setLastCopiedSelection: (selection: any) => {},
isPending: false,
setPending: (pending: boolean) => {},
tabsState: {},
setTabsState: (state: FlowsState) => {},
saveCurrentFlow: () => {},
getNodeId: (nodeType: string) => "",
setTweak: (tweak: any) => {},
getTweak: [],
@ -135,7 +136,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
const [edges, setEdgesInternal, onEdgesChangeInternal] = useEdgesState([]);
const setPending = useCallback(
const setPending =
(pending: boolean) => {
//@ts-ignore
setTabsState((prev: FlowsState) => {
@ -147,17 +148,13 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
},
};
});
},
[setTabsState]
);
}
const isPending = tabsState[tabId]?.isPending ?? false;
const onNodesChange = useCallback(
(nodes: NodeChange[]) => {
onNodesChangeInternal(nodes);
console.log("nodesChangou")
(change: NodeChange[]) => {
onNodesChangeInternal(change);
setPending(true);
},
[onNodesChangeInternal, setTabsState, tabId]
@ -166,7 +163,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
const onEdgesChange = useCallback(
(edges: EdgeChange[]) => {
onEdgesChangeInternal(edges);
console.log("edgesChangou")
setPending(true);
},
[onEdgesChangeInternal, setTabsState, tabId]
@ -174,14 +170,18 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
const setNodes = (change: Node[] | ((oldState: Node[]) => Node[])) => {
let newChange = typeof change === "function" ? change(nodes) : change;
let newEdges = cleanEdges(newChange, edges);
setEdgesInternal(cleanEdges(newChange, edges));
saveCurrentFlow(newChange, newEdges, reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 });
setEdgesInternal(newEdges);
setNodesInternal(newChange);
};
const setNode = (id: string, change: Node | ((oldState: Node) => Node)) => {
let newChange = typeof change === "function" ? change(nodes.find((node) => node.id === id)!) : change;
let newChange =
typeof change === "function"
? change(nodes.find((node) => node.id === id)!)
: change;
setNodes((oldNodes) =>
oldNodes.map((node) => {
@ -197,8 +197,11 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
return nodes.find((node) => node.id === id);
};
const setEdges = (edges: Edge[] | ((oldState: Edge[]) => Edge[])) => {
setEdgesInternal(edges);
const setEdges = (change: Edge[] | ((oldState: Edge[]) => Edge[])) => {
let newChange = typeof change === "function" ? change(edges) : change;
saveCurrentFlow(nodes, newChange, reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 });
setEdgesInternal(newChange);
};
useEffect(() => {
@ -724,14 +727,22 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
});
}
function saveCurrentFlow() {
function saveCurrentFlow(nodes: Node[], edges: Edge[], viewport: Viewport) {
const currentFlow = flows.find((flow) => flow.id === tabId);
if (currentFlow && reactFlowInstance && currentFlow.data) {
updateFlow({ ...currentFlow, data: reactFlowInstance?.toObject()! });
if (currentFlow) {
saveFlow({ ...currentFlow, data: { nodes, edges, viewport } }, true);
}
}
async function saveFlow(newFlow: FlowType, silent?: boolean) {
async function saveFlow(flow?: FlowType, silent?: boolean) {
let newFlow;
if (!flow) {
const currentFlow = flows.find((flow) => flow.id === tabId)!;
newFlow = { ...currentFlow, data: { nodes, edges, viewport: reactFlowInstance?.getViewport() ?? { zoom: 1, x: 0, y: 0 } } }
} else {
newFlow = flow;
}
try {
// updates flow in db
const updatedFlow = await updateFlowInDatabase(newFlow);
@ -740,26 +751,9 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
if (!silent) {
setSuccessData({ title: "Changes saved successfully" });
}
setFlows((prevState) => {
const newFlows = [...prevState];
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
if (index !== -1) {
newFlows[index].description = newFlow.description ?? "";
newFlows[index].data = newFlow.data;
newFlows[index].name = newFlow.name;
}
return newFlows;
});
updateFlow(newFlow);
//update tabs state
setTabsState((prev) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: false,
},
};
});
setPending(false);
}
} catch (err) {
setErrorData({
@ -794,6 +788,31 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
});
}, []);
function deleteNode(idx: string | Array<string>) {
setEdges((oldEdges) =>
oldEdges.filter((edge) =>
typeof idx === "string"
? edge.source !== idx && edge.target !== idx
: !idx.includes(edge.source) && !idx.includes(edge.target)
)
);
setNodes((oldNodes) =>
oldNodes.filter((node) =>
typeof idx === "string" ? node.id !== idx : !idx.includes(node.id)
)
);
}
function deleteEdge(idx: string | Array<string>) {
setEdges((oldEdges) =>
oldEdges.filter((edge) =>
typeof idx === "string" ? edge.id !== idx : !idx.includes(edge.id)
)
);
}
return (
<FlowsContext.Provider
value={{
@ -803,7 +822,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
setIsBuilt,
lastCopiedSelection,
setLastCopiedSelection,
saveCurrentFlow,
hardReset,
tabId,
setTabId,
@ -811,12 +829,13 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
incrementNodeId,
removeFlow,
addFlow,
updateFlow,
downloadFlow,
downloadFlows,
uploadFlows,
uploadFlow,
getNodeId,
deleteNode,
deleteEdge,
isPending,
setPending,
tabsState,

View file

@ -18,7 +18,6 @@ import { AuthContext } from "./authContext";
const initialValue: typesContextType = {
reactFlowInstance: null,
setReactFlowInstance: (newState: ReactFlowInstance) => {},
deleteNode: () => {},
types: {},
setTypes: () => {},
templates: {},
@ -29,7 +28,6 @@ const initialValue: typesContextType = {
fetchError: false,
setFilterEdge: (filter) => {},
getFilterEdge: [],
deleteEdge: () => {},
};
export const typesContext = createContext<typesContextType>(initialValue);
@ -97,37 +95,14 @@ export function TypesProvider({ children }: { children: ReactNode }) {
}
}
function deleteNode(idx: string | Array<string>) {
if (reactFlowInstance === null) return;
const edges = reactFlowInstance!
.getEdges()
.filter((edge) =>
typeof idx === "string"
? edge.source == idx || edge.target == idx
: idx.includes(edge.source) || idx.includes(edge.target)
);
reactFlowInstance!.deleteElements({
nodes:
typeof idx === "string" ? [{ id: idx }] : idx.map((id) => ({ id })),
edges,
});
}
function deleteEdge(idx: string | Array<string>) {
reactFlowInstance!.deleteElements({
edges:
typeof idx === "string" ? [{ id: idx }] : idx.map((id) => ({ id })),
});
}
return (
<typesContext.Provider
value={{
deleteEdge,
types,
setTypes,
reactFlowInstance,
setReactFlowInstance,
deleteNode,
setTemplates,
templates,
data,

View file

@ -536,15 +536,7 @@ const EditNodeModal = forwardRef(
onClick={() => {
data.node = myData.node;
//@ts-ignore
setTabsState((prev: FlowsState) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
setPending(true);
setOpen(false);
}}
type="submit"

View file

@ -12,15 +12,11 @@ import ReactFlow, {
Connection,
Controls,
Edge,
EdgeChange,
NodeChange,
NodeDragHandler,
OnSelectionChangeParams,
SelectionDragHandler,
addEdge,
updateEdge,
useEdgesState,
useNodesState,
useReactFlow,
} from "reactflow";
import GenericNode from "../../../../CustomNodes/GenericNode";
@ -58,17 +54,13 @@ export default function Page({
view?: boolean;
}): JSX.Element {
let {
updateFlow,
uploadFlow,
getNodeId,
paste,
lastCopiedSelection,
setLastCopiedSelection,
tabsState,
saveFlow,
setTabsState,
tabId,
saveCurrentFlow,
deleteNode,
deleteEdge,
} = useContext(FlowsContext);
const {
types,
@ -76,24 +68,16 @@ export default function Page({
setReactFlowInstance,
templates,
setFilterEdge,
deleteNode,
deleteEdge,
} = useContext(typesContext);
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const { takeSnapshot } = useContext(undoRedoContext);
const { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange } = useContext(FlowsContext);
const { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange, setPending, saveFlow } = useContext(FlowsContext);
const position = useRef({ x: 0, y: 0 });
const [lastSelection, setLastSelection] =
useState<OnSelectionChangeParams | null>(null);
const saveCurrentFlowTimeout = () => {
setTimeout(() => {
saveCurrentFlow();
}, 500); // need to do this because ReactFlow is not asynchronous.
};
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (
@ -137,7 +121,6 @@ export default function Page({
takeSnapshot();
deleteNode(lastSelection.nodes.map((node) => node.id));
deleteEdge(lastSelection.edges.map((edge) => edge.id));
saveCurrentFlowTimeout();
}
}
};
@ -157,7 +140,6 @@ export default function Page({
lastCopiedSelection,
lastSelection,
takeSnapshot,
saveCurrentFlowTimeout,
]);
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
@ -165,7 +147,6 @@ export default function Page({
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
const { setErrorData } = useContext(alertContext);
const { setViewport } = useReactFlow();
const edgeUpdateSuccessful = useRef(true);
const [loading, setLoading] = useState(true);
@ -174,9 +155,11 @@ export default function Page({
useEffect(() => {
setLoading(true);
setNodes(flow?.data?.nodes ?? []);
setEdges(flow?.data?.edges ?? []);
setViewport(flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 });
if(reactFlowInstance){
reactFlowInstance.setNodes(flow?.data?.nodes ?? []);
reactFlowInstance.setEdges(flow?.data?.edges ?? []);
reactFlowInstance.setViewport(flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 });
}
// Clear the previous timeout
if (timeoutRef.current) {
@ -194,16 +177,6 @@ export default function Page({
};
}, [flow, reactFlowInstance]);
useEffect(() => {
const interval = setInterval(() => {
saveFlow(flow, true);
}, 30000);
return () => {
clearInterval(interval);
};
}, [flow, flow.data]);
const onConnect = useCallback(
(params: Connection) => {
takeSnapshot();
@ -228,17 +201,6 @@ export default function Page({
eds
)
);
//@ts-ignore
setTabsState((prev: FlowsState) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
saveCurrentFlowTimeout();
},
[setEdges, takeSnapshot, addEdge]
);
@ -249,6 +211,12 @@ export default function Page({
// 👉 you can place your event handlers here
}, [takeSnapshot]);
const onNodeDragStop: NodeDragHandler = useCallback(() => {
// 👇 make dragging a node undoable
saveFlow();
// 👉 you can place your event handlers here
}, [takeSnapshot]);
const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
// 👇 make dragging a selection undoable
takeSnapshot();
@ -342,18 +310,12 @@ export default function Page({
}
},
// Specify dependencies for useCallback
[getNodeId, reactFlowInstance, setNodes, takeSnapshot]
[getNodeId, setNodes, takeSnapshot]
);
useEffect(() => {
setExtraComponent(<ExtraSidebar />);
setExtraNavigation({ title: "Components" });
return () => {
if (tabsState && tabsState[flow.id]?.isPending) {
saveFlow(flow);
}
};
}, []);
const onEdgeUpdateStart = useCallback(() => {
@ -367,7 +329,7 @@ export default function Page({
setEdges((els) => updateEdge(oldEdge, newConnection, els));
}
},
[reactFlowInstance, setEdges]
[setEdges]
);
const onEdgeUpdateEnd = useCallback((_, edge: Edge): void => {
@ -408,18 +370,9 @@ export default function Page({
}, []);
const onMove = useCallback(() => {
saveCurrentFlowTimeout();
//@ts-ignore
setTabsState((prev: FlowsState) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
}, [setTabsState, saveCurrentFlowTimeout]);
setPending(true);
}, [setPending]);
return (
<div className="flex h-full overflow-hidden">
@ -454,6 +407,7 @@ export default function Page({
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart}
onNodeDragStop={onNodeDragStop}
onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart}
@ -488,7 +442,8 @@ export default function Page({
) {
const { newFlow } = generateFlow(
lastSelection!,
reactFlowInstance!,
nodes,
edges,
getRandomName()
);
const newGroupNode = generateNodeFromFlow(

View file

@ -28,13 +28,12 @@ import SidebarDraggableComponent from "./sideBarDraggableComponent";
export default function ExtraSidebar(): JSX.Element {
const { data, templates, getFilterEdge, setFilterEdge } =
useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } =
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, isPending } =
useContext(FlowsContext);
const { hasApiKey, validApiKey, hasStore } = useContext(StoreContext);
const { setErrorData } = useContext(alertContext);
const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState("");
const isPending = tabsState[tabId]?.isPending;
function onDragStart(
event: React.DragEvent<any>,
data: { type: string; node?: APIClassType }
@ -297,7 +296,7 @@ export default function ExtraSidebar(): JSX.Element {
: "button-disable")
}
onClick={(event) => {
saveFlow(flow!);
saveFlow();
}}
>
<IconComponent

View file

@ -1,4 +1,3 @@
import { ReactFlowInstance } from "reactflow";
import { FlowType } from "../flow";
export type ChatType = { flow: FlowType; };

View file

@ -6,11 +6,13 @@ import { Dispatch, SetStateAction } from "react";
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
export type FlowsContextType = {
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void>;
saveFlow: (flow?: FlowType, silent?: boolean) => Promise<void>;
tabId: string;
isLoading: boolean;
setTabId: (index: string) => void;
flows: Array<FlowType>;
deleteNode: (idx: string | Array<string>) => void;
deleteEdge: (idx: string | Array<string>) => void;
removeFlow: (id: string) => void;
addFlow: (
newProject: boolean,
@ -18,7 +20,6 @@ export type FlowsContextType = {
override?: boolean,
position?: XYPosition
) => Promise<String | undefined>;
updateFlow: (newFlow: FlowType) => void;
incrementNodeId: () => string;
downloadFlow: (
flow: FlowType,
@ -29,7 +30,6 @@ export type FlowsContextType = {
uploadFlows: () => void;
isBuilt: boolean;
setIsBuilt: (state: boolean) => void;
saveCurrentFlow: () => void;
uploadFlow: ({
newProject,
file,

View file

@ -9,7 +9,6 @@ const data: { [char: string]: string } = {};
export type typesContextType = {
reactFlowInstance: ReactFlowInstance | null;
setReactFlowInstance: (newState: ReactFlowInstance) => void;
deleteNode: (idx: string | Array<string>) => void;
types: typeof types;
setTypes: (newState: {}) => void;
templates: typeof template;
@ -20,7 +19,6 @@ export type typesContextType = {
setFetchError: (newState: boolean) => void;
setFilterEdge: (newState) => void;
getFilterEdge: any[];
deleteEdge: (idx: string | Array<string>) => void;
};
export type alertContextType = {

View file

@ -4,7 +4,6 @@ import {
Edge,
Node,
OnSelectionChangeParams,
ReactFlowInstance,
ReactFlowJsonObject,
XYPosition,
} from "reactflow";
@ -502,10 +501,11 @@ export function getMiddlePoint(nodes: Node[]) {
export function generateFlow(
selection: OnSelectionChangeParams,
reactFlowInstance: ReactFlowInstance,
nodes: Node[],
edges: Edge[],
name: string
): generateFlowType {
const newFlowData = reactFlowInstance.toObject();
const newFlowData = {nodes, edges, viewport: { zoom: 1, x: 0, y: 0 }};
const uid = new ShortUniqueId({ length: 5 });
/* remove edges that are not connected to selected nodes on both ends
in future we can save this edges to when ungrouping reconect to the old nodes