Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f10c67a5ba | ||
|
|
9437272fe0 | ||
|
|
541e04b732 |
@@ -30,6 +30,13 @@ export function App() {
|
|||||||
const activeRowRef = useRef<HTMLDivElement>(null);
|
const activeRowRef = useRef<HTMLDivElement>(null);
|
||||||
const actionBarAnim = useActionBarAnimation(encounter.combatants.length);
|
const actionBarAnim = useActionBarAnimation(encounter.combatants.length);
|
||||||
|
|
||||||
|
// Close the side panel when the encounter becomes empty
|
||||||
|
useEffect(() => {
|
||||||
|
if (isEmpty) {
|
||||||
|
sidePanel.dismissPanel();
|
||||||
|
}
|
||||||
|
}, [isEmpty, sidePanel.dismissPanel]);
|
||||||
|
|
||||||
// Auto-scroll to active combatant when turn changes
|
// Auto-scroll to active combatant when turn changes
|
||||||
const activeIndex = encounter.activeIndex;
|
const activeIndex = encounter.activeIndex;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -52,8 +52,10 @@ 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()),
|
||||||
undo: vi.fn(),
|
undo: vi.fn(),
|
||||||
redo: vi.fn(),
|
redo: vi.fn(),
|
||||||
canUndo: false,
|
canUndo: false,
|
||||||
|
|||||||
@@ -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,9 +465,11 @@ export function useEncounter() {
|
|||||||
toggleCondition,
|
toggleCondition,
|
||||||
toggleConcentration,
|
toggleConcentration,
|
||||||
addFromBestiary,
|
addFromBestiary,
|
||||||
|
addMultipleFromBestiary,
|
||||||
addFromPlayerCharacter,
|
addFromPlayerCharacter,
|
||||||
undo: undoAction,
|
undo: undoAction,
|
||||||
redo: redoAction,
|
redo: redoAction,
|
||||||
makeStore,
|
makeStore,
|
||||||
|
withUndo,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function rollDice(): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useInitiativeRolls() {
|
export function useInitiativeRolls() {
|
||||||
const { encounter, makeStore } = useEncounterContext();
|
const { encounter, makeStore, withUndo } = useEncounterContext();
|
||||||
const { getCreature } = useBestiaryContext();
|
const { getCreature } = useBestiaryContext();
|
||||||
const { showCreature } = useSidePanelContext();
|
const { showCreature } = useSidePanelContext();
|
||||||
|
|
||||||
@@ -28,12 +28,8 @@ export function useInitiativeRolls() {
|
|||||||
(id: CombatantId, mode: RollMode = "normal") => {
|
(id: CombatantId, mode: RollMode = "normal") => {
|
||||||
const diceRolls: [number, ...number[]] =
|
const diceRolls: [number, ...number[]] =
|
||||||
mode === "normal" ? [rollDice()] : [rollDice(), rollDice()];
|
mode === "normal" ? [rollDice()] : [rollDice(), rollDice()];
|
||||||
const result = rollInitiativeUseCase(
|
const result = withUndo(() =>
|
||||||
makeStore(),
|
rollInitiativeUseCase(makeStore(), id, diceRolls, getCreature, mode),
|
||||||
id,
|
|
||||||
diceRolls,
|
|
||||||
getCreature,
|
|
||||||
mode,
|
|
||||||
);
|
);
|
||||||
if (isDomainError(result)) {
|
if (isDomainError(result)) {
|
||||||
setRollSingleSkipped(true);
|
setRollSingleSkipped(true);
|
||||||
@@ -43,22 +39,19 @@ export function useInitiativeRolls() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[makeStore, getCreature, encounter.combatants, showCreature],
|
[makeStore, getCreature, withUndo, encounter.combatants, showCreature],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRollAllInitiative = useCallback(
|
const handleRollAllInitiative = useCallback(
|
||||||
(mode: RollMode = "normal") => {
|
(mode: RollMode = "normal") => {
|
||||||
const result = rollAllInitiativeUseCase(
|
const result = withUndo(() =>
|
||||||
makeStore(),
|
rollAllInitiativeUseCase(makeStore(), rollDice, getCreature, mode),
|
||||||
rollDice,
|
|
||||||
getCreature,
|
|
||||||
mode,
|
|
||||||
);
|
);
|
||||||
if (!isDomainError(result) && result.skippedNoSource > 0) {
|
if (!isDomainError(result) && result.skippedNoSource > 0) {
|
||||||
setRollSkippedCount(result.skippedNoSource);
|
setRollSkippedCount(result.skippedNoSource);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[makeStore, getCreature],
|
[makeStore, getCreature, withUndo],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user