import { h } from 'preact'; import { useState, useEffect } from 'preact/hooks'; import { wsClient } from './lib/ws'; import ViewMode from './ViewMode'; import EditMode from './EditMode'; import FullscreenMode from './FullscreenMode'; import ControlDialogs from './ControlDialogs'; import { applyZoom, getStoredZoom } from './ZoomControl'; import { AuthContext, DEFAULT_ME, fetchMe, canRead } from './lib/auth'; import type { Interface, Me } from './lib/types'; type AppMode = 'view' | 'edit'; // If URL has ?fs=, render bare fullscreen canvas const fsParam = new URLSearchParams(window.location.search).get('fs'); export default function App() { const [mode, setMode] = useState('view'); const [wsStatus, setWsStatus] = useState('connecting'); const [editTarget, setEditTarget] = useState(null); // Interface to show when returning to view mode after editing const [viewTarget, setViewTarget] = useState(null); // Resolved identity + global access level for the current user. const [me, setMe] = useState(DEFAULT_ME); useEffect(() => { const unsub = wsClient.status.subscribe(setWsStatus); return unsub; }, []); useEffect(() => { const dpr = window.devicePixelRatio ?? 1; document.documentElement.style.setProperty('--dpr', String(dpr)); applyZoom(getStoredZoom()); if (!fsParam) wsClient.connect('/ws'); fetchMe().then(setMe); }, []); if (fsParam) { return ; } function enterEdit(iface?: Interface) { setEditTarget(iface ?? null); setMode('edit'); } // `iface === null` means the editor discarded an unsaved/new panel; keep // showing whatever was previously open (held in viewTarget). function exitEdit(iface: Interface | null) { if (iface) setViewTarget(iface); setMode('view'); setEditTarget(null); } if (!canRead(me.level)) { return (

Access denied

Your account{me.user ? ` (${me.user})` : ''} does not have permission to access this application. Contact an administrator.

); } return (
{wsStatus === 'disconnected' && (
WebSocket disconnected — reconnecting…
)} {mode === 'view' ? ( ) : ( )}
); }