Files
uopi/web/src/App.tsx
T
2026-06-20 14:28:28 +02:00

86 lines
2.7 KiB
TypeScript

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=<id>, render bare fullscreen canvas
const fsParam = new URLSearchParams(window.location.search).get('fs');
export default function App() {
const [mode, setMode] = useState<AppMode>('view');
const [wsStatus, setWsStatus] = useState('connecting');
const [editTarget, setEditTarget] = useState<Interface | null>(null);
// Interface to show when returning to view mode after editing
const [viewTarget, setViewTarget] = useState<Interface | null>(null);
// Resolved identity + global access level for the current user.
const [me, setMe] = useState<Me>(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 <FullscreenMode interfaceId={fsParam} />;
}
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 (
<div class="app-root">
<div class="access-denied">
<h1>Access denied</h1>
<p>
Your account{me.user ? ` (${me.user})` : ''} does not have permission
to access this application. Contact an administrator.
</p>
</div>
</div>
);
}
return (
<AuthContext.Provider value={me}>
<div class="app-root">
<ControlDialogs />
{wsStatus === 'disconnected' && (
<div class="connection-banner">WebSocket disconnected reconnecting</div>
)}
{mode === 'view' ? (
<ViewMode onEdit={enterEdit} initialInterface={viewTarget} onView={setViewTarget} />
) : (
<EditMode key={editTarget?.id || 'new'} initial={editTarget} onDone={exitEdit} />
)}
</div>
</AuthContext.Provider>
);
}