"use client"; import { Activity, Bell, FolderOpen, HardDrive, Package, Search, Settings, ShoppingBag, TerminalSquare, } from "@/components/icons/platform-icons"; import { CurrentUserError, useCurrentUser } from "@/hooks/useCurrentUser"; import { readLockState, writeLockState } from "@/lib/desktop/lock-state"; import { clearPersistedPowerActionCompletion, readPersistedPowerActionCompletion, } from "@/lib/desktop/reboot-state"; import { pushRecentCommandAction, readRecentCommandActions, type RecentCommandAction, } from "@/lib/desktop/recent-actions"; import { AppGrid, type AppActionTarget, } from "@/modules/apps/components/app-grid"; import { AppLogsDialog } from "@/modules/apps/components/app-logs-dialog"; import { AppStore } from "@/modules/apps/components/app-store"; import { AppConfiguratorPanel } from "@/modules/apps/components/configurator/app-configurator-panel"; import { ContainerSettingsPanel } from "@/modules/apps/components/configurator/container-settings-panel"; import { StoreActionsProvider, useSharedStoreActions, } from "@/modules/apps/hooks/StoreActionsContext"; import { FileManager } from "@/modules/files/components/file-manager"; import { SettingsPanel } from "@/modules/settings/components/settings"; import { CommandPalette } from "@/modules/shell/components/command-palette"; import { useDesktopAppearance } from "@/modules/shell/hooks/useDesktopAppearance"; import { useRebootRecovery } from "@/modules/shell/hooks/useRebootRecovery"; import { useServerConnection } from "@/modules/shell/hooks/useServerConnection"; import { Monitor } from "@/modules/system/components/monitor"; import { DiskManager } from "@/modules/system/components/disk-manager"; import { NotificationsPanel } from "@/modules/system/components/notifications-panel"; import { StatusBar } from "@/modules/system/components/status-bar"; import { SystemWidgets } from "@/modules/system/components/system-widgets"; import { UpdateReminder } from "@/modules/system/components/update-reminder"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; import { ConnectionLostScreen } from "./connection-lost-screen"; import { Dock } from "./dock"; import { LockScreen } from "./lock-screen"; import { RebootOverlay } from "./reboot-overlay"; import { SessionLoadingScreen } from "./session-loading-screen"; import { Terminal, type TerminalAttachRequest } from "./terminal"; import { UpdateRecoveryScreen } from "./update-recovery-screen"; import { Window } from "./window"; const WINDOW_CLOSE_ANIMATION_MS = 180; // Long enough to read as a change rather than a flicker; the image is already // cached and preloaded by the time this runs, so the wait is the animation. const WALLPAPER_FADE_MS = 900; const SETTINGS_SEARCH_SECTIONS = [ { id: "general", label: "General" }, { id: "network", label: "Network" }, { id: "storage", label: "Storage" }, { id: "docker", label: "Docker" }, { id: "users", label: "Users & Access" }, { id: "security", label: "Security" }, { id: "notifications", label: "Notifications" }, { id: "backup", label: "Backup & Restore" }, { id: "updates", label: "Updates" }, { id: "appearance", label: "Appearance" }, { id: "power", label: "Power" }, ] as const; export function DesktopShell() { return ( ); } function DesktopShellInner() { const router = useRouter(); const { data: currentUser, error: currentUserError, isLoading: isLoadingUser, isError: isCurrentUserError, } = useCurrentUser(); const [openWindows, setOpenWindows] = useState([]); const [closingWindows, setClosingWindows] = useState([]); const [minimizedWindows, setMinimizedWindows] = useState([]); const [focusedWindow, setFocusedWindow] = useState(null); const [isLocked, setIsLocked] = useState(false); const [isLockStateHydrated, setIsLockStateHydrated] = useState(false); const [isLogoutPending, setIsLogoutPending] = useState(false); const [isSettingsSearchOpen, setIsSettingsSearchOpen] = useState(false); const [settingsSearchQuery, setSettingsSearchQuery] = useState(""); const [settingsSectionRequest, setSettingsSectionRequest] = useState< string | null >(null); const [appStoreLaunchRequest, setAppStoreLaunchRequest] = useState<{ nonce: number; search?: string; appId?: string; } | null>(null); const [recentActions, setRecentActions] = useState([]); const [terminalAttachRequest, setTerminalAttachRequest] = useState(null); const [appSettingsTarget, setAppSettingsTarget] = useState(null); const [logsTarget, setLogsTarget] = useState(null); const shellStoreActions = useSharedStoreActions(); const terminalAttachIdRef = useRef(0); const [displayWallpaper, setDisplayWallpaper] = useState(null); const [nextWallpaper, setNextWallpaper] = useState(null); const [isWallpaperFading, setIsWallpaperFading] = useState(false); const closeTimersRef = useRef>({}); const wallpaperFinalizeTimerRef = useRef(null); const wallpaperFrameRef = useRef(null); const wallpaperTransitionIdRef = useRef(0); const appStoreLaunchNonceRef = useRef(0); const { appearance, isAppearanceLoaded, updateAppearance, wallpapers, accentColors, appIconSize, wallpaperAccentColor, } = useDesktopAppearance(); const rebootRecovery = useRebootRecovery(); const serverConnection = useServerConnection( rebootRecovery.isHydrated && !rebootRecovery.isActive, ); useEffect(() => { if (typeof window === "undefined") return; setRecentActions(readRecentCommandActions(window.localStorage)); }, []); const setLockState = useCallback((value: boolean) => { setIsLocked(value); if (typeof window !== "undefined") { writeLockState(window.localStorage, value); } }, []); useEffect(() => { if (typeof window === "undefined") return; setIsLocked(readLockState(window.localStorage)); setIsLockStateHydrated(true); }, []); useEffect(() => { if (typeof window === "undefined") return; if (!rebootRecovery.isHydrated || rebootRecovery.isActive) return; const completion = readPersistedPowerActionCompletion(window.localStorage); if (!completion) return; clearPersistedPowerActionCompletion(window.localStorage); if (completion.action === "update") { toast.success("Homeio update completed."); return; } toast.success("System action completed."); }, [rebootRecovery.isActive, rebootRecovery.isHydrated]); useEffect(() => { if (rebootRecovery.isActive || !rebootRecovery.isHydrated) return; if (isCurrentUserError) { if ( currentUserError instanceof CurrentUserError && currentUserError.redirectTo ) { router.replace(currentUserError.redirectTo); return; } router.replace("/login"); } }, [ currentUserError, isCurrentUserError, rebootRecovery.isActive, rebootRecovery.isHydrated, router, ]); const filteredSettingsSections = useMemo(() => { const query = settingsSearchQuery.trim().toLowerCase(); if (!query) return SETTINGS_SEARCH_SECTIONS; return SETTINGS_SEARCH_SECTIONS.filter((section) => section.label.toLowerCase().includes(query), ); }, [settingsSearchQuery]); const getNextFocusableWindow = useCallback( (excludedId?: string) => { const visibleWindows = openWindows.filter( (windowId) => windowId !== excludedId && !closingWindows.includes(windowId) && !minimizedWindows.includes(windowId), ); return visibleWindows.at(-1) ?? null; }, [closingWindows, minimizedWindows, openWindows], ); const finalizeCloseWindow = useCallback( (id: string) => { setOpenWindows((prev) => prev.filter((w) => w !== id)); setClosingWindows((prev) => prev.filter((w) => w !== id)); setMinimizedWindows((prev) => prev.filter((w) => w !== id)); setFocusedWindow((prev) => prev === id ? getNextFocusableWindow(id) : prev, ); }, [getNextFocusableWindow], ); const openWindow = useCallback((id: string) => { if (closeTimersRef.current[id]) { window.clearTimeout(closeTimersRef.current[id]); delete closeTimersRef.current[id]; } setClosingWindows((prev) => prev.filter((w) => w !== id)); setMinimizedWindows((prev) => prev.filter((w) => w !== id)); setOpenWindows((prev) => { if (prev.includes(id)) { // Already open, just focus it setFocusedWindow(id); return prev; } return [...prev, id]; }); setFocusedWindow(id); }, []); const minimizeWindow = useCallback( (id: string) => { setMinimizedWindows((prev) => (prev.includes(id) ? prev : [...prev, id])); setFocusedWindow((prev) => prev === id ? getNextFocusableWindow(id) : prev, ); }, [getNextFocusableWindow], ); const closeWindow = useCallback( (id: string) => { if (closeTimersRef.current[id]) { window.clearTimeout(closeTimersRef.current[id]); delete closeTimersRef.current[id]; } if (!appearance.animationsEnabled) { finalizeCloseWindow(id); return; } setClosingWindows((prev) => (prev.includes(id) ? prev : [...prev, id])); closeTimersRef.current[id] = window.setTimeout(() => { delete closeTimersRef.current[id]; finalizeCloseWindow(id); }, WINDOW_CLOSE_ANIMATION_MS); }, [appearance.animationsEnabled, finalizeCloseWindow], ); // The first real wallpaper is assigned outright. Cross-fading into it would // animate a change the operator never made — and did, on every reload. useEffect(() => { if (!isAppearanceLoaded || displayWallpaper !== null) return; setDisplayWallpaper(appearance.wallpaper); }, [appearance.wallpaper, displayWallpaper, isAppearanceLoaded]); useEffect(() => { if (displayWallpaper === null) return; if (appearance.wallpaper === displayWallpaper) return; wallpaperTransitionIdRef.current += 1; const transitionId = wallpaperTransitionIdRef.current; if (wallpaperFinalizeTimerRef.current) { window.clearTimeout(wallpaperFinalizeTimerRef.current); wallpaperFinalizeTimerRef.current = null; } if (wallpaperFrameRef.current) { window.cancelAnimationFrame(wallpaperFrameRef.current); wallpaperFrameRef.current = null; } if (!appearance.animationsEnabled) { setDisplayWallpaper(appearance.wallpaper); setNextWallpaper(null); setIsWallpaperFading(false); return; } const targetWallpaper = appearance.wallpaper; const startFade = () => { if (transitionId !== wallpaperTransitionIdRef.current) return; setNextWallpaper(targetWallpaper); setIsWallpaperFading(false); wallpaperFrameRef.current = window.requestAnimationFrame(() => { if (transitionId !== wallpaperTransitionIdRef.current) return; setIsWallpaperFading(true); wallpaperFrameRef.current = null; }); // Fallback finalize in case transitionend does not fire. wallpaperFinalizeTimerRef.current = window.setTimeout(() => { if (transitionId !== wallpaperTransitionIdRef.current) return; setDisplayWallpaper(targetWallpaper); setNextWallpaper(null); setIsWallpaperFading(false); wallpaperFinalizeTimerRef.current = null; }, WALLPAPER_FADE_MS + 80); }; const preload = new Image(); preload.decoding = "async"; preload.src = targetWallpaper; if (preload.complete) { startFade(); } else { preload.onload = startFade; preload.onerror = startFade; } }, [appearance.wallpaper, appearance.animationsEnabled, displayWallpaper]); useEffect(() => { return () => { Object.values(closeTimersRef.current).forEach((timer) => { window.clearTimeout(timer); }); closeTimersRef.current = {}; if (wallpaperFinalizeTimerRef.current) { window.clearTimeout(wallpaperFinalizeTimerRef.current); } if (wallpaperFrameRef.current) { window.cancelAnimationFrame(wallpaperFrameRef.current); } }; }, []); function finalizeWallpaperFade() { if (!isWallpaperFading || !nextWallpaper) return; if (wallpaperFinalizeTimerRef.current) { window.clearTimeout(wallpaperFinalizeTimerRef.current); wallpaperFinalizeTimerRef.current = null; } setDisplayWallpaper(nextWallpaper); setNextWallpaper(null); setIsWallpaperFading(false); } const openSettingsSection = useCallback( (sectionId: string) => { setSettingsSectionRequest(sectionId); setIsSettingsSearchOpen(false); setSettingsSearchQuery(""); const section = SETTINGS_SEARCH_SECTIONS.find( (entry) => entry.id === sectionId, ); if (typeof window !== "undefined" && section) { setRecentActions( pushRecentCommandAction(window.localStorage, { key: `settings:${section.id}`, kind: "settings-section", title: section.label, subtitle: "Settings section", sectionId: section.id, }), ); } openWindow("settings"); }, [openWindow], ); function handleDockClick(id: string) { if (id === "apps") { // "Show Desktop" — minimize all visible windows. // If every open window is already minimized, restore them all. const visibleWindows = openWindows.filter( (w) => !minimizedWindows.includes(w), ); if (visibleWindows.length > 0) { setMinimizedWindows((prev) => [ ...prev, ...visibleWindows.filter((w) => !prev.includes(w)), ]); setFocusedWindow(null); } else { // All already minimized — restore all so the user can get back setMinimizedWindows([]); } return; } if ( id !== "files" && id !== "settings" && id !== "app-store" && id !== "terminal" && id !== "monitor" && id !== "notifications" && id !== "app-settings" && id !== "disk-manager" ) return; if (id === "terminal" && currentUser?.isDemoMode) { toast.info("Terminal is disabled in demo mode."); return; } if (typeof window !== "undefined") { const metadata = { files: { title: "Open Files", subtitle: "Folder and file browser" }, settings: { title: "Open Settings", subtitle: "System preferences" }, "app-store": { title: "Open App Store", subtitle: "Browse and install apps", }, terminal: { title: "Open Terminal", subtitle: "Shell and logs" }, monitor: { title: "Open Monitor", subtitle: "System performance" }, notifications: { title: "Open Notifications", subtitle: "Activity feed" }, "app-settings": { title: "Open App Settings", subtitle: "App configuration" }, "disk-manager": { title: "Open Disk Manager", subtitle: "Storage management" }, }[id]; if (metadata) { setRecentActions( pushRecentCommandAction(window.localStorage, { key: `window:${id}`, kind: "window", title: metadata.title, subtitle: metadata.subtitle, windowId: id, }), ); } } openWindow(id); } function getWindowZ(id: string) { return focusedWindow === id ? 110 : 100; } const openContainerTerminal = useCallback( (container: string) => { terminalAttachIdRef.current += 1; setTerminalAttachRequest({ id: terminalAttachIdRef.current, container, }); openWindow("terminal"); }, [openWindow], ); const openCommandPaletteWindow = useCallback( (windowId: "files" | "settings" | "app-store" | "terminal" | "monitor" | "notifications" | "app-settings" | "disk-manager") => { if (typeof window !== "undefined") { const metadata = { files: { title: "Open Files", subtitle: "Folder and file browser" }, settings: { title: "Open Settings", subtitle: "System preferences" }, "app-store": { title: "Open App Store", subtitle: "Browse and install apps", }, terminal: { title: "Open Terminal", subtitle: "Shell and logs" }, monitor: { title: "Open Monitor", subtitle: "System performance" }, notifications: { title: "Open Notifications", subtitle: "Activity feed" }, "app-settings": { title: "Open App Settings", subtitle: "App configuration" }, "disk-manager": { title: "Open Disk Manager", subtitle: "Storage management" }, }[windowId]; setRecentActions( pushRecentCommandAction(window.localStorage, { key: `window:${windowId}`, kind: "window", title: metadata.title, subtitle: metadata.subtitle, windowId, }), ); } setIsSettingsSearchOpen(false); setSettingsSearchQuery(""); openWindow(windowId); }, [openWindow], ); const openAppStoreSearch = useCallback( (input: { search: string; appId?: string }) => { appStoreLaunchNonceRef.current += 1; setAppStoreLaunchRequest({ nonce: appStoreLaunchNonceRef.current, search: input.search, appId: input.appId, }); if (typeof window !== "undefined") { setRecentActions( pushRecentCommandAction(window.localStorage, { key: input.appId ? `app-store:app:${input.appId}` : `app-store:search:${input.search.trim().toLowerCase()}`, kind: "app-store-search", title: input.appId ? input.search : `Search "${input.search}"`, subtitle: "App Store app result", search: input.search, appId: input.appId, }), ); } setIsSettingsSearchOpen(false); setSettingsSearchQuery(""); openWindow("app-store"); }, [openWindow], ); const handleSelectRecentAction = useCallback( (action: RecentCommandAction) => { if (action.kind === "window") { openCommandPaletteWindow(action.windowId); return; } if (action.kind === "settings-section") { openSettingsSection(action.sectionId); return; } openAppStoreSearch({ search: action.search, appId: action.appId, }); }, [openAppStoreSearch, openCommandPaletteWindow, openSettingsSection], ); useEffect(() => { function handleKeyboardShortcut(e: KeyboardEvent) { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "l") { e.preventDefault(); setLockState(true); return; } if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); setIsSettingsSearchOpen(true); setSettingsSearchQuery(""); return; } if (e.key === "Escape" && isSettingsSearchOpen) { e.preventDefault(); setIsSettingsSearchOpen(false); } } window.addEventListener("keydown", handleKeyboardShortcut, true); return () => { window.removeEventListener("keydown", handleKeyboardShortcut, true); }; }, [isSettingsSearchOpen, setLockState]); const handleLogout = useCallback(async () => { setIsLogoutPending(true); try { await fetch("/api/auth/logout", { method: "POST", }); } finally { setLockState(false); setIsLogoutPending(false); router.replace("/login"); router.refresh(); } }, [router, setLockState]); const handleUnlock = useCallback( async (password: string) => { const response = await fetch("/api/auth/unlock", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ password }), }); if (!response.ok) { const json = (await response.json().catch(() => ({}))) as { error?: string; }; throw new Error(json.error ?? "Invalid password"); } setLockState(false); }, [setLockState], ); if (!isLockStateHydrated || !rebootRecovery.isHydrated) { return ; } if (rebootRecovery.isActive) { if (rebootRecovery.action === "update") { return ( ); } return ( ); } if (isLoadingUser) { return ; } return (
{/* Wallpaper Background */}
{nextWallpaper && (
)} {/* Depth vignette — transparent at center, darker at edges */}
{/* Bottom ambient shadow (dock area) */}
{/* Desktop Content */}
{/* Demo mode floating badge — overlays the top of the widget column */} {currentUser?.isDemoMode && (
Demo · read-only
)} {isSettingsSearchOpen && ( )} {/* Status Bar */} setLockState(true)} onLogout={handleLogout} isLogoutPending={isLogoutPending} onOpenNotifications={() => openWindow("notifications")} /> {/* Main Desktop Area */}
{/* App Grid (scrollable center) */} setLogsTarget(target)} onOpenTerminal={({ containerName }) => openContainerTerminal(containerName) } onOpenSettings={(target) => { setAppSettingsTarget(target); openWindow("app-settings"); }} /> {/* System Widgets (right sidebar) */}
{/* Windows */} {openWindows.includes("files") && ( } onClose={() => closeWindow("files")} onMinimize={() => minimizeWindow("files")} defaultWidth={1100} defaultHeight={679} zIndex={getWindowZ("files")} dockPosition={appearance.dockPosition} onFocus={() => setFocusedWindow("files")} isClosing={closingWindows.includes("files")} isMinimized={minimizedWindows.includes("files")} animationsEnabled={appearance.animationsEnabled} > )} {openWindows.includes("settings") && ( } onClose={() => closeWindow("settings")} onMinimize={() => minimizeWindow("settings")} defaultWidth={860} defaultHeight={620} zIndex={getWindowZ("settings")} dockPosition={appearance.dockPosition} onFocus={() => setFocusedWindow("settings")} isClosing={closingWindows.includes("settings")} isMinimized={minimizedWindows.includes("settings")} animationsEnabled={appearance.animationsEnabled} > openWindow("disk-manager")} /> )} {openWindows.includes("monitor") && ( } onClose={() => closeWindow("monitor")} onMinimize={() => minimizeWindow("monitor")} defaultWidth={1120} defaultHeight={700} zIndex={getWindowZ("monitor")} dockPosition={appearance.dockPosition} onFocus={() => setFocusedWindow("monitor")} isClosing={closingWindows.includes("monitor")} isMinimized={minimizedWindows.includes("monitor")} animationsEnabled={appearance.animationsEnabled} > openWindow("disk-manager")} /> )} {openWindows.includes("notifications") && ( } onClose={() => closeWindow("notifications")} onMinimize={() => minimizeWindow("notifications")} defaultWidth={640} defaultHeight={520} zIndex={getWindowZ("notifications")} dockPosition={appearance.dockPosition} onFocus={() => setFocusedWindow("notifications")} isClosing={closingWindows.includes("notifications")} isMinimized={minimizedWindows.includes("notifications")} animationsEnabled={appearance.animationsEnabled} > )} {openWindows.includes("app-store") && ( } onClose={() => closeWindow("app-store")} onMinimize={() => minimizeWindow("app-store")} defaultWidth={1080} defaultHeight={680} zIndex={getWindowZ("app-store")} dockPosition={appearance.dockPosition} onFocus={() => setFocusedWindow("app-store")} isClosing={closingWindows.includes("app-store")} isMinimized={minimizedWindows.includes("app-store")} animationsEnabled={appearance.animationsEnabled} > openWindow("custom-install")} launchRequest={appStoreLaunchRequest} /> )} {openWindows.includes("custom-install") && ( } onClose={() => closeWindow("custom-install")} onMinimize={() => minimizeWindow("custom-install")} defaultWidth={720} defaultHeight={600} zIndex={getWindowZ("custom-install")} dockPosition={appearance.dockPosition} onFocus={() => setFocusedWindow("custom-install")} isClosing={closingWindows.includes("custom-install")} isMinimized={minimizedWindows.includes("custom-install")} animationsEnabled={appearance.animationsEnabled} > closeWindow("custom-install")} /> )} {openWindows.includes("app-settings") && appSettingsTarget && ( } onClose={() => { closeWindow("app-settings"); setAppSettingsTarget(null); }} onMinimize={() => minimizeWindow("app-settings")} defaultWidth={appSettingsTarget.unmanaged ? 560 : 920} defaultHeight={appSettingsTarget.unmanaged ? 460 : 700} zIndex={getWindowZ("app-settings")} dockPosition={appearance.dockPosition} onFocus={() => setFocusedWindow("app-settings")} isClosing={closingWindows.includes("app-settings")} isMinimized={minimizedWindows.includes("app-settings")} animationsEnabled={appearance.animationsEnabled} > {appSettingsTarget.unmanaged ? ( { closeWindow("app-settings"); setAppSettingsTarget(null); }} /> ) : ( { closeWindow("app-settings"); setAppSettingsTarget(null); }} /> )} )} {openWindows.includes("terminal") && ( } onClose={() => { closeWindow("terminal"); setTerminalAttachRequest(null); }} onMinimize={() => minimizeWindow("terminal")} defaultWidth={980} defaultHeight={620} zIndex={getWindowZ("terminal")} dockPosition={appearance.dockPosition} onFocus={() => setFocusedWindow("terminal")} isClosing={closingWindows.includes("terminal")} isMinimized={minimizedWindows.includes("terminal")} animationsEnabled={appearance.animationsEnabled} > )} {openWindows.includes("disk-manager") && ( } onClose={() => closeWindow("disk-manager")} onMinimize={() => minimizeWindow("disk-manager")} defaultWidth={980} defaultHeight={640} zIndex={getWindowZ("disk-manager")} dockPosition={appearance.dockPosition} onFocus={() => setFocusedWindow("disk-manager")} isClosing={closingWindows.includes("disk-manager")} isMinimized={minimizedWindows.includes("disk-manager")} animationsEnabled={appearance.animationsEnabled} > )} {/* Command Palette Badge — floats above the dock */}
{/* Dock */} openSettingsSection("updates")} /> {isLocked && ( )}
setLogsTarget(null)} /> {serverConnection.phase !== "online" ? ( ) : null}
); }