added upload and download flows on zustand and utils

This commit is contained in:
Lucas Oliveira 2024-01-06 01:00:59 -03:00
commit 616a4f0031
15 changed files with 114 additions and 28 deletions

View file

@ -5,7 +5,6 @@ import { useSSE } from "../../../contexts/SSEContext";
import { postBuildInit } from "../../../controllers/API";
import { FlowType } from "../../../types/flow";
import { FlowsContext } from "../../../contexts/flowsContext";
import useAlertStore from "../../../stores/alertStore";
import useFlowStore from "../../../stores/flowStore";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";

View file

@ -5,7 +5,6 @@ import AlertDropdown from "../../alerts/alertDropDown";
import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import { useStoreStore } from "../../stores/storeStore";
@ -22,7 +21,6 @@ import {
} from "../ui/dropdown-menu";
import { Separator } from "../ui/separator";
import MenuBar from "./components/menuBar";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
export default function Header(): JSX.Element {
const notificationCenter = useAlertStore((state) => state.notificationCenter);

View file

@ -1,5 +1,4 @@
import { useContext, useEffect, useState } from "react";
import { FlowsContext } from "../../contexts/flowsContext";
import { uploadFile } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import { FileComponentType } from "../../types/components";

View file

@ -4,16 +4,14 @@ import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import { EXPORT_DIALOG_SUBTITLE } from "../../constants/constants";
import { FlowsContext } from "../../contexts/flowsContext";
import useAlertStore from "../../stores/alertStore";
import { removeApiKeys } from "../../utils/reactflowUtils";
import { downloadFlow, removeApiKeys } from "../../utils/reactflowUtils";
import BaseModal from "../baseModal";
import { useDarkStore } from "../../stores/darkStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
const ExportModal = forwardRef(
(props: { children: ReactNode }, ref): JSX.Element => {
const { downloadFlow } = useContext(FlowsContext);
const version = useDarkStore((state) => state.version);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
const [checked, setChecked] = useState(true);

View file

@ -3,7 +3,6 @@ import EditFlowSettings from "../../components/EditFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
import { FlowsContext } from "../../contexts/flowsContext";
import { FlowSettingsPropsType } from "../../types/components";
import { FlowType } from "../../types/flow";
import BaseModal from "../baseModal";

View file

@ -5,7 +5,6 @@ import IconComponent from "../../components/genericIconComponent";
import { TagsSelector } from "../../components/tagsSelectorComponent";
import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import { FlowsContext } from "../../contexts/flowsContext";
import {
getStoreComponents,
getStoreTags,

View file

@ -17,8 +17,6 @@ import ReactFlow, {
SelectionDragHandler,
addEdge,
updateEdge,
useEdgesState,
useNodesState,
} from "reactflow";
import GenericNode from "../../../../CustomNodes/GenericNode";
import Chat from "../../../../components/chatComponent";
@ -28,6 +26,8 @@ import { locationContext } from "../../../../contexts/locationContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { APIClassType } from "../../../../types/api";
import { FlowType, NodeType } from "../../../../types/flow";
import {
@ -41,8 +41,6 @@ import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils";
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,
@ -56,7 +54,9 @@ export default function Page({
view?: boolean;
}): JSX.Element {
let { uploadFlow } = useContext(FlowsContext);
const autoSaveCurrentFlow = useFlowsManagerStore((state) => state.autoSaveCurrentFlow);
const autoSaveCurrentFlow = useFlowsManagerStore(
(state) => state.autoSaveCurrentFlow
);
const types = useTypesStore((state) => state.types);
const templates = useTypesStore((state) => state.templates);
const setFilterEdge = useTypesStore((state) => state.setFilterEdge);
@ -160,13 +160,17 @@ export default function Page({
const [loading, setLoading] = useState(true);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const timeoutRef = useRef<NodeJS.Timeout>();
useEffect(() => {
setLoading(true);
if (reactFlowInstance) {
reactFlowInstance.setNodes(flow?.data?.nodes ?? []);
reactFlowInstance.setEdges(flow?.data?.edges ?? []);
useFlowStore.setState({
nodes: flow?.data?.nodes ?? [],
edges: flow?.data?.edges ?? [],
});
reactFlowInstance.setViewport(
flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 }
);
@ -186,7 +190,7 @@ export default function Page({
return () => {
clearTimeout(timeoutRef.current);
};
}, [flow, reactFlowInstance]);
}, [currentFlowId, reactFlowInstance]);
const onConnectMod = useCallback(
(params: Connection) => {

View file

@ -35,6 +35,8 @@ export default function ExtraSidebar(): JSX.Element {
const { uploadFlow } = useContext(FlowsContext);
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const hasStore = useStoreStore((state) => state.hasStore);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
@ -304,7 +306,7 @@ export default function ExtraSidebar(): JSX.Element {
(isPending ? "" : "button-disable")
}
onClick={(event) => {
saveFlow({...currentFlow, data: {...currentFlow.data!, viewport: reactFlowInstance?.getViewport()!} }, true);
saveFlow({...currentFlow, data: {nodes, edges, viewport: reactFlowInstance?.getViewport()!} }, true);
}}
>
<IconComponent

View file

@ -16,8 +16,9 @@ export default function ComponentsComponent({
}: {
is_component?: boolean;
}) {
const { removeFlow, uploadFlow, addFlow, isLoading } =
const { removeFlow, uploadFlow, addFlow } =
useContext(FlowsContext);
const isLoading = useFlowsManagerStore((state) => state.isLoading);
const flows = useFlowsManagerStore((state) => state.flows);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
@ -51,7 +52,7 @@ export default function ComponentsComponent({
const start = (pageIndex - 1) * pageSize;
const end = start + pageSize;
setData(all.slice(start, end));
}, [flows, pageIndex, pageSize]);
}, [flows, isLoading, pageIndex, pageSize]);
const [data, setData] = useState<FlowType[]>([]);

View file

@ -10,12 +10,16 @@ import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { FlowsContext } from "../../contexts/flowsContext";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { downloadFlows } from "../../utils/reactflowUtils";
export default function HomePage(): JSX.Element {
const { downloadFlows, uploadFlows, addFlow, uploadFlow } =
const { addFlow, uploadFlow } =
useContext(FlowsContext);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
);
const uploadFlows = useFlowsManagerStore(
(state) => state.uploadFlows
);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const location = useLocation();

View file

@ -8,7 +8,6 @@ import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { resetPassword, updateUser } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import {

View file

@ -21,7 +21,6 @@ import {
SelectValue,
} from "../../components/ui/select";
import { AuthContext } from "../../contexts/authContext";
import { FlowsContext } from "../../contexts/flowsContext";
import {
checkHasApiKey,
getStoreComponents,

View file

@ -4,6 +4,7 @@ import { create } from "zustand";
import {
readFlowsFromDatabase,
updateFlowInDatabase,
uploadFlowsToDatabase,
} from "../controllers/API";
import { FlowType } from "../types/flow";
import { FlowState } from "../types/tabs";
@ -25,6 +26,12 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
}));
},
flows: [],
setFlows: (flows: FlowType[]) => {
set({
flows,
currentFlow: flows.find((flow) => flow.id === get().currentFlowId),
});
},
currentFlow: undefined,
isLoading: true,
setIsLoading: (isLoading: boolean) => set({ isLoading }),
@ -52,7 +59,8 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
.then((dbData) => {
if (dbData) {
const { data, flows } = processFlows(dbData, false);
set({ flows, isLoading: false });
get().setFlows(flows);
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
@ -94,14 +102,14 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
.getState()
.setSuccessData({ title: "Changes saved successfully" });
}
set((oldState) => ({
flows: oldState.flows.map((flow) => {
get().setFlows(
get().flows.map((flow) => {
if (flow.id === updatedFlow.id) {
return updatedFlow;
}
return flow;
}),
}));
})
);
//update tabs state
useFlowStore.setState({ isPending: false });
@ -117,6 +125,35 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
});
});
},
uploadFlows: () => {
return new Promise<void>((resolve) => {
const input = document.createElement("input");
input.type = "file";
// add a change event listener to the file input
input.onchange = (event: Event) => {
// check if the file type is application/json
if (
(event.target as HTMLInputElement).files![0].type ===
"application/json"
) {
// get the file from the file input
const file = (event.target as HTMLInputElement).files![0];
// read the file as text
const formData = new FormData();
formData.append("file", file);
uploadFlowsToDatabase(formData).then(() => {
get()
.refreshFlows()
.then(() => {
resolve();
});
});
}
};
// trigger the file input click event to open the file dialog
input.click();
});
},
}));
export default useFlowsManagerStore;

View file

@ -4,6 +4,7 @@ import { FlowState, FlowsState } from "../../tabs";
export type FlowsManagerStoreType = {
flows: Array<FlowType>;
setFlows: (flows: FlowType[]) => void;
currentFlow: FlowType | undefined;
currentFlowId: string;
setCurrentFlowId: (currentFlowId: string) => void;
@ -15,4 +16,5 @@ export type FlowsManagerStoreType = {
refreshFlows: () => Promise<void>;
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void>;
autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => void;
uploadFlows: () => Promise<void>;
};

View file

@ -33,6 +33,7 @@ import {
updateEdgesHandleIdsType,
} from "../types/utils/reactflowUtils";
import { createRandomKey, getFieldTitle, toTitleCase } from "./utils";
import { downloadFlowsFromDatabase } from "../controllers/API";
const uid = new ShortUniqueId({ length: 5 });
export function cleanEdges(nodes: Node[], edges: Edge[]) {
@ -1205,3 +1206,48 @@ export function templatesGenerator(data: APIObjectType) {
return acc;
}, {});
}
export function downloadFlow(
flow: FlowType,
flowName: string,
flowDescription?: string
) {
let clonedFlow = cloneDeep(flow);
removeFileNameFromComponents(clonedFlow);
// create a data URI with the current flow data
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify({
...clonedFlow,
name: flowName,
description: flowDescription,
})
)}`;
// create a link element and set its properties
const link = document.createElement("a");
link.href = jsonString;
link.download = `${
flowName && flowName != ""
? flowName
: flow.name
}.json`;
// simulate a click on the link element to trigger the download
link.click();
}
export function downloadFlows() {
downloadFlowsFromDatabase().then((flows) => {
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify(flows)
)}`;
// create a link element and set its properties
const link = document.createElement("a");
link.href = jsonString;
link.download = `flows.json`;
// simulate a click on the link element to trigger the download
link.click();
});
}