diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 42eec6f76..e4f7a30ab 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -480,7 +480,6 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", - "extraneous": true, "inBundle": true, "license": "MIT", "engines": { diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index ba0d1c9a6..28b92d6bc 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -56,14 +56,14 @@ export default function GenericNode({ const [nodeName, setNodeName] = useState(data.node!.display_name); const [inputDescription, setInputDescription] = useState(false); const [nodeDescription, setNodeDescription] = useState( - data.node?.description! + data.node?.description!, ); const [isOutdated, setIsOutdated] = useState(false); const buildStatus = useFlowStore( - (state) => state.flowBuildStatus[data.id]?.status + (state) => state.flowBuildStatus[data.id]?.status, ); const lastRunTime = useFlowStore( - (state) => state.flowBuildStatus[data.id]?.timestamp + (state) => state.flowBuildStatus[data.id]?.timestamp, ); const [validationStatus, setValidationStatus] = useState(null); @@ -116,7 +116,7 @@ export default function GenericNode({ updateNodeInternals(data.id); }, - [data.id, data.node, setNode, setIsOutdated] + [data.id, data.node, setNode, setIsOutdated], ); if (!data.node!.template) { @@ -255,8 +255,17 @@ export default function GenericNode({ const isDark = useDarkStore((state) => state.dark); const renderIconStatus = ( buildStatus: BuildStatus | undefined, - validationStatus: validationStatusType | null + validationStatus: validationStatusType | null, ) => { + const conditionSuccess = validationStatus && validationStatus.valid; + const conditionInactive = + validationStatus && + !validationStatus.valid && + buildStatus === BuildStatus.INACTIVE; + const conditionError = + buildStatus === BuildStatus.ERROR || + (validationStatus && !validationStatus.valid); + if (buildStatus === BuildStatus.BUILDING) { return ; } else { @@ -264,31 +273,30 @@ export default function GenericNode({ <> - {validationStatus && validationStatus.valid ? ( + {conditionSuccess ? ( - ) : validationStatus && - !validationStatus.valid && - buildStatus === BuildStatus.INACTIVE ? ( + ) : conditionInactive ? ( - ) : buildStatus === BuildStatus.ERROR || - (validationStatus && !validationStatus.valid) ? ( + ) : conditionError ? ( ) : ( - + <> )} ); @@ -296,7 +304,7 @@ export default function GenericNode({ }; const getSpecificClassFromBuildStatus = ( buildStatus: BuildStatus | undefined, - validationStatus: validationStatusType | null + validationStatus: validationStatusType | null, ) => { let isInvalid = validationStatus && !validationStatus.valid; @@ -320,11 +328,11 @@ export default function GenericNode({ selected: boolean, showNode: boolean, buildStatus: BuildStatus | undefined, - validationStatus: validationStatusType | null + validationStatus: validationStatusType | null, ) => { const specificClassFromBuildStatus = getSpecificClassFromBuildStatus( buildStatus, - validationStatus + validationStatus, ); const baseBorderClass = getBaseBorderClass(selected); @@ -332,8 +340,8 @@ export default function GenericNode({ const names = classNames( baseBorderClass, nodeSizeClass, - "generic-node-div", - specificClassFromBuildStatus + "generic-node-div group/node", + specificClassFromBuildStatus, ); return names; }; @@ -393,7 +401,7 @@ export default function GenericNode({ selected, showNode, buildStatus, - validationStatus + validationStatus, )} > {data.node?.beta && showNode && ( @@ -524,7 +532,7 @@ export default function GenericNode({ } title={getFieldTitle( data.node?.template!, - templateField + templateField, )} info={data.node?.template[templateField].info} name={templateField} @@ -552,7 +560,7 @@ export default function GenericNode({ proxy={data.node?.template[templateField].proxy} showNode={showNode} /> - ) + ), )} { setInputDescription(true); @@ -771,13 +779,13 @@ export default function GenericNode({ } title={getFieldTitle( data.node?.template!, - templateField + templateField, )} info={data.node?.template[templateField].info} name={templateField} tooltipTitle={ data.node?.template[templateField].input_types?.join( - "\n" + "\n", ) ?? data.node?.template[templateField].type } required={data.node!.template[templateField].required} @@ -804,7 +812,7 @@ export default function GenericNode({
{" "} diff --git a/src/frontend/src/alerts/error/index.tsx b/src/frontend/src/alerts/error/index.tsx index 3690590b9..b70a5ae45 100644 --- a/src/frontend/src/alerts/error/index.tsx +++ b/src/frontend/src/alerts/error/index.tsx @@ -51,13 +51,15 @@ export default function ErrorAlert({ />
-

{title}

+

{title}

{list?.length !== 0 && list?.some((item) => item !== null && item !== undefined) ? (
    {list.map((item, index) => ( -
  • {item}
  • +
  • + {item} +
  • ))}
diff --git a/src/frontend/src/alerts/notice/index.tsx b/src/frontend/src/alerts/notice/index.tsx index fb29954ea..dcb034691 100644 --- a/src/frontend/src/alerts/notice/index.tsx +++ b/src/frontend/src/alerts/notice/index.tsx @@ -47,7 +47,7 @@ export default function NoticeAlert({ />
-

+

{title}

diff --git a/src/frontend/src/alerts/success/index.tsx b/src/frontend/src/alerts/success/index.tsx index db62c8432..270ae5515 100644 --- a/src/frontend/src/alerts/success/index.tsx +++ b/src/frontend/src/alerts/success/index.tsx @@ -45,7 +45,7 @@ export default function SuccessAlert({ />

-

{title}

+

{title}

diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index 50a0c8cf8..e1b8ce2c5 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -56,7 +56,7 @@ export default function Header(): JSX.Element { const lastFlowVisitedIndex = routeHistory .reverse() .findIndex( - (path) => path.includes("/flow/") && path !== location.pathname + (path) => path.includes("/flow/") && path !== location.pathname, ); const lastFlowVisited = routeHistory[lastFlowVisitedIndex]; @@ -200,6 +200,28 @@ export default function Header(): JSX.Element { /> + {!autoLogin && ( + <> + +
+
+ {userData?.username ?? "User"} +
+ + + + )} General state.takeSnapshot); const [editFolders, setEditFolderName] = useState( - folders.map((obj) => ({ name: obj.name, edit: false })) + folders.map((obj) => ({ name: obj.name, edit: false })), ); const uploadFolder = useFolderStore((state) => state.uploadFolder); const currentFolder = pathname.split("/"); @@ -51,6 +52,7 @@ const SideBarFoldersButtonsComponent = ({ const location = useLocation(); const folderId = location?.state?.folderId ?? myCollectionId; const getFolderById = useFolderStore((state) => state.getFolderById); + const setErrorData = useAlertStore((state) => state.setErrorData); const handleFolderChange = (folderId: string) => { getFolderById(folderId); @@ -58,11 +60,21 @@ const SideBarFoldersButtonsComponent = ({ const { dragOver, dragEnter, dragLeave, onDrop } = useFileDrop( folderId, - handleFolderChange + handleFolderChange, ); const handleUploadFlowsToFolder = () => { - uploadFolder(folderId); + uploadFolder(folderId) + .then(() => { + getFolderById(folderId); + }) + .catch((err) => { + console.log(err); + setErrorData({ + title: `Error on upload`, + list: [err["response"]["data"]], + }); + }); }; const handleDownloadFolder = (id: string) => { @@ -73,7 +85,7 @@ const SideBarFoldersButtonsComponent = ({ addFolder({ name: "New Folder", parent_id: null, description: "" }).then( (res) => { getFoldersApi(true); - } + }, ); } @@ -119,7 +131,7 @@ const SideBarFoldersButtonsComponent = ({ <> {folders.map((item, index) => { const editFolderName = editFolders?.filter( - (folder) => folder.name === item.name + (folder) => folder.name === item.name, )[0]; return (
handleChangeFolder!(item.id!)} > @@ -205,7 +217,7 @@ const SideBarFoldersButtonsComponent = ({ folders.map((obj) => ({ name: obj.name, edit: false, - })) + })), ); } if (e.key === "Enter") { @@ -238,10 +250,10 @@ const SideBarFoldersButtonsComponent = ({ }; const updatedFolder = await updateFolder( body, - item.id! + item.id!, ); const updateFolders = folders.filter( - (f) => f.name !== item.name + (f) => f.name !== item.name, ); setFolders([...updateFolders, updatedFolder]); setFoldersNames({}); @@ -249,7 +261,7 @@ const SideBarFoldersButtonsComponent = ({ folders.map((obj) => ({ name: obj.name, edit: false, - })) + })), ); } else { setFoldersNames((old) => ({ diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index d9ce00554..a113b3911 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -668,7 +668,7 @@ export const ZERO_NOTIFICATIONS = "No new notifications"; export const SUCCESS_BUILD = "Built sucessfully ✨"; export const ALERT_SAVE_WITH_API = - "Caution: Uncheck this box only removes API keys from fields specifically designated for API keys."; + "Caution: Unchecking this box only removes API keys from fields specifically designated for API keys."; export const SAVE_WITH_API_CHECKBOX = "Save with my API keys"; export const EDIT_TEXT_MODAL_TITLE = "Edit Text"; diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index a04891821..9cd81c123 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -45,7 +45,7 @@ const ExportModal = forwardRef( is_component: false, }, name!, - description + description, ); setNoticeData({ title: API_WARNING_NOTICE_ALERT, @@ -61,7 +61,7 @@ const ExportModal = forwardRef( is_component: false, }), name!, - description + description, ); setOpen(false); }} @@ -94,7 +94,7 @@ const ExportModal = forwardRef( {SAVE_WITH_API_CHECKBOX}
- + {ALERT_SAVE_WITH_API} @@ -102,6 +102,6 @@ const ExportModal = forwardRef( ); - } + }, ); export default ExportModal; diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx index f7d52c611..fa5524891 100644 --- a/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx @@ -4,7 +4,9 @@ import { FormProvider, useForm, useWatch } from "react-hook-form"; import { Link, useLocation, useNavigate } from "react-router-dom"; import CollectionCardComponent from "../../../../components/cardComponent"; import CardsWrapComponent from "../../../../components/cardsWrapComponent"; -import IconComponent from "../../../../components/genericIconComponent"; +import IconComponent, { + ForwardedIconComponent, +} from "../../../../components/genericIconComponent"; import PaginatorComponent from "../../../../components/paginatorComponent"; import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent"; import { Button } from "../../../../components/ui/button"; @@ -18,6 +20,9 @@ import { getNameByType } from "../../utils/get-name-by-type"; import { sortFlows } from "../../utils/sort-flows"; import EmptyComponent from "../emptyComponent"; import HeaderComponent from "../headerComponent"; +import { downloadFlow, removeApiKeys } from "../../../../utils/reactflowUtils"; +import { useDarkStore } from "../../../../stores/darkStore"; +import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants"; export default function ComponentsComponent({ type = "all", @@ -31,22 +36,22 @@ export default function ComponentsComponent({ const allFlows = useFlowsManagerStore((state) => state.allFlows); const flowsFromFolder = useFolderStore( - (state) => state.selectedFolder?.flows + (state) => state.selectedFolder?.flows, ); const setSuccessData = useAlertStore((state) => state.setSuccessData); const setErrorData = useAlertStore((state) => state.setErrorData); const [openDelete, setOpenDelete] = useState(false); const searchFlowsComponents = useFlowsManagerStore( - (state) => state.searchFlowsComponents + (state) => state.searchFlowsComponents, ); const setSelectedFlowsComponentsCards = useFlowsManagerStore( - (state) => state.setSelectedFlowsComponentsCards + (state) => state.setSelectedFlowsComponentsCards, ); const selectedFlowsComponentsCards = useFlowsManagerStore( - (state) => state.selectedFlowsComponentsCards + (state) => state.selectedFlowsComponentsCards, ); const [handleFileDrop] = useFileDrop(uploadFlow, type)!; @@ -82,7 +87,7 @@ export default function ComponentsComponent({ f.name.toLowerCase().includes(searchFlowsComponents.toLowerCase()) || f.description .toLowerCase() - .includes(searchFlowsComponents.toLowerCase()) + .includes(searchFlowsComponents.toLowerCase()), ); if (searchFlowsComponents === "") { @@ -129,6 +134,8 @@ export default function ComponentsComponent({ setOpenDelete(true); } else if (action === "duplicate") { handleDuplicate(); + } else if (action === "export") { + handleExport(); } }; @@ -137,9 +144,9 @@ export default function ComponentsComponent({ selectedFlowsComponentsCards.map((selectedFlow) => addFlow( true, - allFlows.find((flow) => flow.id === selectedFlow) - ) - ) + allFlows.find((flow) => flow.id === selectedFlow), + ), + ), ).then(() => { resetFilter(); getFoldersApi(true); @@ -152,6 +159,47 @@ export default function ComponentsComponent({ }); }; + const handleImport = () => { + uploadFlow({ newProject: true, isComponent: false }) + .then(() => { + resetFilter(); + getFoldersApi(true); + if (!folderId || folderId === myCollectionId) { + getFolderById(folderId ? folderId : myCollectionId); + } + setSelectedFlowsComponentsCards([]); + + setSuccessData({ title: "Flows imported successfully" }); + }) + .catch((error) => { + setErrorData({ + title: UPLOAD_ERROR_ALERT, + list: [error], + }); + }); + }; + + const version = useDarkStore((state) => state.version); + + const handleExport = () => { + selectedFlowsComponentsCards.map((selectedFlowId) => { + const selectedFlow = allFlows.find((flow) => flow.id === selectedFlowId); + downloadFlow( + removeApiKeys({ + id: selectedFlow!.id, + data: selectedFlow!.data!, + description: selectedFlow!.description, + name: selectedFlow!.name, + last_tested_version: version, + is_component: false, + }), + selectedFlow!.name, + selectedFlow!.description, + ); + }); + setSuccessData({ title: "Flows exported successfully" }); + }; + const handleDeleteMultiple = () => { removeFlow(selectedFlowsComponentsCards) .then(() => { @@ -161,7 +209,7 @@ export default function ComponentsComponent({ getFolderById(folderId ? folderId : myCollectionId); } setSuccessData({ - title: "Selected items deleted successfully!", + title: "Selected items deleted successfully", }); }) .catch(() => { @@ -180,7 +228,7 @@ export default function ComponentsComponent({ return true; } return false; - } + }, ); setSelectedFlowsComponentsCards(selectedFlows); @@ -215,20 +263,23 @@ export default function ComponentsComponent({ if (type === "all") return allFlows?.length; return allFlows?.filter( - (f) => (f.is_component ?? false) === (type === "component") + (f) => (f.is_component ?? false) === (type === "component"), )?.length; }; return ( <> - {allFlows?.length > 0 && ( - handleSelectOptionsChange("delete")} - handleSelectAll={handleSelectAll} - handleDuplicate={() => handleSelectOptionsChange("duplicate")} - disableFunctions={!(selectedFlowsComponentsCards?.length > 0)} - /> - )} +
+ {allFlows?.length > 0 && ( + handleSelectOptionsChange("delete")} + handleSelectAll={handleSelectAll} + handleDuplicate={() => handleSelectOptionsChange("duplicate")} + handleExport={() => handleSelectOptionsChange("export")} + disableFunctions={!(selectedFlowsComponentsCards?.length > 0)} + /> + )} +
{ return ( <> -
+
diff --git a/src/frontend/src/pages/MainPage/components/headerComponent/index.tsx b/src/frontend/src/pages/MainPage/components/headerComponent/index.tsx index 2d2e43a2a..527c8d423 100644 --- a/src/frontend/src/pages/MainPage/components/headerComponent/index.tsx +++ b/src/frontend/src/pages/MainPage/components/headerComponent/index.tsx @@ -1,13 +1,17 @@ import { useState } from "react"; -import IconComponent from "../../../../components/genericIconComponent"; +import IconComponent, { + ForwardedIconComponent, +} from "../../../../components/genericIconComponent"; import ShadTooltip from "../../../../components/shadTooltipComponent"; import { Checkbox } from "../../../../components/ui/checkbox"; import { cn } from "../../../../utils/utils"; +import { Button } from "../../../../components/ui/button"; type HeaderComponentProps = { handleSelectAll: (select) => void; handleDelete: () => void; handleDuplicate: () => void; + handleExport: () => void; disableFunctions: boolean; }; @@ -15,6 +19,7 @@ const HeaderComponent = ({ handleSelectAll, handleDelete, handleDuplicate, + handleExport, disableFunctions, }: HeaderComponentProps) => { const [shouldSelectAll, setShouldSelectAll] = useState(true); @@ -26,29 +31,46 @@ const HeaderComponent = ({ return ( <> -
-
- -
-
-
- - -
-
+
+ -
+
+
+ Select items to export + ) : ( + Export selected items + ) + } + > + + +
- +
@@ -77,15 +104,20 @@ const HeaderComponent = ({ ) } > - +
diff --git a/src/frontend/src/pages/SettingsPage/index.tsx b/src/frontend/src/pages/SettingsPage/index.tsx index d956f20f4..d201d2596 100644 --- a/src/frontend/src/pages/SettingsPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/index.tsx @@ -8,7 +8,7 @@ import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function SettingsPage(): JSX.Element { const pathname = location.pathname; const setCurrentFlowId = useFlowsManagerStore( - (state) => state.setCurrentFlowId + (state) => state.setCurrentFlowId, ); useEffect(() => { setCurrentFlowId(""); @@ -59,7 +59,10 @@ export default function SettingsPage(): JSX.Element { title: "Messages", href: "/settings/messages", icon: ( - + ), }, ]; @@ -72,8 +75,10 @@ export default function SettingsPage(): JSX.Element { -
- +
+
+ +
diff --git a/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/index.tsx index 8efa2e3f9..cc19346c3 100644 --- a/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/index.tsx @@ -52,23 +52,17 @@ export default function ApiKeysPage() { />
- - - { - setSelectedRows( - event.api.getSelectedRows().map((row) => row.id), - ); - }} - rowSelection="multiple" - suppressRowClickSelection={true} - pagination={true} - columnDefs={columnDefs} - rowData={keysList.current} - /> - - + { + setSelectedRows(event.api.getSelectedRows().map((row) => row.id)); + }} + rowSelection="multiple" + suppressRowClickSelection={true} + pagination={true} + columnDefs={columnDefs} + rowData={keysList.current} + />
); diff --git a/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx index d33761a17..554bfdd28 100644 --- a/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx @@ -16,13 +16,13 @@ import { cn } from "../../../../utils/utils"; export default function GlobalVariablesPage() { const globalVariablesEntries = useGlobalVariablesStore( - (state) => state.globalVariablesEntries + (state) => state.globalVariablesEntries, ); const removeGlobalVariable = useGlobalVariablesStore( - (state) => state.removeGlobalVariable + (state) => state.removeGlobalVariable, ); const globalVariables = useGlobalVariablesStore( - (state) => state.globalVariables + (state) => state.globalVariables, ); const setErrorData = useAlertStore((state) => state.setErrorData); const getVariableId = useGlobalVariablesStore((state) => state.getVariableId); @@ -154,7 +154,7 @@ export default function GlobalVariablesPage() { @@ -168,23 +168,17 @@ export default function GlobalVariablesPage() {
- - - { - setSelectedRows( - event.api.getSelectedRows().map((row) => row.name) - ); - }} - rowSelection="multiple" - suppressRowClickSelection={true} - pagination={true} - columnDefs={colDefs} - rowData={rowData} - /> - - + { + setSelectedRows(event.api.getSelectedRows().map((row) => row.name)); + }} + rowSelection="multiple" + suppressRowClickSelection={true} + pagination={true} + columnDefs={colDefs} + rowData={rowData} + />
); diff --git a/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx index c6a5235d4..ebc0b5ed9 100644 --- a/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx @@ -115,15 +115,11 @@ export default function ShortcutsPage() {
- - - - - +
); diff --git a/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx index 2fff6f360..840460959 100644 --- a/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx @@ -27,7 +27,7 @@ export default function MessagesPage() { setSelectedRows, setSuccessData, setErrorData, - selectedRows + selectedRows, ); const { handleUpdate } = useUpdateMessage(setSuccessData, setErrorData); @@ -52,29 +52,25 @@ export default function MessagesPage() { handleRemoveMessages={handleRemoveMessages} /> -
- - - { - handleUpdateMessage(event); - }} - editable={["Sender Name", "Message"]} - overlayNoRowsTemplate="No data available" - onSelectionChanged={(event: SelectionChangedEvent) => { - setSelectedRows( - event.api.getSelectedRows().map((row) => row.index) - ); - }} - rowSelection="multiple" - suppressRowClickSelection={true} - pagination={true} - columnDefs={columns} - rowData={messages} - /> - - +
+ { + handleUpdateMessage(event); + }} + editable={["Sender Name", "Message"]} + overlayNoRowsTemplate="No data available" + onSelectionChanged={(event: SelectionChangedEvent) => { + setSelectedRows( + event.api.getSelectedRows().map((row) => row.index), + ); + }} + rowSelection="multiple" + suppressRowClickSelection={true} + pagination={true} + columnDefs={columns} + rowData={messages} + />
); diff --git a/src/frontend/src/stores/foldersStore.tsx b/src/frontend/src/stores/foldersStore.tsx index ce063e032..9284c426f 100644 --- a/src/frontend/src/stores/foldersStore.tsx +++ b/src/frontend/src/stores/foldersStore.tsx @@ -7,6 +7,7 @@ import { } from "../pages/MainPage/services"; import { FoldersStoreType } from "../types/zustand/folders"; import useFlowsManagerStore from "./flowsManagerStore"; +import { uploadFlowsToDatabase } from "../controllers/API"; export const useFolderStore = create((set, get) => ({ folders: [], @@ -17,18 +18,18 @@ export const useFolderStore = create((set, get) => ({ getFolders().then( (res) => { const foldersWithoutStarterProjects = res.filter( - (folder) => folder.name !== STARTER_FOLDER_NAME + (folder) => folder.name !== STARTER_FOLDER_NAME, ); const starterProjects = res.find( - (folder) => folder.name === STARTER_FOLDER_NAME + (folder) => folder.name === STARTER_FOLDER_NAME, ); set({ starterProjectId: starterProjects!.id ?? "" }); set({ folders: foldersWithoutStarterProjects }); const myCollectionId = res?.find( - (f) => f.name === DEFAULT_FOLDER + (f) => f.name === DEFAULT_FOLDER, )?.id; set({ myCollectionId }); @@ -45,7 +46,7 @@ export const useFolderStore = create((set, get) => ({ set({ folders: [] }); get().setLoading(false); reject(); - } + }, ); } }); @@ -65,7 +66,7 @@ export const useFolderStore = create((set, get) => ({ }, () => { get().setLoadingById(false); - } + }, ); } }, @@ -100,7 +101,7 @@ export const useFolderStore = create((set, get) => ({ folderIdDragging: "", setFolderIdDragging: (id) => set(() => ({ folderIdDragging: id })), uploadFolder: () => { - return new Promise(() => { + return new Promise((resolve, reject) => { const input = document.createElement("input"); input.type = "file"; input.onchange = (event: Event) => { @@ -111,8 +112,31 @@ export const useFolderStore = create((set, get) => ({ const file = (event.target as HTMLInputElement).files![0]; const formData = new FormData(); formData.append("file", file); - uploadFlowsFromFolders(formData).then(() => { - get().getFoldersApi(true); + file.text().then((text) => { + const data = JSON.parse(text); + if (data.data?.nodes) { + useFlowsManagerStore + .getState() + .addFlow(true, data) + .then(() => { + resolve(); + }) + .catch((error) => { + reject(error); + }); + } else { + uploadFlowsFromFolders(formData) + .then(() => { + get() + .getFoldersApi(true) + .then(() => { + resolve(); + }); + }) + .catch((error) => { + reject(error); + }); + } }); } }; diff --git a/src/frontend/src/types/zustand/folders/index.ts b/src/frontend/src/types/zustand/folders/index.ts index 352be3baf..2c5b2b96b 100644 --- a/src/frontend/src/types/zustand/folders/index.ts +++ b/src/frontend/src/types/zustand/folders/index.ts @@ -20,7 +20,7 @@ export type FoldersStoreType = { setFolderUrl: (folderUrl: string) => void; folderDragging: boolean; setFolderDragging: (set: boolean) => void; - uploadFolder: (folderId: string) => void; + uploadFolder: (folderId: string) => Promise; folderIdDragging: string; setFolderIdDragging: (id: string) => void; starterProjectId: string; diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 32de7dee2..329ca1fa0 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -56,6 +56,7 @@ import { FolderIcon, FolderPlus, FolderPlusIcon, + FolderUp, FormInput, Forward, Gift, @@ -431,6 +432,7 @@ export const nodeIconsLucide: iconsType = { ChevronLeft, SlidersHorizontal, Palette, + FolderUp, Blocks, ChevronDown, ArrowLeft,