This commit is contained in:
Mikan
2026-06-21 02:41:48 +03:00
parent c21a13d2a3
commit bd85e186dc
31 changed files with 1372 additions and 319 deletions

View File

@@ -3,6 +3,7 @@ import { useParams, useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useSessionStore } from "@/stores/sessionStore";
import { useToastStore } from "@/stores/toastStore";
import { toErrorMessage } from "@/lib/api";
import { Button } from "@/components/ui/Button";
import { Card } from "@/components/ui/Card";
import { Spinner } from "@/components/ui/Spinner";
@@ -20,6 +21,7 @@ export function PlayPage() {
const world = useSessionStore((s) => s.world);
const environment = useSessionStore((s) => s.environment);
const nextActions = useSessionStore((s) => s.nextActions);
const recentSteps = useSessionStore((s) => s.recentSteps);
const loading = useSessionStore((s) => s.loading);
const error = useSessionStore((s) => s.error);
const submitting = useSessionStore((s) => s.submitting);
@@ -31,15 +33,26 @@ export function PlayPage() {
const reset = useSessionStore((s) => s.reset);
const [rollbackOpen, setRollbackOpen] = useState(false);
const [redirected, setRedirected] = useState(false);
useEffect(() => {
if (!id) return;
void fetchState(id).catch(() => pushToast("error", t("play.load_failed")));
void fetchState(id).catch((err) => pushToast("error", toErrorMessage(err, t("play.load_failed"))));
return () => {
reset();
};
}, [id, fetchState, reset, pushToast, t]);
// Redirect non-ready worlds to the edit page (once we have the world loaded).
useEffect(() => {
if (!id) return;
if (world && world.status !== "ready" && !redirected) {
pushToast("warning", t("play.not_ready"));
setRedirected(true);
navigate(`/worlds/${id}/edit`, { replace: true });
}
}, [id, world, redirected, navigate, pushToast, t]);
if (!id) return null;
if (loading && !world) {
@@ -60,25 +73,27 @@ export function PlayPage() {
);
}
if (!world) return null;
if (world.status !== "ready") return null;
const player = environment?.player;
const plotRails: PlotRail[] = Array.isArray(world.plot_rails) ? world.plot_rails : [];
const noSteps = recentSteps.length === 0;
const handleSend = (action: string) => {
void sendAction(id, action, "manual").catch((err) => {
pushToast("error", err instanceof Error ? err.message : "Failed");
pushToast("error", toErrorMessage(err, "Failed"));
});
};
const handleSuggested = (action: string) => {
void sendAction(id, action, "suggested").catch((err) => {
pushToast("error", err instanceof Error ? err.message : "Failed");
pushToast("error", toErrorMessage(err, "Failed"));
});
};
const handleRetry = () => {
void retry(id).catch((err) => {
pushToast("error", err instanceof Error ? err.message : "Failed");
pushToast("error", toErrorMessage(err, "Failed"));
});
};
@@ -88,7 +103,7 @@ export function PlayPage() {
await rollback(id);
pushToast("success", t("play.rolled_back"));
} catch (err) {
pushToast("error", err instanceof Error ? err.message : "Failed");
pushToast("error", toErrorMessage(err, "Failed"));
}
};
@@ -160,14 +175,23 @@ export function PlayPage() {
<Card>
<div className="flex flex-col gap-2">
<Button size="sm" variant="secondary" onClick={handleRetry} disabled={submitting}>
<Button
size="sm"
variant="secondary"
onClick={handleRetry}
disabled={submitting || noSteps}
className={noSteps ? "opacity-50 cursor-not-allowed" : ""}
title={noSteps ? t("play.no_steps_to_retry") : undefined}
>
{t("play.retry_last")}
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => setRollbackOpen(true)}
disabled={submitting}
disabled={submitting || noSteps}
className={noSteps ? "opacity-50 cursor-not-allowed" : ""}
title={noSteps ? t("play.no_steps_to_rollback") : undefined}
>
{t("play.rollback")}
</Button>