import type { Creature, 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 { useSwipeToDismiss } from "../hooks/use-swipe-to-dismiss.js"; import { BulkImportPrompt } from "./bulk-import-prompt.js"; import { SourceFetchPrompt } from "./source-fetch-prompt.js"; import { StatBlock } from "./stat-block.js"; interface StatBlockPanelProps { creatureId: CreatureId | null; creature: Creature | null; isSourceCached: (sourceCode: string) => Promise; fetchAndCacheSource: (sourceCode: string, url: string) => Promise; uploadAndCacheSource: ( sourceCode: string, jsonData: unknown, ) => Promise; refreshCache: () => Promise; panelRole: "browse" | "pinned"; isFolded: boolean; onToggleFold: () => void; onPin: () => void; onUnpin: () => void; showPinButton: boolean; side: "left" | "right"; onDismiss: () => void; bulkImportMode?: boolean; bulkImportState?: BulkImportState; onStartBulkImport?: (baseUrl: string) => void; onBulkImportDone?: () => void; } function extractSourceCode(cId: CreatureId): string { const colonIndex = cId.indexOf(":"); if (colonIndex === -1) return ""; return cId.slice(0, colonIndex).toUpperCase(); } function FoldedTab({ creatureName, side, onToggleFold, }: { creatureName: string; side: "left" | "right"; onToggleFold: () => void; }) { return ( ); } function PanelHeader({ panelRole, showPinButton, onToggleFold, onPin, onUnpin, }: { panelRole: "browse" | "pinned"; showPinButton: boolean; onToggleFold: () => void; onPin: () => void; onUnpin: () => void; }) { return (
{panelRole === "browse" && ( )}
{panelRole === "browse" && showPinButton && ( )} {panelRole === "pinned" && ( )}
); } function DesktopPanel({ isFolded, side, creatureName, panelRole, showPinButton, onToggleFold, onPin, onUnpin, children, }: { isFolded: boolean; side: "left" | "right"; creatureName: string; panelRole: "browse" | "pinned"; showPinButton: boolean; onToggleFold: () => void; onPin: () => void; onUnpin: () => void; children: ReactNode; }) { const sideClasses = side === "left" ? "left-0 border-r" : "right-0 border-l"; const foldedTranslate = side === "right" ? "translate-x-[calc(100%-40px)]" : "translate-x-[calc(-100%+40px)]"; return (
{isFolded ? ( ) : ( <>
{children}
)}
); } function MobileDrawer({ onDismiss, children, }: { onDismiss: () => void; children: ReactNode; }) { const { offsetX, isSwiping, handlers } = useSwipeToDismiss(onDismiss); return (
{children}
); } export function StatBlockPanel({ creatureId, creature, isSourceCached, fetchAndCacheSource, uploadAndCacheSource, refreshCache, panelRole, isFolded, onToggleFold, onPin, onUnpin, showPinButton, side, onDismiss, bulkImportMode, bulkImportState, onStartBulkImport, onBulkImportDone, }: StatBlockPanelProps) { const [isDesktop, setIsDesktop] = useState( () => window.matchMedia("(min-width: 1024px)").matches, ); const [needsFetch, setNeedsFetch] = useState(false); const [checkingCache, setCheckingCache] = useState(false); useEffect(() => { const mq = window.matchMedia("(min-width: 1024px)"); const handler = (e: MediaQueryListEvent) => setIsDesktop(e.matches); mq.addEventListener("change", handler); return () => mq.removeEventListener("change", handler); }, []); useEffect(() => { if (!creatureId || creature) { setNeedsFetch(false); return; } const sourceCode = extractSourceCode(creatureId); if (!sourceCode) { setNeedsFetch(false); return; } setCheckingCache(true); isSourceCached(sourceCode).then((cached) => { setNeedsFetch(!cached); setCheckingCache(false); }); }, [creatureId, creature, isSourceCached]); if (!creatureId && !bulkImportMode) return null; const sourceCode = creatureId ? extractSourceCode(creatureId) : ""; const handleSourceLoaded = async () => { await refreshCache(); setNeedsFetch(false); }; const renderContent = () => { if ( bulkImportMode && bulkImportState && onStartBulkImport && onBulkImportDone ) { return ( ); } if (checkingCache) { return (
Loading...
); } if (creature) { return ; } if (needsFetch && sourceCode) { return ( ); } return (
No stat block available
); }; const creatureName = creature?.name ?? (bulkImportMode ? "Bulk Import" : "Creature"); if (isDesktop) { return ( {renderContent()} ); } if (panelRole === "pinned") return null; return {renderContent()}; }