feat(ui): Add dedicated update route and refactor update dialog state management

This commit is contained in:
Adam Shiervani 2025-02-26 00:13:02 +01:00
parent a2652c5265
commit bb4ee2a6c7
6 changed files with 62 additions and 38 deletions

View File

@ -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 (
<div className="w-full transition-all duration-300 ease-in-out opacity-100 select-none">
<div className="w-full select-none opacity-100 transition-all duration-300 ease-in-out">
<GridCard cardClassName="!shadow-xl">
<div className="flex items-center justify-between gap-x-3 px-2.5 py-2.5 text-black dark:text-white">
<div className="flex items-center gap-x-3">
<LoadingSpinner className={cx("h-5 w-5", "shrink-0 text-blue-700")} />
<div className="space-y-1">
<div className="text-sm font-semibold leading-none transition text-ellipsis">
<div className="text-ellipsis text-sm font-semibold leading-none transition">
Update in Progress
</div>
<div className="text-sm leading-none">
@ -39,7 +35,7 @@ export default function UpdateInProgressStatusCard({
text="View Details"
onClick={() => {
setModalView("updating");
setIsUpdateDialogOpen(true);
navigate("update");
}}
/>
</div>

View File

@ -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");
}
};

View File

@ -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<UpdateState>(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 }),
}));

View File

@ -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: <OtherSessionRoute />,
},
{
path: "update",
element: <UpdateRoute />,
},
],
},
@ -129,6 +134,10 @@ if (isOnDevice) {
path: "other-session",
element: <OtherSessionRoute />,
},
{
path: "update",
element: <UpdateRoute />,
},
],
},
{

View File

@ -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 (
<>
<Transition show={!isUpdateDialogOpen && otaState.updating}>
<div className="pointer-events-none fixed inset-0 z-10 mx-auto flex h-full w-full max-w-xl translate-y-8 items-start justify-center">
<div className="transition duration-1000 ease-in data-[closed]:opacity-0">
<UpdateInProgressStatusCard
setIsUpdateDialogOpen={setIsUpdateDialogOpen}
setModalView={setModalView}
/>
<UpdateInProgressStatusCard />
</div>
</div>
</Transition>
@ -494,8 +485,6 @@ export default function KvmIdRoute() {
<Outlet context={{ connectWebRTC }} />
</Modal>
<UpdateDialog open={isUpdateDialogOpen} setOpen={setIsUpdateDialogOpen} />
{kvmTerminal && (
<Terminal type="kvm" dataChannel={kvmTerminal} title="KVM Terminal" />
)}

View File

@ -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 (
<GridCard cardClassName="relative mx-auto max-w-md text-left pointer-events-auto">
{/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */}
<Dialog
setOpen={open => {
if (!open) {
navigate("..");
}
}}
onConfirmUpdate={onConfirmUpdate}
/>
</GridCard>
);
}