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, FETCH_ERROR_MESSAGE,
} from "./constants/constants"; } from "./constants/constants";
import { AuthContext } from "./contexts/authContext"; import { AuthContext } from "./contexts/authContext";
import { FlowsContext } from "./contexts/flowsContext";
import { locationContext } from "./contexts/locationContext"; import { locationContext } from "./contexts/locationContext";
import { getHealth, getVersion } from "./controllers/API"; import { getHealth, getVersion } from "./controllers/API";
import Router from "./routes"; import Router from "./routes";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -24,6 +24,7 @@ import {
scapeJSONParse, scapeJSONParse,
scapedJSONStringfy, scapedJSONStringfy,
} from "../utils/reactflowUtils"; } 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 // 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) => ({ const useFlowStore = create<FlowStoreType>((set, get) => ({
@ -55,11 +56,27 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
set({ edges: newEdges }); set({ edges: newEdges });
set({ nodes: newChange }); set({ nodes: newChange });
useFlowsManagerStore
.getState()
.autoSaveCurrentFlow(
newChange,
newEdges,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
);
}, },
setEdges: (change) => { setEdges: (change) => {
let newChange = typeof change === "function" ? change(get().edges) : change; let newChange = typeof change === "function" ? change(get().edges) : change;
set({ edges: newChange }); 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)) => { setNode: (id: string, change: Node | ((oldState: Node) => Node)) => {
let newChange = let newChange =

View file

@ -1,40 +1,19 @@
import { cloneDeep } from "lodash"; import { AxiosError } from "axios";
import ShortUniqueId from "short-unique-id"; import { Edge, Node, Viewport } from "reactflow";
import { create } from "zustand"; import { create } from "zustand";
import { readFlowsFromDatabase } from "../controllers/API"; import {
import { APIClassType } from "../types/api"; readFlowsFromDatabase,
import { FlowType, NodeDataType } from "../types/flow"; updateFlowInDatabase,
} from "../controllers/API";
import { FlowType } from "../types/flow";
import { FlowState } from "../types/tabs"; import { FlowState } from "../types/tabs";
import { FlowsManagerStoreType } from "../types/zustand/flowsManager"; import { FlowsManagerStoreType } from "../types/zustand/flowsManager";
import { processDataFromFlow } from "../utils/reactflowUtils"; import { processFlows } from "../utils/reactflowUtils";
import { createRandomKey } from "../utils/utils";
import { useTypesStore } from "./typesStore";
import useAlertStore from "./alertStore"; import useAlertStore from "./alertStore";
import useFlowStore from "./flowStore";
import { useTypesStore } from "./typesStore";
const uid = new ShortUniqueId({ length: 5 }); let saveTimeoutId: NodeJS.Timeout | null = null;
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 };
};
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
currentFlowId: "", currentFlowId: "",
@ -69,7 +48,8 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
refreshFlows: () => { refreshFlows: () => {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
set({ isLoading: true }); set({ isLoading: true });
readFlowsFromDatabase().then((dbData) => { readFlowsFromDatabase()
.then((dbData) => {
if (dbData) { if (dbData) {
const { data, flows } = processFlows(dbData, false); const { data, flows } = processFlows(dbData, false);
set({ flows, isLoading: false }); set({ flows, isLoading: false });
@ -78,7 +58,8 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
})); }));
resolve(); resolve();
} }
}).catch((e) => { })
.catch((e) => {
useAlertStore.getState().setErrorData({ useAlertStore.getState().setErrorData({
title: "Could not load flows from database", title: "Could not load flows from database",
}); });
@ -86,6 +67,56 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
}); });
}); });
}, },
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);
});
});
},
})); }));
export default useFlowsManagerStore; export default useFlowsManagerStore;

View file

@ -1,3 +1,4 @@
import { Node, Edge, Viewport } from "reactflow";
import { FlowType } from "../../flow"; import { FlowType } from "../../flow";
import { FlowState, FlowsState } from "../../tabs"; import { FlowState, FlowsState } from "../../tabs";
@ -12,4 +13,6 @@ export type FlowsManagerStoreType = {
currentFlowState: FlowState | undefined; currentFlowState: FlowState | undefined;
setCurrentFlowState: (state: FlowState | ((oldState: FlowState | undefined) => FlowState)) => void; setCurrentFlowState: (state: FlowState | ((oldState: FlowState | undefined) => FlowState)) => void;
refreshFlows: () => Promise<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 { import {
Connection, Connection,
Edge, Edge,
@ -13,6 +13,7 @@ import {
specialCharsRegex, specialCharsRegex,
} from "../constants/constants"; } from "../constants/constants";
import { import {
APIClassType,
APIKindType, APIKindType,
APIObjectType, APIObjectType,
APITemplateType, APITemplateType,
@ -31,7 +32,7 @@ import {
unselectAllNodesType, unselectAllNodesType,
updateEdgesHandleIdsType, updateEdgesHandleIdsType,
} from "../types/utils/reactflowUtils"; } from "../types/utils/reactflowUtils";
import { getFieldTitle, toTitleCase } from "./utils"; import { createRandomKey, getFieldTitle, toTitleCase } from "./utils";
const uid = new ShortUniqueId({ length: 5 }); const uid = new ShortUniqueId({ length: 5 });
export function cleanEdges(nodes: Node[], edges: Edge[]) { export function cleanEdges(nodes: Node[], edges: Edge[]) {
@ -153,6 +154,29 @@ export function updateTemplate(
return clonedObject; 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) => { export const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
let data = flow?.data ? flow.data : null; let data = flow?.data ? flow.data : null;
if (data) { if (data) {