Files
healer-man/src/game/manastormAdminStore.ts
T
2026-09-01 14:35:56 -04:00

115 lines
4.5 KiB
TypeScript

import { create } from "zustand";
import type { PlayerSession } from "../app/types";
import { requestOnlineJson } from "../app/onlineAccountClient";
import {
DEFAULT_MANASTORM_ADMIN_CONFIG,
LEGACY_MANASTORM_ADMIN_CACHE_KEY,
MANASTORM_ADMIN_CACHE_KEY,
normalizeManastormAdminConfig,
publicManastormAdminConfig,
resolvedBossAdminCatalog,
type ManastormAdminConfig,
type PublishedManastormAdminConfig,
} from "./manastormAdminConfig";
interface CachedPayload extends PublishedManastormAdminConfig {}
function readCache(): CachedPayload {
try {
const raw = typeof window !== "undefined"
? window.localStorage.getItem(MANASTORM_ADMIN_CACHE_KEY)
?? window.localStorage.getItem(LEGACY_MANASTORM_ADMIN_CACHE_KEY)
: null;
const parsed = raw ? JSON.parse(raw) as Partial<CachedPayload> : null;
if (!parsed || !Number.isInteger(parsed.revision) || (parsed.revision ?? -1) < 0) throw new Error("missing cache");
return {
revision: parsed.revision!,
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : null,
config: publicManastormAdminConfig(normalizeManastormAdminConfig(parsed.config)),
};
} catch {
return { revision: 0, updatedAt: null, config: DEFAULT_MANASTORM_ADMIN_CONFIG };
}
}
function writeCache(payload: PublishedManastormAdminConfig): void {
try {
window.localStorage.setItem(MANASTORM_ADMIN_CACHE_KEY, JSON.stringify({
...payload,
config: publicManastormAdminConfig(payload.config),
}));
} catch {
// Private browsing and storage quotas must not prevent play.
}
}
export interface ManastormAdminState extends PublishedManastormAdminConfig {
readonly draft: ManastormAdminConfig;
readonly loading: boolean;
readonly saving: boolean;
readonly error: string | null;
readonly editingBossKey: string | null;
readonly dirty: boolean;
refresh: (session?: PlayerSession | null) => Promise<void>;
replaceDraft: (config: ManastormAdminConfig) => void;
resetDraft: () => void;
publish: (session: PlayerSession | null) => Promise<boolean>;
setEditingBossKey: (key: string | null) => void;
}
const cached = readCache();
export const useManastormAdminStore = create<ManastormAdminState>((set, get) => ({
...cached,
draft: cached.config,
loading: false,
saving: false,
error: null,
editingBossKey: null,
dirty: false,
refresh: async (session = null) => {
set({ loading: true, error: null });
try {
const gm = Boolean(session?.accessToken && session.roles?.includes("gm"));
const payload = await requestOnlineJson<PublishedManastormAdminConfig>(
gm ? "/api/gm/manastorm-config" : "/api/manastorm-config",
{},
gm ? session?.accessToken : undefined,
);
const normalized = { ...payload, config: normalizeManastormAdminConfig(payload.config) };
writeCache(normalized);
set({ ...normalized, draft: normalized.config, dirty: false, loading: false });
} catch (error) {
set({ loading: false, error: error instanceof Error ? error.message : "Could not refresh Manastorm configuration." });
}
},
replaceDraft: (draft) => set({ draft: normalizeManastormAdminConfig(draft), dirty: true, error: null }),
resetDraft: () => set((state) => ({ draft: state.config, dirty: false, error: null })),
publish: async (session) => {
if (!session?.accessToken || !session.roles?.includes("gm")) {
set({ error: "Game Master access is required." });
return false;
}
const invalidReadyBoss = resolvedBossAdminCatalog(get().draft).find((entry) => entry.status === "ready" && !entry.runnable);
if (invalidReadyBoss) {
set({ error: `${invalidReadyBoss.name} cannot be published as Ready: ${invalidReadyBoss.validationIssues.join(" ")}` });
return false;
}
set({ saving: true, error: null });
try {
const payload = await requestOnlineJson<PublishedManastormAdminConfig>("/api/gm/manastorm-config", {
method: "PUT",
body: JSON.stringify({ expectedRevision: get().revision, config: get().draft }),
}, session.accessToken);
const normalized = { ...payload, config: normalizeManastormAdminConfig(payload.config) };
writeCache(normalized);
set({ ...normalized, draft: normalized.config, dirty: false, saving: false });
return true;
} catch (error) {
set({ saving: false, error: error instanceof Error ? error.message : "Could not publish Manastorm configuration." });
return false;
}
},
setEditingBossKey: (editingBossKey) => set({ editingBossKey }),
}));