137 lines
4.0 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|