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 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,6 @@ import { ActionBar } from "./components/action-bar";
|
|||||||
import { CombatantRow } from "./components/combatant-row";
|
import { CombatantRow } from "./components/combatant-row";
|
||||||
import { CreatePlayerModal } from "./components/create-player-modal";
|
import { CreatePlayerModal } from "./components/create-player-modal";
|
||||||
import { PlayerManagement } from "./components/player-management";
|
import { PlayerManagement } from "./components/player-management";
|
||||||
import { SourceManager } from "./components/source-manager";
|
|
||||||
import { StatBlockPanel } from "./components/stat-block-panel";
|
import { StatBlockPanel } from "./components/stat-block-panel";
|
||||||
import { Toast } from "./components/toast";
|
import { Toast } from "./components/toast";
|
||||||
import { TurnNavigation } from "./components/turn-navigation";
|
import { TurnNavigation } from "./components/turn-navigation";
|
||||||
@@ -113,7 +112,7 @@ export function App() {
|
|||||||
const [selectedCreatureId, setSelectedCreatureId] =
|
const [selectedCreatureId, setSelectedCreatureId] =
|
||||||
useState<CreatureId | null>(null);
|
useState<CreatureId | null>(null);
|
||||||
const [bulkImportMode, setBulkImportMode] = useState(false);
|
const [bulkImportMode, setBulkImportMode] = useState(false);
|
||||||
const [sourceManagerOpen, setSourceManagerOpen] = useState(false);
|
const [sourceManagerMode, setSourceManagerMode] = useState(false);
|
||||||
const [isRightPanelFolded, setIsRightPanelFolded] = useState(false);
|
const [isRightPanelFolded, setIsRightPanelFolded] = useState(false);
|
||||||
const [pinnedCreatureId, setPinnedCreatureId] = useState<CreatureId | null>(
|
const [pinnedCreatureId, setPinnedCreatureId] = useState<CreatureId | null>(
|
||||||
null,
|
null,
|
||||||
@@ -146,6 +145,8 @@ export function App() {
|
|||||||
|
|
||||||
const handleCombatantStatBlock = useCallback((creatureId: string) => {
|
const handleCombatantStatBlock = useCallback((creatureId: string) => {
|
||||||
setSelectedCreatureId(creatureId as CreatureId);
|
setSelectedCreatureId(creatureId as CreatureId);
|
||||||
|
setBulkImportMode(false);
|
||||||
|
setSourceManagerMode(false);
|
||||||
setIsRightPanelFolded(false);
|
setIsRightPanelFolded(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -167,11 +168,21 @@ export function App() {
|
|||||||
.replace(/(^-|-$)/g, "");
|
.replace(/(^-|-$)/g, "");
|
||||||
const cId = `${result.source.toLowerCase()}:${slug}` as CreatureId;
|
const cId = `${result.source.toLowerCase()}:${slug}` as CreatureId;
|
||||||
setSelectedCreatureId(cId);
|
setSelectedCreatureId(cId);
|
||||||
|
setBulkImportMode(false);
|
||||||
|
setSourceManagerMode(false);
|
||||||
setIsRightPanelFolded(false);
|
setIsRightPanelFolded(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleBulkImport = useCallback(() => {
|
const handleBulkImport = useCallback(() => {
|
||||||
setBulkImportMode(true);
|
setBulkImportMode(true);
|
||||||
|
setSourceManagerMode(false);
|
||||||
|
setSelectedCreatureId(null);
|
||||||
|
setIsRightPanelFolded(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOpenSourceManager = useCallback(() => {
|
||||||
|
setSourceManagerMode(true);
|
||||||
|
setBulkImportMode(false);
|
||||||
setSelectedCreatureId(null);
|
setSelectedCreatureId(null);
|
||||||
setIsRightPanelFolded(false);
|
setIsRightPanelFolded(false);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -196,6 +207,7 @@ export function App() {
|
|||||||
const handleDismissBrowsePanel = useCallback(() => {
|
const handleDismissBrowsePanel = useCallback(() => {
|
||||||
setSelectedCreatureId(null);
|
setSelectedCreatureId(null);
|
||||||
setBulkImportMode(false);
|
setBulkImportMode(false);
|
||||||
|
setSourceManagerMode(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleToggleFold = useCallback(() => {
|
const handleToggleFold = useCallback(() => {
|
||||||
@@ -282,19 +294,13 @@ export function App() {
|
|||||||
onManagePlayers={() => setManagementOpen(true)}
|
onManagePlayers={() => setManagementOpen(true)}
|
||||||
onRollAllInitiative={handleRollAllInitiative}
|
onRollAllInitiative={handleRollAllInitiative}
|
||||||
showRollAllInitiative={showRollAllInitiative}
|
showRollAllInitiative={showRollAllInitiative}
|
||||||
onOpenSourceManager={() => setSourceManagerOpen((o) => !o)}
|
onOpenSourceManager={handleOpenSourceManager}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{sourceManagerOpen && (
|
|
||||||
<div className="shrink-0 rounded-md border border-border bg-card px-4 py-3">
|
|
||||||
<SourceManager onCacheCleared={refreshCache} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Scrollable area — combatant list */}
|
{/* Scrollable area — combatant list */}
|
||||||
<div className="flex-1 overflow-y-auto min-h-0">
|
<div className="flex-1 overflow-y-auto min-h-0">
|
||||||
<div className="flex flex-col px-2 py-2">
|
<div className="flex flex-col px-2 py-2">
|
||||||
@@ -344,7 +350,7 @@ export function App() {
|
|||||||
onManagePlayers={() => setManagementOpen(true)}
|
onManagePlayers={() => setManagementOpen(true)}
|
||||||
onRollAllInitiative={handleRollAllInitiative}
|
onRollAllInitiative={handleRollAllInitiative}
|
||||||
showRollAllInitiative={showRollAllInitiative}
|
showRollAllInitiative={showRollAllInitiative}
|
||||||
onOpenSourceManager={() => setSourceManagerOpen((o) => !o)}
|
onOpenSourceManager={handleOpenSourceManager}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -391,6 +397,7 @@ export function App() {
|
|||||||
bulkImportState={bulkImport.state}
|
bulkImportState={bulkImport.state}
|
||||||
onStartBulkImport={handleStartBulkImport}
|
onStartBulkImport={handleStartBulkImport}
|
||||||
onBulkImportDone={handleBulkImportDone}
|
onBulkImportDone={handleBulkImportDone}
|
||||||
|
sourceManagerMode={sourceManagerMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Toast for bulk import progress when panel is closed */}
|
{/* Toast for bulk import progress when panel is closed */}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { BulkImportState } from "../hooks/use-bulk-import.js";
|
|||||||
import { useSwipeToDismiss } from "../hooks/use-swipe-to-dismiss.js";
|
import { useSwipeToDismiss } from "../hooks/use-swipe-to-dismiss.js";
|
||||||
import { BulkImportPrompt } from "./bulk-import-prompt.js";
|
import { BulkImportPrompt } from "./bulk-import-prompt.js";
|
||||||
import { SourceFetchPrompt } from "./source-fetch-prompt.js";
|
import { SourceFetchPrompt } from "./source-fetch-prompt.js";
|
||||||
|
import { SourceManager } from "./source-manager.js";
|
||||||
import { StatBlock } from "./stat-block.js";
|
import { StatBlock } from "./stat-block.js";
|
||||||
import { Button } from "./ui/button.js";
|
import { Button } from "./ui/button.js";
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ interface StatBlockPanelProps {
|
|||||||
bulkImportState?: BulkImportState;
|
bulkImportState?: BulkImportState;
|
||||||
onStartBulkImport?: (baseUrl: string) => void;
|
onStartBulkImport?: (baseUrl: string) => void;
|
||||||
onBulkImportDone?: () => void;
|
onBulkImportDone?: () => void;
|
||||||
|
sourceManagerMode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractSourceCode(cId: CreatureId): string {
|
function extractSourceCode(cId: CreatureId): string {
|
||||||
@@ -236,6 +238,7 @@ export function StatBlockPanel({
|
|||||||
bulkImportState,
|
bulkImportState,
|
||||||
onStartBulkImport,
|
onStartBulkImport,
|
||||||
onBulkImportDone,
|
onBulkImportDone,
|
||||||
|
sourceManagerMode,
|
||||||
}: StatBlockPanelProps) {
|
}: StatBlockPanelProps) {
|
||||||
const [isDesktop, setIsDesktop] = useState(
|
const [isDesktop, setIsDesktop] = useState(
|
||||||
() => window.matchMedia("(min-width: 1024px)").matches,
|
() => window.matchMedia("(min-width: 1024px)").matches,
|
||||||
@@ -269,7 +272,7 @@ export function StatBlockPanel({
|
|||||||
});
|
});
|
||||||
}, [creatureId, creature, isSourceCached]);
|
}, [creatureId, creature, isSourceCached]);
|
||||||
|
|
||||||
if (!creatureId && !bulkImportMode) return null;
|
if (!creatureId && !bulkImportMode && !sourceManagerMode) return null;
|
||||||
|
|
||||||
const sourceCode = creatureId ? extractSourceCode(creatureId) : "";
|
const sourceCode = creatureId ? extractSourceCode(creatureId) : "";
|
||||||
|
|
||||||
@@ -279,6 +282,10 @@ export function StatBlockPanel({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
|
if (sourceManagerMode) {
|
||||||
|
return <SourceManager onCacheCleared={refreshCache} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
bulkImportMode &&
|
bulkImportMode &&
|
||||||
bulkImportState &&
|
bulkImportState &&
|
||||||
@@ -324,7 +331,12 @@ export function StatBlockPanel({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const creatureName =
|
const creatureName =
|
||||||
creature?.name ?? (bulkImportMode ? "Bulk Import" : "Creature");
|
creature?.name ??
|
||||||
|
(sourceManagerMode
|
||||||
|
? "Sources"
|
||||||
|
: bulkImportMode
|
||||||
|
? "Bulk Import"
|
||||||
|
: "Creature");
|
||||||
|
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user