import type { ReaderState } from "../../state/reader/schema.ts"; import { useGameMeta } from "../../state/gameMeta.tsx"; import { orderGames } from "../../state/gameOrder.ts"; import { useState, type ReactNode } from "react"; import type { CodesFeed } from "../../../shared/codes.ts"; import { unseenUsableCodes } from "../../state/reader/codes.ts"; export type SettingsPage = | "own" | "games" | "appearance" | "time" | "sources" | "backup" | "about" | "changelog"; export const SETTINGS_PAGES: readonly [SettingsPage, string][] = [ ["about", "About Event Clock"], ["changelog", "Changelog"], ["sources", "Data & sources"], ["time", "Region & time"], ["appearance", "Appearance"], ["own", "Your own events and games"], ["backup", "Backup"], ]; export function SettingsLayout({ page, onPage, children, state, game, onGame, codes, }: { state?: ReaderState; /** Marks a followed game with codes not yet seen on its Codes tab. */ codes?: CodesFeed | null; game?: string | undefined; onGame?: (game: string) => void; page: SettingsPage; onPage: (page: SettingsPage) => void; children: ReactNode; }) { const meta = useGameMeta(); const [allGames, setAllGames] = useState(false); const games = state ? orderGames( [ ...new Set( Object.values(state.accounts) .filter((a) => !a.retiredAt) .map((a) => a.game), ), ], Array.isArray(state.prefs.gameOrder) ? (state.prefs.gameOrder as string[]) : undefined, (g) => meta(g).name, ) : []; const isHidden = (g: string) => Array.isArray(state?.prefs.hiddenGames) && state.prefs.hiddenGames.includes(g); games.sort((a, b) => Number(isHidden(a)) - Number(isHidden(b))); const descriptions: Partial> = { own: "Events and games you added", appearance: "Theme, colours & default view", time: "Server defaults & display timezone", sources: "Freshness, sources & list options", backup: "Save, export & restore", about: "Credits & acknowledgements", changelog: "What changed", }; const chevron = ( ); const gameButton = (g: string) => { const accounts = Object.values(state!.accounts).filter( (a) => a.game === g && !a.retiredAt, ); const parts = [ ["events", "events"], ["dailies", "dailies"], ["weeklyMonthly", "weeklies"], ["cycles", "cycles"], ] as const; const summary = isHidden(g) ? "Not followed" : parts .filter(([key]) => accounts.some((a) => a.intent[key])) .map(([, label]) => label) .join(", ") || "Nothing tracked"; // Only games the reader follows: codes for the others are not news. const fresh = codes && !isHidden(g) ? unseenUsableCodes( codes, state!.codeSeen[g] ?? [], g, accounts.map((a) => a.id), state!.codeRedeemed, Date.now(), ).length : 0; return ( ); }; const others = games.filter(isHidden); return (
{children}
); }