Add map import and export with durable refresh progress
This commit is contained in:
136
components/MapTransfer.tsx
Normal file
136
components/MapTransfer.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
import { Download, Upload, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useI18n } from "./I18nProvider";
|
||||
|
||||
export function ExportMap({
|
||||
id,
|
||||
disabled = false,
|
||||
}: {
|
||||
id: string;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
async function download() {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
try {
|
||||
const { exportMap } = await import("@/lib/map-transfer");
|
||||
await exportMap(id, (message, values) => setStatus(t(message, values)));
|
||||
setStatus(t("Map ZIP downloaded. Share it with a friend."));
|
||||
} catch (error) {
|
||||
setStatus("");
|
||||
setError(error instanceof Error ? error.message : "Could not export map");
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="map-export">
|
||||
<button className="button" onClick={download} disabled={disabled || busy}>
|
||||
<Download size={17} aria-hidden="true" />{" "}
|
||||
{t(busy ? "Exporting…" : "Export ZIP")}
|
||||
</button>
|
||||
{status && (
|
||||
<p role="status" className="muted">
|
||||
{status}
|
||||
</p>
|
||||
)}
|
||||
{error && (
|
||||
<p role="alert" className="error-message">
|
||||
{t(error)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ImportMap({ signedIn }: { signedIn: boolean }) {
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const dialog = useRef<HTMLDialogElement>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
async function upload(file: File) {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
try {
|
||||
const { importMap } = await import("@/lib/map-transfer");
|
||||
const id = await importMap(file, (message, values) =>
|
||||
setStatus(t(message, values)),
|
||||
);
|
||||
dialog.current?.close();
|
||||
router.push(`/dashboard/${id}`);
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
setStatus("");
|
||||
setError(error instanceof Error ? error.message : "Could not import map");
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<button className="button" onClick={() => dialog.current?.showModal()}>
|
||||
<Upload size={17} aria-hidden="true" /> {t("Import")}
|
||||
</button>
|
||||
<dialog
|
||||
ref={dialog}
|
||||
className="map-import-dialog"
|
||||
aria-labelledby="map-import-title"
|
||||
onCancel={(event) => {
|
||||
if (busy) event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div className="map-import-heading">
|
||||
<h2 id="map-import-title">{t("Import a shared map")}</h2>
|
||||
<button
|
||||
className="button"
|
||||
aria-label={t("Close")}
|
||||
disabled={busy}
|
||||
onClick={() => dialog.current?.close()}
|
||||
>
|
||||
<X size={18} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="muted">
|
||||
{t(
|
||||
"Choose a MineGeo ZIP exported by the map owner. Play it privately without moderator review. The original owner keeps publication rights. Maximum size: 256 MB.",
|
||||
)}
|
||||
</p>
|
||||
{signedIn ? (
|
||||
<label className="field">
|
||||
{t("Map ZIP file")}
|
||||
<input
|
||||
type="file"
|
||||
accept=".zip,application/zip"
|
||||
disabled={busy}
|
||||
onChange={(event) => {
|
||||
const file = event.currentTarget.files?.[0];
|
||||
event.currentTarget.value = "";
|
||||
if (file) void upload(file);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
) : (
|
||||
<Link className="button primary" href="/log-in?next=/servers">
|
||||
{t("Sign in to import")}
|
||||
</Link>
|
||||
)}
|
||||
{status && <p role="status">{status}</p>}
|
||||
{error && (
|
||||
<p className="error-message" role="alert">
|
||||
{t(error)}
|
||||
</p>
|
||||
)}
|
||||
</dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user