"use client"; import { useEffect, useState } from "react"; import Image from "next/image"; import { api } from "./StartGame"; import type { DatasetManifest } from "@/shared/protocol"; import type { ProcessingJob } from "@/shared/processing-progress"; import { ProcessingProgress } from "./ProcessingProgress"; type Draft = { id: string; name: string; description: string; state: string; source: string; rejection_reason: string | null; map: string | null; manifest: DatasetManifest; previews: { id: string; image: string | null }[]; upload_jobs: ProcessingJob | null; }; export function DraftEditor({ id }: { id: string }) { const [draft, setDraft] = useState(null), [name, setName] = useState(""), [description, setDescription] = useState(""), [selected, setSelected] = useState([]), [error, setError] = useState(""), [message, setMessage] = useState(""), [busy, setBusy] = useState(false); useEffect(() => { let disposed = false; let timer: ReturnType; const load = () => api("datasets/" + id) .then((d) => { if (disposed) return; setError(""); setDraft(d); setName(d.name); setDescription(d.description); setSelected(d.manifest.viewpoints.map((p) => p.id)); if (["uploading", "processing"].includes(d.state)) timer = setTimeout(load, 5000); }) .catch((e) => { if (!disposed) { setError(e.message); timer = setTimeout(load, 5000); } }); load(); return () => { disposed = true; clearTimeout(timer); }; }, [id]); async function save(submit = false) { setBusy(true); setError(""); try { await api( "datasets/" + id, { name, description, selectedIds: selected }, "PATCH", ); if (submit) { await api("datasets/" + id + "/submit", {}); setDraft((d) => (d ? { ...d, state: "review" } : d)); setMessage("Submitted for moderator review."); } else setMessage("Draft saved."); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } async function cancel() { setBusy(true); try { await api("datasets/" + id + "/cancel", {}); setDraft((d) => d ? { ...d, state: "failed", rejection_reason: "Processing cancelled" } : d, ); setMessage("Processing cancelled."); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } if (!draft) return (

{error || "Loading your draft…"}

); const editable = ["draft", "rejected"].includes(draft.state); return ( <> {draft.state} {["uploading", "processing"].includes(draft.state) && (

{draft.state === "uploading" ? "Waiting for the capture tool to finish uploading. Retry upload in the tool if the connection was interrupted." : "Your map and panoramas are being rendered. This page updates automatically."}

)} {draft.rejection_reason && (

{draft.rejection_reason}

)}

{draft.source === "plugin" ? "Server-generated views use vanilla terrain and simplified lighting. Entities, shaders, custom packs and dynamic sign text are omitted." : "Player captures show the terrain received by the client. Unexplored map coverage remains blank."}

{draft.map && (
Draft surface map
)}