Refactor App.tsx from god component to context-based architecture
All checks were successful
CI / check (push) Successful in 1m18s
CI / build-image (push) Has been skipped

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-19 14:19:58 +01:00
parent 6584d8d064
commit 86768842ff
35 changed files with 1065 additions and 795 deletions

View File

@@ -1,9 +1,9 @@
import type { Creature, CreatureId } from "@initiative/domain";
import type { CreatureId } from "@initiative/domain";
import { PanelRightClose, Pin, PinOff } from "lucide-react";
import type { ReactNode } from "react";
import { useEffect, useState } from "react";
import { getSourceDisplayName } from "../adapters/bestiary-index-adapter.js";
import type { BulkImportState } from "../hooks/use-bulk-import.js";
import { useBestiaryContext } from "../contexts/bestiary-context.js";
import { useSidePanelContext } from "../contexts/side-panel-context.js";
import { useSwipeToDismiss } from "../hooks/use-swipe-to-dismiss.js";
import { cn } from "../lib/utils.js";
import { BulkImportPrompt } from "./bulk-import-prompt.js";
@@ -13,28 +13,8 @@ import { StatBlock } from "./stat-block.js";
import { Button } from "./ui/button.js";
interface StatBlockPanelProps {
creatureId: CreatureId | null;
creature: Creature | null;
isSourceCached: (sourceCode: string) => Promise<boolean>;
fetchAndCacheSource: (sourceCode: string, url: string) => Promise<void>;
uploadAndCacheSource: (
sourceCode: string,
jsonData: unknown,
) => Promise<void>;
refreshCache: () => Promise<void>;
panelRole: "browse" | "pinned";
isCollapsed: boolean;
onToggleCollapse: () => void;
onPin: () => void;
onUnpin: () => void;
showPinButton: boolean;
side: "left" | "right";
onDismiss: () => void;
bulkImportMode?: boolean;
bulkImportState?: BulkImportState;
onStartBulkImport?: (baseUrl: string) => void;
onBulkImportDone?: () => void;
sourceManagerMode?: boolean;
}
function extractSourceCode(cId: CreatureId): string {
@@ -228,27 +208,49 @@ function MobileDrawer({
);
}
function usePanelRole(panelRole: "browse" | "pinned") {
const sidePanel = useSidePanelContext();
const { getCreature } = useBestiaryContext();
const creatureId =
panelRole === "browse"
? sidePanel.selectedCreatureId
: sidePanel.pinnedCreatureId;
const creature = creatureId ? (getCreature(creatureId) ?? null) : null;
const isBrowse = panelRole === "browse";
return {
creatureId,
creature,
isCollapsed: isBrowse ? sidePanel.isRightPanelCollapsed : false,
onToggleCollapse: isBrowse ? sidePanel.toggleCollapse : () => {},
onDismiss: isBrowse ? sidePanel.dismissPanel : () => {},
onPin: isBrowse ? sidePanel.togglePin : () => {},
onUnpin: panelRole === "pinned" ? sidePanel.unpin : () => {},
showPinButton: isBrowse && sidePanel.isWideDesktop && !!creature,
bulkImportMode: isBrowse && sidePanel.bulkImportMode,
sourceManagerMode: isBrowse && sidePanel.sourceManagerMode,
};
}
export function StatBlockPanel({
creatureId,
creature,
isSourceCached,
fetchAndCacheSource,
uploadAndCacheSource,
refreshCache,
panelRole,
isCollapsed,
onToggleCollapse,
onPin,
onUnpin,
showPinButton,
side,
onDismiss,
bulkImportMode,
bulkImportState,
onStartBulkImport,
onBulkImportDone,
sourceManagerMode,
}: Readonly<StatBlockPanelProps>) {
const { isSourceCached } = useBestiaryContext();
const {
creatureId,
creature,
isCollapsed,
onToggleCollapse,
onDismiss,
onPin,
onUnpin,
showPinButton,
bulkImportMode,
sourceManagerMode,
} = usePanelRole(panelRole);
const [isDesktop, setIsDesktop] = useState(
() => globalThis.matchMedia("(min-width: 1024px)").matches,
);
@@ -285,29 +287,17 @@ export function StatBlockPanel({
const sourceCode = creatureId ? extractSourceCode(creatureId) : "";
const handleSourceLoaded = async () => {
await refreshCache();
const handleSourceLoaded = () => {
setNeedsFetch(false);
};
const renderContent = () => {
if (sourceManagerMode) {
return <SourceManager onCacheCleared={refreshCache} />;
return <SourceManager />;
}
if (
bulkImportMode &&
bulkImportState &&
onStartBulkImport &&
onBulkImportDone
) {
return (
<BulkImportPrompt
importState={bulkImportState}
onStartImport={onStartBulkImport}
onDone={onBulkImportDone}
/>
);
if (bulkImportMode) {
return <BulkImportPrompt />;
}
if (checkingCache) {
@@ -324,10 +314,7 @@ export function StatBlockPanel({
return (
<SourceFetchPrompt
sourceCode={sourceCode}
sourceDisplayName={getSourceDisplayName(sourceCode)}
fetchAndCacheSource={fetchAndCacheSource}
onSourceLoaded={handleSourceLoaded}
onUploadSource={uploadAndCacheSource}
/>
);
}