Lobby: sheet theme switch in the tab bar, remove extra toolbar

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
This commit is contained in:
Claude
2026-10-22 11:12:32 +00:00
parent 7e69b68b37
commit 59df681cfa
4 changed files with 42 additions and 23 deletions
+3 -9
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import PaperSheet from './PaperSheet.jsx'
import PaperSheet, { ThemeSwitch } from './PaperSheet.jsx'
const J = { 'Content-Type': 'application/json' }
const send = (url, method, body) => fetch(url, { method, headers: J, body: body === undefined ? undefined : JSON.stringify(body) })
@@ -323,14 +323,7 @@ function CharTab({ cid, owner, rules, onOpen, name }) {
return (
<div>
{err && <p className="warn">{err}</p>}
{data && (
<PaperSheet data={data} baseRules={rules} tools={
<>
<span className="muted small">{owner ? `Played by ${owner}` : 'Your character'} · read-only, hover or tap a value for details</span>
<button className="ghost" onClick={onOpen}>{owner ? 'Open in Character tab' : 'Edit in Character tab'}</button>
</>
} />
)}
{data && <PaperSheet data={data} baseRules={rules} bare />}
</div>
)
}
@@ -400,6 +393,7 @@ function LobbyView({ id, rules, user, onOpen }) {
<button key={m.username} role="tab" aria-selected={active === `sheet:${m.username}`} className={active === `sheet:${m.username}` ? 'on' : ''}
onClick={() => setTab(`sheet:${m.username}`)} title={m.username}>{m.me && !lobby.is_dm ? 'My character' : m.character.name}</button>
))}
{active.startsWith('sheet:') && <span className="tabright"><ThemeSwitch /></span>}
</div>
{active.startsWith('sheet:') && (() => {
+31 -13
View File
@@ -1,10 +1,30 @@
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
import { useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'
import Tip from './Tip.jsx'
import { compute, effectiveRules, kitItems, normalize } from './calc.js'
import { explainAttr, explainCapacity, explainCarry, explainDerived, explainSkill } from './explain.js'
const PAPER_PX = 794 // 210 mm at 96 dpi
// Light/dark choice for the sheet, shared by all sheets and switches on the page and remembered in the browser
const THEME_KEY = 'aw_sheettheme'
const listeners = new Set()
const readTheme = () => { try { return localStorage.getItem(THEME_KEY) === 'dark' } catch { return false } }
let darkNow = readTheme()
export function useSheetTheme() {
const dark = useSyncExternalStore((cb) => { listeners.add(cb); return () => listeners.delete(cb) }, () => darkNow)
const set = (d) => { darkNow = d; try { localStorage.setItem(THEME_KEY, d ? 'dark' : 'light') } catch { /* storage may be blocked */ } listeners.forEach((f) => f()) }
return [dark, set]
}
export function ThemeSwitch() {
const [dark, setDark] = useSheetTheme()
return (
<span className="seg" role="group" aria-label="Sheet colours">
<button className={dark ? 'ghost' : 'on'} onClick={() => setDark(false)} title="Light sheet">Light</button>
<button className={dark ? 'on' : 'ghost'} onClick={() => setDark(true)} title="Dark sheet (prints light)">Dark</button>
</span>
)
}
// Opens the sheet pages in a clean window (only the paper, A4, no margins) and starts the print dialog:
// "Save as PDF" there gives an A4 PDF that looks exactly like the screen.
export function printSheet(root, title) {
@@ -48,14 +68,13 @@ const Pips = ({ n, locked }) => (
)
// The character sheet as A4 pages (page 1: attributes and skills, page 2: talents, mutations, equipment, notes).
export default function PaperSheet({ data, baseRules, tools }) {
export default function PaperSheet({ data, baseRules, tools, bare }) {
const ch = useMemo(() => normalize(data, baseRules), [data, baseRules])
const rules = useMemo(() => effectiveRules(baseRules, ch), [baseRules, ch])
const c = useMemo(() => compute(ch, rules), [ch, rules])
const x = { ch, rules, c }
const root = useRef(null)
const [dark, setDark] = useState(() => { try { return localStorage.getItem('aw_sheettheme') === 'dark' } catch { return false } })
const pickTheme = (d) => { setDark(d); try { localStorage.setItem('aw_sheettheme', d ? 'dark' : 'light') } catch { /* storage may be blocked */ } }
const [dark] = useSheetTheme()
const prof = rules.professions.find((p) => p.id === ch.profession)
const cls = rules.classes.find((k) => k.id === ch.class_id)
@@ -73,16 +92,15 @@ export default function PaperSheet({ data, baseRules, tools }) {
return (
<div className="papersheet">
<div className="papertools">
{tools}
<span className="toolright">
<span className="seg" role="group" aria-label="Sheet colours">
<button className={dark ? 'ghost' : 'on'} onClick={() => pickTheme(false)} title="Light sheet">Light</button>
<button className={dark ? 'on' : 'ghost'} onClick={() => pickTheme(true)} title="Dark sheet (prints light)">Dark</button>
{!bare && (
<div className="papertools">
{tools}
<span className="toolright">
<ThemeSwitch />
<button className="ghost" onClick={() => printSheet(root.current, name)} title="Opens the pages for printing, choose Save as PDF there">Print / PDF (A4)</button>
</span>
<button className="ghost" onClick={() => printSheet(root.current, name)} title="Opens the pages for printing, choose Save as PDF there">Print / PDF (A4)</button>
</span>
</div>
</div>
)}
<Fit rootRef={root} dark={dark}>
<section className="paper">
<header className="phead">
+7
View File
@@ -328,3 +328,10 @@ body:has(.lobbychat) main { max-width: none; margin: 0; padding-right: calc(var(
.lobbyhead h1 { font-size: 1.3rem; margin: 0; }
.lobbypage .tabs { margin: .4rem 0 .7rem; }
.lobbypage .tabs button { padding: .3rem .8rem; }
.tabs { align-items: center; }
.tabs .tabright { margin-left: auto; align-self: center; margin-bottom: .3rem; }
.tabs .tabright button { padding: .25rem .75rem; margin-bottom: 0; border-radius: 0; border-bottom: 1px solid var(--line); }
.tabs .tabright button.on { border-color: var(--noxium); }
.tabs .tabright button:first-child { border-radius: var(--radius) 0 0 var(--radius); }
.tabs .tabright button:last-child { border-radius: 0 var(--radius) var(--radius) 0; }