refactor: add flow and upload flow functionality (#3200)
* Removed unused function and fixed tags initial value * Added post save flow * Added update flow patch * removed unused import * created useAddFlow hook to substitute AddFlow function on flowsManager store * fixed post save flow to handle endpoint name as undefined * Fixed add flow hook to use post save flow mutation * removed unused line * changed addFlow to use hook in all components that use addFlow * Removed unused code * removed addFlow of useDuplicateFlows call * made newProject default as true * removed unused variables from addFlow * fixed url of requests of flows * passed functions directly * fix app to display loading on top of the router * fixed promise of addFlow * Added upload flow hook with a lot of modularity * Fixed addFlow naming * Added helper functions for file uploading * Changed upload flow to use helper functions * removed refresh on post * changed paste function to handle when chatinput node exists on paste * Used helper function to create input on FileInput * Used helper function to create input on InputFileComponent * Used helper function to create input on folder upload, and used uploadFlow hook * used uploadFlow hook on dropdown options * used addFlow instead of addComponent on node toolbar * changed upload flow on headerComponent to use hook * Changed pageComponent to use uploadFlow hook * removed useFileDrop dependency * Fixed onFileDrop to use uploadFlow * removed useDropdown dependency * removed unused add and upload functions from flowsManagerStore * Clean flows and refetch when flow change, added loader when is fetching * Changed loading to the useQuery isPending * changed post to add flow * fixed error when uploading other thing that is not a JSON not appearing * changed useAddFlow to handle empty params too * Fixed loading every time we switch tabs * Fixed unnecessary list and ! * Fixed reference bug * Inserted cloneDeep to prevent reference bugs * Fixed tests of drag and drop * Fixed flows not being refreshed when uploading * [autofix.ci] apply automated fixes * fixed folders test --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
dedad1e240
commit
4d66bf351d
34 changed files with 543 additions and 569 deletions
|
|
@ -23,12 +23,12 @@ import { useGetVersionQuery } from "./controllers/API/queries/version";
|
|||
import { setupAxiosDefaults } from "./controllers/API/utils";
|
||||
import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path";
|
||||
import Router from "./routes";
|
||||
import { Case } from "./shared/components/caseComponent";
|
||||
import useAlertStore from "./stores/alertStore";
|
||||
import useAuthStore from "./stores/authStore";
|
||||
import { useDarkStore } from "./stores/darkStore";
|
||||
import useFlowsManagerStore from "./stores/flowsManagerStore";
|
||||
import { useFolderStore } from "./stores/foldersStore";
|
||||
import { cn } from "./utils/utils";
|
||||
|
||||
export default function App() {
|
||||
useTrackLastVisitedPath();
|
||||
|
|
@ -164,15 +164,16 @@ export default function App() {
|
|||
></FetchErrorComponent>
|
||||
}
|
||||
|
||||
<Case condition={isLoadingApplication}>
|
||||
<div className="loading-page-panel">
|
||||
<LoadingComponent remSize={50} />
|
||||
</div>
|
||||
</Case>
|
||||
<div
|
||||
className={cn(
|
||||
"loading-page-panel absolute left-0 top-0 z-[999]",
|
||||
isLoadingApplication ? "" : "hidden",
|
||||
)}
|
||||
>
|
||||
<LoadingComponent remSize={50} />
|
||||
</div>
|
||||
|
||||
<Case condition={!isLoadingApplication}>
|
||||
<Router />
|
||||
</Case>
|
||||
<Router />
|
||||
</>
|
||||
</ErrorBoundary>
|
||||
<div></div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useState } from "react";
|
||||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||
import { getComponent } from "../../../controllers/API";
|
||||
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
|
||||
import { storeComponent } from "../../../types/store";
|
||||
import cloneFlowWithParent from "../../../utils/storeUtils";
|
||||
|
||||
|
|
@ -14,7 +13,7 @@ const useInstallComponent = (
|
|||
setSuccessData: (value: { title: string }) => void,
|
||||
setErrorData: (value: { title: string; list: string[] }) => void,
|
||||
) => {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const addFlow = useAddFlow();
|
||||
|
||||
const handleInstall = () => {
|
||||
const temp = downloadsCount;
|
||||
|
|
@ -24,7 +23,7 @@ const useInstallComponent = (
|
|||
getComponent(data.id)
|
||||
.then((res) => {
|
||||
const newFlow = cloneFlowWithParent(res, res.id, data.is_component);
|
||||
addFlow(true, newFlow)
|
||||
addFlow({ flow: newFlow })
|
||||
.then((id) => {
|
||||
setSuccessData({
|
||||
title: `${name} ${isStore ? "Downloaded" : "Installed"} Successfully.`,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||
import emojiRegex from "emoji-regex";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { FlowType } from "../../types/flow";
|
||||
import { updateIds } from "../../utils/reactflowUtils";
|
||||
import { cn } from "../../utils/utils";
|
||||
|
|
@ -20,10 +21,9 @@ export default function CollectionCardComponent({
|
|||
flow: FlowType;
|
||||
authorized?: boolean;
|
||||
}) {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const addFlow = useAddFlow();
|
||||
const navigate = useNavigate();
|
||||
const emojiRegex = /\p{Emoji}/u;
|
||||
const isEmoji = (str: string) => emojiRegex.test(str);
|
||||
const isEmoji = (str: string) => emojiRegex().test(str);
|
||||
|
||||
return (
|
||||
<Card
|
||||
|
|
@ -77,7 +77,7 @@ export default function CollectionCardComponent({
|
|||
<Button
|
||||
onClick={() => {
|
||||
updateIds(flow.data!);
|
||||
addFlow(true, flow).then((id) => {
|
||||
addFlow({ flow }).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from "../../../ui/dropdown-menu";
|
||||
|
||||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
||||
import { SAVED_HOVER } from "../../../../constants/constants";
|
||||
|
|
@ -26,7 +28,7 @@ import { Button } from "../../../ui/button";
|
|||
|
||||
export const MenuBar = ({}: {}): JSX.Element => {
|
||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const addFlow = useAddFlow();
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
|
|
@ -40,14 +42,14 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
|
||||
const [openSettings, setOpenSettings] = useState(false);
|
||||
const [openLogs, setOpenLogs] = useState(false);
|
||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||
const uploadFlow = useUploadFlow();
|
||||
const navigate = useNavigate();
|
||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||
const getTypes = useTypesStore((state) => state.getTypes);
|
||||
|
||||
function handleAddFlow() {
|
||||
try {
|
||||
addFlow(true).then((id) => {
|
||||
addFlow().then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
} catch (err) {
|
||||
|
|
@ -125,14 +127,18 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
uploadFlow({ newProject: false, isComponent: false }).catch(
|
||||
(error) => {
|
||||
uploadFlow({ position: { x: 300, y: 100 } })
|
||||
.then(() => {
|
||||
setSuccessData({
|
||||
title: "Uploaded successfully",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: UPLOAD_ERROR_ALERT,
|
||||
list: [error],
|
||||
list: [(error as Error).message],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconComponent name="FileUp" className="header-menu-options" />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
|
||||
import { useEffect, useState } from "react";
|
||||
import { createFileUpload } from "@/helpers/create-file-upload";
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
CONSOLE_ERROR_MSG,
|
||||
INVALID_FILE_ALERT,
|
||||
|
|
@ -19,7 +20,6 @@ export default function InputFileComponent({
|
|||
id,
|
||||
}: FileComponentType): JSX.Element {
|
||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
// Clear component state
|
||||
|
|
@ -39,58 +39,42 @@ export default function InputFileComponent({
|
|||
return false;
|
||||
}
|
||||
|
||||
const { mutate } = usePostUploadFile();
|
||||
const { mutate, isPending } = usePostUploadFile();
|
||||
|
||||
const handleButtonClick = (): void => {
|
||||
// Create a file input element
|
||||
const input = document.createElement("input");
|
||||
document.body.appendChild(input);
|
||||
input.type = "file";
|
||||
input.accept = fileTypes?.join(",");
|
||||
input.style.display = "none"; // Hidden from view
|
||||
input.multiple = false; // Allow only one file selection
|
||||
const onChangeFile = (event: Event): void => {
|
||||
setLoading(true);
|
||||
createFileUpload({ multiple: false, accept: fileTypes?.join(",") }).then(
|
||||
(files) => {
|
||||
const file = files[0];
|
||||
if (file) {
|
||||
if (checkFileType(file.name)) {
|
||||
// Upload the file
|
||||
mutate(
|
||||
{ file, id: currentFlowId },
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
// Get the file name from the response
|
||||
const { file_path } = data;
|
||||
|
||||
// Get the selected file
|
||||
const file = (event.target as HTMLInputElement).files?.[0];
|
||||
|
||||
// Check if the file type is correct
|
||||
if (file && checkFileType(file.name)) {
|
||||
// Upload the file
|
||||
mutate(
|
||||
{ file, id: currentFlowId },
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
// Get the file name from the response
|
||||
const { file_path } = data;
|
||||
|
||||
// sets the value that goes to the backend
|
||||
// Update the state and on with the name of the file
|
||||
// sets the value to the user
|
||||
handleOnNewValue({ value: file.name, file_path });
|
||||
setLoading(false);
|
||||
},
|
||||
onError: () => {
|
||||
console.error(CONSOLE_ERROR_MSG);
|
||||
setLoading(false);
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// Show an error if the file type is not allowed
|
||||
setErrorData({
|
||||
title: INVALID_FILE_ALERT,
|
||||
list: fileTypes,
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
input.addEventListener("change", onChangeFile);
|
||||
|
||||
// Trigger the file selection dialog
|
||||
input.click();
|
||||
// sets the value that goes to the backend
|
||||
// Update the state and on with the name of the file
|
||||
// sets the value to the user
|
||||
handleOnNewValue({ value: file.name, file_path });
|
||||
},
|
||||
onError: () => {
|
||||
console.error(CONSOLE_ERROR_MSG);
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// Show an error if the file type is not allowed
|
||||
setErrorData({
|
||||
title: INVALID_FILE_ALERT,
|
||||
list: fileTypes,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -114,7 +98,7 @@ export default function InputFileComponent({
|
|||
unstyled
|
||||
className="inline-flex items-center justify-center"
|
||||
onClick={handleButtonClick}
|
||||
loading={loading}
|
||||
loading={isPending}
|
||||
disabled={disabled}
|
||||
>
|
||||
<IconComponent
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import {
|
|||
usePostUploadFolders,
|
||||
} from "@/controllers/API/queries/folders";
|
||||
import { useGetDownloadFolders } from "@/controllers/API/queries/folders/use-get-download-folders";
|
||||
import { createFileUpload } from "@/helpers/create-file-upload";
|
||||
import { getObjectsFromFilelist } from "@/helpers/get-objects-from-filelist";
|
||||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { FolderType } from "../../../../pages/MainPage/entities";
|
||||
|
|
@ -55,6 +58,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
||||
const uploadFlow = useUploadFlow();
|
||||
|
||||
const handleFolderChange = () => {
|
||||
getFolderById(folderId);
|
||||
|
|
@ -68,46 +72,41 @@ const SideBarFoldersButtonsComponent = ({
|
|||
const { mutate } = usePostUploadFolders();
|
||||
|
||||
const handleUploadFlowsToFolder = () => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = ".json";
|
||||
input.click();
|
||||
|
||||
input.onchange = (event: Event) => {
|
||||
if (
|
||||
(event.target as HTMLInputElement).files![0].type === "application/json"
|
||||
) {
|
||||
const file = (event.target as HTMLInputElement).files![0];
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
file.text().then(async (text) => {
|
||||
const data = JSON.parse(text);
|
||||
if (data.data?.nodes) {
|
||||
await useFlowsManagerStore.getState().addFlow(true, data);
|
||||
createFileUpload().then((files: File[]) => {
|
||||
getObjectsFromFilelist<any>(files).then((objects) => {
|
||||
if (objects.every((flow) => flow.data?.nodes)) {
|
||||
uploadFlow({ files }).then(() => {
|
||||
getFolderById(folderId);
|
||||
} else {
|
||||
setSuccessData({
|
||||
title: "Uploaded successfully",
|
||||
});
|
||||
});
|
||||
} else {
|
||||
files.forEach((folder) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", folder);
|
||||
mutate(
|
||||
{ formData },
|
||||
{
|
||||
onSuccess: () => {
|
||||
getFolderById(folderId);
|
||||
getFoldersApi(true);
|
||||
setSuccessData({
|
||||
title: "Uploaded successfully",
|
||||
title: "Folder uploaded successfully.",
|
||||
});
|
||||
},
|
||||
onError: (err) => {
|
||||
console.log(err);
|
||||
setErrorData({
|
||||
title: `Error on upload`,
|
||||
list: [err["response"]["data"]],
|
||||
list: [err["response"]["data"]["message"]],
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const { mutate: mutateDownloadFolder } = useGetDownloadFolders();
|
||||
|
|
|
|||
|
|
@ -262,32 +262,6 @@ export async function getFlowStylesFromDatabase() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a new flow style to the database.
|
||||
*
|
||||
* @param {FlowStyleType} flowStyle - The flow style data to save.
|
||||
* @returns {Promise<any>} The saved flow style data.
|
||||
* @throws Will throw an error if saving fails.
|
||||
*/
|
||||
export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
|
||||
try {
|
||||
const response = await api.post(`${BASE_URL_API}flow_styles/`, flowStyle, {
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status !== 201) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response?.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the version of the API.
|
||||
*
|
||||
|
|
@ -508,7 +482,7 @@ export async function getStoreComponents({
|
|||
limit = 9999999,
|
||||
is_component = null,
|
||||
sort = "-count(liked_by)",
|
||||
tags = [] || null,
|
||||
tags = [],
|
||||
liked = null,
|
||||
isPrivate = null,
|
||||
search = null,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ interface IDeleteBuilds {
|
|||
}
|
||||
|
||||
// add types for error handling and success
|
||||
export const useDeleteBuilds: useMutationFunctionType<undefined,IDeleteBuilds> = (
|
||||
options,
|
||||
) => {
|
||||
export const useDeleteBuilds: useMutationFunctionType<
|
||||
undefined,
|
||||
IDeleteBuilds
|
||||
> = (options) => {
|
||||
const { mutate } = UseRequestProcessor();
|
||||
|
||||
const deleteBuildsFn = async (payload: IDeleteBuilds): Promise<any> => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import useFlowStore from "@/stores/flowStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import { FlowPoolType } from "@/types/zustand/flow";
|
||||
import { cleanEdges } from "@/utils/reactflowUtils";
|
||||
import { getInputsAndOutputs } from "@/utils/storeUtils";
|
||||
import { keepPreviousData } from "@tanstack/react-query";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { useQueryFunctionType } from "../../../../types/api";
|
||||
|
|
@ -52,6 +50,7 @@ export const useGetBuildsQuery: useQueryFunctionType<
|
|||
|
||||
const queryResult = query(["useGetBuildsQuery"], responseFn, {
|
||||
placeholderData: keepPreviousData,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
return queryResult;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import { useMutationFunctionType } from "@/types/api";
|
||||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
import { ReactFlowJsonObject } from "reactflow";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface IPatchUpdateFlow {
|
||||
name: string;
|
||||
data: ReactFlowJsonObject;
|
||||
description: string;
|
||||
folder_id: string;
|
||||
endpoint_name: string;
|
||||
}
|
||||
|
||||
interface IPatchUpdateFlowParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const usePatchUpdateFlow: useMutationFunctionType<
|
||||
IPatchUpdateFlowParams,
|
||||
IPatchUpdateFlow
|
||||
> = (params, options?) => {
|
||||
const { mutate } = UseRequestProcessor();
|
||||
|
||||
const PatchUpdateFlowFn = async (payload: IPatchUpdateFlow): Promise<any> => {
|
||||
const response = await api.patch(`${getURL("FLOWS")}/${params}`, {
|
||||
name: payload.name,
|
||||
data: payload.data,
|
||||
description: payload.description,
|
||||
folder_id: payload.folder_id === "" ? null : payload.folder_id,
|
||||
endpoint_name: payload.endpoint_name,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const mutation: UseMutationResult<IPatchUpdateFlow, any, IPatchUpdateFlow> =
|
||||
mutate(
|
||||
["usePatchUpdateFlow", { id: params.id }],
|
||||
PatchUpdateFlowFn,
|
||||
options,
|
||||
);
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { useMutationFunctionType } from "@/types/api";
|
||||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
import { ReactFlowJsonObject } from "reactflow";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface IPostAddFlow {
|
||||
name: string;
|
||||
data: ReactFlowJsonObject;
|
||||
description: string;
|
||||
is_component: boolean;
|
||||
folder_id: string;
|
||||
endpoint_name: string | undefined;
|
||||
}
|
||||
|
||||
export const usePostAddFlow: useMutationFunctionType<
|
||||
undefined,
|
||||
IPostAddFlow
|
||||
> = (options?) => {
|
||||
const { mutate } = UseRequestProcessor();
|
||||
|
||||
const postAddFlowFn = async (payload: IPostAddFlow): Promise<any> => {
|
||||
const response = await api.post(`${getURL("FLOWS")}/`, {
|
||||
name: payload.name,
|
||||
data: payload.data,
|
||||
description: payload.description,
|
||||
is_component: payload.is_component,
|
||||
folder_id: payload.folder_id || null,
|
||||
endpoint_name: payload.endpoint_name || null,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const mutation: UseMutationResult<IPostAddFlow, any, IPostAddFlow> = mutate(
|
||||
["usePostAddFlow"],
|
||||
postAddFlowFn,
|
||||
options,
|
||||
);
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
|
@ -32,10 +32,7 @@ export const usePostDownloadMultipleFlows: useMutationFunctionType<
|
|||
IPostDownloadMultipleFlows
|
||||
> = mutate(
|
||||
["usePostDownloadMultipleFlows"],
|
||||
async (payload: IPostDownloadMultipleFlows) => {
|
||||
const res = await postDownloadMultipleFlowsFn(payload);
|
||||
return res;
|
||||
},
|
||||
postDownloadMultipleFlowsFn,
|
||||
options,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import { useFolderStore } from "@/stores/foldersStore";
|
||||
import { Users, useMutationFunctionType } from "@/types/api";
|
||||
import { useMutationFunctionType } from "@/types/api";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
|
@ -22,7 +20,6 @@ export const usePostUploadFolders: useMutationFunctionType<
|
|||
`${getURL("FOLDERS")}/upload/`,
|
||||
payload.formData,
|
||||
);
|
||||
await useFolderStore.getState().getFoldersApi(true);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
|
|
|
|||
35
src/frontend/src/helpers/create-file-upload.ts
Normal file
35
src/frontend/src/helpers/create-file-upload.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
export async function createFileUpload(props?: {
|
||||
accept?: string;
|
||||
multiple?: boolean;
|
||||
}): Promise<File[]> {
|
||||
let lock = false;
|
||||
return new Promise((resolve) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = props?.accept ?? ".json";
|
||||
input.multiple = props?.multiple ?? true;
|
||||
input.style.display = "none";
|
||||
// add a change event listener to the file input
|
||||
input.onchange = async (e: Event) => {
|
||||
lock = true;
|
||||
resolve(Array.from((e.target as HTMLInputElement).files!));
|
||||
document.body.removeChild(input);
|
||||
};
|
||||
window.addEventListener(
|
||||
"focus",
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
if (!lock) {
|
||||
resolve([]);
|
||||
document.body.removeChild(input);
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
// add the input element to the body to ensure it is part of the DOM
|
||||
document.body.appendChild(input);
|
||||
// trigger the file input click event to open the file dialog
|
||||
input.click();
|
||||
});
|
||||
}
|
||||
9
src/frontend/src/helpers/get-objects-from-filelist.ts
Normal file
9
src/frontend/src/helpers/get-objects-from-filelist.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export async function getObjectsFromFilelist<T>(files: File[]): Promise<T[]> {
|
||||
let objects: T[] = [];
|
||||
for (const file of files) {
|
||||
let text = await file.text();
|
||||
let fileData = await JSON.parse(text);
|
||||
objects.push(fileData as T);
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
106
src/frontend/src/hooks/flows/use-add-flow.ts
Normal file
106
src/frontend/src/hooks/flows/use-add-flow.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { usePostAddFlow } from "@/controllers/API/queries/flows/use-post-add-flow";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import { useFolderStore } from "@/stores/foldersStore";
|
||||
import { useGlobalVariablesStore } from "@/stores/globalVariablesStore/globalVariables";
|
||||
import { useTypesStore } from "@/stores/typesStore";
|
||||
import { FlowType } from "@/types/flow";
|
||||
import {
|
||||
addVersionToDuplicates,
|
||||
createNewFlow,
|
||||
extractFieldsFromComponenents,
|
||||
processDataFromFlow,
|
||||
processFlows,
|
||||
updateGroupRecursion,
|
||||
} from "@/utils/reactflowUtils";
|
||||
import { cloneDeep } from "lodash";
|
||||
|
||||
const useAddFlow = () => {
|
||||
const unavaliableFields = useGlobalVariablesStore(
|
||||
(state) => state.unavailableFields,
|
||||
);
|
||||
const globalVariablesEntries = useGlobalVariablesStore(
|
||||
(state) => state.globalVariablesEntries,
|
||||
);
|
||||
const flows = useFlowsManagerStore((state) => state.flows);
|
||||
const setFlows = useFlowsManagerStore((state) => state.setFlows);
|
||||
const deleteComponent = useFlowsManagerStore(
|
||||
(state) => state.deleteComponent,
|
||||
);
|
||||
const setIsLoading = useFlowsManagerStore((state) => state.setIsLoading);
|
||||
|
||||
const { mutate: postAddFlow } = usePostAddFlow();
|
||||
|
||||
const addFlow = async (params?: { flow?: FlowType; override?: boolean }) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const flow = cloneDeep(params?.flow) ?? undefined;
|
||||
let flowData = flow
|
||||
? processDataFromFlow(flow)
|
||||
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
|
||||
flowData?.nodes.forEach((node) => {
|
||||
updateGroupRecursion(
|
||||
node,
|
||||
flowData?.edges,
|
||||
unavaliableFields,
|
||||
globalVariablesEntries,
|
||||
);
|
||||
});
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
const folder_id = useFolderStore.getState().folderUrl;
|
||||
const my_collection_id = useFolderStore.getState().myCollectionId;
|
||||
|
||||
if (params?.override && flow) {
|
||||
await deleteComponent(flow.name);
|
||||
}
|
||||
const newFlow = createNewFlow(
|
||||
flowData!,
|
||||
folder_id || my_collection_id!,
|
||||
flow,
|
||||
);
|
||||
|
||||
const newName = addVersionToDuplicates(newFlow, flows);
|
||||
newFlow.name = newName;
|
||||
newFlow.folder_id = useFolderStore.getState().folderUrl;
|
||||
|
||||
postAddFlow(newFlow, {
|
||||
onSuccess: ({ id }) => {
|
||||
newFlow.id = id;
|
||||
// Add the new flow to the list of flows.
|
||||
const { data, flows: myFlows } = processFlows([newFlow, ...flows]);
|
||||
setFlows(myFlows);
|
||||
useTypesStore.setState((state) => ({
|
||||
data: { ...state.data, ["saved_components"]: data },
|
||||
ComponentFields: extractFieldsFromComponenents({
|
||||
...state.data,
|
||||
["saved_components"]: data,
|
||||
}),
|
||||
}));
|
||||
setIsLoading(false);
|
||||
resolve(id);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error.response?.data?.detail) {
|
||||
useAlertStore.getState().setErrorData({
|
||||
title: "Could not load flows from database",
|
||||
list: [error.response?.data?.detail],
|
||||
});
|
||||
} else {
|
||||
useAlertStore.getState().setErrorData({
|
||||
title: "Could not load flows from database",
|
||||
list: [
|
||||
error.message ??
|
||||
"An unexpected error occurred, please try again",
|
||||
],
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
reject(error); // Re-throw the error so the caller can handle it if needed},
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return addFlow;
|
||||
};
|
||||
|
||||
export default useAddFlow;
|
||||
98
src/frontend/src/hooks/flows/use-upload-flow.ts
Normal file
98
src/frontend/src/hooks/flows/use-upload-flow.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { createFileUpload } from "@/helpers/create-file-upload";
|
||||
import { getObjectsFromFilelist } from "@/helpers/get-objects-from-filelist";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import { FlowType } from "@/types/flow";
|
||||
import useAddFlow from "./use-add-flow";
|
||||
|
||||
const useUploadFlow = () => {
|
||||
const addFlow = useAddFlow();
|
||||
const paste = useFlowStore((state) => state.paste);
|
||||
const refreshFlows = useFlowsManagerStore((state) => state.refreshFlows);
|
||||
|
||||
const getFlowsFromFiles = async ({
|
||||
files,
|
||||
}: {
|
||||
files: File[];
|
||||
}): Promise<FlowType[]> => {
|
||||
const objectList = await getObjectsFromFilelist<any>(files);
|
||||
const flows: FlowType[] = [];
|
||||
objectList.forEach((object) => {
|
||||
if (object.flows) {
|
||||
object.flows.forEach((flow: FlowType) => {
|
||||
flows.push(flow);
|
||||
});
|
||||
} else {
|
||||
flows.push(object as FlowType);
|
||||
}
|
||||
});
|
||||
return flows;
|
||||
};
|
||||
|
||||
const getFlowsToUpload = async ({
|
||||
files,
|
||||
}: {
|
||||
files?: File[];
|
||||
}): Promise<FlowType[]> => {
|
||||
if (!files) {
|
||||
files = await createFileUpload();
|
||||
}
|
||||
if (!files.every((file) => file.type === "application/json")) {
|
||||
throw new Error("Invalid file type");
|
||||
}
|
||||
return await getFlowsFromFiles({
|
||||
files,
|
||||
});
|
||||
};
|
||||
|
||||
const uploadFlow = async ({
|
||||
files,
|
||||
isComponent,
|
||||
position,
|
||||
}: {
|
||||
files?: File[];
|
||||
isComponent?: boolean;
|
||||
position?: { x: number; y: number };
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
let flows = await getFlowsToUpload({ files });
|
||||
if (
|
||||
isComponent !== undefined &&
|
||||
flows.every(
|
||||
(fileData) =>
|
||||
(!fileData.is_component && isComponent === true) ||
|
||||
(fileData.is_component !== undefined &&
|
||||
fileData.is_component !== isComponent),
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
"You cannot upload a component as a flow or vice versa",
|
||||
);
|
||||
} else {
|
||||
let currentPosition = position;
|
||||
for (const flow of flows) {
|
||||
if (flow.data) {
|
||||
if (currentPosition) {
|
||||
paste(flow.data, currentPosition);
|
||||
currentPosition = {
|
||||
x: currentPosition.x + 50,
|
||||
y: currentPosition.y + 50,
|
||||
};
|
||||
} else {
|
||||
await addFlow({ flow });
|
||||
}
|
||||
} else {
|
||||
throw new Error("Invalid flow data");
|
||||
}
|
||||
}
|
||||
refreshFlows();
|
||||
}
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
return uploadFlow;
|
||||
};
|
||||
|
||||
export default useUploadFlow;
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
import { Button } from "../../../../../../components/ui/button";
|
||||
|
||||
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
|
||||
import { createFileUpload } from "@/helpers/create-file-upload";
|
||||
import { useEffect, useState } from "react";
|
||||
import IconComponent from "../../../../../../components/genericIconComponent";
|
||||
import { BASE_URL_API } from "../../../../../../constants/constants";
|
||||
import { uploadFile } from "../../../../../../controllers/API";
|
||||
import {
|
||||
ALLOWED_IMAGE_INPUT_EXTENSIONS,
|
||||
BASE_URL_API,
|
||||
} from "../../../../../../constants/constants";
|
||||
import useFlowsManagerStore from "../../../../../../stores/flowsManagerStore";
|
||||
import { IOFileInputProps } from "../../../../../../types/components";
|
||||
|
||||
|
|
@ -99,18 +102,11 @@ export default function IOFileInput({ field, updateValue }: IOFileInputProps) {
|
|||
};
|
||||
|
||||
const handleButtonClick = (): void => {
|
||||
createFileUpload({
|
||||
multiple: false,
|
||||
accept: ALLOWED_IMAGE_INPUT_EXTENSIONS.join(","),
|
||||
}).then((files) => upload(files[0]));
|
||||
// Create a file input element
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.style.display = "none"; // Hidden from view
|
||||
input.multiple = false; // Allow only one file selection
|
||||
input.onchange = (event: Event): void => {
|
||||
// Get the selected file
|
||||
const file = (event.target as HTMLInputElement).files?.[0];
|
||||
upload(file);
|
||||
};
|
||||
// Trigger the file selection dialog
|
||||
input.click();
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Card,
|
||||
|
|
@ -5,11 +6,10 @@ import {
|
|||
CardDescription,
|
||||
CardTitle,
|
||||
} from "../../../../components/ui/card";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { useFolderStore } from "../../../../stores/foldersStore";
|
||||
|
||||
export default function NewFlowCardComponent() {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const addFlow = useAddFlow();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const folderId = location?.state?.folderId;
|
||||
|
|
@ -18,7 +18,7 @@ export default function NewFlowCardComponent() {
|
|||
return (
|
||||
<Card
|
||||
onClick={() => {
|
||||
addFlow(true).then((id) => {
|
||||
addFlow().then((id) => {
|
||||
setFolderUrl(folderId ?? "");
|
||||
navigate(`/flow/${id}${folderId ? `/folder/${folderId}` : ""}`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ import APIRequest from "../../../../assets/undraw_real_time_analytics_re_yliv.sv
|
|||
import BasicPrompt from "../../../../assets/undraw_short_bio_re_fmx0.svg?react";
|
||||
import TransferFiles from "../../../../assets/undraw_transfer_files_re_a2a9.svg?react";
|
||||
|
||||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardTitle,
|
||||
} from "../../../../components/ui/card";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { useFolderStore } from "../../../../stores/foldersStore";
|
||||
import { UndrawCardComponentProps } from "../../../../types/components";
|
||||
import { updateIds } from "../../../../utils/reactflowUtils";
|
||||
|
|
@ -24,7 +24,7 @@ import { updateIds } from "../../../../utils/reactflowUtils";
|
|||
export default function UndrawCardComponent({
|
||||
flow,
|
||||
}: UndrawCardComponentProps): JSX.Element {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const addFlow = useAddFlow();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const folderId = location?.state?.folderId;
|
||||
|
|
@ -142,7 +142,7 @@ export default function UndrawCardComponent({
|
|||
<Card
|
||||
onClick={() => {
|
||||
updateIds(flow.data!);
|
||||
addFlow(true, flow).then((id) => {
|
||||
addFlow({ flow }).then((id) => {
|
||||
setFolderUrl(folderId ?? "");
|
||||
navigate(`/flow/${id}/folder/${folderIdUrl}`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import LoadingComponent from "@/components/loadingComponent";
|
||||
import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
|
||||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||
import { getInputsAndOutputs } from "@/utils/storeUtils";
|
||||
import _, { cloneDeep } from "lodash";
|
||||
import {
|
||||
|
|
@ -62,7 +64,7 @@ export default function Page({
|
|||
flow: FlowType;
|
||||
view?: boolean;
|
||||
}): JSX.Element {
|
||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||
const uploadFlow = useUploadFlow();
|
||||
const autoSaveCurrentFlow = useFlowsManagerStore(
|
||||
(state) => state.autoSaveCurrentFlow,
|
||||
);
|
||||
|
|
@ -70,9 +72,6 @@ export default function Page({
|
|||
const templates = useTypesStore((state) => state.templates);
|
||||
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
|
||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||
const [showCanvas, setSHowCanvas] = useState(
|
||||
Object.keys(templates).length > 0 && Object.keys(types).length > 0,
|
||||
);
|
||||
|
||||
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
|
||||
const setReactFlowInstance = useFlowStore(
|
||||
|
|
@ -166,7 +165,12 @@ export default function Page({
|
|||
}
|
||||
}, [currentFlowId, reactFlowInstance]);
|
||||
|
||||
const { isFetching } = useGetBuildsQuery({});
|
||||
const { isFetching, refetch } = useGetBuildsQuery({});
|
||||
|
||||
const showCanvas =
|
||||
Object.keys(templates).length > 0 &&
|
||||
Object.keys(types).length > 0 &&
|
||||
!isFetching;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFetching) {
|
||||
|
|
@ -187,13 +191,14 @@ export default function Page({
|
|||
"Components created before Langflow 1.0 may be unstable. Ensure components are up to date.",
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
}, [currentFlowId]);
|
||||
|
||||
useEffect(() => {
|
||||
refetch();
|
||||
return () => {
|
||||
cleanFlow();
|
||||
};
|
||||
}, []);
|
||||
}, [currentFlowId]);
|
||||
|
||||
function handleUndo(e: KeyboardEvent) {
|
||||
if (!isWrappedWithClass(e, "noflow")) {
|
||||
|
|
@ -315,12 +320,6 @@ export default function Page({
|
|||
//@ts-ignore
|
||||
useHotkeys("delete", handleDelete);
|
||||
|
||||
useEffect(() => {
|
||||
setSHowCanvas(
|
||||
Object.keys(templates).length > 0 && Object.keys(types).length > 0,
|
||||
);
|
||||
}, [templates, types]);
|
||||
|
||||
const onConnectMod = useCallback(
|
||||
(params: Connection) => {
|
||||
takeSnapshot();
|
||||
|
|
@ -387,28 +386,24 @@ export default function Page({
|
|||
);
|
||||
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
|
||||
takeSnapshot();
|
||||
if (event.dataTransfer.files.item(0)!.type === "application/json") {
|
||||
const position = {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
};
|
||||
uploadFlow({
|
||||
newProject: false,
|
||||
isComponent: false,
|
||||
file: event.dataTransfer.files.item(0)!,
|
||||
position: position,
|
||||
}).catch((error) => {
|
||||
setErrorData({
|
||||
title: UPLOAD_ERROR_ALERT,
|
||||
list: [error],
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const position = {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
};
|
||||
uploadFlow({
|
||||
files: Array.from(event.dataTransfer.files!),
|
||||
position: position,
|
||||
}).catch((error) => {
|
||||
setErrorData({
|
||||
title: WRONG_FILE_ERROR_ALERT,
|
||||
list: [UPLOAD_ALERT_LIST],
|
||||
title: UPLOAD_ERROR_ALERT,
|
||||
list: [(error as Error).message],
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setErrorData({
|
||||
title: WRONG_FILE_ERROR_ALERT,
|
||||
list: [UPLOAD_ALERT_LIST],
|
||||
});
|
||||
}
|
||||
},
|
||||
// Specify dependencies for useCallback
|
||||
|
|
@ -533,7 +528,9 @@ export default function Page({
|
|||
</ReactFlow>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoadingComponent remSize={30} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value";
|
||||
import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class";
|
||||
import { usePostRetrieveVertexOrder } from "@/controllers/API/queries/vertex";
|
||||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||
import { APIClassType } from "@/types/api";
|
||||
import _, { cloneDeep } from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
|
|
@ -82,6 +83,8 @@ export default function NodeToolbarComponent({
|
|||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||
const unselectAll = useFlowStore((state) => state.unselectAll);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
const addFlow = useAddFlow();
|
||||
|
||||
function handleMinimizeWShortcut(e: KeyboardEvent) {
|
||||
if (isWrappedWithClass(e, "noflow")) return;
|
||||
e.preventDefault();
|
||||
|
|
@ -134,7 +137,10 @@ export default function NodeToolbarComponent({
|
|||
return;
|
||||
}
|
||||
if (hasCode && !isSaved) {
|
||||
saveComponent(cloneDeep(data), false);
|
||||
addFlow({
|
||||
flow: flowComponent,
|
||||
override: false,
|
||||
});
|
||||
setSuccessData({ title: `${data.id} saved successfully` });
|
||||
return;
|
||||
}
|
||||
|
|
@ -208,7 +214,6 @@ export default function NodeToolbarComponent({
|
|||
const setNodes = useFlowStore((state) => state.setNodes);
|
||||
|
||||
const setEdges = useFlowStore((state) => state.setEdges);
|
||||
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
|
||||
const getNodePosition = useFlowStore((state) => state.getNodePosition);
|
||||
const flows = useFlowsManagerStore((state) => state.flows);
|
||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||
|
|
@ -257,7 +262,10 @@ export default function NodeToolbarComponent({
|
|||
if (isSaved) {
|
||||
return setShowOverrideModal(true);
|
||||
}
|
||||
saveComponent(cloneDeep(data), false);
|
||||
addFlow({
|
||||
flow: flowComponent,
|
||||
override: false,
|
||||
});
|
||||
break;
|
||||
case "freeze":
|
||||
setNode(data.id, (old) => ({
|
||||
|
|
@ -291,7 +299,10 @@ export default function NodeToolbarComponent({
|
|||
downloadNode(flowComponent!);
|
||||
break;
|
||||
case "SaveAll":
|
||||
saveComponent(cloneDeep(data), false);
|
||||
addFlow({
|
||||
flow: flowComponent,
|
||||
override: false,
|
||||
});
|
||||
break;
|
||||
case "documentation":
|
||||
if (data.node?.documentation) openInNewTab(data.node?.documentation);
|
||||
|
|
@ -695,12 +706,18 @@ export default function NodeToolbarComponent({
|
|||
icon={"SaveAll"}
|
||||
index={6}
|
||||
onConfirm={(index, user) => {
|
||||
saveComponent(cloneDeep(data), true);
|
||||
addFlow({
|
||||
flow: flowComponent,
|
||||
override: true,
|
||||
});
|
||||
setSuccessData({ title: `${data.id} successfully overridden!` });
|
||||
}}
|
||||
onClose={setShowOverrideModal}
|
||||
onCancel={() => {
|
||||
saveComponent(cloneDeep(data), false);
|
||||
addFlow({
|
||||
flow: flowComponent,
|
||||
override: true,
|
||||
});
|
||||
setSuccessData({ title: "New component successfully saved!" });
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,8 @@
|
|||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||
import { useCallback } from "react";
|
||||
import { XYPosition } from "reactflow";
|
||||
import { FlowType } from "../../../../../types/flow";
|
||||
|
||||
const useDuplicateFlows = (
|
||||
selectedFlowsComponentsCards: string[],
|
||||
addFlow: (
|
||||
newProject: boolean,
|
||||
flow?: FlowType,
|
||||
override?: boolean,
|
||||
position?: XYPosition,
|
||||
fromDragAndDrop?: boolean,
|
||||
) => Promise<string | undefined>,
|
||||
allFlows: any[],
|
||||
resetFilter: () => void,
|
||||
getFoldersApi: (
|
||||
|
|
@ -27,13 +19,11 @@ const useDuplicateFlows = (
|
|||
handleSelectAll: (select: boolean) => void,
|
||||
cardTypes: string,
|
||||
) => {
|
||||
const addFlow = useAddFlow();
|
||||
const handleDuplicate = useCallback(() => {
|
||||
Promise.all(
|
||||
selectedFlowsComponentsCards.map((selectedFlow) =>
|
||||
addFlow(
|
||||
true,
|
||||
allFlows.find((flow) => flow.id === selectedFlow),
|
||||
),
|
||||
addFlow({ flow: allFlows.find((flow) => flow.id === selectedFlow) }),
|
||||
),
|
||||
).then(() => {
|
||||
resetFilter();
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import { sortFlows } from "../../utils/sort-flows";
|
|||
import EmptyComponent from "../emptyComponent";
|
||||
import HeaderComponent from "../headerComponent";
|
||||
import CollectionCard from "./components/collectionCard";
|
||||
import useDeleteMultipleFlows from "./hooks/use-delete-multiple";
|
||||
import useDescriptionModal from "./hooks/use-description-modal";
|
||||
import useFilteredFlows from "./hooks/use-filtered-flows";
|
||||
import useDuplicateFlows from "./hooks/use-handle-duplicate";
|
||||
|
|
@ -30,8 +29,6 @@ export default function ComponentsComponent({
|
|||
}: {
|
||||
type?: string;
|
||||
}) {
|
||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||
const removeFlow = useFlowsManagerStore((state) => state.removeFlow);
|
||||
const isLoading = useFlowsManagerStore((state) => state.isLoading);
|
||||
const setAllFlows = useFlowsManagerStore((state) => state.setAllFlows);
|
||||
const allFlows = useFlowsManagerStore((state) => state.allFlows);
|
||||
|
|
@ -55,7 +52,7 @@ export default function ComponentsComponent({
|
|||
(state) => state.selectedFlowsComponentsCards,
|
||||
);
|
||||
|
||||
const [handleFileDrop] = useFileDrop(uploadFlow, type)!;
|
||||
const handleFileDrop = useFileDrop(type);
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
const [pageIndex, setPageIndex] = useState(1);
|
||||
const location = useLocation();
|
||||
|
|
@ -71,7 +68,6 @@ export default function ComponentsComponent({
|
|||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
||||
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
||||
const setFolderUrl = useFolderStore((state) => state.setFolderUrl);
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const isLoadingFolders = useFolderStore((state) => state.isLoadingFolders);
|
||||
const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder);
|
||||
|
||||
|
|
@ -114,7 +110,6 @@ export default function ComponentsComponent({
|
|||
|
||||
const { handleDuplicate } = useDuplicateFlows(
|
||||
selectedFlowsComponentsCards,
|
||||
addFlow,
|
||||
allFlows,
|
||||
resetFilter,
|
||||
getFoldersApi,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import NewFlowModal from "../../../../modals/newFlowModal";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
|
||||
type EmptyComponentProps = {};
|
||||
|
||||
const EmptyComponent = ({}: EmptyComponentProps) => {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,31 +1,19 @@
|
|||
import { XYPosition } from "reactflow";
|
||||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||
import { CONSOLE_ERROR_MSG } from "../../../constants/alerts_constants";
|
||||
import useAlertStore from "../../../stores/alertStore";
|
||||
|
||||
const useDropdownOptions = ({
|
||||
uploadFlow,
|
||||
navigate,
|
||||
is_component,
|
||||
}: {
|
||||
uploadFlow: ({
|
||||
newProject,
|
||||
file,
|
||||
isComponent,
|
||||
position,
|
||||
}: {
|
||||
newProject: boolean;
|
||||
file?: File;
|
||||
isComponent: boolean | null;
|
||||
position?: XYPosition;
|
||||
}) => Promise<string | never>;
|
||||
navigate: (url: string) => void;
|
||||
is_component: boolean;
|
||||
}) => {
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const uploadFlow = useUploadFlow();
|
||||
const handleImportFromJSON = () => {
|
||||
uploadFlow({
|
||||
newProject: true,
|
||||
isComponent: is_component,
|
||||
})
|
||||
.then((id) => {
|
||||
|
|
|
|||
|
|
@ -1,69 +1,27 @@
|
|||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { XYPosition } from "reactflow";
|
||||
import {
|
||||
CONSOLE_ERROR_MSG,
|
||||
UPLOAD_ALERT_LIST,
|
||||
WRONG_FILE_ERROR_ALERT,
|
||||
} from "../../../constants/alerts_constants";
|
||||
import { CONSOLE_ERROR_MSG } from "../../../constants/alerts_constants";
|
||||
import useAlertStore from "../../../stores/alertStore";
|
||||
import { useFolderStore } from "../../../stores/foldersStore";
|
||||
|
||||
const useFileDrop = (
|
||||
uploadFlow: ({
|
||||
newProject,
|
||||
file,
|
||||
isComponent,
|
||||
position,
|
||||
}: {
|
||||
newProject: boolean;
|
||||
file?: File;
|
||||
isComponent: boolean | null;
|
||||
position?: XYPosition;
|
||||
}) => Promise<string | never>,
|
||||
type,
|
||||
) => {
|
||||
const useFileDrop = (type?: string) => {
|
||||
const location = useLocation();
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const getFolderById = useFolderStore((state) => state.getFolderById);
|
||||
const folderId = location?.state?.folderId;
|
||||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
||||
const uploadFlow = useUploadFlow();
|
||||
|
||||
const handleFileDrop = (e) => {
|
||||
e.preventDefault();
|
||||
if (e.dataTransfer.types.some((type) => type === "Files")) {
|
||||
const files: FileList = e.dataTransfer.files;
|
||||
|
||||
const uploadPromises: Promise<any>[] = [];
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
if (file.type === "application/json") {
|
||||
const reader = new FileReader();
|
||||
const FileReaderPromise: Promise<void> = new Promise(
|
||||
(resolve, reject) => {
|
||||
reader.onload = (event) => {
|
||||
const fileContent = event.target!.result;
|
||||
const fileContentJson = JSON.parse(fileContent as string);
|
||||
const is_component = fileContentJson.is_component;
|
||||
uploadFlow({
|
||||
newProject: true,
|
||||
file: file,
|
||||
isComponent: type === "all" ? null : type === "component",
|
||||
})
|
||||
.then((_) => resolve())
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
};
|
||||
reader.readAsText(file);
|
||||
},
|
||||
);
|
||||
uploadPromises.push(FileReaderPromise);
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(uploadPromises)
|
||||
if (e.dataTransfer.types.every((type) => type === "Files")) {
|
||||
const files: File[] = Array.from(e.dataTransfer.files);
|
||||
uploadFlow({
|
||||
files,
|
||||
isComponent:
|
||||
type === "component" ? true : type === "flow" ? false : undefined,
|
||||
})
|
||||
.then(() => {
|
||||
setSuccessData({
|
||||
title: `All files uploaded successfully`,
|
||||
|
|
@ -71,14 +29,15 @@ const useFileDrop = (
|
|||
getFolderById(folderId ? folderId : myCollectionId);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setErrorData({
|
||||
title: CONSOLE_ERROR_MSG,
|
||||
list: [error],
|
||||
list: [(error as Error).message],
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
return [handleFileDrop];
|
||||
return handleFileDrop;
|
||||
};
|
||||
|
||||
export default useFileDrop;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import useDropdownOptions from "../../hooks/use-dropdown-options";
|
|||
import { getFolderById } from "../../services";
|
||||
|
||||
export default function HomePage(): JSX.Element {
|
||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||
const setCurrentFlowId = useFlowsManagerStore(
|
||||
(state) => state.setCurrentFlowId,
|
||||
);
|
||||
|
|
@ -41,7 +40,6 @@ export default function HomePage(): JSX.Element {
|
|||
}, [pathname]);
|
||||
|
||||
const dropdownOptions = useDropdownOptions({
|
||||
uploadFlow,
|
||||
navigate,
|
||||
is_component,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -294,11 +294,17 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
selection.nodes.some((node) => node.data.type === "ChatInput") &&
|
||||
checkChatInput(get().nodes)
|
||||
) {
|
||||
useAlertStore.getState().setErrorData({
|
||||
title: "Error pasting components",
|
||||
list: ["You can only have one ChatInput component in the flow"],
|
||||
useAlertStore.getState().setNoticeData({
|
||||
title: "You can only have one Chat Input component in a flow.",
|
||||
});
|
||||
return;
|
||||
selection.nodes = selection.nodes.filter(
|
||||
(node) => node.data.type !== "ChatInput",
|
||||
);
|
||||
selection.edges = selection.edges.filter(
|
||||
(edge) =>
|
||||
selection.nodes.some((node) => edge.source === node.id) &&
|
||||
selection.nodes.some((node) => edge.target === node.id),
|
||||
);
|
||||
}
|
||||
if (selection.nodes) {
|
||||
if (checkOldComponents({ nodes: selection.nodes ?? [] })) {
|
||||
|
|
|
|||
|
|
@ -1,45 +1,29 @@
|
|||
import { brokenEdgeMessage } from "@/utils/utils";
|
||||
import { AxiosError } from "axios";
|
||||
import { cloneDeep } from "lodash";
|
||||
import pDebounce from "p-debounce";
|
||||
import { Edge, Node, Viewport, XYPosition } from "reactflow";
|
||||
import { Edge, Node, Viewport } from "reactflow";
|
||||
import { create } from "zustand";
|
||||
import {
|
||||
BROKEN_EDGES_WARNING,
|
||||
SAVE_DEBOUNCE_TIME,
|
||||
} from "../constants/constants";
|
||||
import { SAVE_DEBOUNCE_TIME } from "../constants/constants";
|
||||
import {
|
||||
deleteFlowFromDatabase,
|
||||
multipleDeleteFlowsComponents,
|
||||
readFlowsFromDatabase,
|
||||
saveFlowToDatabase,
|
||||
updateFlowInDatabase,
|
||||
uploadFlowsToDatabase,
|
||||
} from "../controllers/API";
|
||||
import { FlowType, NodeDataType } from "../types/flow";
|
||||
import { FlowType } from "../types/flow";
|
||||
import {
|
||||
FlowsManagerStoreType,
|
||||
UseUndoRedoOptions,
|
||||
} from "../types/zustand/flowsManager";
|
||||
import {
|
||||
addVersionToDuplicates,
|
||||
createFlowComponent,
|
||||
createNewFlow,
|
||||
detectBrokenEdgesEdges,
|
||||
extractFieldsFromComponenents,
|
||||
processDataFromFlow,
|
||||
processFlows,
|
||||
updateGroupRecursion,
|
||||
} from "../utils/reactflowUtils";
|
||||
import useAlertStore from "./alertStore";
|
||||
import { useDarkStore } from "./darkStore";
|
||||
import useFlowStore from "./flowStore";
|
||||
import { useFolderStore } from "./foldersStore";
|
||||
import { useGlobalVariablesStore } from "./globalVariablesStore/globalVariables";
|
||||
import { useTypesStore } from "./typesStore";
|
||||
|
||||
let saveTimeoutId: NodeJS.Timeout | null = null;
|
||||
|
||||
const defaultOptions: UseUndoRedoOptions = {
|
||||
maxHistorySize: 100,
|
||||
enableShortcuts: true,
|
||||
|
|
@ -179,148 +163,6 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
});
|
||||
});
|
||||
}, SAVE_DEBOUNCE_TIME),
|
||||
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();
|
||||
});
|
||||
},
|
||||
addFlow: async (
|
||||
newProject: Boolean,
|
||||
flow?: FlowType,
|
||||
override?: boolean,
|
||||
position?: XYPosition,
|
||||
fromDragAndDrop?: boolean,
|
||||
): Promise<string | undefined> => {
|
||||
let flowData = flow
|
||||
? processDataFromFlow(flow)
|
||||
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
|
||||
flowData?.nodes.forEach((node) => {
|
||||
updateGroupRecursion(
|
||||
node,
|
||||
flowData?.edges,
|
||||
useGlobalVariablesStore.getState().unavailableFields,
|
||||
useGlobalVariablesStore.getState().globalVariablesEntries,
|
||||
);
|
||||
});
|
||||
if (newProject) {
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
const folder_id = useFolderStore.getState().folderUrl;
|
||||
const my_collection_id = useFolderStore.getState().myCollectionId;
|
||||
|
||||
if (override) {
|
||||
get().deleteComponent(flow!.name);
|
||||
const newFlow = createNewFlow(
|
||||
flowData!,
|
||||
flow!,
|
||||
folder_id || my_collection_id!,
|
||||
);
|
||||
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 },
|
||||
ComponentFields: extractFieldsFromComponenents({
|
||||
...state.data,
|
||||
["saved_components"]: data,
|
||||
}),
|
||||
}));
|
||||
}, 200);
|
||||
// addFlowToLocalState(newFlow);
|
||||
return;
|
||||
}
|
||||
const newFlow = createNewFlow(
|
||||
flowData!,
|
||||
flow!,
|
||||
folder_id || my_collection_id!,
|
||||
);
|
||||
|
||||
const newName = addVersionToDuplicates(newFlow, get().flows);
|
||||
|
||||
newFlow.name = newName;
|
||||
newFlow.folder_id = useFolderStore.getState().folderUrl;
|
||||
|
||||
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 },
|
||||
ComponentFields: extractFieldsFromComponenents({
|
||||
...state.data,
|
||||
["saved_components"]: data,
|
||||
}),
|
||||
}));
|
||||
|
||||
// Return the id
|
||||
return id;
|
||||
} catch (error: any) {
|
||||
if (error.response?.data?.detail) {
|
||||
useAlertStore.getState().setErrorData({
|
||||
title: "Could not load flows from database",
|
||||
list: [error.response?.data?.detail],
|
||||
});
|
||||
} else {
|
||||
useAlertStore.getState().setErrorData({
|
||||
title: "Could not load flows from database",
|
||||
list: [
|
||||
error.message ?? "An unexpected error occurred, please try again",
|
||||
],
|
||||
});
|
||||
}
|
||||
throw error; // Re-throw the error so the caller can handle it if needed
|
||||
}
|
||||
} else {
|
||||
let brokenEdges = detectBrokenEdgesEdges(
|
||||
flow!.data!.nodes,
|
||||
flow!.data!.edges,
|
||||
);
|
||||
if (brokenEdges.length > 0) {
|
||||
useAlertStore.getState().setErrorData({
|
||||
title: BROKEN_EDGES_WARNING,
|
||||
list: brokenEdges.map((edge) => brokenEdgeMessage(edge)),
|
||||
});
|
||||
}
|
||||
useFlowStore
|
||||
.getState()
|
||||
.paste(
|
||||
{ nodes: flow!.data!.nodes, edges: flow!.data!.edges },
|
||||
position ?? { x: 10, y: 10 },
|
||||
);
|
||||
}
|
||||
},
|
||||
removeFlow: async (id: string | string[]) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (Array.isArray(id)) {
|
||||
|
|
@ -381,86 +223,6 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
}
|
||||
});
|
||||
},
|
||||
uploadFlow: async ({
|
||||
newProject,
|
||||
file,
|
||||
isComponent,
|
||||
position = { x: 10, y: 10 },
|
||||
}: {
|
||||
newProject: boolean;
|
||||
file?: File;
|
||||
isComponent: boolean | null;
|
||||
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 &&
|
||||
isComponent !== null &&
|
||||
((!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,
|
||||
true,
|
||||
);
|
||||
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,
|
||||
);
|
||||
},
|
||||
takeSnapshot: () => {
|
||||
const currentFlowId = get().currentFlowId;
|
||||
// push the current graph to the past state
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Edge, Node, Viewport, XYPosition } from "reactflow";
|
||||
import { Edge, Node, Viewport } from "reactflow";
|
||||
import { FlowType } from "../../flow";
|
||||
|
||||
export type FlowsManagerStoreType = {
|
||||
|
|
@ -30,31 +30,8 @@ export type FlowsManagerStoreType = {
|
|||
edges: Edge[],
|
||||
viewport: Viewport,
|
||||
) => void;
|
||||
uploadFlows: () => Promise<void>;
|
||||
uploadFlow: ({
|
||||
newProject,
|
||||
file,
|
||||
isComponent,
|
||||
position,
|
||||
}: {
|
||||
newProject: boolean;
|
||||
file?: File;
|
||||
isComponent: boolean | null;
|
||||
position?: XYPosition;
|
||||
}) => Promise<string | never>;
|
||||
addFlow: (
|
||||
newProject: boolean,
|
||||
flow?: FlowType,
|
||||
override?: boolean,
|
||||
position?: XYPosition,
|
||||
fromDragAndDrop?: boolean,
|
||||
) => Promise<string | undefined>;
|
||||
deleteComponent: (key: string) => Promise<void>;
|
||||
removeFlow: (id: string | string[]) => Promise<void>;
|
||||
saveComponent: (
|
||||
component: any,
|
||||
override: boolean,
|
||||
) => Promise<string | undefined>;
|
||||
undo: () => void;
|
||||
redo: () => void;
|
||||
takeSnapshot: () => void;
|
||||
|
|
|
|||
|
|
@ -1527,8 +1527,8 @@ export function getRandomDescription(): string {
|
|||
|
||||
export const createNewFlow = (
|
||||
flowData: ReactFlowJsonObject,
|
||||
flow: FlowType,
|
||||
folderId: string,
|
||||
flow?: FlowType,
|
||||
) => {
|
||||
return {
|
||||
description: flow?.description ?? getRandomDescription(),
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ test.describe("drag and drop test", () => {
|
|||
|
||||
// Now dispatch
|
||||
await page.dispatchEvent(
|
||||
'//*[@id="root"]/div/div[1]/div[2]/div[3]/div/div',
|
||||
'//*[@id="root"]/div/div[2]/div[2]/div[3]/div',
|
||||
"drop",
|
||||
{
|
||||
dataTransfer,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ test("add folder by drag and drop", async ({ page }) => {
|
|||
|
||||
// Wait for the target element to be available before evaluation
|
||||
await page.waitForSelector(
|
||||
'//*[@id="root"]/div/div[1]/div[2]/div[3]/aside/nav/div/div[2]',
|
||||
'//*[@id="root"]/div/div[2]/div[2]/div[3]/aside/nav/div/div[2]',
|
||||
);
|
||||
|
||||
// Create the DataTransfer and File
|
||||
|
|
@ -93,7 +93,7 @@ test("add folder by drag and drop", async ({ page }) => {
|
|||
|
||||
// Now dispatch
|
||||
await page.dispatchEvent(
|
||||
'//*[@id="root"]/div/div[1]/div[2]/div[3]/aside/nav/div/div[2]',
|
||||
'//*[@id="root"]/div/div[2]/div[2]/div[3]/aside/nav/div/div[2]',
|
||||
"drop",
|
||||
{
|
||||
dataTransfer,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue