Release Healer Man 0.1.12
This commit is contained in:
1 parent
13970d03c8
commit
65e24f644e
201 files changed
+5061
-123
No files matched your search
@@ -1,9 +1,10 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { enterGmManastormTest } from "../app/gmManastormRuntime";
|
||||
import { enterGmManastormTest, enterRandomGmReview } from "../app/gmManastormRuntime";
|
||||
import { installDungeonShellRuntime } from "../app/dungeonShellRuntime";
|
||||
import { useShellStore } from "../app/shellStore";
|
||||
import {
|
||||
bossAdminRecordForStatus,
|
||||
GM_REVIEW_NOTE_MAX_LENGTH,
|
||||
isGameMasterSession,
|
||||
resolvedBossAdminCatalog,
|
||||
type ManastormBossStatus,
|
||||
@@ -11,6 +12,11 @@ import {
|
||||
} from "../game/manastormAdminConfig";
|
||||
import { useManastormAdminStore } from "../game/manastormAdminStore";
|
||||
import { useGameStore } from "../game/store";
|
||||
import {
|
||||
configWithGmReviewNote,
|
||||
gmReviewQueue,
|
||||
type GmReviewKind,
|
||||
} from "../game/gmContentReview";
|
||||
import { BrandMark, FrontSurface } from "./FrontSurface";
|
||||
|
||||
const STATUS_LABELS: Readonly<Record<ManastormBossStatus, string>> = {
|
||||
@@ -51,6 +57,9 @@ export function GmAdminScreen() {
|
||||
const [poolFilter, setPoolFilter] = useState<"all" | "public" | "excluded">("all");
|
||||
const [selectedKey, setSelectedKey] = useState<string | null>(null);
|
||||
const catalog = useMemo(() => resolvedBossAdminCatalog(draft), [draft]);
|
||||
const randomQueueCounts = useMemo(() => Object.fromEntries(
|
||||
(["dungeon", "boss", "manastorm"] as const).map((kind) => [kind, gmReviewQueue(kind, draft).length]),
|
||||
) as Readonly<Record<GmReviewKind, number>>, [draft]);
|
||||
const dungeonOptions = useMemo(() => [...new Map(catalog.map((entry) => [entry.dungeonTitle, entry.dungeonTitle])).keys()].sort(), [catalog]);
|
||||
const filtered = useMemo(() => {
|
||||
const query = search.trim().toLowerCase();
|
||||
@@ -60,7 +69,7 @@ export function GmAdminScreen() {
|
||||
&& (sourceFilter === "all" || entry.source.includes(sourceFilter))
|
||||
&& (runnableFilter === "all" || (runnableFilter === "runnable" ? entry.runnable : !entry.runnable))
|
||||
&& (poolFilter === "all" || (poolFilter === "public" ? entry.inPublicPool : !entry.inPublicPool))
|
||||
&& (!query || `${entry.name} ${entry.dungeonTitle} ${entry.source.join(" ")}`.toLowerCase().includes(query))
|
||||
&& (!query || `${entry.name} ${entry.dungeonTitle} ${entry.source.join(" ")} ${entry.reviewNote}`.toLowerCase().includes(query))
|
||||
));
|
||||
}, [catalog, dungeonFilter, poolFilter, runnableFilter, search, sourceFilter, statusFilter]);
|
||||
const selected = catalog.find((entry) => entry.key === selectedKey) ?? filtered[0] ?? null;
|
||||
@@ -96,6 +105,11 @@ export function GmAdminScreen() {
|
||||
});
|
||||
};
|
||||
|
||||
const setReviewNote = (key: string, note: string) => {
|
||||
const next = configWithGmReviewNote(draft, { kind: "boss", bossKey: key }, note);
|
||||
if (next) replaceDraft(next);
|
||||
};
|
||||
|
||||
const editInDungeon = () => {
|
||||
if (!selected?.dungeonId) return;
|
||||
setEditingBossKey(selected.key);
|
||||
@@ -126,6 +140,28 @@ export function GmAdminScreen() {
|
||||
|
||||
<main className="gm-admin-layout">
|
||||
<aside className="gm-admin-options">
|
||||
<section className="gm-random-qa" aria-labelledby="gm-random-qa-title">
|
||||
<p className="front-kicker">Random QA</p>
|
||||
<h2 id="gm-random-qa-title">Review queue</h2>
|
||||
<p>Needs tested first, then Not ready. Ready content stays out of the draw.</p>
|
||||
{(["dungeon", "boss", "manastorm"] as const).map((kind) => {
|
||||
const label = kind === "dungeon" ? "Dungeon" : kind === "boss" ? "Boss" : "Manastorm";
|
||||
const count = randomQueueCounts[kind];
|
||||
return (
|
||||
<button
|
||||
key={kind}
|
||||
type="button"
|
||||
className="front-button"
|
||||
disabled={dirty || saving || count === 0}
|
||||
title={dirty ? "Save changes before testing." : count === 0 ? `All runnable ${label.toLowerCase()} content is Ready.` : undefined}
|
||||
onClick={() => enterRandomGmReview(kind)}
|
||||
>
|
||||
<span>Random {label}</span>
|
||||
<small>{count ? `${count} queued` : "All reviewed"}</small>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
<p className="front-kicker">Global switches</p>
|
||||
<h2>Storm rules</h2>
|
||||
{GLOBAL_OPTIONS.map((option) => (
|
||||
@@ -220,6 +256,27 @@ export function GmAdminScreen() {
|
||||
>{STATUS_LABELS[status]}</button>
|
||||
))}
|
||||
</div>
|
||||
<section className="gm-review-note-editor" aria-label={`Review note for ${selected.name}`}>
|
||||
<header>
|
||||
<strong>Review note</strong>
|
||||
<small>{selected.reviewNote.length} / {GM_REVIEW_NOTE_MAX_LENGTH}</small>
|
||||
</header>
|
||||
<textarea
|
||||
aria-label={`Review note for ${selected.name}`}
|
||||
value={selected.reviewNote}
|
||||
maxLength={GM_REVIEW_NOTE_MAX_LENGTH}
|
||||
rows={5}
|
||||
disabled={saving}
|
||||
placeholder="Record what needs testing or fixing."
|
||||
onChange={(event) => setReviewNote(selected.key, event.currentTarget.value)}
|
||||
/>
|
||||
<footer>
|
||||
<small>{selected.reviewedAt && selected.reviewedBy
|
||||
? `Last reviewed by ${selected.reviewedBy} · ${new Date(selected.reviewedAt).toLocaleString()}`
|
||||
: "Not yet reviewed."}</small>
|
||||
<button type="button" disabled={saving || !selected.reviewNote} onClick={() => setReviewNote(selected.key, "")}>Clear note</button>
|
||||
</footer>
|
||||
</section>
|
||||
<div className="gm-boss-actions">
|
||||
<button type="button" className="front-button" disabled={!selected.dungeonId} onClick={editInDungeon}>Edit in dungeon</button>
|
||||
<button type="button" className="front-button" disabled={!selected.dungeonId} onClick={enterSelectedDungeon}>Enter dungeon</button>
|
||||
|
||||
Reference in new issue
Block a user