134 lines
4.5 KiB
TypeScript
134 lines
4.5 KiB
TypeScript
import { api } from "./client-api";
|
|
import type { ArchiveMetadata } from "../shared/map-archive";
|
|
|
|
type Progress = (message: string, values?: Record<string, number>) => void;
|
|
|
|
export async function exportMap(id: string, progress: Progress) {
|
|
const {
|
|
archiveMetadataSchema,
|
|
archiveSchema,
|
|
archiveLimit,
|
|
archiveFileLimit,
|
|
archiveManifestName,
|
|
sha256,
|
|
} = await import("../shared/map-archive");
|
|
const { zipSync, strToU8 } = await import("fflate");
|
|
const result = await api<{
|
|
manifest: ArchiveMetadata;
|
|
assets: { path: string; url: string }[];
|
|
}>(`datasets/${id}/export`);
|
|
const metadata = archiveMetadataSchema.parse(result.manifest);
|
|
const files: Record<string, Uint8Array> = Object.create(null);
|
|
const assets = [];
|
|
let total = 0;
|
|
for (const [index, asset] of result.assets.entries()) {
|
|
progress("Exporting images: {done} / {total}", {
|
|
done: index + 1,
|
|
total: result.assets.length,
|
|
});
|
|
const response = await fetch(asset.url, {
|
|
signal: AbortSignal.timeout(60000),
|
|
cache: "no-store",
|
|
});
|
|
if (!response.ok || !response.body)
|
|
throw new Error("Could not download map images. Please try again.");
|
|
const reader = response.body.getReader();
|
|
const chunks: Uint8Array[] = [];
|
|
let size = 0;
|
|
while (true) {
|
|
const { value, done } = await reader.read();
|
|
if (done) break;
|
|
size += value.length;
|
|
total += value.length;
|
|
if (size > archiveFileLimit || total > archiveLimit - 128 * 1024) {
|
|
await reader.cancel();
|
|
throw new Error("Map archive exceeds the 256 MB limit");
|
|
}
|
|
chunks.push(value);
|
|
}
|
|
const bytes = new Uint8Array(size);
|
|
let offset = 0;
|
|
for (const chunk of chunks) {
|
|
bytes.set(chunk, offset);
|
|
offset += chunk.length;
|
|
}
|
|
files[asset.path] = bytes;
|
|
assets.push({ path: asset.path, bytes: size, sha256: await sha256(bytes) });
|
|
}
|
|
const archive = archiveSchema.parse({ ...metadata, assets });
|
|
files[archiveManifestName] = strToU8(JSON.stringify(archive));
|
|
progress("Preparing ZIP…");
|
|
const zip = zipSync(files, { level: 0 });
|
|
if (zip.length > archiveLimit)
|
|
throw new Error("Map archive exceeds the 256 MB limit");
|
|
const url = URL.createObjectURL(
|
|
new Blob([new Uint8Array(zip).buffer], { type: "application/zip" }),
|
|
);
|
|
const link = document.createElement("a");
|
|
link.href = url;
|
|
link.download = `${metadata.name.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80) || "minegeo-map"}.zip`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.remove();
|
|
setTimeout(() => URL.revokeObjectURL(url), 60000);
|
|
}
|
|
|
|
export async function importMap(file: File, progress: Progress) {
|
|
const { archiveLimit, readMapArchive } = await import(
|
|
"../shared/map-archive"
|
|
);
|
|
if (!file.name.toLowerCase().endsWith(".zip"))
|
|
throw new Error("Choose a MineGeo ZIP file");
|
|
if (file.size > archiveLimit)
|
|
throw new Error("Map archive exceeds the 256 MB limit");
|
|
progress("Checking map ZIP…");
|
|
const { archive, files } = await readMapArchive(
|
|
new Uint8Array(await file.arrayBuffer()),
|
|
);
|
|
const { datasetId } = await api<{ datasetId: string }>("uploads/import", {
|
|
manifest: archive,
|
|
});
|
|
const signed = await api<{
|
|
assets: {
|
|
path: string;
|
|
objectName: string;
|
|
token: string;
|
|
apiKey: string;
|
|
endpoint: string;
|
|
}[];
|
|
}>("uploads/sign", { datasetId });
|
|
const { createBrowserClient } = await import("@supabase/ssr");
|
|
const client = createBrowserClient(
|
|
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
|
|
);
|
|
try {
|
|
for (const [index, asset] of signed.assets.entries()) {
|
|
progress("Importing images: {done} / {total}", {
|
|
done: index + 1,
|
|
total: signed.assets.length,
|
|
});
|
|
const content = files.get(asset.path);
|
|
if (!content) throw new Error("Invalid or incomplete map ZIP");
|
|
const { error } = await client.storage
|
|
.from("minegeo-raw")
|
|
.uploadToSignedUrl(
|
|
asset.objectName,
|
|
asset.token,
|
|
new Blob([new Uint8Array(content).buffer], { type: "image/webp" }),
|
|
{ contentType: "image/webp" },
|
|
);
|
|
if (error)
|
|
throw new Error(
|
|
"Map upload failed. Check your connection and import the ZIP again.",
|
|
);
|
|
}
|
|
progress("Preparing your private draft…");
|
|
await api("uploads/finalize", { datasetId });
|
|
return datasetId;
|
|
} catch (error) {
|
|
await api(`datasets/${datasetId}/cancel`, {}).catch(() => {});
|
|
throw error;
|
|
}
|
|
}
|