Show toast when roll-all-initiative skips combatants without loaded sources

Previously the button silently did nothing for creatures whose bestiary
source wasn't loaded. Now it reports how many were skipped and why. Also
keeps the roll-all button visible (but disabled) when there's nothing
left to roll, and moves toasts to the bottom-left corner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-13 18:58:25 +01:00
parent 6ac8e67970
commit 72195e90f6
5 changed files with 48 additions and 10 deletions

View File

@@ -2,7 +2,12 @@ import {
rollAllInitiativeUseCase,
rollInitiativeUseCase,
} from "@initiative/application";
import type { CombatantId, Creature, CreatureId } from "@initiative/domain";
import {
type CombatantId,
type Creature,
type CreatureId,
isDomainError,
} from "@initiative/domain";
import {
useCallback,
useEffect,
@@ -109,6 +114,8 @@ export function App() {
const bulkImport = useBulkImport();
const [rollSkippedCount, setRollSkippedCount] = useState(0);
const [selectedCreatureId, setSelectedCreatureId] =
useState<CreatureId | null>(null);
const [bulkImportMode, setBulkImportMode] = useState(false);
@@ -158,7 +165,10 @@ export function App() {
);
const handleRollAllInitiative = useCallback(() => {
rollAllInitiativeUseCase(makeStore(), rollDice, getCreature);
const result = rollAllInitiativeUseCase(makeStore(), rollDice, getCreature);
if (!isDomainError(result) && result.skippedNoSource > 0) {
setRollSkippedCount(result.skippedNoSource);
}
}, [makeStore, getCreature]);
const handleViewStatBlock = useCallback((result: SearchResult) => {
@@ -252,7 +262,10 @@ export function App() {
}, [encounter.activeIndex, encounter.combatants, isLoaded]);
const isEmpty = encounter.combatants.length === 0;
const showRollAllInitiative = encounter.combatants.some(
const hasCreatureCombatants = encounter.combatants.some(
(c) => c.creatureId != null,
);
const canRollAllInitiative = encounter.combatants.some(
(c) => c.creatureId != null && c.initiative == null,
);
@@ -293,7 +306,8 @@ export function App() {
onAddFromPlayerCharacter={addFromPlayerCharacter}
onManagePlayers={() => setManagementOpen(true)}
onRollAllInitiative={handleRollAllInitiative}
showRollAllInitiative={showRollAllInitiative}
showRollAllInitiative={hasCreatureCombatants}
rollAllInitiativeDisabled={!canRollAllInitiative}
onOpenSourceManager={handleOpenSourceManager}
autoFocus
/>
@@ -349,7 +363,8 @@ export function App() {
onAddFromPlayerCharacter={addFromPlayerCharacter}
onManagePlayers={() => setManagementOpen(true)}
onRollAllInitiative={handleRollAllInitiative}
showRollAllInitiative={showRollAllInitiative}
showRollAllInitiative={hasCreatureCombatants}
rollAllInitiativeDisabled={!canRollAllInitiative}
onOpenSourceManager={handleOpenSourceManager}
/>
</div>
@@ -427,6 +442,14 @@ export function App() {
/>
)}
{rollSkippedCount > 0 && (
<Toast
message={`${rollSkippedCount} skipped — bestiary source not loaded`}
onDismiss={() => setRollSkippedCount(0)}
autoDismissMs={4000}
/>
)}
<CreatePlayerModal
open={createPlayerOpen}
onClose={() => {

View File

@@ -40,6 +40,7 @@ interface ActionBarProps {
onManagePlayers?: () => void;
onRollAllInitiative?: () => void;
showRollAllInitiative?: boolean;
rollAllInitiativeDisabled?: boolean;
onOpenSourceManager?: () => void;
autoFocus?: boolean;
}
@@ -262,6 +263,7 @@ export function ActionBar({
onManagePlayers,
onRollAllInitiative,
showRollAllInitiative,
rollAllInitiativeDisabled,
onOpenSourceManager,
autoFocus,
}: ActionBarProps) {
@@ -575,6 +577,7 @@ export function ActionBar({
variant="ghost"
className="text-muted-foreground hover:text-hover-action"
onClick={onRollAllInitiative}
disabled={rollAllInitiativeDisabled}
title="Roll all initiative"
aria-label="Roll all initiative"
>

View File

@@ -23,7 +23,7 @@ export function Toast({
}, [autoDismissMs, onDismiss]);
return createPortal(
<div className="fixed bottom-4 left-1/2 z-50 -translate-x-1/2">
<div className="fixed bottom-4 left-4 z-50">
<div className="flex items-center gap-3 rounded-lg border border-border bg-card px-4 py-3 shadow-lg">
<span className="text-sm text-foreground">{message}</span>
{progress !== undefined && (