| |
| |
| |
| |
| |
| |
| |
| |
| |
| import React, { |
| createContext, |
| useCallback, |
| useContext, |
| useEffect, |
| useMemo, |
| useState, |
| } from 'react' |
|
|
| import { getCurrentUserId, userScopedKey } from '../lib/userScopedStorage' |
| import { useAccount } from './HomePilotAccountProvider' |
| import type { MirrorNode, PresenceState, SelectionMode } from './types' |
|
|
| const LS_SELECTED = 'homepilot_selected_computer' |
| const LS_MODE = 'homepilot_selection_mode' |
|
|
| function readScoped(baseKey: string): string | null { |
| try { |
| const uid = getCurrentUserId() |
| return localStorage.getItem(uid ? userScopedKey(baseKey, uid) : `${baseKey}:user:anon`) |
| } catch { |
| return null |
| } |
| } |
| function writeScoped(baseKey: string, value: string | null): void { |
| try { |
| const uid = getCurrentUserId() |
| const key = uid ? userScopedKey(baseKey, uid) : `${baseKey}:user:anon` |
| if (value == null) localStorage.removeItem(key) |
| else localStorage.setItem(key, value) |
| } catch { |
| |
| } |
| } |
|
|
| export interface ComputerContextValue { |
| |
| computers: MirrorNode[] |
| |
| selectedComputerId: string | null |
| |
| selectedComputer: MirrorNode | null |
| selectionMode: SelectionMode |
| |
| presenceOf: (nodeId: string) => PresenceState |
| |
| anyOnline: boolean |
| |
| selectComputer: (nodeId: string | null) => void |
| setSelectionMode: (mode: SelectionMode) => void |
| } |
|
|
| const DEFAULT: ComputerContextValue = { |
| computers: [], |
| selectedComputerId: null, |
| selectedComputer: null, |
| selectionMode: 'automatic', |
| presenceOf: () => 'unknown', |
| anyOnline: false, |
| selectComputer: () => {}, |
| setSelectionMode: () => {}, |
| } |
|
|
| const ComputerCtx = createContext<ComputerContextValue>(DEFAULT) |
|
|
| export function ComputerProvider({ children }: { children: React.ReactNode }): JSX.Element { |
| const { computers, enabled } = useAccount() |
|
|
| const [selectedComputerId, setSelectedId] = useState<string | null>(() => readScoped(LS_SELECTED)) |
| const [selectionMode, setModeState] = useState<SelectionMode>(() => { |
| const m = readScoped(LS_MODE) |
| return m === 'fixed' || m === 'ask' || m === 'automatic' ? m : 'automatic' |
| }) |
|
|
| const selectComputer = useCallback((nodeId: string | null) => { |
| setSelectedId(nodeId) |
| writeScoped(LS_SELECTED, nodeId) |
| |
| const nextMode: SelectionMode = nodeId ? 'fixed' : 'automatic' |
| setModeState(nextMode) |
| writeScoped(LS_MODE, nextMode) |
| }, []) |
|
|
| const setSelectionMode = useCallback((mode: SelectionMode) => { |
| setModeState(mode) |
| writeScoped(LS_MODE, mode) |
| if (mode !== 'fixed') { |
| setSelectedId(null) |
| writeScoped(LS_SELECTED, null) |
| } |
| }, []) |
|
|
| |
| useEffect(() => { |
| if (!enabled) return |
| if (selectedComputerId && computers.length > 0 && |
| !computers.some((c) => c.node_id === selectedComputerId)) { |
| selectComputer(null) |
| } |
| }, [enabled, computers, selectedComputerId, selectComputer]) |
|
|
| const presenceOf = useCallback((nodeId: string): PresenceState => { |
| const c = computers.find((x) => x.node_id === nodeId) |
| if (!c) return 'unknown' |
| return c.online ? 'online' : 'offline' |
| }, [computers]) |
|
|
| const selectedComputer = useMemo( |
| () => computers.find((c) => c.node_id === selectedComputerId) ?? null, |
| [computers, selectedComputerId], |
| ) |
| const anyOnline = useMemo(() => computers.some((c) => c.online), [computers]) |
|
|
| const value = useMemo<ComputerContextValue>(() => ({ |
| computers, |
| selectedComputerId, |
| selectedComputer, |
| selectionMode, |
| presenceOf, |
| anyOnline, |
| selectComputer, |
| setSelectionMode, |
| }), [computers, selectedComputerId, selectedComputer, selectionMode, presenceOf, anyOnline, selectComputer, setSelectionMode]) |
|
|
| return <ComputerCtx.Provider value={value}>{children}</ComputerCtx.Provider> |
| } |
|
|
| |
| export function useComputer(): ComputerContextValue { |
| return useContext(ComputerCtx) |
| } |
|
|