Maps page (list, upload, edit, share, copy), wall/room/door tools in the map editor, server-side visibility polygon: the image is cut per player and other tokens are filtered, DM can view as a player, open doors and switch sight on/off. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
93 lines
4.3 KiB
React
93 lines
4.3 KiB
React
import { useEffect, useState } from 'react'
|
|
import Creator from './Creator.jsx'
|
|
import Rules from './Rules.jsx'
|
|
import Maps from './Maps.jsx'
|
|
import Characters from './Characters.jsx'
|
|
import Presets from './Presets.jsx'
|
|
import Lobbies from './Lobbies.jsx'
|
|
import Login from './Login.jsx'
|
|
import Account from './Account.jsx'
|
|
import Editor from './Editor.jsx'
|
|
import { claimLegacy } from './legacy.js'
|
|
|
|
const useHash = () => {
|
|
const [h, setH] = useState(window.location.hash.slice(1) || 'creator')
|
|
useEffect(() => {
|
|
const f = () => setH(window.location.hash.slice(1) || 'creator')
|
|
window.addEventListener('hashchange', f)
|
|
return () => window.removeEventListener('hashchange', f)
|
|
}, [])
|
|
return h
|
|
}
|
|
|
|
export default function App() {
|
|
const hash = useHash()
|
|
const [auth, setAuth] = useState(null) // null = checking, {user, registration_open}
|
|
const [rules, setRules] = useState(null)
|
|
const [error, setError] = useState('')
|
|
// Used to load a saved character into the creator
|
|
const [loadRequest, setLoadRequest] = useState(null)
|
|
const [charName, setCharName] = useState('') // name of the character being worked on, shown in the nav
|
|
|
|
// Ask the server who we are; any later 401 (session expired) sends us back to the login
|
|
useEffect(() => {
|
|
fetch('/api/auth/me').then((r) => r.json()).then(setAuth).catch(() => setError('Could not reach the server.'))
|
|
const out = () => { setAuth((a) => ({ user: null, registration_open: a?.registration_open })); setRules(null) }
|
|
window.addEventListener('aw-unauthorized', out)
|
|
return () => window.removeEventListener('aw-unauthorized', out)
|
|
}, [])
|
|
|
|
const username = auth?.user?.username
|
|
const loadRules = () => fetch('/api/rules').then((r) => r.json()).then(setRules).catch(() => setError('Could not load the rules.'))
|
|
useEffect(() => {
|
|
if (!username) return
|
|
claimLegacy().finally(loadRules)
|
|
}, [username]) // eslint-disable-line
|
|
|
|
const logout = async () => {
|
|
await fetch('/api/auth/logout', { method: 'POST' }).catch(() => {})
|
|
window.location.hash = 'creator'
|
|
setAuth({ user: null, registration_open: auth.registration_open }); setRules(null)
|
|
}
|
|
|
|
if (error) return <main><p className="warn">{error}</p></main>
|
|
if (!auth) return <main><p className="muted">Loading …</p></main>
|
|
if (!auth.user) return <main><Login registrationOpen={auth.registration_open} onDone={(u) => setAuth({ ...auth, user: u })} /></main>
|
|
|
|
const page = hash.split('/')[0]
|
|
const nav = [['creator', charName ? `Character: ${charName}` : 'Character'], ['rules', 'Rules'], ['presets', 'Presets'], ['maps', 'Maps'], ['characters', 'My characters'], ['lobbies', 'Lobbies'], ...(auth.user.admin ? [['editor', 'Editor']] : [])]
|
|
|
|
return (
|
|
<>
|
|
<header className="top">
|
|
<a className="brand" href="#creator">
|
|
<span className="brand-mark" aria-hidden="true" />
|
|
<span>Arcane Wasteland</span>
|
|
</a>
|
|
<nav>
|
|
{nav.map(([id, label]) => (
|
|
<a key={id} href={`#${id}`} className={`${page === id ? 'active' : ''} ${id === 'creator' ? 'navchar' : ''}`}>{label}</a>
|
|
))}
|
|
<a href="#account" className={page === 'account' ? 'active' : ''} title="Account">{username}</a>
|
|
</nav>
|
|
</header>
|
|
<main>
|
|
{!rules && <p className="muted">Loading rules …</p>}
|
|
{rules && page === 'creator' && <Creator rules={rules} user={username} loadRequest={loadRequest} clearLoad={() => setLoadRequest(null)} onName={setCharName} />}
|
|
{rules && page === 'rules' && <Rules rules={rules} section={hash.split('/')[1]} />}
|
|
{rules && page === 'presets' && <Presets rules={rules} reload={loadRules} />}
|
|
{page === 'maps' && <Maps />}
|
|
{rules && page === 'characters' && (
|
|
<Characters onOpen={(entry) => { setLoadRequest(entry); window.location.hash = 'creator' }}
|
|
onNew={() => { setLoadRequest({ new: true }); window.location.hash = 'creator' }} />
|
|
)}
|
|
{rules && page === 'lobbies' && (
|
|
<Lobbies section={hash.split('/')[1]} rules={rules} user={username} onOpen={(entry) => { setLoadRequest(entry); window.location.hash = 'creator' }} />
|
|
)}
|
|
{rules && page === 'editor' && auth.user.admin && <Editor rules={rules} onReload={loadRules} />}
|
|
{page === 'account' && <Account user={username} onLogout={logout} />}
|
|
</main>
|
|
</>
|
|
)
|
|
}
|