Files
MineGeo/components/MapTransfer.tsx

137 lines
4.0 KiB
TypeScript

"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>
</>
);
}