From 6ac8e679709cbec902532474714a5b96934041fd Mon Sep 17 00:00:00 2001 From: Lukas Date: Fri, 13 Mar 2026 18:40:28 +0100 Subject: [PATCH] Move source manager from combatant area to side panel Source management now opens in the right side panel (like bulk import and stat blocks) instead of rendering inline above the combatant list. All three panel modes properly clear each other on activation. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/App.tsx | 27 ++++++++++++-------- apps/web/src/components/stat-block-panel.tsx | 16 ++++++++++-- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 516de4f..f08446e 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -14,7 +14,6 @@ import { ActionBar } from "./components/action-bar"; import { CombatantRow } from "./components/combatant-row"; import { CreatePlayerModal } from "./components/create-player-modal"; import { PlayerManagement } from "./components/player-management"; -import { SourceManager } from "./components/source-manager"; import { StatBlockPanel } from "./components/stat-block-panel"; import { Toast } from "./components/toast"; import { TurnNavigation } from "./components/turn-navigation"; @@ -113,7 +112,7 @@ export function App() { const [selectedCreatureId, setSelectedCreatureId] = useState(null); const [bulkImportMode, setBulkImportMode] = useState(false); - const [sourceManagerOpen, setSourceManagerOpen] = useState(false); + const [sourceManagerMode, setSourceManagerMode] = useState(false); const [isRightPanelFolded, setIsRightPanelFolded] = useState(false); const [pinnedCreatureId, setPinnedCreatureId] = useState( null, @@ -146,6 +145,8 @@ export function App() { const handleCombatantStatBlock = useCallback((creatureId: string) => { setSelectedCreatureId(creatureId as CreatureId); + setBulkImportMode(false); + setSourceManagerMode(false); setIsRightPanelFolded(false); }, []); @@ -167,11 +168,21 @@ export function App() { .replace(/(^-|-$)/g, ""); const cId = `${result.source.toLowerCase()}:${slug}` as CreatureId; setSelectedCreatureId(cId); + setBulkImportMode(false); + setSourceManagerMode(false); setIsRightPanelFolded(false); }, []); const handleBulkImport = useCallback(() => { setBulkImportMode(true); + setSourceManagerMode(false); + setSelectedCreatureId(null); + setIsRightPanelFolded(false); + }, []); + + const handleOpenSourceManager = useCallback(() => { + setSourceManagerMode(true); + setBulkImportMode(false); setSelectedCreatureId(null); setIsRightPanelFolded(false); }, []); @@ -196,6 +207,7 @@ export function App() { const handleDismissBrowsePanel = useCallback(() => { setSelectedCreatureId(null); setBulkImportMode(false); + setSourceManagerMode(false); }, []); const handleToggleFold = useCallback(() => { @@ -282,19 +294,13 @@ export function App() { onManagePlayers={() => setManagementOpen(true)} onRollAllInitiative={handleRollAllInitiative} showRollAllInitiative={showRollAllInitiative} - onOpenSourceManager={() => setSourceManagerOpen((o) => !o)} + onOpenSourceManager={handleOpenSourceManager} autoFocus /> ) : ( <> - {sourceManagerOpen && ( -
- -
- )} - {/* Scrollable area — combatant list */}
@@ -344,7 +350,7 @@ export function App() { onManagePlayers={() => setManagementOpen(true)} onRollAllInitiative={handleRollAllInitiative} showRollAllInitiative={showRollAllInitiative} - onOpenSourceManager={() => setSourceManagerOpen((o) => !o)} + onOpenSourceManager={handleOpenSourceManager} />
@@ -391,6 +397,7 @@ export function App() { bulkImportState={bulkImport.state} onStartBulkImport={handleStartBulkImport} onBulkImportDone={handleBulkImportDone} + sourceManagerMode={sourceManagerMode} /> {/* Toast for bulk import progress when panel is closed */} diff --git a/apps/web/src/components/stat-block-panel.tsx b/apps/web/src/components/stat-block-panel.tsx index a6686a0..1a715ee 100644 --- a/apps/web/src/components/stat-block-panel.tsx +++ b/apps/web/src/components/stat-block-panel.tsx @@ -7,6 +7,7 @@ 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 { SourceManager } from "./source-manager.js"; import { StatBlock } from "./stat-block.js"; import { Button } from "./ui/button.js"; @@ -32,6 +33,7 @@ interface StatBlockPanelProps { bulkImportState?: BulkImportState; onStartBulkImport?: (baseUrl: string) => void; onBulkImportDone?: () => void; + sourceManagerMode?: boolean; } function extractSourceCode(cId: CreatureId): string { @@ -236,6 +238,7 @@ export function StatBlockPanel({ bulkImportState, onStartBulkImport, onBulkImportDone, + sourceManagerMode, }: StatBlockPanelProps) { const [isDesktop, setIsDesktop] = useState( () => window.matchMedia("(min-width: 1024px)").matches, @@ -269,7 +272,7 @@ export function StatBlockPanel({ }); }, [creatureId, creature, isSourceCached]); - if (!creatureId && !bulkImportMode) return null; + if (!creatureId && !bulkImportMode && !sourceManagerMode) return null; const sourceCode = creatureId ? extractSourceCode(creatureId) : ""; @@ -279,6 +282,10 @@ export function StatBlockPanel({ }; const renderContent = () => { + if (sourceManagerMode) { + return ; + } + if ( bulkImportMode && bulkImportState && @@ -324,7 +331,12 @@ export function StatBlockPanel({ }; const creatureName = - creature?.name ?? (bulkImportMode ? "Bulk Import" : "Creature"); + creature?.name ?? + (sourceManagerMode + ? "Sources" + : bulkImportMode + ? "Bulk Import" + : "Creature"); if (isDesktop) { return (