diff --git a/src/App.tsx b/src/App.tsx index 38a99c2..c44d7c9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -51,6 +51,8 @@ function MainContent(): React.JSX.Element { const [authVisible, setAuthVisible] = useState(false) const [authPassword, setAuthPassword] = useState("") const [authLoading, setAuthLoading] = useState(false) + const [isPortraitMode, setIsPortraitMode] = useState(false) + const [sidebarCollapsed, setSidebarCollapsed] = useState(false) const activeMenu = useMemo(() => { const p = location.pathname @@ -127,6 +129,30 @@ function MainContent(): React.JSX.Element { if (key === "settings") navigate("/settings") } + const toggleOrientationMode = async () => { + const api = (window as any).api + if (!api) return + + const nextPortraitMode = !isPortraitMode + try { + const maximized = await api.windowIsMaximized() + if (maximized) { + await api.windowMaximize() + } + if (nextPortraitMode) { + await api.windowResize(800, 1000) + setSidebarCollapsed(true) + } else { + await api.windowResize(1180, 680) + setSidebarCollapsed(false) + } + setIsPortraitMode(nextPortraitMode) + } catch (error) { + messageApi.error(t("common.error")) + console.error("Failed to toggle orientation mode:", error) + } + } + const isDark = currentTheme?.mode === "dark" const brandColor = currentTheme?.config?.tdesign?.brandColor || "#0052D9" @@ -141,12 +167,21 @@ function MainContent(): React.JSX.Element { > {contextHolder} - + setAuthVisible(true)} onLogout={logout} + isPortraitMode={isPortraitMode} + onToggleOrientation={toggleOrientationMode} /> setWizardVisible(false)} /> diff --git a/src/components/ContentArea.tsx b/src/components/ContentArea.tsx index 059ec76..698fc75 100644 --- a/src/components/ContentArea.tsx +++ b/src/components/ContentArea.tsx @@ -45,6 +45,8 @@ interface ContentAreaProps { hasAnyPassword: boolean onAuthClick: () => void onLogout: () => void + isPortraitMode: boolean + onToggleOrientation: () => void } export function ContentArea({ @@ -52,6 +54,8 @@ export function ContentArea({ hasAnyPassword, onAuthClick, onLogout, + isPortraitMode, + onToggleOrientation, }: ContentAreaProps): React.JSX.Element { const { t } = useTranslation() @@ -132,6 +136,13 @@ export function ContentArea({ } > + {permissionTag} {hasAnyPassword && ( <> diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 84b9e8e..73bf6b5 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -22,6 +22,9 @@ interface SidebarProps { activeMenu: string permission: "admin" | "points" | "view" onMenuChange: (value: string) => void + collapsed: boolean + floatingExpand: boolean + onCollapsedChange: (collapsed: boolean) => void } interface DbStatus { @@ -30,9 +33,16 @@ interface DbStatus { error?: string } -export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps): React.JSX.Element { +export function Sidebar({ + activeMenu, + permission, + onMenuChange, + collapsed, + floatingExpand, + onCollapsedChange, +}: SidebarProps): React.JSX.Element { const { t } = useTranslation() - const [collapsed, setCollapsed] = useState(false) + const [floatingExpanded, setFloatingExpanded] = useState(false) const [dbStatus, setDbStatus] = useState({ type: "sqlite", connected: true }) const [syncLoading, setSyncLoading] = useState(false) const [messageApi, contextHolder] = message.useMessage() @@ -95,6 +105,12 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps): const [forceSyncLoading, setForceSyncLoading] = useState(false) + useEffect(() => { + if (!floatingExpand || !collapsed) { + setFloatingExpanded(false) + } + }, [floatingExpand, collapsed]) + const handleForceSync = async () => { if (!(window as any).api) return @@ -175,25 +191,15 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps): }, ] - return ( - + const showFloatingPanel = floatingExpand && collapsed && floatingExpanded + + const renderSidebarBody = (isCollapsedView: boolean, isFloatingPanel = false) => ( + <>
setCollapsed((prev) => !prev)} - icon={collapsed ? : } - style={{ - position: "absolute", - top: "8px", - right: "8px", - WebkitAppRegion: "no-drag", + onClick={() => { + if (floatingExpand && collapsed) { + setFloatingExpanded((prev) => !prev) + return + } + onCollapsedChange(!collapsed) }} + icon={ + floatingExpand && collapsed + ? isFloatingPanel + ? + : + : isCollapsedView + ? + : + } + style={ + { + position: "absolute", + top: "8px", + right: "8px", + WebkitAppRegion: "no-drag", + } as React.CSSProperties + } /> logo - {!collapsed && ( + {!isCollapsedView && ( <>

onMenuChange(key)} + onClick={({ key }) => { + onMenuChange(key) + if (floatingExpand && collapsed) { + setFloatingExpanded(false) + } + }} style={{ width: "100%", border: "none", @@ -263,7 +290,7 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps): />

- {!collapsed && dbStatus.type === "postgresql" && ( + {!isCollapsedView && dbStatus.type === "postgresql" && ( - {contextHolder}
@@ -324,6 +350,47 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps): )} + + ) + + return ( + + {contextHolder} + {renderSidebarBody(collapsed)} + + {showFloatingPanel && ( +
+ {renderSidebarBody(false, true)} +
+ )}
) }