mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-01-20 02:04:15 +01:00
Move settings to modals & better modal handling (#194)
* feat(ui): Add other session handling route and modal * feat(ui): Add dedicated update route and refactor update dialog state management * feat(ui): Add local authentication route * refactor(ui): Remove LocalAuthPasswordDialog component and clean up related code * refactor(ui): Remove OtherSessionConnectedModal component * feat(ui): Add dedicated mount route and refactor mount media dialog * refactor(ui): Simplify Escape key navigation in device route * refactor(ui): Add TODO comments for future URL-based state migration * refactor(ui): Migrate settings and update routes to dedicated routes This commit introduces a comprehensive refactoring of the UI routing and state management: - Removed sidebar-based settings view - Replaced global modal state with URL-based routing - Added dedicated routes for settings, including general, security, and update sections - Simplified modal and sidebar interactions - Improved animation and transition handling using motion library - Removed deprecated components and simplified route structure * fix(ui): Add TODO comment for modal session interaction * refactor(ui): Move USB configuration to new settings setup This commit introduces several improvements to the USB configuration workflow: - Refactored USB configuration dialog component - Simplified USB config state management - Moved USB configuration to hardware settings route - Updated JSON-RPC type definitions - Cleaned up unused imports and components - Improved error handling and notifications * refactor(ui): Replace react-router-dom navigation with custom navigation hook This commit introduces a new custom navigation hook `useDeviceUiNavigation` to replace direct usage of `useNavigate` across multiple components: - Removed direct `useNavigate` imports in various components - Added `navigateTo` method from new navigation hook - Updated navigation calls in ActionBar, MountPopover, UpdateInProgressStatusCard, and other routes - Simplified navigation logic and prepared for potential future navigation enhancements - Removed console logs and unnecessary comments * refactor(ui): Remove unused react-router-dom import Clean up unnecessary import of `useNavigate` from react-router-dom in device settings route * feat(ui): Improve mobile navigation and scrolling in device settings * refactor(ui): Reorganize device access and security settings This commit introduces several changes to the device access and security settings: - Renamed "Security" section to "Access" in settings navigation - Moved local authentication routes from security to access - Removed deprecated security settings route - Added new route for device access settings with cloud and local authentication management - Updated cloud URL and adoption logic to be part of the access settings - Simplified routing and component structure for better user experience * fix(ui): Update logout button hover state color * fix(ui): Adjust device de-registration button size to small * fix(ui): Update appearance settings section header and description * refactor(ui): Replace SectionHeader with new SettingsPageHeader and SettingsSectionHeader components This commit introduces two new header components for settings pages: - Created SettingsPageHeader for main page headers - Created SettingsSectionHeader for subsection headers - Replaced all existing SectionHeader imports with new components - Updated styling and type definitions to support more flexible header rendering * feat(ui): Add dev channel toggle to advanced settings Move dev channel update option from general settings to advanced settings - Introduced new state and handler for dev channel toggle - Removed dev channel option from general settings route - Added dev channel toggle in advanced settings with error handling
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { cx } from "@/cva.config";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import {
|
||||
HidState,
|
||||
UpdateState,
|
||||
@@ -16,27 +15,30 @@ import {
|
||||
import WebRTCVideo from "@components/WebRTCVideo";
|
||||
import {
|
||||
LoaderFunctionArgs,
|
||||
Outlet,
|
||||
Params,
|
||||
redirect,
|
||||
useLoaderData,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useOutlet,
|
||||
useParams,
|
||||
useSearchParams,
|
||||
} from "react-router-dom";
|
||||
import { checkAuth, isInCloud, isOnDevice } from "@/main";
|
||||
import DashboardNavbar from "@components/Header";
|
||||
import { useInterval } from "usehooks-ts";
|
||||
import SettingsSidebar from "@/components/sidebar/settings";
|
||||
import ConnectionStatsSidebar from "@/components/sidebar/connectionStats";
|
||||
import { JsonRpcRequest, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import UpdateDialog from "@components/UpdateDialog";
|
||||
import UpdateInProgressStatusCard from "../components/UpdateInProgressStatusCard";
|
||||
import api from "../api";
|
||||
import { DeviceStatus } from "./welcome-local";
|
||||
import FocusTrap from "focus-trap-react";
|
||||
import OtherSessionConnectedModal from "@/components/OtherSessionConnectedModal";
|
||||
import Terminal from "@components/Terminal";
|
||||
import { CLOUD_API, DEVICE_API } from "@/ui.config";
|
||||
import Modal from "../components/Modal";
|
||||
import { motion, AnimatePresence } from "motion/react";
|
||||
import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
|
||||
|
||||
interface LocalLoaderResp {
|
||||
authMode: "password" | "noPassword" | null;
|
||||
@@ -50,8 +52,9 @@ interface CloudLoaderResp {
|
||||
} | null;
|
||||
}
|
||||
|
||||
export type AuthMode = "password" | "noPassword" | null;
|
||||
export interface LocalDevice {
|
||||
authMode: "password" | "noPassword" | null;
|
||||
authMode: AuthMode;
|
||||
deviceId: string;
|
||||
}
|
||||
|
||||
@@ -123,16 +126,7 @@ export default function KvmIdRoute() {
|
||||
const setTransceiver = useRTCStore(state => state.setTransceiver);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
otaState,
|
||||
setOtaState,
|
||||
isUpdateDialogOpen,
|
||||
setIsUpdateDialogOpen,
|
||||
setModalView,
|
||||
} = useUpdateStore();
|
||||
|
||||
const [isOtherSessionConnectedModalOpen, setIsOtherSessionConnectedModalOpen] =
|
||||
useState(false);
|
||||
const { otaState, setOtaState, setModalView } = useUpdateStore();
|
||||
|
||||
const sdp = useCallback(
|
||||
async (event: RTCPeerConnectionIceEvent, pc: RTCPeerConnection) => {
|
||||
@@ -243,8 +237,7 @@ export default function KvmIdRoute() {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// We don't want to connect if another session is connected
|
||||
if (isOtherSessionConnectedModalOpen) return;
|
||||
if (location.pathname.includes("other-session")) return;
|
||||
connectWebRTC();
|
||||
}, 3000);
|
||||
|
||||
@@ -330,11 +323,11 @@ export default function KvmIdRoute() {
|
||||
const setHdmiState = useVideoStore(state => state.setHdmiState);
|
||||
|
||||
const [hasUpdated, setHasUpdated] = useState(false);
|
||||
const { navigateTo } = useDeviceUiNavigation();
|
||||
|
||||
function onJsonRpcRequest(resp: JsonRpcRequest) {
|
||||
if (resp.method === "otherSessionConnected") {
|
||||
console.log("otherSessionConnected", resp.params);
|
||||
setIsOtherSessionConnectedModalOpen(true);
|
||||
navigateTo("/other-session");
|
||||
}
|
||||
|
||||
if (resp.method === "usbState") {
|
||||
@@ -358,7 +351,7 @@ export default function KvmIdRoute() {
|
||||
|
||||
if (otaState.error) {
|
||||
setModalView("error");
|
||||
setIsUpdateDialogOpen(true);
|
||||
navigateTo("/settings/general/update");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -388,11 +381,9 @@ export default function KvmIdRoute() {
|
||||
// When the update is successful, we need to refresh the client javascript and show a success modal
|
||||
useEffect(() => {
|
||||
if (queryParams.get("updateSuccess")) {
|
||||
setModalView("updateCompleted");
|
||||
setIsUpdateDialogOpen(true);
|
||||
setQueryParams({});
|
||||
navigateTo("/settings/general/update", { state: { updateSuccess: true } });
|
||||
}
|
||||
}, [queryParams, setIsUpdateDialogOpen, setModalView, setQueryParams]);
|
||||
}, [navigate, navigateTo, queryParams, setModalView, setQueryParams]);
|
||||
|
||||
const diskChannel = useRTCStore(state => state.diskChannel)!;
|
||||
const file = useMountMediaStore(state => state.localFile)!;
|
||||
@@ -445,18 +436,27 @@ export default function KvmIdRoute() {
|
||||
};
|
||||
}, [kvmTerminal]);
|
||||
|
||||
const outlet = useOutlet();
|
||||
const location = useLocation();
|
||||
const onModalClose = useCallback(() => {
|
||||
if (location.pathname !== "/other-session") navigateTo("..");
|
||||
}, [navigateTo, location.pathname]);
|
||||
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
{!outlet && otaState.updating && (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
className="pointer-events-none fixed inset-0 top-16 z-10 mx-auto flex h-full w-full max-w-xl translate-y-8 items-start justify-center"
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.3, ease: "easeInOut" }}
|
||||
>
|
||||
<UpdateInProgressStatusCard />
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
)}
|
||||
<div className="relative h-full">
|
||||
<FocusTrap
|
||||
paused={disableKeyboardFocusTrap}
|
||||
@@ -486,21 +486,24 @@ export default function KvmIdRoute() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UpdateDialog open={isUpdateDialogOpen} setOpen={setIsUpdateDialogOpen} />
|
||||
<OtherSessionConnectedModal
|
||||
open={isOtherSessionConnectedModalOpen}
|
||||
setOpen={state => {
|
||||
if (!state) connectWebRTC().then(r => r);
|
||||
|
||||
// It takes some time for the WebRTC connection to be established, so we wait a bit before closing the modal
|
||||
setTimeout(() => {
|
||||
setIsOtherSessionConnectedModalOpen(state);
|
||||
}, 1000);
|
||||
<div
|
||||
className="isolate"
|
||||
onKeyUp={e => e.stopPropagation()}
|
||||
onKeyDown={e => {
|
||||
e.stopPropagation();
|
||||
if (e.key === "Escape") navigateTo("/");
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Modal open={outlet !== null} onClose={onModalClose}>
|
||||
<Outlet context={{ connectWebRTC }} />
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
{kvmTerminal && (
|
||||
<Terminal type="kvm" dataChannel={kvmTerminal} title="KVM Terminal" />
|
||||
)}
|
||||
|
||||
{serialConsole && (
|
||||
<Terminal type="serial" dataChannel={serialConsole} title="Serial Console" />
|
||||
)}
|
||||
@@ -518,16 +521,22 @@ function SidebarContainer({ sidebarView }: { sidebarView: string | null }) {
|
||||
style={{ width: sidebarView ? "493px" : 0 }}
|
||||
>
|
||||
<div className="relative w-[493px] shrink-0">
|
||||
<Transition show={sidebarView === "system"} unmount={false}>
|
||||
<div className="absolute inset-0">
|
||||
<SettingsSidebar />
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition show={sidebarView === "connection-stats"} unmount={false}>
|
||||
<div className="absolute inset-0">
|
||||
<ConnectionStatsSidebar />
|
||||
</div>
|
||||
</Transition>
|
||||
<AnimatePresence>
|
||||
{sidebarView === "connection-stats" && (
|
||||
<motion.div
|
||||
className="absolute inset-0"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
>
|
||||
<ConnectionStatsSidebar />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user