From bb4ee2a6c75dd5547ab7414fb17c53982eff4858 Mon Sep 17 00:00:00 2001 From: Adam Shiervani Date: Wed, 26 Feb 2025 00:13:02 +0100 Subject: [PATCH] feat(ui): Add dedicated update route and refactor update dialog state management --- .../components/UpdateInProgressStatusCard.tsx | 20 +++++------- ui/src/components/sidebar/settings.tsx | 10 +++--- ui/src/hooks/stores.ts | 7 ++--- ui/src/main.tsx | 9 ++++++ ui/src/routes/devices.$id.tsx | 23 ++++---------- ui/src/routes/devices.$id.update.tsx | 31 +++++++++++++++++++ 6 files changed, 62 insertions(+), 38 deletions(-) create mode 100644 ui/src/routes/devices.$id.update.tsx diff --git a/ui/src/components/UpdateInProgressStatusCard.tsx b/ui/src/components/UpdateInProgressStatusCard.tsx index ac3dc67..006decf 100644 --- a/ui/src/components/UpdateInProgressStatusCard.tsx +++ b/ui/src/components/UpdateInProgressStatusCard.tsx @@ -2,25 +2,21 @@ import { cx } from "@/cva.config"; import { Button } from "./Button"; import { GridCard } from "./Card"; import LoadingSpinner from "./LoadingSpinner"; -import { UpdateState } from "@/hooks/stores"; +import { useNavigate } from "react-router-dom"; +import { useUpdateStore } from "@/hooks/stores"; -interface UpdateInProgressStatusCardProps { - setIsUpdateDialogOpen: (isOpen: boolean) => void; - setModalView: (view: UpdateState["modalView"]) => void; -} +export default function UpdateInProgressStatusCard() { + const navigate = useNavigate(); + const { setModalView } = useUpdateStore(); -export default function UpdateInProgressStatusCard({ - setIsUpdateDialogOpen, - setModalView, -}: UpdateInProgressStatusCardProps) { return ( -
+
-
+
Update in Progress
@@ -39,7 +35,7 @@ export default function UpdateInProgressStatusCard({ text="View Details" onClick={() => { setModalView("updating"); - setIsUpdateDialogOpen(true); + navigate("update"); }} />
diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index 340fe80..b7bd949 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -24,7 +24,7 @@ import notifications from "@/notifications"; import api from "../../api"; import LocalAuthPasswordDialog from "@/components/LocalAuthPasswordDialog"; import { LocalDevice } from "@routes/devices.$id"; -import { useRevalidator } from "react-router-dom"; +import { useRevalidator, useNavigate } from "react-router-dom"; import { ShieldCheckIcon } from "@heroicons/react/20/solid"; import { CLOUD_APP, DEVICE_API } from "@/ui.config"; import { InputFieldWithLabel } from "../InputField"; @@ -267,14 +267,16 @@ export default function SettingsSidebar() { }); }, [send, sshKey]); - const { setIsUpdateDialogOpen, setModalView, otaState } = useUpdateStore(); + const { setModalView, otaState } = useUpdateStore(); + const navigate = useNavigate(); + const handleCheckForUpdates = () => { if (otaState.updating) { setModalView("updating"); - setIsUpdateDialogOpen(true); + navigate("update"); } else { setModalView("loading"); - setIsUpdateDialogOpen(true); + navigate("update"); } }; diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts index 5b1366c..ad8cb2b 100644 --- a/ui/src/hooks/stores.ts +++ b/ui/src/hooks/stores.ts @@ -303,7 +303,8 @@ export const useSettingsStore = create( dim_after: 10000, off_after: 50000, }, - setBacklightSettings: (settings: BacklightSettings) => set({ backlightSettings: settings }), + setBacklightSettings: (settings: BacklightSettings) => + set({ backlightSettings: settings }), }), { name: "settings", @@ -484,8 +485,6 @@ export interface UpdateState { | "updateCompleted" | "error"; setModalView: (view: UpdateState["modalView"]) => void; - isUpdateDialogOpen: boolean; - setIsUpdateDialogOpen: (isOpen: boolean) => void; setUpdateErrorMessage: (errorMessage: string) => void; updateErrorMessage: string | null; } @@ -520,8 +519,6 @@ export const useUpdateStore = create(set => ({ set({ updateDialogHasBeenMinimized: hasBeenMinimized }), modalView: "loading", setModalView: view => set({ modalView: view }), - isUpdateDialogOpen: false, - setIsUpdateDialogOpen: isOpen => set({ isUpdateDialogOpen: isOpen }), updateErrorMessage: null, setUpdateErrorMessage: errorMessage => set({ updateErrorMessage: errorMessage }), })); diff --git a/ui/src/main.tsx b/ui/src/main.tsx index 9b3599a..dd47073 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -29,6 +29,7 @@ import WelcomeRoute from "./routes/welcome-local"; import WelcomeLocalPasswordRoute from "./routes/welcome-local.password"; import { CLOUD_API } from "./ui.config"; import OtherSessionRoute from "./routes/devices.$id.other-session"; +import UpdateRoute from "./routes/devices.$id.update"; export const isOnDevice = import.meta.env.MODE === "device"; export const isInCloud = !isOnDevice; @@ -81,6 +82,10 @@ if (isOnDevice) { path: "other-session", element: , }, + { + path: "update", + element: , + }, ], }, @@ -129,6 +134,10 @@ if (isOnDevice) { path: "other-session", element: , }, + { + path: "update", + element: , + }, ], }, { diff --git a/ui/src/routes/devices.$id.tsx b/ui/src/routes/devices.$id.tsx index d530e8c..142f29c 100644 --- a/ui/src/routes/devices.$id.tsx +++ b/ui/src/routes/devices.$id.tsx @@ -125,13 +125,7 @@ export default function KvmIdRoute() { const setTransceiver = useRTCStore(state => state.setTransceiver); const navigate = useNavigate(); - const { - otaState, - setOtaState, - isUpdateDialogOpen, - setIsUpdateDialogOpen, - setModalView, - } = useUpdateStore(); + const { otaState, setOtaState, setModalView } = useUpdateStore(); const sdp = useCallback( async (event: RTCPeerConnectionIceEvent, pc: RTCPeerConnection) => { @@ -356,7 +350,7 @@ export default function KvmIdRoute() { if (otaState.error) { setModalView("error"); - setIsUpdateDialogOpen(true); + navigate("update"); return; } @@ -387,10 +381,10 @@ export default function KvmIdRoute() { useEffect(() => { if (queryParams.get("updateSuccess")) { setModalView("updateCompleted"); - setIsUpdateDialogOpen(true); + navigate("update"); setQueryParams({}); } - }, [queryParams, setIsUpdateDialogOpen, setModalView, setQueryParams]); + }, [navigate, queryParams, setModalView, setQueryParams]); const diskChannel = useRTCStore(state => state.diskChannel)!; const file = useMountMediaStore(state => state.localFile)!; @@ -444,16 +438,13 @@ export default function KvmIdRoute() { }, [kvmTerminal]); const outlet = useOutlet(); - + const isUpdateDialogOpen = location.pathname.includes("/update"); return ( <>
- +
@@ -494,8 +485,6 @@ export default function KvmIdRoute() { - - {kvmTerminal && ( )} diff --git a/ui/src/routes/devices.$id.update.tsx b/ui/src/routes/devices.$id.update.tsx new file mode 100644 index 0000000..c8a6641 --- /dev/null +++ b/ui/src/routes/devices.$id.update.tsx @@ -0,0 +1,31 @@ +import { useNavigate } from "react-router-dom"; +import { GridCard } from "@/components/Card"; +import { useUpdateStore } from "@/hooks/stores"; +import { Dialog } from "@/components/UpdateDialog"; +import { useJsonRpc } from "@/hooks/useJsonRpc"; +import { useCallback } from "react"; + +export default function UpdateRoute() { + const navigate = useNavigate(); + const { setModalView } = useUpdateStore(); + const [send] = useJsonRpc(); + + const onConfirmUpdate = useCallback(() => { + send("tryUpdate", {}); + setModalView("updating"); + }, [send, setModalView]); + + return ( + + {/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */} + { + if (!open) { + navigate(".."); + } + }} + onConfirmUpdate={onConfirmUpdate} + /> + + ); +}