diff --git a/frontend-modern/src/App.tsx b/frontend-modern/src/App.tsx index c77fa7e05..15dde60a9 100644 --- a/frontend-modern/src/App.tsx +++ b/frontend-modern/src/App.tsx @@ -44,6 +44,8 @@ import MonitorIcon from 'lucide-solid/icons/monitor'; import BellIcon from 'lucide-solid/icons/bell'; import SettingsIcon from 'lucide-solid/icons/settings'; import NetworkIcon from 'lucide-solid/icons/network'; +import Maximize2Icon from 'lucide-solid/icons/maximize-2'; +import Minimize2Icon from 'lucide-solid/icons/minimize-2'; import { TokenRevealDialog } from './components/TokenRevealDialog'; import { useAlertsActivation } from './stores/alertsActivation'; import { UpdateProgressModal } from './components/UpdateProgressModal'; @@ -53,7 +55,7 @@ import { AIStatusIndicator } from './components/AI/AIStatusIndicator'; import { aiChatStore } from './stores/aiChat'; import { useResourcesAsLegacy } from './hooks/useResources'; import { updateSystemSettingsFromResponse, markSystemSettingsLoadedWithDefaults } from './stores/systemSettings'; -import { initKioskMode } from './utils/url'; +import { initKioskMode, isKioskMode, setKioskMode, subscribeToKioskMode } from './utils/url'; const Dashboard = lazy(() => @@ -1041,6 +1043,23 @@ function AppLayout(props: { const [seenPlatforms, setSeenPlatforms] = createSignal>(readSeenPlatforms()); + // Reactive kiosk mode state + const [kioskMode, setKioskModeSignal] = createSignal(isKioskMode()); + + // Subscribe to kiosk mode changes from other sources (like URL params) + onMount(() => { + const unsubscribe = subscribeToKioskMode((enabled) => { + setKioskModeSignal(enabled); + }); + onCleanup(unsubscribe); + }); + + const toggleKioskMode = () => { + const newValue = !kioskMode(); + setKioskMode(newValue); + setKioskModeSignal(newValue); + }; + const persistSeenPlatforms = (map: Record) => { if (typeof window === 'undefined') return; try { @@ -1288,6 +1307,27 @@ function AppLayout(props: {
{/* AI Patrol Status Indicator */} + {/* Kiosk Mode Toggle */} + {props.proxyAuthInfo()?.username} diff --git a/frontend-modern/src/utils/url.ts b/frontend-modern/src/utils/url.ts index c961e8702..82b7803ea 100644 --- a/frontend-modern/src/utils/url.ts +++ b/frontend-modern/src/utils/url.ts @@ -1,6 +1,23 @@ const FALLBACK_BASE_URL = 'http://localhost:7655'; const KIOSK_MODE_KEY = 'pulse_kiosk_mode'; +// Reactive kiosk mode tracking - listeners are notified when kiosk mode changes +type KioskListener = (enabled: boolean) => void; +const kioskListeners = new Set(); + +function notifyKioskListeners(enabled: boolean): void { + kioskListeners.forEach(listener => listener(enabled)); +} + +/** + * Subscribe to kiosk mode changes. + * Returns an unsubscribe function. + */ +export function subscribeToKioskMode(listener: KioskListener): () => void { + kioskListeners.add(listener); + return () => kioskListeners.delete(listener); +} + /** * Check and persist kiosk mode from URL parameters. * When ?kiosk=1 or ?kiosk=true is in the URL, this stores the preference @@ -49,6 +66,25 @@ export function isKioskMode(): boolean { } } +/** + * Set kiosk mode on or off. + * Updates sessionStorage and notifies reactive listeners. + */ +export function setKioskMode(enabled: boolean): void { + if (typeof window === 'undefined') return; + + try { + if (enabled) { + window.sessionStorage.setItem(KIOSK_MODE_KEY, 'true'); + } else { + window.sessionStorage.removeItem(KIOSK_MODE_KEY); + } + notifyKioskListeners(enabled); + } catch { + // Ignore storage errors + } +} + export function getPulseBaseUrl(): string { if (typeof window === 'undefined' || !window.location) { return FALLBACK_BASE_URL;