Translates the five client-side error-message maps (world, hunting, combat, inventory, local-location stores) that re-translate already-English API error codes back to display text, per design doc §4. Also fixes four component specs that hard-coded the same German strings when asserting rendered error text (combat-page, hunt-page, location-interaction-panel, location-page).
98 lines
3.5 KiB
TypeScript
98 lines
3.5 KiB
TypeScript
import { HttpErrorResponse } from '@angular/common/http';
|
|
import { Injectable, computed, inject, signal } from '@angular/core';
|
|
import { firstValueFrom } from 'rxjs';
|
|
import {
|
|
LocationInteractionResult,
|
|
LocationPointOfInterest,
|
|
LocationPrimaryAction,
|
|
} from '../../core/api/game-api.models';
|
|
import { GameApiService } from '../../core/api/game-api.service';
|
|
import { WorldStore } from './world.store';
|
|
|
|
const GENERIC_INTERACTION_ERROR = "This action isn't possible right now.";
|
|
|
|
// Mirrors the codes in `apps/api/src/world/world.errors.ts`.
|
|
const INTERACTION_ERROR_MESSAGES: Readonly<Record<string, string>> = {
|
|
LOCATION_INTERACTION_UNAVAILABLE: "There's nothing to discover here.",
|
|
CHARACTER_NOT_FOUND: 'Your character could not be found.',
|
|
};
|
|
|
|
/**
|
|
* State for the local location view.
|
|
*
|
|
* Location data comes from `WorldStore`, not from a second fetch path: that
|
|
* store already settles due travel before answering, so arriving at a place
|
|
* and looking around cannot disagree about where the character is. This store
|
|
* adds only what is local to the screen — the open interaction result.
|
|
*/
|
|
@Injectable({ providedIn: 'root' })
|
|
export class LocalLocationStore {
|
|
private readonly api = inject(GameApiService);
|
|
private readonly worldStore = inject(WorldStore);
|
|
|
|
private readonly interactionResultState = signal<LocationInteractionResult | null>(null);
|
|
private readonly interactionErrorState = signal<string | null>(null);
|
|
private readonly interactionPendingState = signal<string | null>(null);
|
|
|
|
readonly location = this.worldStore.currentLocation;
|
|
readonly loading = this.worldStore.loading;
|
|
readonly error = this.worldStore.error;
|
|
|
|
readonly interactionResult = this.interactionResultState.asReadonly();
|
|
readonly interactionError = this.interactionErrorState.asReadonly();
|
|
/** Key of the interaction currently in flight, so only that control busies. */
|
|
readonly interactionPending = this.interactionPendingState.asReadonly();
|
|
readonly interactionOpen = computed(
|
|
() => this.interactionResultState() !== null || this.interactionErrorState() !== null,
|
|
);
|
|
|
|
load(): Promise<void> {
|
|
return this.worldStore.load();
|
|
}
|
|
|
|
/**
|
|
* Runs a hotspot or action that reveals text. Navigation types (HUNT, MAP)
|
|
* never reach here — the page routes those itself.
|
|
*/
|
|
async runInteraction(interactionKey: string): Promise<void> {
|
|
if (this.interactionPendingState() !== null) {
|
|
return;
|
|
}
|
|
|
|
this.interactionPendingState.set(interactionKey);
|
|
this.interactionResultState.set(null);
|
|
this.interactionErrorState.set(null);
|
|
|
|
try {
|
|
this.interactionResultState.set(
|
|
await firstValueFrom(this.api.runLocationInteraction(interactionKey)),
|
|
);
|
|
} catch (error) {
|
|
this.interactionErrorState.set(this.toErrorMessage(error));
|
|
} finally {
|
|
this.interactionPendingState.set(null);
|
|
}
|
|
}
|
|
|
|
closeInteraction(): void {
|
|
this.interactionResultState.set(null);
|
|
this.interactionErrorState.set(null);
|
|
}
|
|
|
|
/** The hotspot an action mirrors, so an action bar entry can highlight it. */
|
|
interactionKeyOf(
|
|
action: LocationPrimaryAction | LocationPointOfInterest,
|
|
): string | undefined {
|
|
return 'poiKey' in action ? action.poiKey : action.key;
|
|
}
|
|
|
|
private toErrorMessage(error: unknown): string {
|
|
if (error instanceof HttpErrorResponse) {
|
|
const code = (error.error as { code?: string } | null)?.code;
|
|
return (code && INTERACTION_ERROR_MESSAGES[code]) || GENERIC_INTERACTION_ERROR;
|
|
}
|
|
|
|
return GENERIC_INTERACTION_ERROR;
|
|
}
|
|
}
|