The close cross on a detail pane is drawn, not a glyph, and sits in no box. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
46 lines
2.0 KiB
TypeScript
46 lines
2.0 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react'
|
|
import { api, Me, Unauthenticated } from './api'
|
|
import Login from './Login'
|
|
import Shell from './Shell'
|
|
import { Dialogs } from './ui'
|
|
import { applyTheme, currentThemeId } from './themes'
|
|
import { getHistorySec, getRefreshMs, setHistorySec, setRefreshMs } from './prefs'
|
|
import { setAccents } from './accents'
|
|
import { setRailOrder } from './railorder'
|
|
|
|
export default function App() {
|
|
const [me, setMe] = useState<Me | null | undefined>(undefined)
|
|
|
|
const refresh = useCallback(async () => {
|
|
try {
|
|
const m = await api.meHere(); setMe(m)
|
|
api.uiPrefs().then((p) => {
|
|
if (p.theme && p.theme !== currentThemeId()) applyTheme(p.theme)
|
|
if (p.refreshMs && p.refreshMs !== getRefreshMs()) setRefreshMs(p.refreshMs)
|
|
if (p.accents && typeof p.accents === 'object') setAccents(p.accents, false)
|
|
if (Array.isArray(p.rail)) setRailOrder(p.rail, false)
|
|
}).catch(() => { /* keep local */ })
|
|
// The graph window is the machines' own setting; the copy here only labels the curves
|
|
// and spaces the live points, so take the hub's word for it at every login.
|
|
api.historyWindow().then((h) => { if (h.seconds && h.seconds !== getHistorySec()) setHistorySec(h.seconds) }).catch(() => { /* keep local */ })
|
|
} catch (e) { if (e instanceof Unauthenticated) setMe(null) }
|
|
}, [])
|
|
|
|
useEffect(() => { refresh() }, [refresh])
|
|
// Any page that gets a 401 from the hub sends us straight back to the login form.
|
|
useEffect(() => {
|
|
const h = () => setMe((m) => (m ? null : m))
|
|
window.addEventListener('hamlab:unauthenticated', h)
|
|
return () => window.removeEventListener('hamlab:unauthenticated', h)
|
|
}, [])
|
|
useEffect(() => {
|
|
if (!me) return
|
|
const t = setInterval(refresh, 30000)
|
|
return () => clearInterval(t)
|
|
}, [me, refresh])
|
|
|
|
if (me === undefined) return <div className="center muted">…</div>
|
|
if (me === null) return <Login onLogin={refresh} />
|
|
return <><Dialogs /><Shell me={me} onLoggedOut={() => setMe(null)} /></>
|
|
}
|