added saveFlow function to Zustand state and implemented it on the project

This commit is contained in:
Lucas Oliveira 2024-01-06 00:27:36 -03:00
commit d44648d6c1
16 changed files with 141 additions and 62 deletions

View file

@ -16,7 +16,6 @@ import {
FETCH_ERROR_MESSAGE,
} from "./constants/constants";
import { AuthContext } from "./contexts/authContext";
import { FlowsContext } from "./contexts/flowsContext";
import { locationContext } from "./contexts/locationContext";
import { getHealth, getVersion } from "./controllers/API";
import Router from "./routes";

View file

@ -26,7 +26,6 @@ import {
LANGFLOW_SUPPORTED_TYPES,
TOOLTIP_EMPTY,
} from "../../../../constants/constants";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import { postCustomComponentUpdate } from "../../../../controllers/API";
import useAlertStore from "../../../../stores/alertStore";

View file

@ -26,7 +26,6 @@ export default function BuildTrigger({
isBuilt: boolean;
}): JSX.Element {
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
const { saveFlow } = useContext(FlowsContext);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setErrorData = useAlertStore((state) => state.setErrorData);
@ -34,6 +33,7 @@ export default function BuildTrigger({
const setCurrentFlowState = useFlowsManagerStore(
(state) => state.setCurrentFlowState
);
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const [isIconTouched, setIsIconTouched] = useState(false);
const eventClick = isBuilding ? "pointer-events-none" : "";

View file

@ -5,7 +5,6 @@ import BuildTrigger from "./buildTrigger";
import ChatTrigger from "./chatTrigger";
import * as _ from "lodash";
import { FlowsContext } from "../../contexts/flowsContext";
import { getBuildStatus } from "../../controllers/API";
import FormModal from "../../modals/formModal";
import useFlowStore from "../../stores/flowStore";

View file

@ -13,7 +13,6 @@ import {
undoRedoContextType,
} from "../types/typesContext";
import { isWrappedWithClass } from "../utils/utils";
import { FlowsContext } from "./flowsContext";
import useFlowsManagerStore from "../stores/flowsManagerStore";
const initialValue = {

View file

@ -13,7 +13,7 @@ export default function FlowSettingsModal({
open,
setOpen,
}: FlowSettingsPropsType): JSX.Element {
const { saveFlow } = useContext(FlowsContext);
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const flows = useFlowsManagerStore((state) => state.flows);
useEffect(() => {

View file

@ -24,6 +24,7 @@ import { getTagsIds } from "../../utils/storeUtils";
import ConfirmationModal from "../ConfirmationModal";
import BaseModal from "../baseModal";
import { useDarkStore } from "../../stores/darkStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
export default function ShareModal({
component,
@ -57,7 +58,7 @@ export default function ShareModal({
const [unavaliableNames, setUnavaliableNames] = useState<
{ id: string; name: string }[]
>([]);
const { saveFlow } = useContext(FlowsContext);
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const [loadingNames, setLoadingNames] = useState(false);

View file

@ -21,7 +21,6 @@ import {
ADMIN_HEADER_TITLE,
} from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import {
addUser,
deleteUser,

View file

@ -42,6 +42,7 @@ import ConnectionLineComponent from "../ConnectionLineComponent";
import SelectionMenu from "../SelectionMenuComponent";
import ExtraSidebar from "../extraSidebarComponent";
import { useTypesStore } from "../../../../stores/typesStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
const nodeTypes = {
genericNode: GenericNode,
@ -54,7 +55,8 @@ export default function Page({
flow: FlowType;
view?: boolean;
}): JSX.Element {
let { uploadFlow, saveFlow } = useContext(FlowsContext);
let { uploadFlow } = useContext(FlowsContext);
const autoSaveCurrentFlow = useFlowsManagerStore((state) => state.autoSaveCurrentFlow);
const types = useTypesStore((state) => state.types);
const templates = useTypesStore((state) => state.templates);
const setFilterEdge = useTypesStore((state) => state.setFilterEdge);
@ -202,7 +204,7 @@ export default function Page({
const onNodeDragStop: NodeDragHandler = useCallback(() => {
// 👇 make dragging a node undoable
saveFlow(undefined, true);
autoSaveCurrentFlow(nodes, edges, reactFlowInstance?.getViewport()!);
// 👉 you can place your event handlers here
}, [takeSnapshot]);

View file

@ -32,7 +32,9 @@ export default function ExtraSidebar(): JSX.Element {
const templates = useTypesStore((state) => state.templates);
const getFilterEdge = useTypesStore((state) => state.getFilterEdge);
const setFilterEdge = useTypesStore((state) => state.setFilterEdge);
const { uploadFlow, saveFlow } = useContext(FlowsContext);
const { uploadFlow } = useContext(FlowsContext);
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const hasStore = useStoreStore((state) => state.hasStore);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
@ -302,7 +304,7 @@ export default function ExtraSidebar(): JSX.Element {
(isPending ? "" : "button-disable")
}
onClick={(event) => {
saveFlow();
saveFlow({...currentFlow, data: {...currentFlow.data!, viewport: reactFlowInstance?.getViewport()!} }, true);
}}
>
<IconComponent

View file

@ -25,6 +25,7 @@ import {
} from "../../../../utils/reactflowUtils";
import { classNames } from "../../../../utils/utils";
import { useDarkStore } from "../../../../stores/darkStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
export default function NodeToolbarComponent({
data,
@ -73,7 +74,8 @@ export default function NodeToolbarComponent({
const setNodes = useFlowStore((state) => state.setNodes);
const setEdges = useFlowStore((state) => state.setEdges);
const { saveComponent, flows } = useContext(FlowsContext);
const { saveComponent } = useContext(FlowsContext);
const flows = useFlowsManagerStore((state) => state.flows);
const version = useDarkStore((state) => state.version);
const { takeSnapshot } = useContext(undoRedoContext);
const [showModalAdvanced, setShowModalAdvanced] = useState(false);

View file

@ -9,14 +9,16 @@ import { Button } from "../../../../components/ui/button";
import { FlowsContext } from "../../../../contexts/flowsContext";
import useAlertStore from "../../../../stores/alertStore";
import { FlowType } from "../../../../types/flow";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
export default function ComponentsComponent({
is_component = true,
}: {
is_component?: boolean;
}) {
const { flows, removeFlow, uploadFlow, addFlow, isLoading } =
const { removeFlow, uploadFlow, addFlow, isLoading } =
useContext(FlowsContext);
const flows = useFlowsManagerStore((state) => state.flows);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const [pageSize, setPageSize] = useState(10);

View file

@ -24,6 +24,7 @@ import {
scapeJSONParse,
scapedJSONStringfy,
} from "../utils/reactflowUtils";
import useFlowsManagerStore from "./flowsManagerStore";
// this is our useStore hook that we can use in our components to get parts of the store and call actions
const useFlowStore = create<FlowStoreType>((set, get) => ({
@ -55,11 +56,27 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
set({ edges: newEdges });
set({ nodes: newChange });
useFlowsManagerStore
.getState()
.autoSaveCurrentFlow(
newChange,
newEdges,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
);
},
setEdges: (change) => {
let newChange = typeof change === "function" ? change(get().edges) : change;
set({ edges: newChange });
useFlowsManagerStore
.getState()
.autoSaveCurrentFlow(
get().nodes,
newChange,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
);
},
setNode: (id: string, change: Node | ((oldState: Node) => Node)) => {
let newChange =

View file

@ -1,40 +1,19 @@
import { cloneDeep } from "lodash";
import ShortUniqueId from "short-unique-id";
import { AxiosError } from "axios";
import { Edge, Node, Viewport } from "reactflow";
import { create } from "zustand";
import { readFlowsFromDatabase } from "../controllers/API";
import { APIClassType } from "../types/api";
import { FlowType, NodeDataType } from "../types/flow";
import {
readFlowsFromDatabase,
updateFlowInDatabase,
} from "../controllers/API";
import { FlowType } from "../types/flow";
import { FlowState } from "../types/tabs";
import { FlowsManagerStoreType } from "../types/zustand/flowsManager";
import { processDataFromFlow } from "../utils/reactflowUtils";
import { createRandomKey } from "../utils/utils";
import { useTypesStore } from "./typesStore";
import { processFlows } from "../utils/reactflowUtils";
import useAlertStore from "./alertStore";
import useFlowStore from "./flowStore";
import { useTypesStore } from "./typesStore";
const uid = new ShortUniqueId({ length: 5 });
const processFlows = (DbData: FlowType[], skipUpdate = true) => {
let savedComponents: { [key: string]: APIClassType } = {};
DbData.forEach((flow: FlowType) => {
try {
if (!flow.data) {
return;
}
if (flow.data && flow.is_component) {
(flow.data.nodes[0].data as NodeDataType).node!.display_name =
flow.name;
savedComponents[
createRandomKey((flow.data.nodes[0].data as NodeDataType).type, uid())
] = cloneDeep((flow.data.nodes[0].data as NodeDataType).node!);
return;
}
if (!skipUpdate) processDataFromFlow(flow, false);
} catch (e) {
console.log(e);
}
});
return { data: savedComponents, flows: DbData };
};
let saveTimeoutId: NodeJS.Timeout | null = null;
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
currentFlowId: "",
@ -69,21 +48,73 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
refreshFlows: () => {
return new Promise<void>((resolve, reject) => {
set({ isLoading: true });
readFlowsFromDatabase().then((dbData) => {
if (dbData) {
const { data, flows } = processFlows(dbData, false);
set({ flows, isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
resolve();
}
}).catch((e) => {
useAlertStore.getState().setErrorData({
title: "Could not load flows from database",
readFlowsFromDatabase()
.then((dbData) => {
if (dbData) {
const { data, flows } = processFlows(dbData, false);
set({ flows, isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
resolve();
}
})
.catch((e) => {
useAlertStore.getState().setErrorData({
title: "Could not load flows from database",
});
reject(e);
});
});
},
autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => {
// Clear the previous timeout if it exists.
if (saveTimeoutId) {
clearTimeout(saveTimeoutId);
}
// Set up a new timeout.
saveTimeoutId = setTimeout(() => {
if (get().currentFlow) {
get().saveFlow(
{ ...get().currentFlow!, data: { nodes, edges, viewport } },
true
);
}
}, 300); // Delay of 300ms.
},
saveFlow: (flow: FlowType, silent?: boolean) => {
return new Promise<void>((resolve, reject) => {
updateFlowInDatabase(flow)
.then((updatedFlow) => {
if (updatedFlow) {
// updates flow in state
if (!silent) {
useAlertStore
.getState()
.setSuccessData({ title: "Changes saved successfully" });
}
set((oldState) => ({
flows: oldState.flows.map((flow) => {
if (flow.id === updatedFlow.id) {
return updatedFlow;
}
return flow;
}),
}));
//update tabs state
useFlowStore.setState({ isPending: false });
resolve();
}
})
.catch((err) => {
useAlertStore.getState().setErrorData({
title: "Error while saving changes",
list: [(err as AxiosError).message],
});
reject(err);
});
reject(e);
});
});
},
}));

View file

@ -1,3 +1,4 @@
import { Node, Edge, Viewport } from "reactflow";
import { FlowType } from "../../flow";
import { FlowState, FlowsState } from "../../tabs";
@ -12,4 +13,6 @@ export type FlowsManagerStoreType = {
currentFlowState: FlowState | undefined;
setCurrentFlowState: (state: FlowState | ((oldState: FlowState | undefined) => FlowState)) => void;
refreshFlows: () => Promise<void>;
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void>;
autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => void;
};

View file

@ -1,4 +1,4 @@
import _ from "lodash";
import _, { cloneDeep } from "lodash";
import {
Connection,
Edge,
@ -13,6 +13,7 @@ import {
specialCharsRegex,
} from "../constants/constants";
import {
APIClassType,
APIKindType,
APIObjectType,
APITemplateType,
@ -31,7 +32,7 @@ import {
unselectAllNodesType,
updateEdgesHandleIdsType,
} from "../types/utils/reactflowUtils";
import { getFieldTitle, toTitleCase } from "./utils";
import { createRandomKey, getFieldTitle, toTitleCase } from "./utils";
const uid = new ShortUniqueId({ length: 5 });
export function cleanEdges(nodes: Node[], edges: Edge[]) {
@ -153,6 +154,29 @@ export function updateTemplate(
return clonedObject;
}
export const processFlows = (DbData: FlowType[], skipUpdate = true) => {
let savedComponents: { [key: string]: APIClassType } = {};
DbData.forEach((flow: FlowType) => {
try {
if (!flow.data) {
return;
}
if (flow.data && flow.is_component) {
(flow.data.nodes[0].data as NodeDataType).node!.display_name =
flow.name;
savedComponents[
createRandomKey((flow.data.nodes[0].data as NodeDataType).type, uid())
] = cloneDeep((flow.data.nodes[0].data as NodeDataType).node!);
return;
}
if (!skipUpdate) processDataFromFlow(flow, false);
} catch (e) {
console.log(e);
}
});
return { data: savedComponents, flows: DbData };
};
export const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
let data = flow?.data ? flow.data : null;
if (data) {