NPC editor, NPC tokens from library, admin management
- NPC library (own + shared) built on the character wizard with a free NPC preset - NPC tokens in map editor and lobby, DM sheet on token click, DM checks for NPCs - Admins can promote/demote other users Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
This commit is contained in:
@@ -65,6 +65,42 @@ function Field({ f, value, onChange }) {
|
||||
)
|
||||
}
|
||||
|
||||
// Admins can make other accounts admins (and take it away again)
|
||||
function AdminUsers() {
|
||||
const [users, setUsers] = useState(null)
|
||||
const [err, setErr] = useState('')
|
||||
const [open, setOpen] = useState(false)
|
||||
const load = () => fetch('/api/admin/users').then((r) => (r.ok ? r.json() : Promise.reject())).then(setUsers).catch(() => setErr('Could not load the users.'))
|
||||
useEffect(() => { if (open && !users) load() }, [open]) // eslint-disable-line
|
||||
const set = async (u, admin) => {
|
||||
setErr('')
|
||||
const r = await fetch(`/api/admin/users/${encodeURIComponent(u.username)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ admin }) })
|
||||
if (!r.ok) setErr((await r.json().catch(() => ({}))).detail || 'That did not work.')
|
||||
load()
|
||||
}
|
||||
return (
|
||||
<div className="card adminusers">
|
||||
<button className="linklike" aria-expanded={open} onClick={() => setOpen(!open)}>{open ? '▾' : '▸'} Users and admins</button>
|
||||
{open && (
|
||||
<>
|
||||
<p className="small muted">Admins can use this editor and make other users admins. Admins named in <code>AW_ADMINS</code> on the server can only be changed there.</p>
|
||||
{err && <p className="warn small">{err}</p>}
|
||||
<ul className="userlist">
|
||||
{(users || []).map((u) => (
|
||||
<li key={u.username} className="row">
|
||||
<span><strong>{u.username}</strong>{u.me ? ' (you)' : ''} {u.admin && <span className="chip on">admin</span>}{u.fixed && <span className="muted small"> set on the server</span>}</span>
|
||||
{u.admin
|
||||
? <button className="ghost" disabled={u.fixed} onClick={() => window.confirm(`Take admin rights from ${u.username}?`) && set(u, false)}>Remove admin</button>
|
||||
: <button className="ghost" onClick={() => window.confirm(`Make ${u.username} an admin? Admins can change the rules and make others admins.`) && set(u, true)}>Make admin</button>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Editor({ rules, onReload }) {
|
||||
const [state, setState] = useState(null)
|
||||
const [tab, setTab] = useState('equipment')
|
||||
@@ -139,6 +175,7 @@ export default function Editor({ rules, onReload }) {
|
||||
<h1>Rules editor</h1>
|
||||
<p className="muted small">Changes are stored next to the database and replace the entries of rules.json for that list; the shipped file stays as it is.
|
||||
Existing characters keep working. Talents, mutations and implants are still edited in rules.json.</p>
|
||||
<AdminUsers />
|
||||
<div className="tabs" role="tablist">
|
||||
{Object.entries(D).map(([id, x]) => (
|
||||
<button key={id} role="tab" aria-selected={tab === id} className={tab === id ? 'on' : ''} onClick={() => { setTab(id); setEditing(''); setQ(''); setMsg({ text: '', bad: false }) }}>
|
||||
|
||||
Reference in New Issue
Block a user