import React from 'react'; import { RiDownloadLine, RiSettings3Line } from '@remixicon/react'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; import { ErrorBoundary } from '../ui/ErrorBoundary'; import { useUIStore } from '@/stores/useUIStore'; import { useUpdateCheck } from '@/hooks/useUpdateCheck'; import { UpdateDialog } from '../ui/UpdateDialog'; export const SIDEBAR_CONTENT_WIDTH = 264; const SIDEBAR_MIN_WIDTH = 200; const SIDEBAR_MAX_WIDTH = 500; const MAC_TITLEBAR_SAFE_AREA = 40; const CHECK_FOR_UPDATES_EVENT = 'openchamber:check-for-updates'; interface SidebarProps { isOpen: boolean; isMobile: boolean; children: React.ReactNode; } export const Sidebar: React.FC = ({ isOpen, isMobile, children }) => { const { sidebarWidth, setSidebarWidth, setSettingsDialogOpen } = useUIStore(); const [isResizing, setIsResizing] = React.useState(false); const startXRef = React.useRef(0); const startWidthRef = React.useRef(sidebarWidth || SIDEBAR_CONTENT_WIDTH); const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false); const update = useUpdateCheck(); const pendingMenuUpdateCheckRef = React.useRef(false); const checkForUpdates = update.checkForUpdates; const { available, downloaded, checking } = update; const [isDesktopApp, setIsDesktopApp] = React.useState(() => { if (typeof window === 'undefined') { return false; } return typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined'; }); const isMacPlatform = React.useMemo(() => { if (typeof navigator === 'undefined') { return false; } return /Macintosh|Mac OS X/.test(navigator.userAgent || ''); }, []); React.useEffect(() => { if (typeof window === 'undefined') { return; } const detected = typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined'; setIsDesktopApp(detected); }, []); React.useEffect(() => { if (typeof window === 'undefined') { return; } const handleMenuUpdateCheck = () => { const hasDesktopApi = typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined'; if (!hasDesktopApi) { return; } pendingMenuUpdateCheckRef.current = true; void checkForUpdates(); }; window.addEventListener(CHECK_FOR_UPDATES_EVENT, handleMenuUpdateCheck as EventListener); return () => { window.removeEventListener(CHECK_FOR_UPDATES_EVENT, handleMenuUpdateCheck as EventListener); }; }, [checkForUpdates]); React.useEffect(() => { if (!pendingMenuUpdateCheckRef.current) { return; } if (checking) { return; } if (available || downloaded) { setUpdateDialogOpen(true); } else { toast.success('No updates available', { description: 'You are running the latest version.', }); } pendingMenuUpdateCheckRef.current = false; }, [available, downloaded, checking]); React.useEffect(() => { if (isMobile || !isResizing) { return; } const handlePointerMove = (event: PointerEvent) => { const delta = event.clientX - startXRef.current; const nextWidth = Math.min( SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, startWidthRef.current + delta) ); setSidebarWidth(nextWidth); }; const handlePointerUp = () => { setIsResizing(false); }; window.addEventListener('pointermove', handlePointerMove); window.addEventListener('pointerup', handlePointerUp, { once: true }); return () => { window.removeEventListener('pointermove', handlePointerMove); window.removeEventListener('pointerup', handlePointerUp); }; }, [isMobile, isResizing, setSidebarWidth]); React.useEffect(() => { if (isMobile && isResizing) { setIsResizing(false); } }, [isMobile, isResizing]); const handleTitlebarDragStart = React.useCallback(async (e: React.MouseEvent) => { if (e.button !== 0) { return; } if (isDesktopApp) { try { const { getCurrentWindow } = await import('@tauri-apps/api/window'); const window = getCurrentWindow(); await window.startDragging(); } catch (error) { console.error('Failed to start window dragging from sidebar:', error); } } }, [isDesktopApp]); if (isMobile) { return null; } const appliedWidth = isOpen ? Math.min( SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, sidebarWidth || SIDEBAR_CONTENT_WIDTH) ) : 0; const shouldRenderTitlebarSpacer = isDesktopApp && isMacPlatform; const handlePointerDown = (event: React.PointerEvent) => { if (!isOpen) { return; } setIsResizing(true); startXRef.current = event.clientX; startWidthRef.current = appliedWidth; event.preventDefault(); }; return ( ); };