Fixed Flows state not being updated always

This commit is contained in:
Lucas Oliveira 2023-12-08 19:10:32 -03:00
commit d6ea72cbea
9 changed files with 77 additions and 81 deletions

View file

@ -1,4 +1,3 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { NodeToolbar, useUpdateNodeInternals } from "reactflow"; import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent"; import ShadTooltip from "../../components/ShadTooltipComponent";
@ -14,11 +13,7 @@ import { undoRedoContext } from "../../contexts/undoRedoContext";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent"; import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import { validationStatusType } from "../../types/components"; import { validationStatusType } from "../../types/components";
import { NodeDataType } from "../../types/flow"; import { NodeDataType } from "../../types/flow";
import { import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
cleanEdges,
handleKeyDown,
scapedJSONStringfy,
} from "../../utils/reactflowUtils";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils"; import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
import { classNames, getFieldTitle } from "../../utils/utils"; import { classNames, getFieldTitle } from "../../utils/utils";
import ParameterComponent from "./components/parameterComponent"; import ParameterComponent from "./components/parameterComponent";
@ -34,7 +29,8 @@ export default function GenericNode({
xPos: number; xPos: number;
yPos: number; yPos: number;
}): JSX.Element { }): JSX.Element {
const { updateFlow, flows, tabId } = useContext(FlowsContext); const { updateFlow, flows, tabId, saveCurrentFlow } =
useContext(FlowsContext);
const updateNodeInternals = useUpdateNodeInternals(); const updateNodeInternals = useUpdateNodeInternals();
const { types, deleteNode, reactFlowInstance, setFilterEdge, getFilterEdge } = const { types, deleteNode, reactFlowInstance, setFilterEdge, getFilterEdge } =
useContext(typesContext); useContext(typesContext);
@ -87,18 +83,15 @@ export default function GenericNode({
// State for outline color // State for outline color
const { sseData, isBuilding } = useSSE(); const { sseData, isBuilding } = useSSE();
useEffect(() => { /* useEffect(() => {
let myFlow = flows.find((flow) => flow.id === tabId); let flow = flows.find((flow) => flow.id === tabId);
if (reactFlowInstance && myFlow) { if (reactFlowInstance && flow && flow.data) {
let flow = cloneDeep(myFlow);
flow.data = reactFlowInstance.toObject();
cleanEdges({ cleanEdges({
flow: { flow: {
edges: flow.data.edges, edges: flow.data!.edges,
nodes: flow.data.nodes, nodes: flow.data!.nodes,
}, },
updateEdge: (edge) => { updateEdge: (edge) => {
flow.data!.edges = edge;
reactFlowInstance.setEdges(edge); reactFlowInstance.setEdges(edge);
updateNodeInternals(data.id); updateNodeInternals(data.id);
}, },
@ -106,7 +99,7 @@ export default function GenericNode({
updateFlow(flow); updateFlow(flow);
} }
countHandles(); countHandles();
}, [data]); }, [data]); */
useEffect(() => { useEffect(() => {
setNodeDescription(data.node!.description); setNodeDescription(data.node!.description);
@ -138,6 +131,7 @@ export default function GenericNode({
deleteNode={(id) => { deleteNode={(id) => {
takeSnapshot(); takeSnapshot();
deleteNode(id); deleteNode(id);
saveCurrentFlow();
}} }}
setShowNode={(show: boolean) => { setShowNode={(show: boolean) => {
data.showNode = show; data.showNode = show;

View file

@ -76,11 +76,8 @@ export default function BuildTrigger({
} }
async function streamNodeData(flow: FlowType) { async function streamNodeData(flow: FlowType) {
// Step 1: Make a POST request to send the flow data and receive a unique session ID // Step 1: Make a POST request to send the flow data and receive a unique session ID
const id = saveFlow({ ...flow, data: reactFlowInstance!.toObject() }, true); const id = saveFlow(flow, true);
const response = await postBuildInit({ const response = await postBuildInit(flow);
...flow,
data: reactFlowInstance!.toObject(),
});
const { flowId } = response.data; const { flowId } = response.data;
// Step 2: Use the session ID to establish an SSE connection using EventSource // Step 2: Use the session ID to establish an SSE connection using EventSource
let validationResults: boolean[] = []; let validationResults: boolean[] = [];

View file

@ -82,6 +82,7 @@ const FlowsContextInitialValue: FlowsContextType = {
setLastCopiedSelection: (selection: any) => {}, setLastCopiedSelection: (selection: any) => {},
tabsState: {}, tabsState: {},
setTabsState: (state: FlowsState) => {}, setTabsState: (state: FlowsState) => {},
saveCurrentFlow: () => {},
getNodeId: (nodeType: string) => "", getNodeId: (nodeType: string) => "",
setTweak: (tweak: any) => {}, setTweak: (tweak: any) => {},
getTweak: [], getTweak: [],
@ -638,8 +639,14 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
}); });
} }
function saveCurrentFlow() {
const currentFlow = flows.find((flow) => flow.id === tabId);
if (currentFlow && reactFlowInstance && currentFlow.data) {
updateFlow({ ...currentFlow, data: reactFlowInstance?.toObject()! });
}
}
async function saveFlow(newFlow: FlowType, silent?: boolean) { async function saveFlow(newFlow: FlowType, silent?: boolean) {
console.log(newFlow);
try { try {
// updates flow in db // updates flow in db
const updatedFlow = await updateFlowInDatabase(newFlow); const updatedFlow = await updateFlowInDatabase(newFlow);
@ -668,8 +675,6 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
}, },
}; };
}); });
console.log(tabsState);
} }
} catch (err) { } catch (err) {
setErrorData({ setErrorData({
@ -713,6 +718,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
setIsBuilt, setIsBuilt,
lastCopiedSelection, lastCopiedSelection,
setLastCopiedSelection, setLastCopiedSelection,
saveCurrentFlow,
hardReset, hardReset,
tabId, tabId,
setTabId, setTabId,

View file

@ -68,7 +68,7 @@ const ExportModal = forwardRef(
downloadFlow( downloadFlow(
{ {
id: tabId, id: tabId,
data: reactFlowInstance?.toObject()!, data: flow!.data!,
description, description,
name, name,
last_tested_version: version, last_tested_version: version,
@ -85,7 +85,7 @@ const ExportModal = forwardRef(
downloadFlow( downloadFlow(
removeApiKeys({ removeApiKeys({
id: tabId, id: tabId,
data: reactFlowInstance?.toObject()!, data: flow!.data!,
description, description,
name, name,
last_tested_version: version, last_tested_version: version,

View file

@ -393,7 +393,7 @@ export default function FormModal({
const message = inputs; const message = inputs;
addChatHistory(message!, true, chatKey!, template.current); addChatHistory(message!, true, chatKey!, template.current);
sendAll({ sendAll({
...reactFlowInstance?.toObject()!, ...flow.data!,
inputs: inputs!, inputs: inputs!,
chatHistory, chatHistory,
name: flow.name, name: flow.name,

View file

@ -8,6 +8,7 @@ import { Checkbox } from "../../components/ui/checkbox";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext"; import { FlowsContext } from "../../contexts/flowsContext";
import { StoreContext } from "../../contexts/storeContext"; import { StoreContext } from "../../contexts/storeContext";
import { typesContext } from "../../contexts/typesContext";
import { import {
getStoreComponents, getStoreComponents,
getStoreTags, getStoreTags,
@ -41,6 +42,7 @@ export default function ShareModal({
const { version, addFlow } = useContext(FlowsContext); const { version, addFlow } = useContext(FlowsContext);
const { hasApiKey, hasStore } = useContext(StoreContext); const { hasApiKey, hasStore } = useContext(StoreContext);
const { setSuccessData, setErrorData } = useContext(alertContext); const { setSuccessData, setErrorData } = useContext(alertContext);
const { reactFlowInstance } = useContext(typesContext);
const [checked, setChecked] = useState(false); const [checked, setChecked] = useState(false);
const [name, setName] = useState(component?.name ?? ""); const [name, setName] = useState(component?.name ?? "");
const [description, setDescription] = useState(component?.description ?? ""); const [description, setDescription] = useState(component?.description ?? "");
@ -127,7 +129,8 @@ export default function ShareModal({
}); });
} }
await saveFlow(flows.find((flow) => flow.id === tabId)!, true); await saveFlow({ ...flows.find((flow) => flow.id === tabId)! }, true);
if (!update) if (!update)
saveFlowStore(flow!, getTagsIds(selectedTags, tags), sharePublic).then( saveFlowStore(flow!, getTagsIds(selectedTags, tags), sharePublic).then(
successShare, successShare,

View file

@ -60,7 +60,6 @@ export default function Page({
let { let {
updateFlow, updateFlow,
uploadFlow, uploadFlow,
addFlow,
getNodeId, getNodeId,
paste, paste,
lastCopiedSelection, lastCopiedSelection,
@ -69,7 +68,7 @@ export default function Page({
saveFlow, saveFlow,
setTabsState, setTabsState,
tabId, tabId,
flows, saveCurrentFlow,
} = useContext(FlowsContext); } = useContext(FlowsContext);
const { const {
types, types,
@ -89,6 +88,12 @@ export default function Page({
const [lastSelection, setLastSelection] = const [lastSelection, setLastSelection] =
useState<OnSelectionChangeParams | null>(null); useState<OnSelectionChangeParams | null>(null);
const saveCurrentFlowTimeout = () => {
setTimeout(() => {
saveCurrentFlow();
}, 500); // need to do this because ReactFlow is not asynchronous.
};
useEffect(() => { useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => { const onKeyDown = (event: KeyboardEvent) => {
if ( if (
@ -132,6 +137,7 @@ export default function Page({
takeSnapshot(); takeSnapshot();
deleteNode(lastSelection.nodes.map((node) => node.id)); deleteNode(lastSelection.nodes.map((node) => node.id));
deleteEdge(lastSelection.edges.map((edge) => edge.id)); deleteEdge(lastSelection.edges.map((edge) => edge.id));
saveCurrentFlowTimeout();
} }
} }
}; };
@ -147,7 +153,12 @@ export default function Page({
document.removeEventListener("keydown", onKeyDown); document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mousemove", handleMouseMove);
}; };
}, [lastCopiedSelection, lastSelection, takeSnapshot]); }, [
lastCopiedSelection,
lastSelection,
takeSnapshot,
saveCurrentFlowTimeout,
]);
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false); const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
@ -190,16 +201,8 @@ export default function Page({
}, [flow, reactFlowInstance]); }, [flow, reactFlowInstance]);
useEffect(() => { useEffect(() => {
const index = flows.findIndex((flowId) => flowId.id === flow.id);
const interval = setInterval(() => { const interval = setInterval(() => {
saveFlow( saveFlow(flow, true);
{
...flows[index]!,
data: reactFlowInstance ? reactFlowInstance!.toObject() : flow!.data,
},
true
);
}, 30000); }, 30000);
return () => { return () => {
@ -209,10 +212,6 @@ export default function Page({
const onEdgesChangeMod = useCallback( const onEdgesChangeMod = useCallback(
(change: EdgeChange[]) => { (change: EdgeChange[]) => {
updateFlow({
...flow!,
data: reactFlowInstance ? reactFlowInstance!.toObject() : flow!.data,
});
onEdgesChange(change); onEdgesChange(change);
//@ts-ignore //@ts-ignore
setTabsState((prev: FlowsState) => { setTabsState((prev: FlowsState) => {
@ -220,18 +219,18 @@ export default function Page({
...prev, ...prev,
[tabId]: { [tabId]: {
...prev[tabId], ...prev[tabId],
isPending: false, isPending: true,
}, },
}; };
}); });
saveCurrentFlowTimeout();
}, },
[onEdgesChange, setNodes, setTabsState, tabId] [onEdgesChange, setNodes, setTabsState, saveCurrentFlowTimeout, tabId]
); );
const onNodesChangeMod = useCallback( const onNodesChangeMod = useCallback(
(change: NodeChange[]) => { (change: NodeChange[]) => {
const changeString = JSON.stringify(change); const changeString = JSON.stringify(change);
if (changeString !== nodesOnFlow) { if (changeString !== nodesOnFlow) {
onNodesChange(change); onNodesChange(change);
updateNodeFlow(changeString); updateNodeFlow(changeString);
@ -245,9 +244,10 @@ export default function Page({
}, },
}; };
}); });
saveCurrentFlowTimeout();
} }
}, },
[onNodesChange, setTabsState, tabId, updateNodeFlow] [onNodesChange, setTabsState, tabId, updateNodeFlow, saveCurrentFlowTimeout]
); );
function updateNodeFlow(changeString: string) { function updateNodeFlow(changeString: string) {
@ -283,7 +283,7 @@ export default function Page({
return newX; return newX;
}); });
}, },
[setEdges, setNodes, takeSnapshot] [setEdges, setNodes, takeSnapshot, addEdge]
); );
const onNodeDragStart: NodeDragHandler = useCallback(() => { const onNodeDragStart: NodeDragHandler = useCallback(() => {
@ -394,10 +394,7 @@ export default function Page({
return () => { return () => {
if (tabsState && tabsState[flow.id]?.isPending) { if (tabsState && tabsState[flow.id]?.isPending) {
saveFlow({ saveFlow(flow);
...flow!,
data: reactFlowInstance ? reactFlowInstance!.toObject() : flow!.data,
});
} }
}; };
}, []); }, []);
@ -475,11 +472,7 @@ export default function Page({
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
onMove={() => { onMove={() => {
if (reactFlowInstance) saveCurrentFlowTimeout();
updateFlow({
...flow,
data: reactFlowInstance.toObject(),
});
}} }}
edges={edges} edges={edges}
onNodesChange={onNodesChangeMod} onNodesChange={onNodesChangeMod}

View file

@ -289,32 +289,34 @@ export default function ExtraSidebar(): JSX.Element {
</div> </div>
</ShadTooltip> </ShadTooltip>
<div className="side-bar-button"> <div className="side-bar-button">
<ShadTooltip content="Save" side="top"> {flow && flow.data && (
<div> <ShadTooltip content="Save" side="top">
<button <div>
disabled={flow?.data?.nodes.length === 0} <button
className={ disabled={flow?.data?.nodes.length === 0}
"extra-side-bar-buttons " +
(isPending && flow!.data!.nodes?.length > 0
? ""
: "button-disable")
}
onClick={(event) => {
saveFlow({ ...flow!, data: reactFlowInstance!.toObject() });
}}
>
<IconComponent
name="Save"
className={ className={
"side-bar-button-size" + "extra-side-bar-buttons " +
(isPending && flow!.data!.nodes?.length > 0 (isPending && flow!.data!.nodes?.length > 0
? " " ? ""
: " extra-side-bar-save-disable") : "button-disable")
} }
/> onClick={(event) => {
</button> saveFlow(flow!);
</div> }}
</ShadTooltip> >
<IconComponent
name="Save"
className={
"side-bar-button-size" +
(isPending && flow!.data!.nodes?.length > 0
? " "
: " extra-side-bar-save-disable")
}
/>
</button>
</div>
</ShadTooltip>
)}
</div> </div>
<ShadTooltip content="Share" side="top" styleClasses="cursor-default"> <ShadTooltip content="Share" side="top" styleClasses="cursor-default">
<div className="side-bar-button">{ModalMemo}</div> <div className="side-bar-button">{ModalMemo}</div>

View file

@ -26,6 +26,7 @@ export type FlowsContextType = {
uploadFlows: () => void; uploadFlows: () => void;
isBuilt: boolean; isBuilt: boolean;
setIsBuilt: (state: boolean) => void; setIsBuilt: (state: boolean) => void;
saveCurrentFlow: () => void;
uploadFlow: ({ uploadFlow: ({
newProject, newProject,
file, file,