Batch bestiary add produces a single undo entry
Extract addOneFromBestiary (no undo) and build addMultipleFromBestiary on top so confirming N creatures from the bestiary panel creates one undo entry that restores the entire batch, not N individual entries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,7 @@ function mockContext(overrides: Partial<Encounter> = {}) {
|
|||||||
toggleCondition: vi.fn(),
|
toggleCondition: vi.fn(),
|
||||||
toggleConcentration: vi.fn(),
|
toggleConcentration: vi.fn(),
|
||||||
addFromBestiary: vi.fn(),
|
addFromBestiary: vi.fn(),
|
||||||
|
addMultipleFromBestiary: vi.fn(),
|
||||||
addFromPlayerCharacter: vi.fn(),
|
addFromPlayerCharacter: vi.fn(),
|
||||||
makeStore: vi.fn(),
|
makeStore: vi.fn(),
|
||||||
withUndo: vi.fn((action: () => unknown) => action()),
|
withUndo: vi.fn((action: () => unknown) => action()),
|
||||||
|
|||||||
@@ -26,8 +26,12 @@ export function creatureKey(r: SearchResult): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useActionBarState() {
|
export function useActionBarState() {
|
||||||
const { addCombatant, addFromBestiary, addFromPlayerCharacter } =
|
const {
|
||||||
useEncounterContext();
|
addCombatant,
|
||||||
|
addFromBestiary,
|
||||||
|
addMultipleFromBestiary,
|
||||||
|
addFromPlayerCharacter,
|
||||||
|
} = useEncounterContext();
|
||||||
const { search: bestiarySearch, isLoaded: bestiaryLoaded } =
|
const { search: bestiarySearch, isLoaded: bestiaryLoaded } =
|
||||||
useBestiaryContext();
|
useBestiaryContext();
|
||||||
const { characters: playerCharacters } = usePlayerCharactersContext();
|
const { characters: playerCharacters } = usePlayerCharactersContext();
|
||||||
@@ -92,11 +96,24 @@ export function useActionBarState() {
|
|||||||
|
|
||||||
const confirmQueued = useCallback(() => {
|
const confirmQueued = useCallback(() => {
|
||||||
if (!queued) return;
|
if (!queued) return;
|
||||||
for (let i = 0; i < queued.count; i++) {
|
if (queued.count === 1) {
|
||||||
handleAddFromBestiary(queued.result);
|
handleAddFromBestiary(queued.result);
|
||||||
|
} else {
|
||||||
|
const creatureId = addMultipleFromBestiary(queued.result, queued.count);
|
||||||
|
const isDesktop = globalThis.matchMedia("(min-width: 1024px)").matches;
|
||||||
|
if (creatureId && panelView.mode === "closed" && isDesktop) {
|
||||||
|
showCreature(creatureId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
clearInput();
|
clearInput();
|
||||||
}, [queued, handleAddFromBestiary, clearInput]);
|
}, [
|
||||||
|
queued,
|
||||||
|
handleAddFromBestiary,
|
||||||
|
addMultipleFromBestiary,
|
||||||
|
panelView.mode,
|
||||||
|
showCreature,
|
||||||
|
clearInput,
|
||||||
|
]);
|
||||||
|
|
||||||
const parseNum = (v: string): number | undefined => {
|
const parseNum = (v: string): number | undefined => {
|
||||||
if (v.trim() === "") return undefined;
|
if (v.trim() === "") return undefined;
|
||||||
|
|||||||
@@ -298,9 +298,10 @@ export function useEncounter() {
|
|||||||
setEvents((prev) => [...prev, ...result]);
|
setEvents((prev) => [...prev, ...result]);
|
||||||
}, [makeStore]);
|
}, [makeStore]);
|
||||||
|
|
||||||
const addFromBestiary = useCallback(
|
const addOneFromBestiary = useCallback(
|
||||||
(entry: BestiaryIndexEntry): CreatureId | null => {
|
(
|
||||||
const snapshot = encounterRef.current;
|
entry: BestiaryIndexEntry,
|
||||||
|
): { cId: CreatureId; events: DomainEvent[] } | null => {
|
||||||
const store = makeStore();
|
const store = makeStore();
|
||||||
const existingNames = store.get().combatants.map((c) => c.name);
|
const existingNames = store.get().combatants.map((c) => c.name);
|
||||||
const { newName, renames } = resolveCreatureName(
|
const { newName, renames } = resolveCreatureName(
|
||||||
@@ -328,8 +329,20 @@ export function useEncounter() {
|
|||||||
creatureId: cId,
|
creatureId: cId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isDomainError(result)) {
|
if (isDomainError(result)) return null;
|
||||||
store.save(snapshot);
|
|
||||||
|
return { cId, events: result };
|
||||||
|
},
|
||||||
|
[makeStore],
|
||||||
|
);
|
||||||
|
|
||||||
|
const addFromBestiary = useCallback(
|
||||||
|
(entry: BestiaryIndexEntry): CreatureId | null => {
|
||||||
|
const snapshot = encounterRef.current;
|
||||||
|
const added = addOneFromBestiary(entry);
|
||||||
|
|
||||||
|
if (!added) {
|
||||||
|
makeStore().save(snapshot);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,10 +350,36 @@ export function useEncounter() {
|
|||||||
undoRedoRef.current = newState;
|
undoRedoRef.current = newState;
|
||||||
setUndoRedoState(newState);
|
setUndoRedoState(newState);
|
||||||
|
|
||||||
setEvents((prev) => [...prev, ...result]);
|
setEvents((prev) => [...prev, ...added.events]);
|
||||||
return cId;
|
return added.cId;
|
||||||
},
|
},
|
||||||
[makeStore],
|
[makeStore, addOneFromBestiary],
|
||||||
|
);
|
||||||
|
|
||||||
|
const addMultipleFromBestiary = useCallback(
|
||||||
|
(entry: BestiaryIndexEntry, count: number): CreatureId | null => {
|
||||||
|
const snapshot = encounterRef.current;
|
||||||
|
const allEvents: DomainEvent[] = [];
|
||||||
|
let lastCId: CreatureId | null = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const added = addOneFromBestiary(entry);
|
||||||
|
if (!added) {
|
||||||
|
makeStore().save(snapshot);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
allEvents.push(...added.events);
|
||||||
|
lastCId = added.cId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newState = pushUndo(undoRedoRef.current, snapshot);
|
||||||
|
undoRedoRef.current = newState;
|
||||||
|
setUndoRedoState(newState);
|
||||||
|
|
||||||
|
setEvents((prev) => [...prev, ...allEvents]);
|
||||||
|
return lastCId;
|
||||||
|
},
|
||||||
|
[makeStore, addOneFromBestiary],
|
||||||
);
|
);
|
||||||
|
|
||||||
const addFromPlayerCharacter = useCallback(
|
const addFromPlayerCharacter = useCallback(
|
||||||
@@ -426,6 +465,7 @@ export function useEncounter() {
|
|||||||
toggleCondition,
|
toggleCondition,
|
||||||
toggleConcentration,
|
toggleConcentration,
|
||||||
addFromBestiary,
|
addFromBestiary,
|
||||||
|
addMultipleFromBestiary,
|
||||||
addFromPlayerCharacter,
|
addFromPlayerCharacter,
|
||||||
undo: undoAction,
|
undo: undoAction,
|
||||||
redo: redoAction,
|
redo: redoAction,
|
||||||
|
|||||||
Reference in New Issue
Block a user