Replace panel mode booleans with discriminated union

Three mutually exclusive state variables (selectedCreatureId,
bulkImportMode, sourceManagerMode) replaced with a single PanelView
union type, eliminating impossible states and boolean-clearing
boilerplate in handlers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-14 11:13:23 +01:00
parent c75d148d1e
commit 3ef2370a34

View File

@@ -27,6 +27,12 @@ import { useBulkImport } from "./hooks/use-bulk-import";
import { useEncounter } from "./hooks/use-encounter"; import { useEncounter } from "./hooks/use-encounter";
import { usePlayerCharacters } from "./hooks/use-player-characters"; import { usePlayerCharacters } from "./hooks/use-player-characters";
type PanelView =
| { mode: "closed" }
| { mode: "creature"; creatureId: CreatureId }
| { mode: "bulk-import" }
| { mode: "source-manager" };
function rollDice(): number { function rollDice(): number {
return Math.floor(Math.random() * 20) + 1; return Math.floor(Math.random() * 20) + 1;
} }
@@ -116,10 +122,11 @@ export function App() {
const [rollSkippedCount, setRollSkippedCount] = useState(0); const [rollSkippedCount, setRollSkippedCount] = useState(0);
const [selectedCreatureId, setSelectedCreatureId] = const [panelView, setPanelView] = useState<PanelView>({ mode: "closed" });
useState<CreatureId | null>(null); const selectedCreatureId =
const [bulkImportMode, setBulkImportMode] = useState(false); panelView.mode === "creature" ? panelView.creatureId : null;
const [sourceManagerMode, setSourceManagerMode] = useState(false); const bulkImportMode = panelView.mode === "bulk-import";
const sourceManagerMode = panelView.mode === "source-manager";
const [isRightPanelFolded, setIsRightPanelFolded] = useState(false); const [isRightPanelFolded, setIsRightPanelFolded] = useState(false);
const [pinnedCreatureId, setPinnedCreatureId] = useState<CreatureId | null>( const [pinnedCreatureId, setPinnedCreatureId] = useState<CreatureId | null>(
null, null,
@@ -151,9 +158,7 @@ export function App() {
); );
const handleCombatantStatBlock = useCallback((creatureId: string) => { const handleCombatantStatBlock = useCallback((creatureId: string) => {
setSelectedCreatureId(creatureId as CreatureId); setPanelView({ mode: "creature", creatureId: creatureId as CreatureId });
setBulkImportMode(false);
setSourceManagerMode(false);
setIsRightPanelFolded(false); setIsRightPanelFolded(false);
}, []); }, []);
@@ -177,23 +182,17 @@ export function App() {
.replace(/[^a-z0-9]+/g, "-") .replace(/[^a-z0-9]+/g, "-")
.replace(/(^-|-$)/g, ""); .replace(/(^-|-$)/g, "");
const cId = `${result.source.toLowerCase()}:${slug}` as CreatureId; const cId = `${result.source.toLowerCase()}:${slug}` as CreatureId;
setSelectedCreatureId(cId); setPanelView({ mode: "creature", creatureId: cId });
setBulkImportMode(false);
setSourceManagerMode(false);
setIsRightPanelFolded(false); setIsRightPanelFolded(false);
}, []); }, []);
const handleBulkImport = useCallback(() => { const handleBulkImport = useCallback(() => {
setBulkImportMode(true); setPanelView({ mode: "bulk-import" });
setSourceManagerMode(false);
setSelectedCreatureId(null);
setIsRightPanelFolded(false); setIsRightPanelFolded(false);
}, []); }, []);
const handleOpenSourceManager = useCallback(() => { const handleOpenSourceManager = useCallback(() => {
setSourceManagerMode(true); setPanelView({ mode: "source-manager" });
setBulkImportMode(false);
setSelectedCreatureId(null);
setIsRightPanelFolded(false); setIsRightPanelFolded(false);
}, []); }, []);
@@ -210,14 +209,12 @@ export function App() {
); );
const handleBulkImportDone = useCallback(() => { const handleBulkImportDone = useCallback(() => {
setBulkImportMode(false); setPanelView({ mode: "closed" });
bulkImport.reset(); bulkImport.reset();
}, [bulkImport.reset]); }, [bulkImport.reset]);
const handleDismissBrowsePanel = useCallback(() => { const handleDismissBrowsePanel = useCallback(() => {
setSelectedCreatureId(null); setPanelView({ mode: "closed" });
setBulkImportMode(false);
setSourceManagerMode(false);
}, []); }, []);
const handleToggleFold = useCallback(() => { const handleToggleFold = useCallback(() => {
@@ -258,9 +255,10 @@ export function App() {
if (!window.matchMedia("(min-width: 1024px)").matches) return; if (!window.matchMedia("(min-width: 1024px)").matches) return;
const active = encounter.combatants[encounter.activeIndex]; const active = encounter.combatants[encounter.activeIndex];
if (!active?.creatureId || !isLoaded) return; if (!active?.creatureId || !isLoaded) return;
setSelectedCreatureId(active.creatureId as CreatureId); setPanelView({
setBulkImportMode(false); mode: "creature",
setSourceManagerMode(false); creatureId: active.creatureId as CreatureId,
});
}, [encounter.activeIndex, encounter.combatants, isLoaded]); }, [encounter.activeIndex, encounter.combatants, isLoaded]);
const isEmpty = encounter.combatants.length === 0; const isEmpty = encounter.combatants.length === 0;