diff --git a/web/hwlab-cloud-web/src/App.tsx b/web/hwlab-cloud-web/src/App.tsx index a77423a5..bd9e2e6b 100644 --- a/web/hwlab-cloud-web/src/App.tsx +++ b/web/hwlab-cloud-web/src/App.tsx @@ -1,7 +1,8 @@ import type { ReactElement } from "react"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useAuth } from "./hooks/useAuth"; +import { useSidebarResize } from "./hooks/useSidebarResize"; import { useWorkbenchStore } from "./state/workbench"; import type { RouteId } from "./types/domain"; import { LoginShell } from "./components/auth/LoginShell"; @@ -14,6 +15,15 @@ import { SettingsView } from "./components/settings/SettingsView"; import { SkillsView } from "./components/skills/SkillsView"; import { fetchText } from "./services/api/client"; +const SESSION_SIDEBAR_STORAGE_KEY = "hwlab.workbench.sessionSidebarWidth.v1"; +const SESSION_SIDEBAR_DEFAULT_WIDTH = 330; +const SESSION_SIDEBAR_MIN_WIDTH = 180; +const SESSION_SIDEBAR_MAX_WIDTH = 640; +const RIGHT_SIDEBAR_STORAGE_KEY = "hwlab.workbench.rightSidebarWidth.v1"; +const RIGHT_SIDEBAR_DEFAULT_WIDTH = 728; +const RIGHT_SIDEBAR_MIN_WIDTH = 560; +const RIGHT_SIDEBAR_MAX_WIDTH = 900; + export function App(): ReactElement { const auth = useAuth(); const store = useWorkbenchStore(auth.authState === "authenticated"); @@ -21,6 +31,28 @@ export function App(): ReactElement { const [leftCollapsed, setLeftCollapsed] = useState(false); const [rightCollapsed, setRightCollapsed] = useState(false); const [help, setHelp] = useState("加载中"); + const shellRef = useRef(null); + const narrow = useMatchMedia("(max-width: 1240px)"); + + const sessionResize = useSidebarResize({ + storageKey: SESSION_SIDEBAR_STORAGE_KEY, + defaultWidth: SESSION_SIDEBAR_DEFAULT_WIDTH, + bounds: { min: SESSION_SIDEBAR_MIN_WIDTH, max: SESSION_SIDEBAR_MAX_WIDTH }, + side: "left", + disabled: narrow || leftCollapsed, + shellElement: shellRef.current, + cssVariable: "--session-sidebar-width" + }); + + const rightResize = useSidebarResize({ + storageKey: RIGHT_SIDEBAR_STORAGE_KEY, + defaultWidth: RIGHT_SIDEBAR_DEFAULT_WIDTH, + bounds: { min: RIGHT_SIDEBAR_MIN_WIDTH, max: RIGHT_SIDEBAR_MAX_WIDTH }, + side: "right", + disabled: narrow || rightCollapsed, + shellElement: shellRef.current, + cssVariable: "--right-sidebar-width" + }); useEffect(() => { const listener = (): void => setRoute(routeFromLocation()); @@ -44,10 +76,36 @@ export function App(): ReactElement { if (auth.authState === "login") return ; return ( -
+
setLeftCollapsed((value) => !value)} /> void store.createSession()} onDelete={() => void store.deleteCurrentSession()} onSelect={(tab) => void store.selectConversation(tab)} /> -
); } +function useMatchMedia(query: string): boolean { + const [match, setMatch] = useState(() => typeof window !== "undefined" && window.matchMedia(query).matches); + useEffect(() => { + if (typeof window === "undefined") return; + const media = window.matchMedia(query); + const listener = (event: MediaQueryListEvent): void => setMatch(event.matches); + setMatch(media.matches); + media.addEventListener("change", listener); + return () => media.removeEventListener("change", listener); + }, [query]); + return match; +} + function AuthLoadingShell(): ReactElement { return (
diff --git a/web/hwlab-cloud-web/src/components/device-pod/DevicePodSidebar.tsx b/web/hwlab-cloud-web/src/components/device-pod/DevicePodSidebar.tsx index b74f379c..94bd29c1 100644 --- a/web/hwlab-cloud-web/src/components/device-pod/DevicePodSidebar.tsx +++ b/web/hwlab-cloud-web/src/components/device-pod/DevicePodSidebar.tsx @@ -23,13 +23,14 @@ export function DevicePodSidebar({ live, selectedDevicePodId, onSelect, collapse const summary = status?.summary ?? selected.summary ?? {}; return ( -