From dbf3126f29543668b6ab229ffea453d6a0f574c2 Mon Sep 17 00:00:00 2001 From: Drew Malzahn Date: Sun, 28 Jun 2026 13:10:15 -0400 Subject: [PATCH] refactor: Split CharacterSheet.tsx into separate components --- src/CharacterSheet.tsx | 1422 ++------------------------------ src/components/ClassesPage.tsx | 232 ++++++ src/components/Header.tsx | 51 ++ src/components/MainPage.tsx | 683 +++++++++++++++ src/components/ManagePage.tsx | 96 +++ src/components/SpellsPage.tsx | 236 ++++++ src/constants.ts | 31 + src/types.ts | 64 ++ src/utils.ts | 4 + 9 files changed, 1475 insertions(+), 1344 deletions(-) create mode 100644 src/components/ClassesPage.tsx create mode 100644 src/components/Header.tsx create mode 100644 src/components/MainPage.tsx create mode 100644 src/components/ManagePage.tsx create mode 100644 src/components/SpellsPage.tsx create mode 100644 src/constants.ts create mode 100644 src/types.ts create mode 100644 src/utils.ts diff --git a/src/CharacterSheet.tsx b/src/CharacterSheet.tsx index a86809f..a8dcac9 100644 --- a/src/CharacterSheet.tsx +++ b/src/CharacterSheet.tsx @@ -1,106 +1,17 @@ import React, { useState, useEffect, useCallback, useRef } from "react"; import "./fabula-ultima-sheet.css"; -import spellsFile from "../data/spells.yml"; -import weaponsFile from "../data/weapons.yml"; -import armorShieldsFile from "../data/armor_shields.yml"; - -const STATUSES = ["Slow", "Enraged", "Dazed", "Weak", "Poisoned", "Shaken"]; -const FEELINGS = [ - "Admiration", - "Loyalty", - "Hatred", - "Inferiority", - "Mistrust", - "Affection", -]; -const MUTUAL_EXCLUSIVE: Record = { - Admiration: "Inferiority", - Inferiority: "Admiration", - Loyalty: "Mistrust", - Mistrust: "Loyalty", - Hatred: "Affection", - Affection: "Hatred", -}; -const MARTIAL_ITEMS = [ - "Martial Armor", - "Martial Shields", - "Martial Melee", - "Martial Ranged", -]; -const DISCIPLINES = [ - "Arcanism", - "Chimerism", - "Elementalism", - "Entropism", - "Ritualism", - "Spiritism", -]; - -interface Fields { - charName: string; - charPronouns: string; - charIdentity: string; - charTheme: string; - charOrigin: string; - charTraits: string; - xpCurrent: string | number; - zenit: string | number; - initMod: string | number; - defense: string | number; - magDef: string | number; - dexBase: string | number; - dexCur: string | number; - insBase: string | number; - insCur: string | number; - migBase: string | number; - migCur: string | number; - wlpBase: string | number; - wlpCur: string | number; - hpMax: string | number; - hpCur: string | number; - mpMax: string | number; - mpCur: string | number; - ipMax: string | number; - ipCur: string | number; - backpack: string; - heroicSkills: string; - ritualsNotes: string; - accName: string; - accDesc: string; - armName: string; - armDesc: string; - mhName: string; - mhDesc: string; - ohName: string; - ohDesc: string; -} - -interface Bond { - name: string; - feelings: string[]; -} - -interface ClassEntry { - name: string; - level: number; - benefits: string; - skills: string; -} - -interface Spell { - name: string; - spellClass: string; - notes: string; - mp: string; - targets: string; - duration: string; -} - -type CheckMap = Record; - -// Saved/shared payloads use abbreviated keys (with legacy long-name -// fallbacks), so the shape is intentionally loose. -type SavedData = Record; +import { Fields, Bond, ClassEntry, Spell, CheckMap, SavedData } from "./types"; +import { + STATUSES, + MARTIAL_ITEMS, + DISCIPLINES, + MUTUAL_EXCLUSIVE, +} from "./constants"; +import Header from "./components/Header"; +import MainPage from "./components/MainPage"; +import ClassesPage from "./components/ClassesPage"; +import SpellsPage from "./components/SpellsPage"; +import ManagePage from "./components/ManagePage"; const BLANK_FIELDS: Fields = { charName: "", @@ -205,11 +116,6 @@ function apiURL(path: string) { return new URL(path, window.location.href).toString(); } -function autoResize(el: HTMLTextAreaElement) { - el.style.height = "auto"; - el.style.height = el.scrollHeight + "px"; -} - // Copy text to the clipboard. navigator.clipboard is only available in a // secure context (HTTPS or localhost); over plain HTTP it's undefined, so we // fall back to the deprecated execCommand("copy"), which still works there. @@ -254,9 +160,7 @@ export default function CharacterSheet() { const [theme, setTheme] = useState( () => localStorage.getItem("fabulaUltimaTheme") || - (window.matchMedia("(prefers-color-scheme: light)").matches - ? "light" - : "dark"), + (window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"), ); const importFileRef = useRef(null); @@ -613,1250 +517,80 @@ export default function CharacterSheet() { return ( <> -
-
Fabula Ultima
-
- {["main", "classes", "spells", "manage"].map((tab, i) => ( - - ))} -
-
- - Saved! - - - -
-
+
{urlMode && (
- Viewing a shared character - — auto-save is disabled. Use Manage → Save to Browser{" "} - to keep any changes. + Viewing a shared character — auto-save is + disabled. Use Manage → Save to Browser to keep any changes.
)} - {/* ── PAGE 1: MAIN ── */} -
- {/* Row 1: Identity + Level */} -
-
-
- Identity & Traits -
-
-
- - f("charName", e.target.value)} - placeholder="Character name…" - /> -
-
- - f("charPronouns", e.target.value)} - placeholder="they/them" - /> -
-
-
- - f("charIdentity", e.target.value)} - placeholder="Who are you?" - /> -
-
-
- - f("charTheme", e.target.value)} - placeholder="Your theme…" - /> -
-
- - f("charOrigin", e.target.value)} - placeholder="Where from?" - /> -
-
-
- -