Passed the rest of the flowsContext functions to zustand, removed flowsContext

This commit is contained in:
Lucas Oliveira 2024-01-06 01:27:54 -03:00
commit 882f365ff6
13 changed files with 225 additions and 534 deletions

View file

@ -1,5 +1,4 @@
import { useContext, useEffect, useState } from "react";
import { FlowsContext } from "../../contexts/flowsContext";
import { getComponent, postLikeComponent } from "../../controllers/API";
import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal";
import useAlertStore from "../../stores/alertStore";
@ -18,6 +17,7 @@ import {
CardHeader,
CardTitle,
} from "../ui/card";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
export default function CollectionCardComponent({
data,
@ -32,7 +32,7 @@ export default function CollectionCardComponent({
button?: JSX.Element;
onDelete?: () => void;
}) {
const { addFlow } = useContext(FlowsContext);
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);

View file

@ -1,5 +1,4 @@
import { useContext, useState } from "react";
import { FlowsContext } from "../../../../contexts/flowsContext";
import {
DropdownMenu,
DropdownMenuContent,
@ -17,7 +16,7 @@ import IconComponent from "../../../genericIconComponent";
import { Button } from "../../../ui/button";
export const MenuBar = (): JSX.Element => {
const { addFlow } = useContext(FlowsContext);
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { undo, redo } = useContext(undoRedoContext);

View file

@ -1,500 +0,0 @@
import { AxiosError } from "axios";
import _, { cloneDeep } from "lodash";
import { ReactNode, createContext, useContext, useRef, useState } from "react";
import {
Edge,
Node,
ReactFlowJsonObject,
Viewport,
XYPosition,
} from "reactflow";
import ShortUniqueId from "short-unique-id";
import {
deleteFlowFromDatabase,
downloadFlowsFromDatabase,
readFlowsFromDatabase,
saveFlowToDatabase,
updateFlowInDatabase,
uploadFlowsToDatabase,
} from "../controllers/API";
import useAlertStore from "../stores/alertStore";
import useFlowStore from "../stores/flowStore";
import { APIClassType } from "../types/api";
import { FlowType, NodeDataType } from "../types/flow";
import { FlowsContextType, FlowsState } from "../types/tabs";
import {
addVersionToDuplicates,
createFlowComponent,
processFlowEdges,
removeFileNameFromComponents,
updateEdges,
updateIds,
} from "../utils/reactflowUtils";
import {
createRandomKey,
getRandomDescription,
getRandomName,
} from "../utils/utils";
import { useTypesStore } from "../stores/typesStore";
const uid = new ShortUniqueId({ length: 5 });
const FlowsContextInitialValue: FlowsContextType = {
//Remove tab id and get current id from url
tabId: "",
setTabId: (index: string) => {},
isLoading: true,
flows: [],
setVersion: () => {},
removeFlow: (id: string) => {},
addFlow: async (
newProject: boolean,
flowData?: FlowType,
override?: boolean
) => "",
downloadFlow: (flow: FlowType) => {},
downloadFlows: () => {},
uploadFlows: () => {},
uploadFlow: async () => "",
saveFlow: async (flow?: FlowType, silent?: boolean) => {},
tabsState: {},
setTabsState: () => {},
saveComponent: async (component: NodeDataType, override: boolean) => "",
deleteComponent: (key: string) => {},
version: "",
refreshFlows: () => {},
};
export const FlowsContext = createContext<FlowsContextType>(
FlowsContextInitialValue
);
export function FlowsProvider({ children }: { children: ReactNode }) {
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const [tabId, setTabId] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [flows, setFlows] = useState<Array<FlowType>>([]);
const [tabsState, setTabsState] = useState<FlowsState>({});
const setData = useTypesStore((state) => state.setData);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
const setPending = useFlowStore((state) => state.setPending);
const paste = useFlowStore((state) => state.paste);
function refreshFlows() {
setIsLoading(true);
getTabsDataFromDB().then((DbData) => {
if (DbData) {
try {
processFlows(DbData, false);
setFlows(DbData);
setIsLoading(false);
} catch (e) {}
}
});
}
function getTabsDataFromDB() {
//get tabs from db
return readFlowsFromDatabase();
}
function 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);
}
});
setData((prev) => {
let newData = cloneDeep(prev);
newData["saved_components"] = cloneDeep(savedComponents);
return newData;
});
}
/**
* Downloads the current flow as a JSON file
*/
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
: flows.find((f) => f.id === tabId)!.name
}.json`;
// simulate a click on the link element to trigger the download
link.click();
}
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();
});
}
/**
* Creates a file input and listens to a change event to upload a JSON flow file.
* If the file type is application/json, the file is read and parsed into a JSON object.
* The resulting JSON object is passed to the addFlow function.
*/
async function uploadFlow({
newProject,
file,
isComponent = false,
position = { x: 10, y: 10 },
}: {
newProject: boolean;
file?: File;
isComponent?: boolean;
position?: XYPosition;
}): Promise<String | never> {
return new Promise(async (resolve, reject) => {
let id;
if (file) {
let text = await file.text();
let fileData = JSON.parse(text);
if (
newProject &&
((!fileData.is_component && isComponent === true) ||
(fileData.is_component !== undefined &&
fileData.is_component !== isComponent))
) {
reject("You cannot upload a component as a flow or vice versa");
} else {
if (fileData.flows) {
fileData.flows.forEach((flow: FlowType) => {
id = addFlow(newProject, flow, undefined, position);
});
resolve("");
} else {
id = await addFlow(newProject, fileData, undefined, position);
resolve(id);
}
}
} else {
// create a file input
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
// add a change event listener to the file input
input.onchange = async (e: Event) => {
if (
(e.target as HTMLInputElement).files![0].type === "application/json"
) {
const currentfile = (e.target as HTMLInputElement).files![0];
let text = await currentfile.text();
let fileData: FlowType = await JSON.parse(text);
if (
(!fileData.is_component && isComponent === true) ||
(fileData.is_component !== undefined &&
fileData.is_component !== isComponent)
) {
reject("You cannot upload a component as a flow or vice versa");
} else {
id = await addFlow(newProject, fileData);
resolve(id);
}
}
};
// trigger the file input click event to open the file dialog
input.click();
}
});
}
function uploadFlows() {
// create a file input
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(() => {
refreshFlows();
});
}
};
// trigger the file input click event to open the file dialog
input.click();
}
/**
* Removes a flow from an array of flows based on its id.
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
* @param {string} id - The id of the flow to remove.
*/
async function removeFlow(id: string) {
const index = flows.findIndex((flow) => flow.id === id);
if (index >= 0) {
await deleteFlowFromDatabase(id);
//removes component from data if there is any
setFlows(flows.filter((flow) => flow.id !== id));
processFlows(flows.filter((flow) => flow.id !== id));
}
}
const addFlow = async (
newProject: Boolean,
flow?: FlowType,
override?: boolean,
position?: XYPosition
): Promise<String | undefined> => {
if (newProject) {
let flowData = flow
? processDataFromFlow(flow)
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
// Create a new flow with a default name if no flow is provided.
if (override) {
deleteComponent(flow!.name);
const newFlow = createNewFlow(flowData, flow!);
const { id } = await saveFlowToDatabase(newFlow);
newFlow.id = id;
//setTimeout to prevent update state with wrong state
setTimeout(() => {
addFlowToLocalState(newFlow);
}, 200);
// addFlowToLocalState(newFlow);
return;
}
const newFlow = createNewFlow(flowData, flow!);
const newName = addVersionToDuplicates(newFlow, flows);
newFlow.name = newName;
try {
const { id } = await saveFlowToDatabase(newFlow);
// Change the id to the new id.
newFlow.id = id;
// Add the new flow to the list of flows.
addFlowToLocalState(newFlow);
// Return the id
return id;
} catch (error) {
// Handle the error if needed
throw error; // Re-throw the error so the caller can handle it if needed
}
} else {
paste(
{ nodes: flow!.data!.nodes, edges: flow!.data!.edges },
position ?? { x: 10, y: 10 }
);
}
};
const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
let data = flow?.data ? flow.data : null;
if (data) {
processFlowEdges(flow);
//prevent node update for now
// processFlowNodes(flow);
//add animation to text type edges
updateEdges(data.edges);
// updateNodes(data.nodes, data.edges);
if (refreshIds) updateIds(data); // Assuming updateIds is defined elsewhere
}
return data;
};
const createNewFlow = (
flowData: ReactFlowJsonObject | null,
flow: FlowType
) => ({
description: flow?.description ?? getRandomDescription(),
name: flow?.name ?? getRandomName(),
data: flowData,
id: "",
is_component: flow?.is_component ?? false,
});
const addFlowToLocalState = (newFlow: FlowType) => {
let newFlows: FlowType[] = [];
setFlows((prevState) => {
newFlows = newFlows.concat(prevState);
newFlows.push(newFlow);
return [...prevState, newFlow];
});
processFlows(newFlows);
};
/**
* Updates an existing flow with new data
* @param newFlow - The new flow object containing the updated data
*/
function updateFlow(newFlow: FlowType) {
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;
}
newFlow = {
...newFlow,
};
return newFlows;
});
}
const saveTimeoutId = useRef<NodeJS.Timeout | null>(null);
const saveCurrentFlow = (
nodes: Node[],
edges: Edge[],
viewport: Viewport
) => {
// Clear the previous timeout if it exists.
if (saveTimeoutId.current) {
clearTimeout(saveTimeoutId.current);
}
// Set up a new timeout.
saveTimeoutId.current = setTimeout(() => {
const currentFlow = flows.find((flow: FlowType) => flow.id === tabId);
if (currentFlow) {
saveFlow({ ...currentFlow, data: { nodes, edges, viewport } }, true);
}
}, 300); // Delay of 300ms.
};
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);
if (updatedFlow) {
// updates flow in state
if (!silent) {
setSuccessData({ title: "Changes saved successfully" });
}
updateFlow(newFlow);
//update tabs state
setPending(false);
}
} catch (err) {
setErrorData({
title: "Error while saving changes",
list: [(err as AxiosError).message],
});
}
}
function saveComponent(component: NodeDataType, override: boolean) {
component.node!.official = false;
return addFlow(true, createFlowComponent(component, version), override);
}
function deleteComponent(key: string) {
let componentFlow = flows.find(
(componentFlow) =>
componentFlow.is_component && componentFlow.name === key
);
if (componentFlow) {
removeFlow(componentFlow.id);
}
}
// Initialize state variable for the version
const [version, setVersion] = useState("");
return (
<FlowsContext.Provider
value={{
version,
setVersion,
flows,
saveFlow,
tabId,
setTabId,
removeFlow,
addFlow,
downloadFlow,
downloadFlows,
uploadFlows,
uploadFlow,
tabsState,
setTabsState,
refreshFlows,
isLoading,
saveComponent,
deleteComponent,
}}
>
{children}
</FlowsContext.Provider>
);
}

View file

@ -5,7 +5,6 @@ import { TooltipProvider } from "../components/ui/tooltip";
import { ApiInterceptor } from "../controllers/API/api";
import { SSEProvider } from "./SSEContext";
import { AuthProvider } from "./authContext";
import { FlowsProvider } from "./flowsContext";
import { LocationProvider } from "./locationContext";
import { UndoRedoProvider } from "./undoRedoContext";
@ -21,9 +20,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
<LocationProvider>
<ApiInterceptor />
<SSEProvider>
<FlowsProvider>
<UndoRedoProvider>{children}</UndoRedoProvider>
</FlowsProvider>
<UndoRedoProvider>{children}</UndoRedoProvider>
</SSEProvider>
</LocationProvider>
</ReactFlowProvider>

View file

@ -21,7 +21,6 @@ import ReactFlow, {
import GenericNode from "../../../../CustomNodes/GenericNode";
import Chat from "../../../../components/chatComponent";
import Loading from "../../../../components/ui/loading";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { locationContext } from "../../../../contexts/locationContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import useAlertStore from "../../../../stores/alertStore";
@ -53,7 +52,7 @@ export default function Page({
flow: FlowType;
view?: boolean;
}): JSX.Element {
let { uploadFlow } = useContext(FlowsContext);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const autoSaveCurrentFlow = useFlowsManagerStore(
(state) => state.autoSaveCurrentFlow
);

View file

@ -4,7 +4,6 @@ import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import { Input } from "../../../../components/ui/input";
import { Separator } from "../../../../components/ui/separator";
import { FlowsContext } from "../../../../contexts/flowsContext";
import ApiModal from "../../../../modals/ApiModal";
import ExportModal from "../../../../modals/exportModal";
import ShareModal from "../../../../modals/shareModal";
@ -32,7 +31,7 @@ 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 } = useContext(FlowsContext);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
const nodes = useFlowStore((state) => state.nodes);

View file

@ -7,7 +7,6 @@ import {
SelectTrigger,
} from "../../../../../components/ui/select-custom";
import { AuthContext } from "../../../../../contexts/authContext";
import { FlowsContext } from "../../../../../contexts/flowsContext";
import { APIClassType } from "../../../../../types/api";
import {
createFlowComponent,
@ -16,6 +15,7 @@ import {
} from "../../../../../utils/reactflowUtils";
import { removeCountFromString } from "../../../../../utils/utils";
import { useDarkStore } from "../../../../../stores/darkStore";
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
export default function SidebarDraggableComponent({
sectionName,
@ -37,7 +37,9 @@ export default function SidebarDraggableComponent({
official: boolean;
}) {
const [open, setOpen] = useState(false);
const { deleteComponent } = useContext(FlowsContext);
const deleteComponent = useFlowsManagerStore(
(state) => state.deleteComponent
);
const version = useDarkStore((state) => state.version);
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
const popoverRef = useRef<HTMLDivElement>(null);

View file

@ -8,7 +8,6 @@ import {
SelectItem,
SelectTrigger,
} from "../../../../components/ui/select-custom";
import { FlowsContext } from "../../../../contexts/flowsContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import ConfirmationModal from "../../../../modals/ConfirmationModal";
import EditNodeModal from "../../../../modals/EditNodeModal";
@ -74,7 +73,7 @@ export default function NodeToolbarComponent({
const setNodes = useFlowStore((state) => state.setNodes);
const setEdges = useFlowStore((state) => state.setEdges);
const { saveComponent } = useContext(FlowsContext);
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
const flows = useFlowsManagerStore((state) => state.flows);
const version = useDarkStore((state) => state.version);
const { takeSnapshot } = useContext(undoRedoContext);

View file

@ -6,7 +6,6 @@ import CardsWrapComponent from "../../../../components/cardsWrapComponent";
import IconComponent from "../../../../components/genericIconComponent";
import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent";
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";
@ -16,8 +15,9 @@ export default function ComponentsComponent({
}: {
is_component?: boolean;
}) {
const { removeFlow, uploadFlow, addFlow } =
useContext(FlowsContext);
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const removeFlow = useFlowsManagerStore((state) => state.removeFlow);
const isLoading = useFlowsManagerStore((state) => state.isLoading);
const flows = useFlowsManagerStore((state) => state.flows);
const setSuccessData = useAlertStore((state) => state.setSuccessData);

View file

@ -7,13 +7,12 @@ import PageLayout from "../../components/pageLayout";
import SidebarNav from "../../components/sidebarComponent";
import { Button } from "../../components/ui/button";
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 { addFlow, uploadFlow } =
useContext(FlowsContext);
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
);

View file

@ -1,18 +1,27 @@
import { AxiosError } from "axios";
import { Edge, Node, Viewport } from "reactflow";
import { Edge, Node, Viewport, XYPosition } from "reactflow";
import { create } from "zustand";
import {
deleteFlowFromDatabase,
readFlowsFromDatabase,
saveFlowToDatabase,
updateFlowInDatabase,
uploadFlowsToDatabase,
} from "../controllers/API";
import { FlowType } from "../types/flow";
import { FlowType, NodeDataType } from "../types/flow";
import { FlowState } from "../types/tabs";
import { FlowsManagerStoreType } from "../types/zustand/flowsManager";
import { processFlows } from "../utils/reactflowUtils";
import {
addVersionToDuplicates,
createFlowComponent,
createNewFlow,
processDataFromFlow,
processFlows,
} from "../utils/reactflowUtils";
import useAlertStore from "./alertStore";
import useFlowStore from "./flowStore";
import { useTypesStore } from "./typesStore";
import { useDarkStore } from "./darkStore";
let saveTimeoutId: NodeJS.Timeout | null = null;
@ -154,6 +163,173 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
input.click();
});
},
addFlow: async (
newProject: Boolean,
flow?: FlowType,
override?: boolean,
position?: XYPosition
): Promise<string | undefined> => {
if (newProject) {
let flowData = flow
? processDataFromFlow(flow)
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
// Create a new flow with a default name if no flow is provided.
if (override) {
get().deleteComponent(flow!.name);
const newFlow = createNewFlow(flowData!, flow!);
const { id } = await saveFlowToDatabase(newFlow);
newFlow.id = id;
//setTimeout to prevent update state with wrong state
setTimeout(() => {
const { data, flows } = processFlows([newFlow, ...get().flows]);
get().setFlows(flows);
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
}, 200);
// addFlowToLocalState(newFlow);
return;
}
const newFlow = createNewFlow(flowData!, flow!);
const newName = addVersionToDuplicates(newFlow, get().flows);
newFlow.name = newName;
try {
const { id } = await saveFlowToDatabase(newFlow);
// Change the id to the new id.
newFlow.id = id;
// Add the new flow to the list of flows.
const { data, flows } = processFlows([newFlow, ...get().flows]);
get().setFlows(flows);
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
// Return the id
return id;
} catch (error) {
// Handle the error if needed
throw error; // Re-throw the error so the caller can handle it if needed
}
} else {
useFlowStore
.getState()
.paste(
{ nodes: flow!.data!.nodes, edges: flow!.data!.edges },
position ?? { x: 10, y: 10 }
);
}
},
removeFlow: async (id: string) => {
return new Promise<void>((resolve) => {
const index = get().flows.findIndex((flow) => flow.id === id);
if (index >= 0) {
deleteFlowFromDatabase(id).then(() => {
const { data, flows } = processFlows(
get().flows.filter((flow) => flow.id !== id)
);
get().setFlows(flows);
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
resolve();
});
}
});
},
deleteComponent: async (key: string) => {
return new Promise<void>((resolve) => {
let componentFlow = get().flows.find(
(componentFlow) =>
componentFlow.is_component && componentFlow.name === key
);
if (componentFlow) {
get()
.removeFlow(componentFlow.id)
.then(() => {
resolve();
});
}
});
},
uploadFlow: async ({
newProject,
file,
isComponent = false,
position = { x: 10, y: 10 },
}: {
newProject: boolean;
file?: File;
isComponent?: boolean;
position?: XYPosition;
}): Promise<string | never> => {
return new Promise(async (resolve, reject) => {
let id;
if (file) {
let text = await file.text();
let fileData = JSON.parse(text);
if (
newProject &&
((!fileData.is_component && isComponent === true) ||
(fileData.is_component !== undefined &&
fileData.is_component !== isComponent))
) {
reject("You cannot upload a component as a flow or vice versa");
} else {
if (fileData.flows) {
fileData.flows.forEach((flow: FlowType) => {
id = get().addFlow(newProject, flow, undefined, position);
});
resolve("");
} else {
id = await get().addFlow(newProject, fileData, undefined, position);
resolve(id);
}
}
} else {
// create a file input
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
// add a change event listener to the file input
input.onchange = async (e: Event) => {
if (
(e.target as HTMLInputElement).files![0].type === "application/json"
) {
const currentfile = (e.target as HTMLInputElement).files![0];
let text = await currentfile.text();
let fileData: FlowType = await JSON.parse(text);
if (
(!fileData.is_component && isComponent === true) ||
(fileData.is_component !== undefined &&
fileData.is_component !== isComponent)
) {
reject("You cannot upload a component as a flow or vice versa");
} else {
id = await get().addFlow(newProject, fileData);
resolve(id);
}
}
};
// trigger the file input click event to open the file dialog
input.click();
}
});
},
saveComponent: (component: NodeDataType, override: boolean) => {
component.node!.official = false;
return get().addFlow(true, createFlowComponent(component, useDarkStore.getState().version), override);
},
}));
export default useFlowsManagerStore;

View file

@ -1,4 +1,4 @@
import { Node, Edge, Viewport } from "reactflow";
import { Node, Edge, Viewport, XYPosition } from "reactflow";
import { FlowType } from "../../flow";
import { FlowState, FlowsState } from "../../tabs";
@ -17,4 +17,9 @@ export type FlowsManagerStoreType = {
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void>;
autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => void;
uploadFlows: () => Promise<void>;
uploadFlow: ({newProject, file, isComponent, position}: {newProject: boolean, file?: File, isComponent?: boolean, position?: XYPosition}) => Promise<string | never>;
addFlow: (newProject: boolean, flow?: FlowType, override?: boolean, position?: XYPosition) => Promise<string | undefined>;
deleteComponent: (key: string) => Promise<void>;
removeFlow: (id: string) => Promise<void>;
saveComponent: (component: any, override: boolean) => Promise<string | undefined>;
};

View file

@ -12,6 +12,7 @@ import {
LANGFLOW_SUPPORTED_TYPES,
specialCharsRegex,
} from "../constants/constants";
import { downloadFlowsFromDatabase } from "../controllers/API";
import {
APIClassType,
APIKindType,
@ -32,8 +33,13 @@ import {
unselectAllNodesType,
updateEdgesHandleIdsType,
} from "../types/utils/reactflowUtils";
import { createRandomKey, getFieldTitle, toTitleCase } from "./utils";
import { downloadFlowsFromDatabase } from "../controllers/API";
import {
createRandomKey,
getFieldTitle,
getRandomDescription,
getRandomName,
toTitleCase,
} from "./utils";
const uid = new ShortUniqueId({ length: 5 });
export function cleanEdges(nodes: Node[], edges: Edge[]) {
@ -189,6 +195,7 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
// updateNodes(data.nodes, data.edges);
if (refreshIds) updateIds(data); // Assuming updateIds is defined elsewhere
}
return data;
};
export function updateIds(newFlow: ReactFlowJsonObject) {
@ -1226,11 +1233,7 @@ export function downloadFlow(
// create a link element and set its properties
const link = document.createElement("a");
link.href = jsonString;
link.download = `${
flowName && flowName != ""
? flowName
: flow.name
}.json`;
link.download = `${flowName && flowName != "" ? flowName : flow.name}.json`;
// simulate a click on the link element to trigger the download
link.click();
@ -1250,4 +1253,17 @@ export function downloadFlows() {
// simulate a click on the link element to trigger the download
link.click();
});
}
}
export const createNewFlow = (
flowData: ReactFlowJsonObject,
flow: FlowType
) => {
return {
description: flow?.description ?? getRandomDescription(),
name: flow?.name ?? getRandomName(),
data: flowData,
id: "",
is_component: flow?.is_component ?? false,
};
};