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 { setupAxiosDefaults } from "./controllers/API/utils";
|
||||||
import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path";
|
import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path";
|
||||||
import Router from "./routes";
|
import Router from "./routes";
|
||||||
import { Case } from "./shared/components/caseComponent";
|
|
||||||
import useAlertStore from "./stores/alertStore";
|
import useAlertStore from "./stores/alertStore";
|
||||||
import useAuthStore from "./stores/authStore";
|
import useAuthStore from "./stores/authStore";
|
||||||
import { useDarkStore } from "./stores/darkStore";
|
import { useDarkStore } from "./stores/darkStore";
|
||||||
import useFlowsManagerStore from "./stores/flowsManagerStore";
|
import useFlowsManagerStore from "./stores/flowsManagerStore";
|
||||||
import { useFolderStore } from "./stores/foldersStore";
|
import { useFolderStore } from "./stores/foldersStore";
|
||||||
|
import { cn } from "./utils/utils";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
useTrackLastVisitedPath();
|
useTrackLastVisitedPath();
|
||||||
|
|
@ -164,15 +164,16 @@ export default function App() {
|
||||||
></FetchErrorComponent>
|
></FetchErrorComponent>
|
||||||
}
|
}
|
||||||
|
|
||||||
<Case condition={isLoadingApplication}>
|
<div
|
||||||
<div className="loading-page-panel">
|
className={cn(
|
||||||
|
"loading-page-panel absolute left-0 top-0 z-[999]",
|
||||||
|
isLoadingApplication ? "" : "hidden",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<LoadingComponent remSize={50} />
|
<LoadingComponent remSize={50} />
|
||||||
</div>
|
</div>
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case condition={!isLoadingApplication}>
|
|
||||||
<Router />
|
<Router />
|
||||||
</Case>
|
|
||||||
</>
|
</>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
<div></div>
|
<div></div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { useState } from "react";
|
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||||
import { getComponent } from "../../../controllers/API";
|
import { getComponent } from "../../../controllers/API";
|
||||||
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
|
|
||||||
import { storeComponent } from "../../../types/store";
|
import { storeComponent } from "../../../types/store";
|
||||||
import cloneFlowWithParent from "../../../utils/storeUtils";
|
import cloneFlowWithParent from "../../../utils/storeUtils";
|
||||||
|
|
||||||
|
|
@ -14,7 +13,7 @@ const useInstallComponent = (
|
||||||
setSuccessData: (value: { title: string }) => void,
|
setSuccessData: (value: { title: string }) => void,
|
||||||
setErrorData: (value: { title: string; list: string[] }) => void,
|
setErrorData: (value: { title: string; list: string[] }) => void,
|
||||||
) => {
|
) => {
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
const addFlow = useAddFlow();
|
||||||
|
|
||||||
const handleInstall = () => {
|
const handleInstall = () => {
|
||||||
const temp = downloadsCount;
|
const temp = downloadsCount;
|
||||||
|
|
@ -24,7 +23,7 @@ const useInstallComponent = (
|
||||||
getComponent(data.id)
|
getComponent(data.id)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const newFlow = cloneFlowWithParent(res, res.id, data.is_component);
|
const newFlow = cloneFlowWithParent(res, res.id, data.is_component);
|
||||||
addFlow(true, newFlow)
|
addFlow({ flow: newFlow })
|
||||||
.then((id) => {
|
.then((id) => {
|
||||||
setSuccessData({
|
setSuccessData({
|
||||||
title: `${name} ${isStore ? "Downloaded" : "Installed"} Successfully.`,
|
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 { useNavigate } from "react-router-dom";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
|
||||||
import { FlowType } from "../../types/flow";
|
import { FlowType } from "../../types/flow";
|
||||||
import { updateIds } from "../../utils/reactflowUtils";
|
import { updateIds } from "../../utils/reactflowUtils";
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
|
|
@ -20,10 +21,9 @@ export default function CollectionCardComponent({
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
authorized?: boolean;
|
authorized?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
const addFlow = useAddFlow();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const emojiRegex = /\p{Emoji}/u;
|
const isEmoji = (str: string) => emojiRegex().test(str);
|
||||||
const isEmoji = (str: string) => emojiRegex.test(str);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
|
@ -77,7 +77,7 @@ export default function CollectionCardComponent({
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateIds(flow.data!);
|
updateIds(flow.data!);
|
||||||
addFlow(true, flow).then((id) => {
|
addFlow({ flow }).then((id) => {
|
||||||
navigate("/flow/" + id);
|
navigate("/flow/" + id);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "../../../ui/dropdown-menu";
|
} 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 { useNavigate } from "react-router-dom";
|
||||||
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
||||||
import { SAVED_HOVER } from "../../../../constants/constants";
|
import { SAVED_HOVER } from "../../../../constants/constants";
|
||||||
|
|
@ -26,7 +28,7 @@ import { Button } from "../../../ui/button";
|
||||||
|
|
||||||
export const MenuBar = ({}: {}): JSX.Element => {
|
export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
const addFlow = useAddFlow();
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
|
|
@ -40,14 +42,14 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
|
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
|
||||||
const [openSettings, setOpenSettings] = useState(false);
|
const [openSettings, setOpenSettings] = useState(false);
|
||||||
const [openLogs, setOpenLogs] = useState(false);
|
const [openLogs, setOpenLogs] = useState(false);
|
||||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
const uploadFlow = useUploadFlow();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||||
const getTypes = useTypesStore((state) => state.getTypes);
|
const getTypes = useTypesStore((state) => state.getTypes);
|
||||||
|
|
||||||
function handleAddFlow() {
|
function handleAddFlow() {
|
||||||
try {
|
try {
|
||||||
addFlow(true).then((id) => {
|
addFlow().then((id) => {
|
||||||
navigate("/flow/" + id);
|
navigate("/flow/" + id);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -125,14 +127,18 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
uploadFlow({ newProject: false, isComponent: false }).catch(
|
uploadFlow({ position: { x: 300, y: 100 } })
|
||||||
(error) => {
|
.then(() => {
|
||||||
|
setSuccessData({
|
||||||
|
title: "Uploaded successfully",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: UPLOAD_ERROR_ALERT,
|
title: UPLOAD_ERROR_ALERT,
|
||||||
list: [error],
|
list: [(error as Error).message],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconComponent name="FileUp" className="header-menu-options" />
|
<IconComponent name="FileUp" className="header-menu-options" />
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
|
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 {
|
import {
|
||||||
CONSOLE_ERROR_MSG,
|
CONSOLE_ERROR_MSG,
|
||||||
INVALID_FILE_ALERT,
|
INVALID_FILE_ALERT,
|
||||||
|
|
@ -19,7 +20,6 @@ export default function InputFileComponent({
|
||||||
id,
|
id,
|
||||||
}: FileComponentType): JSX.Element {
|
}: FileComponentType): JSX.Element {
|
||||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
|
||||||
// Clear component state
|
// Clear component state
|
||||||
|
|
@ -39,24 +39,14 @@ export default function InputFileComponent({
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { mutate } = usePostUploadFile();
|
const { mutate, isPending } = usePostUploadFile();
|
||||||
|
|
||||||
const handleButtonClick = (): void => {
|
const handleButtonClick = (): void => {
|
||||||
// Create a file input element
|
createFileUpload({ multiple: false, accept: fileTypes?.join(",") }).then(
|
||||||
const input = document.createElement("input");
|
(files) => {
|
||||||
document.body.appendChild(input);
|
const file = files[0];
|
||||||
input.type = "file";
|
if (file) {
|
||||||
input.accept = fileTypes?.join(",");
|
if (checkFileType(file.name)) {
|
||||||
input.style.display = "none"; // Hidden from view
|
|
||||||
input.multiple = false; // Allow only one file selection
|
|
||||||
const onChangeFile = (event: Event): void => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
// 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
|
// Upload the file
|
||||||
mutate(
|
mutate(
|
||||||
{ file, id: currentFlowId },
|
{ file, id: currentFlowId },
|
||||||
|
|
@ -69,11 +59,9 @@ export default function InputFileComponent({
|
||||||
// Update the state and on with the name of the file
|
// Update the state and on with the name of the file
|
||||||
// sets the value to the user
|
// sets the value to the user
|
||||||
handleOnNewValue({ value: file.name, file_path });
|
handleOnNewValue({ value: file.name, file_path });
|
||||||
setLoading(false);
|
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
console.error(CONSOLE_ERROR_MSG);
|
console.error(CONSOLE_ERROR_MSG);
|
||||||
setLoading(false);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -83,14 +71,10 @@ export default function InputFileComponent({
|
||||||
title: INVALID_FILE_ALERT,
|
title: INVALID_FILE_ALERT,
|
||||||
list: fileTypes,
|
list: fileTypes,
|
||||||
});
|
});
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
},
|
||||||
input.addEventListener("change", onChangeFile);
|
);
|
||||||
|
|
||||||
// Trigger the file selection dialog
|
|
||||||
input.click();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -114,7 +98,7 @@ export default function InputFileComponent({
|
||||||
unstyled
|
unstyled
|
||||||
className="inline-flex items-center justify-center"
|
className="inline-flex items-center justify-center"
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
loading={loading}
|
loading={isPending}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<IconComponent
|
<IconComponent
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import {
|
||||||
usePostUploadFolders,
|
usePostUploadFolders,
|
||||||
} from "@/controllers/API/queries/folders";
|
} from "@/controllers/API/queries/folders";
|
||||||
import { useGetDownloadFolders } from "@/controllers/API/queries/folders/use-get-download-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 { useEffect, useRef, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { FolderType } from "../../../../pages/MainPage/entities";
|
import { FolderType } from "../../../../pages/MainPage/entities";
|
||||||
|
|
@ -55,6 +58,7 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
||||||
|
const uploadFlow = useUploadFlow();
|
||||||
|
|
||||||
const handleFolderChange = () => {
|
const handleFolderChange = () => {
|
||||||
getFolderById(folderId);
|
getFolderById(folderId);
|
||||||
|
|
@ -68,46 +72,41 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
const { mutate } = usePostUploadFolders();
|
const { mutate } = usePostUploadFolders();
|
||||||
|
|
||||||
const handleUploadFlowsToFolder = () => {
|
const handleUploadFlowsToFolder = () => {
|
||||||
const input = document.createElement("input");
|
createFileUpload().then((files: File[]) => {
|
||||||
input.type = "file";
|
getObjectsFromFilelist<any>(files).then((objects) => {
|
||||||
input.accept = ".json";
|
if (objects.every((flow) => flow.data?.nodes)) {
|
||||||
input.click();
|
uploadFlow({ files }).then(() => {
|
||||||
|
|
||||||
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);
|
|
||||||
getFolderById(folderId);
|
getFolderById(folderId);
|
||||||
|
setSuccessData({
|
||||||
|
title: "Uploaded successfully",
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
files.forEach((folder) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", folder);
|
||||||
mutate(
|
mutate(
|
||||||
{ formData },
|
{ formData },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
getFolderById(folderId);
|
getFoldersApi(true);
|
||||||
setSuccessData({
|
setSuccessData({
|
||||||
title: "Uploaded successfully",
|
title: "Folder uploaded successfully.",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: `Error on upload`,
|
title: `Error on upload`,
|
||||||
list: [err["response"]["data"]],
|
list: [err["response"]["data"]["message"]],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutate: mutateDownloadFolder } = useGetDownloadFolders();
|
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.
|
* Fetches the version of the API.
|
||||||
*
|
*
|
||||||
|
|
@ -508,7 +482,7 @@ export async function getStoreComponents({
|
||||||
limit = 9999999,
|
limit = 9999999,
|
||||||
is_component = null,
|
is_component = null,
|
||||||
sort = "-count(liked_by)",
|
sort = "-count(liked_by)",
|
||||||
tags = [] || null,
|
tags = [],
|
||||||
liked = null,
|
liked = null,
|
||||||
isPrivate = null,
|
isPrivate = null,
|
||||||
search = null,
|
search = null,
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@ interface IDeleteBuilds {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add types for error handling and success
|
// add types for error handling and success
|
||||||
export const useDeleteBuilds: useMutationFunctionType<undefined,IDeleteBuilds> = (
|
export const useDeleteBuilds: useMutationFunctionType<
|
||||||
options,
|
undefined,
|
||||||
) => {
|
IDeleteBuilds
|
||||||
|
> = (options) => {
|
||||||
const { mutate } = UseRequestProcessor();
|
const { mutate } = UseRequestProcessor();
|
||||||
|
|
||||||
const deleteBuildsFn = async (payload: IDeleteBuilds): Promise<any> => {
|
const deleteBuildsFn = async (payload: IDeleteBuilds): Promise<any> => {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import useFlowStore from "@/stores/flowStore";
|
import useFlowStore from "@/stores/flowStore";
|
||||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||||
import { FlowPoolType } from "@/types/zustand/flow";
|
import { FlowPoolType } from "@/types/zustand/flow";
|
||||||
import { cleanEdges } from "@/utils/reactflowUtils";
|
|
||||||
import { getInputsAndOutputs } from "@/utils/storeUtils";
|
|
||||||
import { keepPreviousData } from "@tanstack/react-query";
|
import { keepPreviousData } from "@tanstack/react-query";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { useQueryFunctionType } from "../../../../types/api";
|
import { useQueryFunctionType } from "../../../../types/api";
|
||||||
|
|
@ -52,6 +50,7 @@ export const useGetBuildsQuery: useQueryFunctionType<
|
||||||
|
|
||||||
const queryResult = query(["useGetBuildsQuery"], responseFn, {
|
const queryResult = query(["useGetBuildsQuery"], responseFn, {
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return queryResult;
|
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
|
IPostDownloadMultipleFlows
|
||||||
> = mutate(
|
> = mutate(
|
||||||
["usePostDownloadMultipleFlows"],
|
["usePostDownloadMultipleFlows"],
|
||||||
async (payload: IPostDownloadMultipleFlows) => {
|
postDownloadMultipleFlowsFn,
|
||||||
const res = await postDownloadMultipleFlowsFn(payload);
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
import { useMutationFunctionType } from "@/types/api";
|
||||||
import { useFolderStore } from "@/stores/foldersStore";
|
|
||||||
import { Users, useMutationFunctionType } from "@/types/api";
|
|
||||||
import { api } from "../../api";
|
import { api } from "../../api";
|
||||||
import { getURL } from "../../helpers/constants";
|
import { getURL } from "../../helpers/constants";
|
||||||
import { UseRequestProcessor } from "../../services/request-processor";
|
import { UseRequestProcessor } from "../../services/request-processor";
|
||||||
|
|
@ -22,7 +20,6 @@ export const usePostUploadFolders: useMutationFunctionType<
|
||||||
`${getURL("FOLDERS")}/upload/`,
|
`${getURL("FOLDERS")}/upload/`,
|
||||||
payload.formData,
|
payload.formData,
|
||||||
);
|
);
|
||||||
await useFolderStore.getState().getFoldersApi(true);
|
|
||||||
return res.data;
|
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 { Button } from "../../../../../../components/ui/button";
|
||||||
|
|
||||||
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
|
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
|
||||||
|
import { createFileUpload } from "@/helpers/create-file-upload";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import IconComponent from "../../../../../../components/genericIconComponent";
|
import IconComponent from "../../../../../../components/genericIconComponent";
|
||||||
import { BASE_URL_API } from "../../../../../../constants/constants";
|
import {
|
||||||
import { uploadFile } from "../../../../../../controllers/API";
|
ALLOWED_IMAGE_INPUT_EXTENSIONS,
|
||||||
|
BASE_URL_API,
|
||||||
|
} from "../../../../../../constants/constants";
|
||||||
import useFlowsManagerStore from "../../../../../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../../../../../stores/flowsManagerStore";
|
||||||
import { IOFileInputProps } from "../../../../../../types/components";
|
import { IOFileInputProps } from "../../../../../../types/components";
|
||||||
|
|
||||||
|
|
@ -99,18 +102,11 @@ export default function IOFileInput({ field, updateValue }: IOFileInputProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleButtonClick = (): void => {
|
const handleButtonClick = (): void => {
|
||||||
|
createFileUpload({
|
||||||
|
multiple: false,
|
||||||
|
accept: ALLOWED_IMAGE_INPUT_EXTENSIONS.join(","),
|
||||||
|
}).then((files) => upload(files[0]));
|
||||||
// Create a file input element
|
// 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 (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
|
@ -5,11 +6,10 @@ import {
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../../../../components/ui/card";
|
} from "../../../../components/ui/card";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
|
||||||
import { useFolderStore } from "../../../../stores/foldersStore";
|
import { useFolderStore } from "../../../../stores/foldersStore";
|
||||||
|
|
||||||
export default function NewFlowCardComponent() {
|
export default function NewFlowCardComponent() {
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
const addFlow = useAddFlow();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const folderId = location?.state?.folderId;
|
const folderId = location?.state?.folderId;
|
||||||
|
|
@ -18,7 +18,7 @@ export default function NewFlowCardComponent() {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
addFlow(true).then((id) => {
|
addFlow().then((id) => {
|
||||||
setFolderUrl(folderId ?? "");
|
setFolderUrl(folderId ?? "");
|
||||||
navigate(`/flow/${id}${folderId ? `/folder/${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 BasicPrompt from "../../../../assets/undraw_short_bio_re_fmx0.svg?react";
|
||||||
import TransferFiles from "../../../../assets/undraw_transfer_files_re_a2a9.svg?react";
|
import TransferFiles from "../../../../assets/undraw_transfer_files_re_a2a9.svg?react";
|
||||||
|
|
||||||
|
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../../../../components/ui/card";
|
} from "../../../../components/ui/card";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
|
||||||
import { useFolderStore } from "../../../../stores/foldersStore";
|
import { useFolderStore } from "../../../../stores/foldersStore";
|
||||||
import { UndrawCardComponentProps } from "../../../../types/components";
|
import { UndrawCardComponentProps } from "../../../../types/components";
|
||||||
import { updateIds } from "../../../../utils/reactflowUtils";
|
import { updateIds } from "../../../../utils/reactflowUtils";
|
||||||
|
|
@ -24,7 +24,7 @@ import { updateIds } from "../../../../utils/reactflowUtils";
|
||||||
export default function UndrawCardComponent({
|
export default function UndrawCardComponent({
|
||||||
flow,
|
flow,
|
||||||
}: UndrawCardComponentProps): JSX.Element {
|
}: UndrawCardComponentProps): JSX.Element {
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
const addFlow = useAddFlow();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const folderId = location?.state?.folderId;
|
const folderId = location?.state?.folderId;
|
||||||
|
|
@ -142,7 +142,7 @@ export default function UndrawCardComponent({
|
||||||
<Card
|
<Card
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateIds(flow.data!);
|
updateIds(flow.data!);
|
||||||
addFlow(true, flow).then((id) => {
|
addFlow({ flow }).then((id) => {
|
||||||
setFolderUrl(folderId ?? "");
|
setFolderUrl(folderId ?? "");
|
||||||
navigate(`/flow/${id}/folder/${folderIdUrl}`);
|
navigate(`/flow/${id}/folder/${folderIdUrl}`);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
import LoadingComponent from "@/components/loadingComponent";
|
||||||
import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
|
import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
|
||||||
|
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||||
import { getInputsAndOutputs } from "@/utils/storeUtils";
|
import { getInputsAndOutputs } from "@/utils/storeUtils";
|
||||||
import _, { cloneDeep } from "lodash";
|
import _, { cloneDeep } from "lodash";
|
||||||
import {
|
import {
|
||||||
|
|
@ -62,7 +64,7 @@ export default function Page({
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
view?: boolean;
|
view?: boolean;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
const uploadFlow = useUploadFlow();
|
||||||
const autoSaveCurrentFlow = useFlowsManagerStore(
|
const autoSaveCurrentFlow = useFlowsManagerStore(
|
||||||
(state) => state.autoSaveCurrentFlow,
|
(state) => state.autoSaveCurrentFlow,
|
||||||
);
|
);
|
||||||
|
|
@ -70,9 +72,6 @@ export default function Page({
|
||||||
const templates = useTypesStore((state) => state.templates);
|
const templates = useTypesStore((state) => state.templates);
|
||||||
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
|
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
|
||||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
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 reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
|
||||||
const setReactFlowInstance = useFlowStore(
|
const setReactFlowInstance = useFlowStore(
|
||||||
|
|
@ -166,7 +165,12 @@ export default function Page({
|
||||||
}
|
}
|
||||||
}, [currentFlowId, reactFlowInstance]);
|
}, [currentFlowId, reactFlowInstance]);
|
||||||
|
|
||||||
const { isFetching } = useGetBuildsQuery({});
|
const { isFetching, refetch } = useGetBuildsQuery({});
|
||||||
|
|
||||||
|
const showCanvas =
|
||||||
|
Object.keys(templates).length > 0 &&
|
||||||
|
Object.keys(types).length > 0 &&
|
||||||
|
!isFetching;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetching) {
|
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.",
|
"Components created before Langflow 1.0 may be unstable. Ensure components are up to date.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, [currentFlowId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
refetch();
|
||||||
return () => {
|
return () => {
|
||||||
cleanFlow();
|
cleanFlow();
|
||||||
};
|
};
|
||||||
}, []);
|
}, [currentFlowId]);
|
||||||
|
|
||||||
function handleUndo(e: KeyboardEvent) {
|
function handleUndo(e: KeyboardEvent) {
|
||||||
if (!isWrappedWithClass(e, "noflow")) {
|
if (!isWrappedWithClass(e, "noflow")) {
|
||||||
|
|
@ -315,12 +320,6 @@ export default function Page({
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
useHotkeys("delete", handleDelete);
|
useHotkeys("delete", handleDelete);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSHowCanvas(
|
|
||||||
Object.keys(templates).length > 0 && Object.keys(types).length > 0,
|
|
||||||
);
|
|
||||||
}, [templates, types]);
|
|
||||||
|
|
||||||
const onConnectMod = useCallback(
|
const onConnectMod = useCallback(
|
||||||
(params: Connection) => {
|
(params: Connection) => {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
|
|
@ -387,20 +386,17 @@ export default function Page({
|
||||||
);
|
);
|
||||||
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
|
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
if (event.dataTransfer.files.item(0)!.type === "application/json") {
|
|
||||||
const position = {
|
const position = {
|
||||||
x: event.clientX,
|
x: event.clientX,
|
||||||
y: event.clientY,
|
y: event.clientY,
|
||||||
};
|
};
|
||||||
uploadFlow({
|
uploadFlow({
|
||||||
newProject: false,
|
files: Array.from(event.dataTransfer.files!),
|
||||||
isComponent: false,
|
|
||||||
file: event.dataTransfer.files.item(0)!,
|
|
||||||
position: position,
|
position: position,
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: UPLOAD_ERROR_ALERT,
|
title: UPLOAD_ERROR_ALERT,
|
||||||
list: [error],
|
list: [(error as Error).message],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -409,7 +405,6 @@ export default function Page({
|
||||||
list: [UPLOAD_ALERT_LIST],
|
list: [UPLOAD_ALERT_LIST],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// Specify dependencies for useCallback
|
// Specify dependencies for useCallback
|
||||||
[getNodeId, setNodes, takeSnapshot, paste],
|
[getNodeId, setNodes, takeSnapshot, paste],
|
||||||
|
|
@ -533,7 +528,9 @@ export default function Page({
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
<LoadingComponent remSize={30} />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value";
|
import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value";
|
||||||
import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class";
|
import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class";
|
||||||
import { usePostRetrieveVertexOrder } from "@/controllers/API/queries/vertex";
|
import { usePostRetrieveVertexOrder } from "@/controllers/API/queries/vertex";
|
||||||
|
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||||
import { APIClassType } from "@/types/api";
|
import { APIClassType } from "@/types/api";
|
||||||
import _, { cloneDeep } from "lodash";
|
import _, { cloneDeep } from "lodash";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
@ -82,6 +83,8 @@ export default function NodeToolbarComponent({
|
||||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||||
const unselectAll = useFlowStore((state) => state.unselectAll);
|
const unselectAll = useFlowStore((state) => state.unselectAll);
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
|
const addFlow = useAddFlow();
|
||||||
|
|
||||||
function handleMinimizeWShortcut(e: KeyboardEvent) {
|
function handleMinimizeWShortcut(e: KeyboardEvent) {
|
||||||
if (isWrappedWithClass(e, "noflow")) return;
|
if (isWrappedWithClass(e, "noflow")) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -134,7 +137,10 @@ export default function NodeToolbarComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (hasCode && !isSaved) {
|
if (hasCode && !isSaved) {
|
||||||
saveComponent(cloneDeep(data), false);
|
addFlow({
|
||||||
|
flow: flowComponent,
|
||||||
|
override: false,
|
||||||
|
});
|
||||||
setSuccessData({ title: `${data.id} saved successfully` });
|
setSuccessData({ title: `${data.id} saved successfully` });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +214,6 @@ export default function NodeToolbarComponent({
|
||||||
const setNodes = useFlowStore((state) => state.setNodes);
|
const setNodes = useFlowStore((state) => state.setNodes);
|
||||||
|
|
||||||
const setEdges = useFlowStore((state) => state.setEdges);
|
const setEdges = useFlowStore((state) => state.setEdges);
|
||||||
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
|
|
||||||
const getNodePosition = useFlowStore((state) => state.getNodePosition);
|
const getNodePosition = useFlowStore((state) => state.getNodePosition);
|
||||||
const flows = useFlowsManagerStore((state) => state.flows);
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
|
|
@ -257,7 +262,10 @@ export default function NodeToolbarComponent({
|
||||||
if (isSaved) {
|
if (isSaved) {
|
||||||
return setShowOverrideModal(true);
|
return setShowOverrideModal(true);
|
||||||
}
|
}
|
||||||
saveComponent(cloneDeep(data), false);
|
addFlow({
|
||||||
|
flow: flowComponent,
|
||||||
|
override: false,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case "freeze":
|
case "freeze":
|
||||||
setNode(data.id, (old) => ({
|
setNode(data.id, (old) => ({
|
||||||
|
|
@ -291,7 +299,10 @@ export default function NodeToolbarComponent({
|
||||||
downloadNode(flowComponent!);
|
downloadNode(flowComponent!);
|
||||||
break;
|
break;
|
||||||
case "SaveAll":
|
case "SaveAll":
|
||||||
saveComponent(cloneDeep(data), false);
|
addFlow({
|
||||||
|
flow: flowComponent,
|
||||||
|
override: false,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case "documentation":
|
case "documentation":
|
||||||
if (data.node?.documentation) openInNewTab(data.node?.documentation);
|
if (data.node?.documentation) openInNewTab(data.node?.documentation);
|
||||||
|
|
@ -695,12 +706,18 @@ export default function NodeToolbarComponent({
|
||||||
icon={"SaveAll"}
|
icon={"SaveAll"}
|
||||||
index={6}
|
index={6}
|
||||||
onConfirm={(index, user) => {
|
onConfirm={(index, user) => {
|
||||||
saveComponent(cloneDeep(data), true);
|
addFlow({
|
||||||
|
flow: flowComponent,
|
||||||
|
override: true,
|
||||||
|
});
|
||||||
setSuccessData({ title: `${data.id} successfully overridden!` });
|
setSuccessData({ title: `${data.id} successfully overridden!` });
|
||||||
}}
|
}}
|
||||||
onClose={setShowOverrideModal}
|
onClose={setShowOverrideModal}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
saveComponent(cloneDeep(data), false);
|
addFlow({
|
||||||
|
flow: flowComponent,
|
||||||
|
override: true,
|
||||||
|
});
|
||||||
setSuccessData({ title: "New component successfully saved!" });
|
setSuccessData({ title: "New component successfully saved!" });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,8 @@
|
||||||
|
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { XYPosition } from "reactflow";
|
|
||||||
import { FlowType } from "../../../../../types/flow";
|
|
||||||
|
|
||||||
const useDuplicateFlows = (
|
const useDuplicateFlows = (
|
||||||
selectedFlowsComponentsCards: string[],
|
selectedFlowsComponentsCards: string[],
|
||||||
addFlow: (
|
|
||||||
newProject: boolean,
|
|
||||||
flow?: FlowType,
|
|
||||||
override?: boolean,
|
|
||||||
position?: XYPosition,
|
|
||||||
fromDragAndDrop?: boolean,
|
|
||||||
) => Promise<string | undefined>,
|
|
||||||
allFlows: any[],
|
allFlows: any[],
|
||||||
resetFilter: () => void,
|
resetFilter: () => void,
|
||||||
getFoldersApi: (
|
getFoldersApi: (
|
||||||
|
|
@ -27,13 +19,11 @@ const useDuplicateFlows = (
|
||||||
handleSelectAll: (select: boolean) => void,
|
handleSelectAll: (select: boolean) => void,
|
||||||
cardTypes: string,
|
cardTypes: string,
|
||||||
) => {
|
) => {
|
||||||
|
const addFlow = useAddFlow();
|
||||||
const handleDuplicate = useCallback(() => {
|
const handleDuplicate = useCallback(() => {
|
||||||
Promise.all(
|
Promise.all(
|
||||||
selectedFlowsComponentsCards.map((selectedFlow) =>
|
selectedFlowsComponentsCards.map((selectedFlow) =>
|
||||||
addFlow(
|
addFlow({ flow: allFlows.find((flow) => flow.id === selectedFlow) }),
|
||||||
true,
|
|
||||||
allFlows.find((flow) => flow.id === selectedFlow),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
).then(() => {
|
).then(() => {
|
||||||
resetFilter();
|
resetFilter();
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import { sortFlows } from "../../utils/sort-flows";
|
||||||
import EmptyComponent from "../emptyComponent";
|
import EmptyComponent from "../emptyComponent";
|
||||||
import HeaderComponent from "../headerComponent";
|
import HeaderComponent from "../headerComponent";
|
||||||
import CollectionCard from "./components/collectionCard";
|
import CollectionCard from "./components/collectionCard";
|
||||||
import useDeleteMultipleFlows from "./hooks/use-delete-multiple";
|
|
||||||
import useDescriptionModal from "./hooks/use-description-modal";
|
import useDescriptionModal from "./hooks/use-description-modal";
|
||||||
import useFilteredFlows from "./hooks/use-filtered-flows";
|
import useFilteredFlows from "./hooks/use-filtered-flows";
|
||||||
import useDuplicateFlows from "./hooks/use-handle-duplicate";
|
import useDuplicateFlows from "./hooks/use-handle-duplicate";
|
||||||
|
|
@ -30,8 +29,6 @@ export default function ComponentsComponent({
|
||||||
}: {
|
}: {
|
||||||
type?: string;
|
type?: string;
|
||||||
}) {
|
}) {
|
||||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
|
||||||
const removeFlow = useFlowsManagerStore((state) => state.removeFlow);
|
|
||||||
const isLoading = useFlowsManagerStore((state) => state.isLoading);
|
const isLoading = useFlowsManagerStore((state) => state.isLoading);
|
||||||
const setAllFlows = useFlowsManagerStore((state) => state.setAllFlows);
|
const setAllFlows = useFlowsManagerStore((state) => state.setAllFlows);
|
||||||
const allFlows = useFlowsManagerStore((state) => state.allFlows);
|
const allFlows = useFlowsManagerStore((state) => state.allFlows);
|
||||||
|
|
@ -55,7 +52,7 @@ export default function ComponentsComponent({
|
||||||
(state) => state.selectedFlowsComponentsCards,
|
(state) => state.selectedFlowsComponentsCards,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [handleFileDrop] = useFileDrop(uploadFlow, type)!;
|
const handleFileDrop = useFileDrop(type);
|
||||||
const [pageSize, setPageSize] = useState(20);
|
const [pageSize, setPageSize] = useState(20);
|
||||||
const [pageIndex, setPageIndex] = useState(1);
|
const [pageIndex, setPageIndex] = useState(1);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
@ -71,7 +68,6 @@ export default function ComponentsComponent({
|
||||||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
||||||
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
||||||
const setFolderUrl = useFolderStore((state) => state.setFolderUrl);
|
const setFolderUrl = useFolderStore((state) => state.setFolderUrl);
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
|
||||||
const isLoadingFolders = useFolderStore((state) => state.isLoadingFolders);
|
const isLoadingFolders = useFolderStore((state) => state.isLoadingFolders);
|
||||||
const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder);
|
const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder);
|
||||||
|
|
||||||
|
|
@ -114,7 +110,6 @@ export default function ComponentsComponent({
|
||||||
|
|
||||||
const { handleDuplicate } = useDuplicateFlows(
|
const { handleDuplicate } = useDuplicateFlows(
|
||||||
selectedFlowsComponentsCards,
|
selectedFlowsComponentsCards,
|
||||||
addFlow,
|
|
||||||
allFlows,
|
allFlows,
|
||||||
resetFilter,
|
resetFilter,
|
||||||
getFoldersApi,
|
getFoldersApi,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import NewFlowModal from "../../../../modals/newFlowModal";
|
import NewFlowModal from "../../../../modals/newFlowModal";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
|
||||||
|
|
||||||
type EmptyComponentProps = {};
|
type EmptyComponentProps = {};
|
||||||
|
|
||||||
const EmptyComponent = ({}: EmptyComponentProps) => {
|
const EmptyComponent = ({}: EmptyComponentProps) => {
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
|
||||||
return (
|
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 { CONSOLE_ERROR_MSG } from "../../../constants/alerts_constants";
|
||||||
import useAlertStore from "../../../stores/alertStore";
|
import useAlertStore from "../../../stores/alertStore";
|
||||||
|
|
||||||
const useDropdownOptions = ({
|
const useDropdownOptions = ({
|
||||||
uploadFlow,
|
|
||||||
navigate,
|
navigate,
|
||||||
is_component,
|
is_component,
|
||||||
}: {
|
}: {
|
||||||
uploadFlow: ({
|
|
||||||
newProject,
|
|
||||||
file,
|
|
||||||
isComponent,
|
|
||||||
position,
|
|
||||||
}: {
|
|
||||||
newProject: boolean;
|
|
||||||
file?: File;
|
|
||||||
isComponent: boolean | null;
|
|
||||||
position?: XYPosition;
|
|
||||||
}) => Promise<string | never>;
|
|
||||||
navigate: (url: string) => void;
|
navigate: (url: string) => void;
|
||||||
is_component: boolean;
|
is_component: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
const uploadFlow = useUploadFlow();
|
||||||
const handleImportFromJSON = () => {
|
const handleImportFromJSON = () => {
|
||||||
uploadFlow({
|
uploadFlow({
|
||||||
newProject: true,
|
|
||||||
isComponent: is_component,
|
isComponent: is_component,
|
||||||
})
|
})
|
||||||
.then((id) => {
|
.then((id) => {
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,27 @@
|
||||||
|
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { XYPosition } from "reactflow";
|
import { CONSOLE_ERROR_MSG } from "../../../constants/alerts_constants";
|
||||||
import {
|
|
||||||
CONSOLE_ERROR_MSG,
|
|
||||||
UPLOAD_ALERT_LIST,
|
|
||||||
WRONG_FILE_ERROR_ALERT,
|
|
||||||
} from "../../../constants/alerts_constants";
|
|
||||||
import useAlertStore from "../../../stores/alertStore";
|
import useAlertStore from "../../../stores/alertStore";
|
||||||
import { useFolderStore } from "../../../stores/foldersStore";
|
import { useFolderStore } from "../../../stores/foldersStore";
|
||||||
|
|
||||||
const useFileDrop = (
|
const useFileDrop = (type?: string) => {
|
||||||
uploadFlow: ({
|
|
||||||
newProject,
|
|
||||||
file,
|
|
||||||
isComponent,
|
|
||||||
position,
|
|
||||||
}: {
|
|
||||||
newProject: boolean;
|
|
||||||
file?: File;
|
|
||||||
isComponent: boolean | null;
|
|
||||||
position?: XYPosition;
|
|
||||||
}) => Promise<string | never>,
|
|
||||||
type,
|
|
||||||
) => {
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const getFolderById = useFolderStore((state) => state.getFolderById);
|
const getFolderById = useFolderStore((state) => state.getFolderById);
|
||||||
const folderId = location?.state?.folderId;
|
const folderId = location?.state?.folderId;
|
||||||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
||||||
|
const uploadFlow = useUploadFlow();
|
||||||
|
|
||||||
const handleFileDrop = (e) => {
|
const handleFileDrop = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.dataTransfer.types.some((type) => type === "Files")) {
|
if (e.dataTransfer.types.every((type) => type === "Files")) {
|
||||||
const files: FileList = e.dataTransfer.files;
|
const files: File[] = Array.from(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({
|
uploadFlow({
|
||||||
newProject: true,
|
files,
|
||||||
file: file,
|
isComponent:
|
||||||
isComponent: type === "all" ? null : type === "component",
|
type === "component" ? true : type === "flow" ? false : undefined,
|
||||||
})
|
})
|
||||||
.then((_) => resolve())
|
|
||||||
.catch((error) => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
uploadPromises.push(FileReaderPromise);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.all(uploadPromises)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setSuccessData({
|
setSuccessData({
|
||||||
title: `All files uploaded successfully`,
|
title: `All files uploaded successfully`,
|
||||||
|
|
@ -71,14 +29,15 @@ const useFileDrop = (
|
||||||
getFolderById(folderId ? folderId : myCollectionId);
|
getFolderById(folderId ? folderId : myCollectionId);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: CONSOLE_ERROR_MSG,
|
title: CONSOLE_ERROR_MSG,
|
||||||
list: [error],
|
list: [(error as Error).message],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return [handleFileDrop];
|
return handleFileDrop;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useFileDrop;
|
export default useFileDrop;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import useDropdownOptions from "../../hooks/use-dropdown-options";
|
||||||
import { getFolderById } from "../../services";
|
import { getFolderById } from "../../services";
|
||||||
|
|
||||||
export default function HomePage(): JSX.Element {
|
export default function HomePage(): JSX.Element {
|
||||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
const setCurrentFlowId = useFlowsManagerStore(
|
||||||
(state) => state.setCurrentFlowId,
|
(state) => state.setCurrentFlowId,
|
||||||
);
|
);
|
||||||
|
|
@ -41,7 +40,6 @@ export default function HomePage(): JSX.Element {
|
||||||
}, [pathname]);
|
}, [pathname]);
|
||||||
|
|
||||||
const dropdownOptions = useDropdownOptions({
|
const dropdownOptions = useDropdownOptions({
|
||||||
uploadFlow,
|
|
||||||
navigate,
|
navigate,
|
||||||
is_component,
|
is_component,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -294,11 +294,17 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
selection.nodes.some((node) => node.data.type === "ChatInput") &&
|
selection.nodes.some((node) => node.data.type === "ChatInput") &&
|
||||||
checkChatInput(get().nodes)
|
checkChatInput(get().nodes)
|
||||||
) {
|
) {
|
||||||
useAlertStore.getState().setErrorData({
|
useAlertStore.getState().setNoticeData({
|
||||||
title: "Error pasting components",
|
title: "You can only have one Chat Input component in a flow.",
|
||||||
list: ["You can only have one ChatInput component in the 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 (selection.nodes) {
|
||||||
if (checkOldComponents({ nodes: selection.nodes ?? [] })) {
|
if (checkOldComponents({ nodes: selection.nodes ?? [] })) {
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,29 @@
|
||||||
import { brokenEdgeMessage } from "@/utils/utils";
|
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import pDebounce from "p-debounce";
|
import pDebounce from "p-debounce";
|
||||||
import { Edge, Node, Viewport, XYPosition } from "reactflow";
|
import { Edge, Node, Viewport } from "reactflow";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import {
|
import { SAVE_DEBOUNCE_TIME } from "../constants/constants";
|
||||||
BROKEN_EDGES_WARNING,
|
|
||||||
SAVE_DEBOUNCE_TIME,
|
|
||||||
} from "../constants/constants";
|
|
||||||
import {
|
import {
|
||||||
deleteFlowFromDatabase,
|
deleteFlowFromDatabase,
|
||||||
multipleDeleteFlowsComponents,
|
multipleDeleteFlowsComponents,
|
||||||
readFlowsFromDatabase,
|
readFlowsFromDatabase,
|
||||||
saveFlowToDatabase,
|
|
||||||
updateFlowInDatabase,
|
updateFlowInDatabase,
|
||||||
uploadFlowsToDatabase,
|
|
||||||
} from "../controllers/API";
|
} from "../controllers/API";
|
||||||
import { FlowType, NodeDataType } from "../types/flow";
|
import { FlowType } from "../types/flow";
|
||||||
import {
|
import {
|
||||||
FlowsManagerStoreType,
|
FlowsManagerStoreType,
|
||||||
UseUndoRedoOptions,
|
UseUndoRedoOptions,
|
||||||
} from "../types/zustand/flowsManager";
|
} from "../types/zustand/flowsManager";
|
||||||
import {
|
import {
|
||||||
addVersionToDuplicates,
|
|
||||||
createFlowComponent,
|
|
||||||
createNewFlow,
|
|
||||||
detectBrokenEdgesEdges,
|
|
||||||
extractFieldsFromComponenents,
|
extractFieldsFromComponenents,
|
||||||
processDataFromFlow,
|
|
||||||
processFlows,
|
processFlows,
|
||||||
updateGroupRecursion,
|
|
||||||
} from "../utils/reactflowUtils";
|
} from "../utils/reactflowUtils";
|
||||||
import useAlertStore from "./alertStore";
|
import useAlertStore from "./alertStore";
|
||||||
import { useDarkStore } from "./darkStore";
|
|
||||||
import useFlowStore from "./flowStore";
|
import useFlowStore from "./flowStore";
|
||||||
import { useFolderStore } from "./foldersStore";
|
import { useFolderStore } from "./foldersStore";
|
||||||
import { useGlobalVariablesStore } from "./globalVariablesStore/globalVariables";
|
|
||||||
import { useTypesStore } from "./typesStore";
|
import { useTypesStore } from "./typesStore";
|
||||||
|
|
||||||
let saveTimeoutId: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
const defaultOptions: UseUndoRedoOptions = {
|
const defaultOptions: UseUndoRedoOptions = {
|
||||||
maxHistorySize: 100,
|
maxHistorySize: 100,
|
||||||
enableShortcuts: true,
|
enableShortcuts: true,
|
||||||
|
|
@ -179,148 +163,6 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, SAVE_DEBOUNCE_TIME),
|
}, 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[]) => {
|
removeFlow: async (id: string | string[]) => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
if (Array.isArray(id)) {
|
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: () => {
|
takeSnapshot: () => {
|
||||||
const currentFlowId = get().currentFlowId;
|
const currentFlowId = get().currentFlowId;
|
||||||
// push the current graph to the past state
|
// 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";
|
import { FlowType } from "../../flow";
|
||||||
|
|
||||||
export type FlowsManagerStoreType = {
|
export type FlowsManagerStoreType = {
|
||||||
|
|
@ -30,31 +30,8 @@ export type FlowsManagerStoreType = {
|
||||||
edges: Edge[],
|
edges: Edge[],
|
||||||
viewport: Viewport,
|
viewport: Viewport,
|
||||||
) => void;
|
) => 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>;
|
deleteComponent: (key: string) => Promise<void>;
|
||||||
removeFlow: (id: string | string[]) => Promise<void>;
|
removeFlow: (id: string | string[]) => Promise<void>;
|
||||||
saveComponent: (
|
|
||||||
component: any,
|
|
||||||
override: boolean,
|
|
||||||
) => Promise<string | undefined>;
|
|
||||||
undo: () => void;
|
undo: () => void;
|
||||||
redo: () => void;
|
redo: () => void;
|
||||||
takeSnapshot: () => void;
|
takeSnapshot: () => void;
|
||||||
|
|
|
||||||
|
|
@ -1527,8 +1527,8 @@ export function getRandomDescription(): string {
|
||||||
|
|
||||||
export const createNewFlow = (
|
export const createNewFlow = (
|
||||||
flowData: ReactFlowJsonObject,
|
flowData: ReactFlowJsonObject,
|
||||||
flow: FlowType,
|
|
||||||
folderId: string,
|
folderId: string,
|
||||||
|
flow?: FlowType,
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
description: flow?.description ?? getRandomDescription(),
|
description: flow?.description ?? getRandomDescription(),
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ test.describe("drag and drop test", () => {
|
||||||
|
|
||||||
// Now dispatch
|
// Now dispatch
|
||||||
await page.dispatchEvent(
|
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",
|
"drop",
|
||||||
{
|
{
|
||||||
dataTransfer,
|
dataTransfer,
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ test("add folder by drag and drop", async ({ page }) => {
|
||||||
|
|
||||||
// Wait for the target element to be available before evaluation
|
// Wait for the target element to be available before evaluation
|
||||||
await page.waitForSelector(
|
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
|
// Create the DataTransfer and File
|
||||||
|
|
@ -93,7 +93,7 @@ test("add folder by drag and drop", async ({ page }) => {
|
||||||
|
|
||||||
// Now dispatch
|
// Now dispatch
|
||||||
await page.dispatchEvent(
|
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",
|
"drop",
|
||||||
{
|
{
|
||||||
dataTransfer,
|
dataTransfer,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue