Add map import and export with durable refresh progress

This commit is contained in:
2026-10-07 19:44:49 +05:00
parent dac8650ac1
commit 1307436e65
37 changed files with 2207 additions and 85 deletions

View File

@@ -2,6 +2,7 @@ import { NextRequest } from "next/server";
import { handle } from "@/lib/api";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
export const maxDuration = 300;
async function route(
request: NextRequest,
{ params }: { params: Promise<{ path: string[] }> },

View File

@@ -706,10 +706,32 @@ nav a:hover,
}
.catalog-controls {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 20px;
justify-content: space-between;
margin: 35px 0;
}
.map-import-dialog {
margin: auto;
background: var(--panel);
color: var(--text);
border: 1px solid var(--line);
border-radius: 12px;
padding: 24px;
width: min(540px, calc(100vw - 32px));
max-height: calc(100dvh - 32px);
overflow: auto;
}
.map-import-dialog::backdrop { background: rgb(0 0 0 / 65%); }
.map-import-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.map-import-heading h2 { margin: 0; }
.map-import-dialog .field input { max-width: 100%; }
.map-export { margin: 16px 0; }
.catalog-search { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0; }
.catalog-search .search-field { flex: 1; min-width: 0; }
.catalog-search .search-field input { min-width: 0; }
.catalog-search > .button { flex-shrink: 0; }
.search-field {
border: 1px solid var(--line);
background: var(--panel);
@@ -1399,6 +1421,7 @@ pre {
}
.catalog-controls {
flex-direction: column;
align-items: stretch;
}
.difficulty-options {
gap: 8px;

View File

@@ -6,6 +6,8 @@ import { StartGame } from "@/components/StartGame";
import { getMap } from "@/lib/catalog";
import { ContributionInstructions } from "@/components/ContributionInstructions";
import { ReportContent } from "@/components/ReportContent";
import { user } from "@/lib/supabase";
import { ExportMap } from "@/components/MapTransfer";
export default async function ServerPage({
params,
}: {
@@ -15,6 +17,7 @@ export default async function ServerPage({
const { id } = await params;
const map = await getMap(id);
if (!map) notFound();
const account = await user();
return (
<>
<Header />
@@ -31,6 +34,7 @@ export default async function ServerPage({
{t("Created by")} {map.credit}
</p>
<ReportContent datasetId={id} />
{account?.id === map.owner_id && <ExportMap id={id} />}
<p className="muted">
{t("Map ID:")} <code>{map.id}</code>
</p>

View File

@@ -1,11 +1,12 @@
import { getI18n } from "@/lib/i18n";
import { Header, Footer, EmptyState } from "@/components/Shell";
import { Header, Footer } from "@/components/Shell";
import { catalog } from "@/lib/catalog";
import { CatalogBrowser } from "@/components/CatalogBrowser";
import { user } from "@/lib/supabase";
export const dynamic = "force-dynamic";
export default async function Servers() {
const { t } = await getI18n();
const maps = await catalog();
const [maps, account] = await Promise.all([catalog(), user()]);
return (
<>
<Header />
@@ -19,15 +20,7 @@ export default async function Servers() {
"From familiar spawn towns to places nobody’s quite sure about. Pick your next expedition.",
)}
</p>
{maps.length ? (
<CatalogBrowser maps={maps} />
) : (
<EmptyState title={t("Fresh chunks, fresh start")}>
{t(
"No maps have been approved yet. Your server could be the first.",
)}
</EmptyState>
)}
<CatalogBrowser maps={maps} signedIn={!!account} />
</main>
<Footer />
</>