124 lines
3.2 KiB
JavaScript
124 lines
3.2 KiB
JavaScript
import { useState, useEffect } from 'react'
|
|
import { generateToken, loadState, saveState } from './api.js'
|
|
import { createState } from './gameState.js'
|
|
import { BACKGROUNDS } from './constants.js'
|
|
import Shell from './components/Shell.jsx'
|
|
import Actor from './screens/Actor.jsx'
|
|
import Forum from './screens/Forum.jsx'
|
|
import Map from './screens/Map.jsx'
|
|
import './App.css'
|
|
|
|
import contextsJson from './config/contexts.json'
|
|
|
|
const TOKEN_KEY = 'otivm_token'
|
|
|
|
export default function App() {
|
|
const [state, setState] = useState(null)
|
|
const [token, setToken] = useState(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [context, setContext] = useState('actor')
|
|
|
|
useEffect(() => {
|
|
async function bootstrap() {
|
|
let tok = localStorage.getItem(TOKEN_KEY)
|
|
if (!tok) {
|
|
tok = generateToken()
|
|
localStorage.setItem(TOKEN_KEY, tok)
|
|
}
|
|
setToken(tok)
|
|
const saved = await loadState(tok)
|
|
if (saved) {
|
|
setState(saved)
|
|
setContext(saved.background_id && saved.background_id !== 'unknown' ? 'forum' : 'actor')
|
|
} else {
|
|
const fresh = createState(tok)
|
|
setState(fresh)
|
|
await saveState(tok, fresh)
|
|
setContext('actor')
|
|
}
|
|
setLoading(false)
|
|
}
|
|
bootstrap()
|
|
}, [])
|
|
|
|
async function onStateChange(newState) {
|
|
setState(newState)
|
|
await saveState(token, newState)
|
|
}
|
|
|
|
async function onSelectBackground(backgroundId) {
|
|
const bg = BACKGROUNDS.find(b => b.id === backgroundId)
|
|
if (!bg) return
|
|
const updated = { ...state, background_id: backgroundId, den: bg.starting_den }
|
|
setState(updated)
|
|
await saveState(token, updated)
|
|
setContext('forum')
|
|
}
|
|
|
|
async function onNewGame() {
|
|
if (state && token) {
|
|
const abandoned = {
|
|
...state,
|
|
events: [
|
|
...(state.events || []),
|
|
{ type: 'session_abandoned', route_id: null, timestamp_utc: new Date().toISOString() },
|
|
],
|
|
}
|
|
await saveState(token, abandoned)
|
|
}
|
|
const newTok = generateToken()
|
|
localStorage.setItem(TOKEN_KEY, newTok)
|
|
setToken(newTok)
|
|
const fresh = createState(newTok)
|
|
setState(fresh)
|
|
await saveState(newTok, fresh)
|
|
setContext('actor')
|
|
}
|
|
|
|
if (loading) {
|
|
return <div className="otivm-loading"><span>Consulting the ledger...</span></div>
|
|
}
|
|
|
|
const activeCtx = contextsJson.find(c => c.id === context) || contextsJson[0]
|
|
const layout = activeCtx.layout
|
|
const subitems = activeCtx.subitems || []
|
|
|
|
return (
|
|
<Shell
|
|
contexts={contextsJson}
|
|
activeId={context}
|
|
onContext={setContext}
|
|
subitems={subitems}
|
|
layout={layout}
|
|
token={token}
|
|
onNewGame={onNewGame}
|
|
ctxName={activeCtx.name}
|
|
ctxSubtitle={activeCtx.subtitle}
|
|
>
|
|
{/* ACTOR */}
|
|
{context === 'actor' && (
|
|
<Actor
|
|
state={state}
|
|
onSelectBackground={onSelectBackground}
|
|
layout={layout}
|
|
/>
|
|
)}
|
|
|
|
{/* FORUM */}
|
|
{context === 'forum' && (
|
|
<Forum
|
|
state={state}
|
|
onStateChange={onStateChange}
|
|
onNewGame={onNewGame}
|
|
layout={layout}
|
|
/>
|
|
)}
|
|
|
|
{/* MAP */}
|
|
{context === 'map' && (
|
|
<Map state={state} layout={layout} />
|
|
)}
|
|
</Shell>
|
|
)
|
|
}
|