diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx
index 835f98c69d6..1dc7e2afa2a 100644
--- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx
@@ -72,7 +72,7 @@ export const SidebarDraggableComponent = forwardRef(
);
break;
case "delete":
- const flowId = flows.find((f) => f.name === display_name);
+ const flowId = flows?.find((f) => f.name === display_name);
if (flowId) deleteFlow({ id: flowId.id });
break;
}
diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx
index d2c80c02dd3..743c5acd444 100644
--- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx
@@ -82,7 +82,7 @@ export default function NodeToolbarComponent({
const validApiKey = useStoreStore((state) => state.validApiKey);
const shortcuts = useShortcutsStore((state) => state.shortcuts);
const unselectAll = useFlowStore((state) => state.unselectAll);
- const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const addFlow = useAddFlow();
function handleMinimizeWShortcut(e: KeyboardEvent) {
@@ -179,7 +179,7 @@ export default function NodeToolbarComponent({
function handleFreezeAll(e: KeyboardEvent) {
if (isWrappedWithClass(e, "noflow")) return;
e.preventDefault();
- FreezeAllVertices({ flowId: currentFlow!.id, stopNodeId: data.id });
+ FreezeAllVertices({ flowId: currentFlowId, stopNodeId: data.id });
}
const advanced = useShortcutsStore((state) => state.advanced);
@@ -280,7 +280,7 @@ export default function NodeToolbarComponent({
}));
break;
case "freezeAll":
- FreezeAllVertices({ flowId: currentFlow!.id, stopNodeId: data.id });
+ FreezeAllVertices({ flowId: currentFlowId, stopNodeId: data.id });
break;
case "code":
setOpenModal(!openModal);
@@ -355,7 +355,7 @@ export default function NodeToolbarComponent({
}
};
- const isSaved = flows.some((flow) =>
+ const isSaved = flows?.some((flow) =>
Object.values(flow).includes(data.node?.display_name!),
);
@@ -480,7 +480,7 @@ export default function NodeToolbarComponent({
event.preventDefault();
takeSnapshot();
FreezeAllVertices({
- flowId: currentFlow!.id,
+ flowId: currentFlowId,
stopNodeId: data.id,
});
}}
@@ -543,7 +543,8 @@ export default function NodeToolbarComponent({
obj.name === "Save")?.shortcut!
+ shortcuts.find((obj) => obj.name === "Save Component")
+ ?.shortcut!
}
value={"Save"}
icon={"SaveAll"}
@@ -712,7 +713,7 @@ export default function NodeToolbarComponent({
});
setSuccessData({ title: `${data.id} successfully overridden!` });
}}
- onClose={setShowOverrideModal}
+ onClose={() => setShowOverrideModal(false)}
onCancel={() => {
addFlow({
flow: flowComponent,
diff --git a/src/frontend/src/pages/FlowPage/index.tsx b/src/frontend/src/pages/FlowPage/index.tsx
index c8e4f334443..e4a6553d996 100644
--- a/src/frontend/src/pages/FlowPage/index.tsx
+++ b/src/frontend/src/pages/FlowPage/index.tsx
@@ -1,7 +1,10 @@
import FeatureFlags from "@/../feature-config.json";
import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
+import useSaveFlow from "@/hooks/flows/use-save-flow";
+import { SaveChangesModal } from "@/modals/saveChangesModal";
+import { customStringify } from "@/utils/reactflowUtils";
import { useEffect } from "react";
-import { useNavigate, useParams } from "react-router-dom";
+import { useBlocker, useNavigate, useParams } from "react-router-dom";
import FlowToolbar from "../../components/chatComponent";
import Header from "../../components/headerComponent";
import { useDarkStore } from "../../stores/darkStore";
@@ -11,34 +14,66 @@ import Page from "./components/PageComponent";
import ExtraSidebar from "./components/extraSidebarComponent";
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
- const setCurrentFlowId = useFlowsManagerStore(
- (state) => state.setCurrentFlowId,
- );
+ const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
+ const currentFlow = useFlowStore((state) => state.currentFlow);
+ const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
+
+ const changesNotSaved =
+ customStringify(currentFlow) !== customStringify(currentSavedFlow);
+
+ const blocker = useBlocker(changesNotSaved);
const version = useDarkStore((state) => state.version);
const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
- const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const { id } = useParams();
const navigate = useNavigate();
useGetGlobalVariables();
+ const saveFlow = useSaveFlow();
const flows = useFlowsManagerStore((state) => state.flows);
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+
+ const handleSave = () => {
+ saveFlow().then(() => (blocker.proceed ? blocker.proceed() : null));
+ };
+
+ useEffect(() => {
+ const handleBeforeUnload = (event: BeforeUnloadEvent) => {
+ if (changesNotSaved) {
+ event.preventDefault();
+ event.returnValue = ""; // Required for Chrome
+ }
+ };
+
+ window.addEventListener("beforeunload", handleBeforeUnload);
+
+ return () => {
+ window.removeEventListener("beforeunload", handleBeforeUnload);
+ };
+ }, [changesNotSaved, navigate]);
// Set flow tab id
useEffect(() => {
- const isAnExistingFlow = flows.some((flow) => flow.id === id);
+ if (flows && currentFlowId === "") {
+ const isAnExistingFlow = flows.find((flow) => flow.id === id);
- if (!isAnExistingFlow) {
- navigate("/all");
- return;
+ if (!isAnExistingFlow) {
+ navigate("/all");
+ return;
+ }
+
+ setCurrentFlow(isAnExistingFlow);
}
+ }, [id, flows]);
- setCurrentFlowId(id!);
+ useEffect(() => {
setOnFlowPage(true);
return () => {
setOnFlowPage(false);
+ setCurrentFlow();
};
}, [id]);
+
return (
<>
@@ -49,7 +84,7 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
{/* Primary column */}
{!view && }
@@ -66,6 +101,13 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
⛓️ v{version}
(blocker.reset ? blocker.reset() : null)}
+ onProceed={() => (blocker.proceed ? blocker.proceed() : null)}
+ />
+ )}
>
);
}
diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx
index ba05f3c0a9b..2184149a78d 100644
--- a/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx
+++ b/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx
@@ -89,7 +89,7 @@ export default function ComponentsComponent({
handleSelectAll(false);
setShouldSelectAll(true);
getFolderById(folderId ? folderId : myCollectionId);
- }, [location]);
+ }, [location, folderId, myCollectionId]);
useFilteredFlows(flowsFromFolder!, searchFlowsComponents, setAllFlows);
diff --git a/src/frontend/src/pages/MainPage/pages/mainPage/index.tsx b/src/frontend/src/pages/MainPage/pages/mainPage/index.tsx
index 98d26452c0e..6847ad5794c 100644
--- a/src/frontend/src/pages/MainPage/pages/mainPage/index.tsx
+++ b/src/frontend/src/pages/MainPage/pages/mainPage/index.tsx
@@ -1,6 +1,6 @@
import { useDeleteFolders } from "@/controllers/API/queries/folders";
import useAlertStore from "@/stores/alertStore";
-import { useEffect, useState } from "react";
+import { useState } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import DropdownButton from "../../../../components/dropdownButtonComponent";
import PageLayout from "../../../../components/pageLayout";
@@ -9,17 +9,12 @@ import {
MY_COLLECTION_DESC,
USER_PROJECTS_HEADER,
} from "../../../../constants/constants";
-import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useFolderStore } from "../../../../stores/foldersStore";
import ModalsComponent from "../../components/modalsComponent";
import useDropdownOptions from "../../hooks/use-dropdown-options";
import { getFolderById } from "../../services";
export default function HomePage(): JSX.Element {
- const setCurrentFlowId = useFlowsManagerStore(
- (state) => state.setCurrentFlowId,
- );
-
const location = useLocation();
const pathname = location.pathname;
const [openModal, setOpenModal] = useState(false);
@@ -35,10 +30,6 @@ export default function HomePage(): JSX.Element {
const myCollectionId = useFolderStore((state) => state.myCollectionId);
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
- useEffect(() => {
- setCurrentFlowId("");
- }, [pathname]);
-
const dropdownOptions = useDropdownOptions({
navigate,
is_component,
diff --git a/src/frontend/src/pages/Playground/index.tsx b/src/frontend/src/pages/Playground/index.tsx
index 4ea3cc506db..47c2aa17744 100644
--- a/src/frontend/src/pages/Playground/index.tsx
+++ b/src/frontend/src/pages/Playground/index.tsx
@@ -1,26 +1,20 @@
-import { useEffect, useState } from "react";
+import { useStoreStore } from "@/stores/storeStore";
+import { useEffect } from "react";
import { useParams } from "react-router-dom";
import LoadingComponent from "../../components/loadingComponent";
import { getComponent } from "../../controllers/API";
import IOModal from "../../modals/IOModal";
-import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import cloneFLowWithParent from "../../utils/storeUtils";
export default function PlaygroundPage() {
- const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
- const setCurrentFlowId = useFlowsManagerStore(
- (state) => state.setCurrentFlowId,
- );
- const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+ const flows = useFlowsManagerStore((state) => state.flows);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
- const setNodes = useFlowStore((state) => state.setNodes);
- const setEdges = useFlowStore((state) => state.setEdges);
- const cleanFlowPool = useFlowStore((state) => state.CleanFlowPool);
+ const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
+ const validApiKey = useStoreStore((state) => state.validApiKey);
const { id } = useParams();
- const [loading, setLoading] = useState(true);
async function getFlowData() {
const res = await getComponent(id!);
const newFlow = cloneFLowWithParent(res, res.id, false, true);
@@ -29,32 +23,22 @@ export default function PlaygroundPage() {
// Set flow tab id
useEffect(() => {
- if (getFlowById(id!)) {
- setCurrentFlowId(id!);
- } else {
- getFlowData().then((flow) => {
+ if (flows) {
+ const flow = getFlowById(id!);
+ if (flow) {
setCurrentFlow(flow);
- });
- }
- }, [id]);
-
- useEffect(() => {
- if (currentFlow) {
- setNodes(currentFlow?.data?.nodes ?? [], true);
- setEdges(currentFlow?.data?.edges ?? [], true);
- cleanFlowPool();
- setLoading(false);
+ } else {
+ if (validApiKey)
+ getFlowData().then((flow) => {
+ setCurrentFlow(flow);
+ });
+ }
}
- return () => {
- setNodes([], true);
- setEdges([], true);
- cleanFlowPool();
- };
- }, [currentFlow]);
+ }, [id, flows, validApiKey]);
return (
- {loading ? (
+ {!currentSavedFlow ? (
diff --git a/src/frontend/src/pages/ProfileSettingsPage/index.tsx b/src/frontend/src/pages/ProfileSettingsPage/index.tsx
index 5d0135d9f62..ef8beadaa5a 100644
--- a/src/frontend/src/pages/ProfileSettingsPage/index.tsx
+++ b/src/frontend/src/pages/ProfileSettingsPage/index.tsx
@@ -4,7 +4,7 @@ import {
} from "@/controllers/API/queries/auth";
import * as Form from "@radix-ui/react-form";
import { cloneDeep } from "lodash";
-import { useContext, useEffect, useState } from "react";
+import { useContext, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import InputComponent from "../../components/inputComponent";
@@ -18,7 +18,6 @@ import {
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import useAlertStore from "../../stores/alertStore";
-import useFlowsManagerStore from "../../stores/flowsManagerStore";
import {
inputHandlerEventType,
patchUserInputStateType,
@@ -26,18 +25,9 @@ import {
import { gradients } from "../../utils/styleUtils";
import GradientChooserComponent from "../SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent";
export default function ProfileSettingsPage(): JSX.Element {
- const setCurrentFlowId = useFlowsManagerStore(
- (state) => state.setCurrentFlowId,
- );
-
const [inputState, setInputState] = useState
(
CONTROL_PATCH_USER_STATE,
);
-
- // set null id
- useEffect(() => {
- setCurrentFlowId("");
- }, []);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { userData, setUserData } = useContext(AuthContext);
diff --git a/src/frontend/src/pages/SettingsPage/index.tsx b/src/frontend/src/pages/SettingsPage/index.tsx
index bb08417d4d6..297aba41874 100644
--- a/src/frontend/src/pages/SettingsPage/index.tsx
+++ b/src/frontend/src/pages/SettingsPage/index.tsx
@@ -1,19 +1,12 @@
import FeatureFlags from "@/../feature-config.json";
import useAuthStore from "@/stores/authStore";
import { useStoreStore } from "@/stores/storeStore";
-import { useEffect } from "react";
import { Outlet } from "react-router-dom";
import ForwardedIconComponent from "../../components/genericIconComponent";
import PageLayout from "../../components/pageLayout";
import SidebarNav from "../../components/sidebarComponent";
-import useFlowsManagerStore from "../../stores/flowsManagerStore";
export default function SettingsPage(): JSX.Element {
- const pathname = location.pathname;
- const setCurrentFlowId = useFlowsManagerStore(
- (state) => state.setCurrentFlowId,
- );
-
const autoLogin = useAuthStore((state) => state.autoLogin);
const hasStore = useStoreStore((state) => state.hasStore);
@@ -21,10 +14,6 @@ export default function SettingsPage(): JSX.Element {
const showGeneralSettings =
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
- useEffect(() => {
- setCurrentFlowId("");
- }, [pathname]);
-
const sidebarNavItems: {
href?: string;
title: string;
diff --git a/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx
index 5218edb5d22..a4570f7c5d1 100644
--- a/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx
+++ b/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx
@@ -18,7 +18,6 @@ import { useParams } from "react-router-dom";
import { CONTROL_PATCH_USER_STATE } from "../../../../constants/constants";
import { AuthContext } from "../../../../contexts/authContext";
import useAlertStore from "../../../../stores/alertStore";
-import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useStoreStore } from "../../../../stores/storeStore";
import {
inputHandlerEventType,
@@ -31,10 +30,6 @@ import ProfilePictureFormComponent from "./components/ProfilePictureForm";
import StoreApiKeyFormComponent from "./components/StoreApiKeyForm";
export const GeneralPage = () => {
- const setCurrentFlowId = useFlowsManagerStore(
- (state) => state.setCurrentFlowId,
- );
-
const { scrollId } = useParams();
const [inputState, setInputState] = useState(
@@ -112,7 +107,7 @@ export const GeneralPage = () => {
}
};
- useScrollToElement(scrollId, setCurrentFlowId);
+ useScrollToElement(scrollId);
const { mutate } = usePostAddApiKey({
onSuccess: () => {
diff --git a/src/frontend/src/pages/SettingsPage/pages/hooks/use-scroll-to-element.tsx b/src/frontend/src/pages/SettingsPage/pages/hooks/use-scroll-to-element.tsx
index faa7408a333..966d4bb3481 100644
--- a/src/frontend/src/pages/SettingsPage/pages/hooks/use-scroll-to-element.tsx
+++ b/src/frontend/src/pages/SettingsPage/pages/hooks/use-scroll-to-element.tsx
@@ -1,9 +1,6 @@
import { useEffect } from "react";
-const useScrollToElement = (
- scrollId: string | null | undefined,
- setCurrentFlowId: (currentFlowId: string) => void,
-) => {
+const useScrollToElement = (scrollId: string | null | undefined) => {
useEffect(() => {
const element = document.getElementById(scrollId ?? "null");
if (element) {
@@ -11,10 +8,6 @@ const useScrollToElement = (
element.scrollIntoView({ behavior: "smooth" });
}
}, [scrollId]);
-
- useEffect(() => {
- setCurrentFlowId("");
- }, [setCurrentFlowId]);
};
export default useScrollToElement;
diff --git a/src/frontend/src/pages/StorePage/index.tsx b/src/frontend/src/pages/StorePage/index.tsx
index 36e6ce08de1..5342eb248ee 100644
--- a/src/frontend/src/pages/StorePage/index.tsx
+++ b/src/frontend/src/pages/StorePage/index.tsx
@@ -28,7 +28,7 @@ import {
} from "../../constants/alerts_constants";
import { STORE_DESC, STORE_TITLE } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
-import { getStoreComponents, getStoreTags } from "../../controllers/API";
+import { getStoreComponents } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
@@ -46,9 +46,6 @@ export default function StorePage(): JSX.Element {
const { apiKey } = useContext(AuthContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
- const setCurrentFlowId = useFlowsManagerStore(
- (state) => state.setCurrentFlowId,
- );
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const [loading, setLoading] = useState(true);
const { id } = useParams();
@@ -148,11 +145,6 @@ export default function StorePage(): JSX.Element {
});
}
- // Set a null id
- useEffect(() => {
- setCurrentFlowId("");
- }, []);
-
function resetPagination() {
setPageIndex(1);
setPageSize(12);
diff --git a/src/frontend/src/pages/ViewPage/index.tsx b/src/frontend/src/pages/ViewPage/index.tsx
index 5498b77bd1e..456e8c35d77 100644
--- a/src/frontend/src/pages/ViewPage/index.tsx
+++ b/src/frontend/src/pages/ViewPage/index.tsx
@@ -1,23 +1,47 @@
+import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
+import useFlowStore from "@/stores/flowStore";
import { useEffect } from "react";
-import { useParams } from "react-router-dom";
+import { useNavigate, useParams } from "react-router-dom";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "../FlowPage/components/PageComponent";
export default function ViewPage() {
- const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
- const setCurrentFlowId = useFlowsManagerStore(
- (state) => state.setCurrentFlowId,
- );
+ const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
+
+ const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
const { id } = useParams();
+ const navigate = useNavigate();
+ useGetGlobalVariables();
+
+ const flows = useFlowsManagerStore((state) => state.flows);
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
// Set flow tab id
useEffect(() => {
- setCurrentFlowId(id!);
+ if (flows && currentFlowId === "") {
+ const isAnExistingFlow = flows.find((flow) => flow.id === id);
+
+ if (!isAnExistingFlow) {
+ navigate("/all");
+ return;
+ }
+
+ setCurrentFlow(isAnExistingFlow);
+ }
+ }, [id, flows]);
+
+ useEffect(() => {
+ setOnFlowPage(true);
+
+ return () => {
+ setOnFlowPage(false);
+ setCurrentFlow();
+ };
}, [id]);
return (
);
}
diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx
index fd2bc6bda0f..028b44e23c1 100644
--- a/src/frontend/src/routes.tsx
+++ b/src/frontend/src/routes.tsx
@@ -1,16 +1,20 @@
-import FeatureFlags from "@/../feature-config.json";
-import useAuthStore from "@/stores/authStore";
-import { useStoreStore } from "@/stores/storeStore";
-import { Suspense, lazy } from "react";
-import { Navigate, Route, Routes } from "react-router-dom";
+import { lazy } from "react";
+import {
+ createBrowserRouter,
+ createRoutesFromElements,
+ Navigate,
+ Route,
+} from "react-router-dom";
+import App from "./App";
import { ProtectedAdminRoute } from "./components/authAdminGuard";
import { ProtectedRoute } from "./components/authGuard";
import { ProtectedLoginRoute } from "./components/authLoginGuard";
+import { AuthSettingsGuard } from "./components/authSettingsGuard";
import { CatchAllRoute } from "./components/catchAllRoutes";
-import LoadingComponent from "./components/loadingComponent";
import { StoreGuard } from "./components/storeGuard";
-import MessagesPage from "./pages/SettingsPage/pages/messagesPage";
-
+const MessagesPage = lazy(
+ () => import("./pages/SettingsPage/pages/messagesPage"),
+);
const AdminPage = lazy(() => import("./pages/AdminPage"));
const LoginAdminPage = lazy(() => import("./pages/AdminPage/LoginPage"));
const ApiKeysPage = lazy(
@@ -38,184 +42,161 @@ const SignUp = lazy(() => import("./pages/SignUpPage"));
const StorePage = lazy(() => import("./pages/StorePage"));
const ViewPage = lazy(() => import("./pages/ViewPage"));
-const Router = () => {
- const autoLogin = useAuthStore((state) => state.autoLogin);
- const hasStore = useStoreStore((state) => state.hasStore);
-
- // Hides the General settings if there is nothing to show
- const showGeneralSettings =
- FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
-
- return (
-
-
-
- }
- >
-
+const router = createBrowserRouter(
+ createRoutesFromElements([
+ }>
+
+
+
+ }
+ >
+ } />
-
-
- }
- >
- } />
- }
- />
-
- }
- />
- }
- />
-
+ path="flows/*"
+ element={}
+ />
-
-
- }
- >
-
- }
- />
- } />
- } />
- {showGeneralSettings && (
- } />
- )}
- } />
- } />
-
+ path="components/*"
+ element={}
+ />
+ }
+ />
+
+
+
+
+ }
+ >
+ } />
+ } />
+ } />
-
-
-
-
+
+
+
}
/>
+ } />
+ } />
+
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ />
+
-
-
-
+
}
/>
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
+
+
-
+
}
/>
-
-
-
-
- }
- />
-
-
+
+
+
}
/>
-
-
+
+
+
}
/>
-
+
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
-
-
+
+
+
}
- />
-
-
-
-
-
- }
- >
-
-
-
- );
-};
+ >
+
+ ,
+ ]),
+);
-export default Router;
+export default router;
diff --git a/src/frontend/src/stores/darkStore.ts b/src/frontend/src/stores/darkStore.ts
index a652525925a..6e3c28afa56 100644
--- a/src/frontend/src/stores/darkStore.ts
+++ b/src/frontend/src/stores/darkStore.ts
@@ -1,5 +1,5 @@
import { create } from "zustand";
-import { getRepoStars, getVersion } from "../controllers/API";
+import { getRepoStars } from "../controllers/API";
import { DarkStoreType } from "../types/zustand/dark";
const startedStars = Number(window.localStorage.getItem("githubStars")) ?? 0;
diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts
index 489cfb1ead0..3a8289e2e50 100644
--- a/src/frontend/src/stores/flowStore.ts
+++ b/src/frontend/src/stores/flowStore.ts
@@ -19,7 +19,6 @@ import {
MISSED_ERROR_ALERT,
} from "../constants/alerts_constants";
import { BuildStatus } from "../constants/enums";
-import { getFlowPool } from "../controllers/API";
import { VertexBuildTypeAPI } from "../types/api";
import { ChatInputType, ChatOutputType } from "../types/chat";
import {
@@ -51,6 +50,7 @@ import { useTypesStore } from "./typesStore";
// this is our useStore hook that we can use in our components to get parts of the store and call actions
const useFlowStore = create((set, get) => ({
+ autoSaveFlow: undefined,
componentsToUpdate: false,
updateComponentsToUpdate: (nodes) => {
let outdatedNodes = false;
@@ -153,8 +153,10 @@ const useFlowStore = create((set, get) => ({
setPending: (isPending) => {
set({ isPending });
},
- resetFlow: ({ nodes, edges, viewport }) => {
- const currentFlow = useFlowsManagerStore.getState().currentFlow;
+ resetFlow: (flow) => {
+ const nodes = flow?.data?.nodes ?? [];
+ const edges = flow?.data?.edges ?? [];
+ const viewport = flow?.data?.viewport ?? { zoom: 1, x: 0, y: 0 };
let brokenEdges = detectBrokenEdgesEdges(nodes, edges);
if (brokenEdges.length > 0) {
useAlertStore.getState().setErrorData({
@@ -172,13 +174,10 @@ const useFlowStore = create((set, get) => ({
inputs,
outputs,
hasIO: inputs.length > 0 || outputs.length > 0,
+ flowPool: {},
+ currentFlow: flow,
});
- get().reactFlowInstance!.setViewport(viewport);
- if (currentFlow) {
- getFlowPool({ flowId: currentFlow.id }).then((flowPool) => {
- set({ flowPool: flowPool.data.vertex_builds });
- });
- }
+ get().reactFlowInstance?.setViewport(viewport);
},
setIsBuilding: (isBuilding) => {
set({ isBuilding });
@@ -195,6 +194,16 @@ const useFlowStore = create((set, get) => ({
},
setReactFlowInstance: (newState) => {
set({ reactFlowInstance: newState });
+ const viewport = get().currentFlow?.data?.viewport ?? {
+ zoom: 1,
+ x: 0,
+ y: 0,
+ };
+ if (viewport.x == 0 && viewport.y == 0) {
+ newState.fitView();
+ } else {
+ newState.setViewport(viewport);
+ }
},
onNodesChange: (changes: NodeChange[]) => {
set({
@@ -206,7 +215,7 @@ const useFlowStore = create((set, get) => ({
edges: applyEdgeChanges(changes, get().edges),
});
},
- setNodes: (change, skipSave = false) => {
+ setNodes: (change) => {
let newChange = typeof change === "function" ? change(get().nodes) : change;
let newEdges = cleanEdges(newChange, get().edges);
const { inputs, outputs } = getInputsAndOutputs(newChange);
@@ -219,30 +228,18 @@ const useFlowStore = create((set, get) => ({
outputs,
hasIO: inputs.length > 0 || outputs.length > 0,
});
-
- const flowsManager = useFlowsManagerStore.getState();
- if (!get().isBuilding && !skipSave && get().onFlowPage) {
- flowsManager.autoSaveCurrentFlow(
- newChange,
- newEdges,
- get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 },
- );
+ if (get().autoSaveFlow) {
+ get().autoSaveFlow!();
}
},
- setEdges: (change, skipSave = false) => {
+ setEdges: (change) => {
let newChange = typeof change === "function" ? change(get().edges) : change;
set({
edges: newChange,
flowState: undefined,
});
-
- const flowsManager = useFlowsManagerStore.getState();
- if (!get().isBuilding && !skipSave && get().onFlowPage) {
- flowsManager.autoSaveCurrentFlow(
- get().nodes,
- newChange,
- get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 },
- );
+ if (get().autoSaveFlow) {
+ get().autoSaveFlow!();
}
},
setNode: (
@@ -485,13 +482,6 @@ const useFlowStore = create((set, get) => ({
return newEdges;
});
- useFlowsManagerStore
- .getState()
- .autoSaveCurrentFlow(
- get().nodes,
- newEdges,
- get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 },
- );
},
unselectAll: () => {
let newNodes = cloneDeep(get().nodes);
@@ -672,8 +662,9 @@ const useFlowStore = create((set, get) => ({
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
},
onValidateNodes: validateSubgraph,
- nodes: get().onFlowPage ? get().nodes : undefined,
- edges: get().onFlowPage ? get().edges : undefined,
+ nodes: get().nodes || undefined,
+ edges: get().edges || undefined,
+ logBuilds: get().onFlowPage,
});
get().setIsBuilding(false);
get().setLockChat(false);
@@ -744,6 +735,27 @@ const useFlowStore = create((set, get) => ({
});
set({ flowBuildStatus: newFlowBuildStatus });
},
+ currentFlow: undefined,
+ setCurrentFlow: (flow) => {
+ set({ currentFlow: flow });
+ },
+ updateCurrentFlow: ({ nodes, edges, viewport }) => {
+ set({
+ currentFlow: {
+ ...get().currentFlow!,
+ data: {
+ nodes: nodes ?? get().currentFlow?.data?.nodes ?? [],
+ edges: edges ?? get().currentFlow?.data?.edges ?? [],
+ viewport: viewport ??
+ get().currentFlow?.data?.viewport ?? {
+ x: 0,
+ y: 0,
+ zoom: 1,
+ },
+ },
+ },
+ });
+ },
}));
export default useFlowStore;
diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts
index 1c2c69c3730..5b5bb451b03 100644
--- a/src/frontend/src/stores/flowsManagerStore.ts
+++ b/src/frontend/src/stores/flowsManagerStore.ts
@@ -1,13 +1,6 @@
-import { AxiosError } from "axios";
import { cloneDeep } from "lodash";
-import pDebounce from "p-debounce";
-import { Edge, Node, Viewport } from "reactflow";
import { create } from "zustand";
-import { SAVE_DEBOUNCE_TIME } from "../constants/constants";
-import {
- readFlowsFromDatabase,
- updateFlowInDatabase,
-} from "../controllers/API";
+import { readFlowsFromDatabase } from "../controllers/API";
import { FlowType } from "../types/flow";
import {
FlowsManagerStoreType,
@@ -36,22 +29,17 @@ const useFlowsManagerStore = create((set, get) => ({
set({ examples });
},
currentFlowId: "",
- setCurrentFlow: (flow: FlowType) => {
- set((state) => ({
+ setCurrentFlow: (flow: FlowType | undefined) => {
+ set({
currentFlow: flow,
- currentFlowId: flow.id,
- }));
+ currentFlowId: flow?.id ?? "",
+ });
+ useFlowStore.getState().resetFlow(flow);
},
getFlowById: (id: string) => {
- return get().flows.find((flow) => flow.id === id);
+ return get().flows?.find((flow) => flow.id === id);
},
- setCurrentFlowId: (currentFlowId: string) => {
- set((state) => ({
- currentFlowId,
- currentFlow: state.flows.find((flow) => flow.id === currentFlowId),
- }));
- },
- flows: [],
+ flows: undefined,
allFlows: [],
setAllFlows: (allFlows: FlowType[]) => {
set({ allFlows });
@@ -107,60 +95,6 @@ const useFlowsManagerStore = create((set, get) => ({
});
});
},
- autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => {
- if (get().currentFlow) {
- get().saveFlow(
- { ...get().currentFlow!, data: { nodes, edges, viewport } },
- true,
- );
- }
- },
- saveFlow: (flow: FlowType, silent?: boolean) => {
- set({ saveLoading: true }); // set saveLoading true immediately
- return get().saveFlowDebounce(flow, silent); // call the debounced function directly
- },
- saveFlowDebounce: pDebounce((flow: FlowType, silent?: boolean) => {
- const folderUrl = useFolderStore.getState().folderUrl;
- const hasFolderUrl = folderUrl != null && folderUrl !== "";
-
- flow.folder_id = hasFolderUrl
- ? useFolderStore.getState().folderUrl
- : useFolderStore.getState().myCollectionId ?? "";
-
- set({ saveLoading: true });
- return new Promise((resolve, reject) => {
- updateFlowInDatabase(flow)
- .then((updatedFlow) => {
- if (updatedFlow) {
- // updates flow in state
- if (!silent) {
- useAlertStore
- .getState()
- .setSuccessData({ title: "Changes saved successfully" });
- }
- get().setFlows(
- get().flows.map((flow) => {
- if (flow.id === updatedFlow.id) {
- return updatedFlow;
- }
- return flow;
- }),
- );
- //update tabs state
-
- resolve();
- set({ saveLoading: false });
- }
- })
- .catch((err) => {
- useAlertStore.getState().setErrorData({
- title: "Error while saving changes",
- list: [(err as AxiosError).message],
- });
- reject(err);
- });
- });
- }, SAVE_DEBOUNCE_TIME),
takeSnapshot: () => {
const currentFlowId = get().currentFlowId;
// push the current graph to the past state
diff --git a/src/frontend/src/stores/shortcuts.ts b/src/frontend/src/stores/shortcuts.ts
index 774d0ec273a..98702e0054d 100644
--- a/src/frontend/src/stores/shortcuts.ts
+++ b/src/frontend/src/stores/shortcuts.ts
@@ -20,7 +20,8 @@ export const useShortcutsStore = create((set, get) => ({
duplicate: "mod+d",
component: "mod+shift+s",
docs: "mod+shift+d",
- save: "mod+s",
+ changes: "mod+s",
+ save: "mod+alt+s",
delete: "backspace",
group: "mod+g",
cut: "mod+x",
diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts
index 058c6a83355..e65101ffeeb 100644
--- a/src/frontend/src/types/components/index.ts
+++ b/src/frontend/src/types/components/index.ts
@@ -435,6 +435,7 @@ export type ConfirmationModalType = {
title: string;
titleHeader?: string;
destructive?: boolean;
+ destructiveCancel?: boolean;
modalContentTitle?: string;
cancelText: string;
confirmationText: string;
@@ -446,7 +447,7 @@ export type ConfirmationModalType = {
index?: number;
onConfirm: (index, data) => void;
open?: boolean;
- onClose?: (close: boolean) => void;
+ onClose?: () => void;
size?:
| "x-small"
| "smaller"
diff --git a/src/frontend/src/types/store/index.ts b/src/frontend/src/types/store/index.ts
index c074f85c285..43254c8511a 100644
--- a/src/frontend/src/types/store/index.ts
+++ b/src/frontend/src/types/store/index.ts
@@ -38,6 +38,7 @@ export type shortcutsStoreType = {
duplicate: string;
component: string;
docs: string;
+ changes: string;
save: string;
delete: string;
update: string;
diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts
index 59ea979fad9..c27e056e24b 100644
--- a/src/frontend/src/types/zustand/flow/index.ts
+++ b/src/frontend/src/types/zustand/flow/index.ts
@@ -1,3 +1,4 @@
+import { FlowType } from "@/types/flow";
import {
Connection,
Edge,
@@ -53,6 +54,7 @@ export type FlowPoolType = {
};
export type FlowStoreType = {
+ autoSaveFlow: (() => void) | undefined;
componentsToUpdate: boolean;
updateComponentsToUpdate: (nodes: Node[]) => void;
onFlowPage: boolean;
@@ -83,11 +85,7 @@ export type FlowStoreType = {
isPending: boolean;
setIsBuilding: (isBuilding: boolean) => void;
setPending: (isPending: boolean) => void;
- resetFlow: (flow: {
- nodes: Node[];
- edges: Edge[];
- viewport: Viewport;
- }) => void;
+ resetFlow: (flow: FlowType | undefined) => void;
reactFlowInstance: ReactFlowInstance | null;
setReactFlowInstance: (newState: ReactFlowInstance) => void;
flowState: FlowState | undefined;
@@ -101,14 +99,8 @@ export type FlowStoreType = {
edges: Edge[];
onNodesChange: OnNodesChange;
onEdgesChange: OnEdgesChange;
- setNodes: (
- update: Node[] | ((oldState: Node[]) => Node[]),
- skipSave?: boolean,
- ) => void;
- setEdges: (
- update: Edge[] | ((oldState: Edge[]) => Edge[]),
- skipSave?: boolean,
- ) => void;
+ setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void;
+ setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void;
setNode: (
id: string,
update: Node | ((oldState: Node) => Node),
@@ -177,4 +169,15 @@ export type FlowStoreType = {
setLockChat: (lock: boolean) => void;
lockChat: boolean;
updateFreezeStatus: (nodeIds: string[], freeze: boolean) => void;
+ currentFlow: FlowType | undefined;
+ setCurrentFlow: (flow: FlowType | undefined) => void;
+ updateCurrentFlow: ({
+ nodes,
+ edges,
+ viewport,
+ }: {
+ nodes?: Node[];
+ edges?: Edge[];
+ viewport?: Viewport;
+ }) => void;
};
diff --git a/src/frontend/src/types/zustand/flowsManager/index.ts b/src/frontend/src/types/zustand/flowsManager/index.ts
index c99c090bb69..ae1252f26eb 100644
--- a/src/frontend/src/types/zustand/flowsManager/index.ts
+++ b/src/frontend/src/types/zustand/flowsManager/index.ts
@@ -1,41 +1,24 @@
-import { Edge, Node, Viewport } from "reactflow";
import { FlowType } from "../../flow";
export type FlowsManagerStoreType = {
getFlowById: (id: string) => FlowType | undefined;
- flows: Array;
+ flows: Array | undefined;
allFlows: Array;
setAllFlows: (flows: FlowType[]) => void;
setFlows: (flows: FlowType[]) => void;
currentFlow: FlowType | undefined;
currentFlowId: string;
- setCurrentFlowId: (currentFlowId: string) => void;
saveLoading: boolean;
setSaveLoading: (saveLoading: boolean) => void;
isLoading: boolean;
setIsLoading: (isLoading: boolean) => void;
refreshFlows: () => Promise;
- saveFlow: (
- flow: FlowType,
- silent?: boolean,
- folderId?: string,
- ) => Promise | undefined;
- saveFlowDebounce: (
- flow: FlowType,
- silent?: boolean,
- folderId?: string,
- ) => Promise | undefined;
- autoSaveCurrentFlow: (
- nodes: Node[],
- edges: Edge[],
- viewport: Viewport,
- ) => void;
undo: () => void;
redo: () => void;
takeSnapshot: () => void;
examples: Array;
setExamples: (examples: FlowType[]) => void;
- setCurrentFlow: (flow: FlowType) => void;
+ setCurrentFlow: (flow?: FlowType) => void;
setSearchFlowsComponents: (search: string) => void;
searchFlowsComponents: string;
selectedFlowsComponentsCards: string[];
diff --git a/src/frontend/src/utils/buildUtils.ts b/src/frontend/src/utils/buildUtils.ts
index 77dbb8054e7..1a6a9f31c98 100644
--- a/src/frontend/src/utils/buildUtils.ts
+++ b/src/frontend/src/utils/buildUtils.ts
@@ -30,6 +30,7 @@ type BuildVerticesParams = {
onValidateNodes?: (nodes: string[]) => void;
nodes?: Node[];
edges?: Edge[];
+ logBuilds?: boolean;
};
function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
@@ -146,6 +147,7 @@ export async function buildFlowVertices({
onValidateNodes,
nodes,
edges,
+ logBuilds,
setLockChat,
}: BuildVerticesParams) {
let url = `${BASE_URL_API}build/${flowId}/flow?`;
@@ -155,6 +157,9 @@ export async function buildFlowVertices({
if (stopNodeId) {
url = `${url}&stop_component_id=${stopNodeId}`;
}
+ if (logBuilds !== undefined) {
+ url = `${url}&log_builds=${logBuilds}`;
+ }
const postData = {};
if (typeof input_value !== "undefined") {
postData["inputs"] = { input_value: input_value };
diff --git a/src/frontend/tests/end-to-end/flowSettings.spec.ts b/src/frontend/tests/end-to-end/flowSettings.spec.ts
index 062bb5bea62..04dd0694ab4 100644
--- a/src/frontend/tests/end-to-end/flowSettings.spec.ts
+++ b/src/frontend/tests/end-to-end/flowSettings.spec.ts
@@ -47,10 +47,6 @@ test("flowSettings", async ({ page }) => {
await page.getByTestId("save-flow-settings").click();
- await page.getByText("Close").last().click();
-
- await page.waitForTimeout(1000);
-
await page.getByText("Changes saved successfully").isVisible();
await page.getByTestId("flow_name").click();
diff --git a/src/frontend/tests/end-to-end/store-shard-2.spec.ts b/src/frontend/tests/end-to-end/store-shard-2.spec.ts
index 9c017b8219a..c5f4881e80b 100644
--- a/src/frontend/tests/end-to-end/store-shard-2.spec.ts
+++ b/src/frontend/tests/end-to-end/store-shard-2.spec.ts
@@ -1,4 +1,4 @@
-import { expect, test } from "@playwright/test";
+import { test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
@@ -111,7 +111,6 @@ test("should share component with share button", async ({ page }) => {
.inputValue();
await page.getByPlaceholder("Flow name").fill(randomName);
await page.getByText("Save").last().click();
- await page.getByText("Close").last().click();
await page.waitForSelector('[data-testid="shared-button-flow"]', {
timeout: 100000,
diff --git a/src/frontend/vite.config.mts b/src/frontend/vite.config.mts
index 24e4c179540..43b0d85adaa 100644
--- a/src/frontend/vite.config.mts
+++ b/src/frontend/vite.config.mts
@@ -31,6 +31,9 @@ export default defineConfig(({ mode }) => {
outDir: "build",
},
define: {
+ "process.env.LANGFLOW_AUTO_SAVE": JSON.stringify(
+ process.env.LANGFLOW_AUTO_SAVE,
+ ),
"process.env.BACKEND_URL": JSON.stringify(process.env.BACKEND_URL),
"process.env.ACCESS_TOKEN_EXPIRE_SECONDS": JSON.stringify(
process.env.ACCESS_TOKEN_EXPIRE_SECONDS,