Passed the rest of the flowsContext functions to zustand, removed flowsContext
This commit is contained in:
parent
616a4f0031
commit
882f365ff6
13 changed files with 225 additions and 534 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue