import { saveAs } from "file-saver"; import { createZip, readZip } from "@/lib/zip"; import { getMediaBlob, setMediaBlob } from "@/services/file-storage"; import { getImageBlob, setImageBlob } from "@/services/image-storage"; import type { Asset } from "@/stores/use-asset-store"; type AssetExportFile = { app: "infinite-canvas"; version: 1; exportedAt: string; assets: Asset[]; files: AssetExportItem[]; }; type AssetExportItem = { storageKey: string; path: string; mimeType: string; bytes: number; }; export async function exportAssets(assets: Asset[]) { const files: AssetExportItem[] = []; const zipFiles: { name: string; data: BlobPart }[] = []; await Promise.all( assets.map(async (asset) => { if (asset.kind !== "image" && asset.kind !== "video") return; const storageKey = asset.data.storageKey; if (!storageKey) return; const blob = asset.kind === "image" ? await getImageBlob(storageKey) : await getMediaBlob(storageKey); if (!blob) return; const path = `files/${safeFileName(storageKey)}.${fileExtension(blob.type, asset.kind)}`; files.push({ storageKey, path, mimeType: blob.type || asset.data.mimeType, bytes: blob.size }); zipFiles.push({ name: path, data: blob }); }), ); const data: AssetExportFile = { app: "infinite-canvas", version: 1, exportedAt: new Date().toISOString(), assets, files }; const zip = await createZip([{ name: "assets.json", data: JSON.stringify(data, null, 2) }, ...zipFiles]); saveAs(zip, "我的素材.zip"); } export async function readAssetPackage(file: File) { const zip = await readZip(file); const assetFile = zip.get("assets.json"); if (!assetFile) throw new Error("missing assets.json"); const data = JSON.parse(await assetFile.text()) as AssetExportFile; await Promise.all( data.files.map(async (item) => { const blob = zip.get(item.path); if (!blob) return; const typedBlob = blob.type ? blob : blob.slice(0, blob.size, item.mimeType); await (item.storageKey.startsWith("image:") ? setImageBlob(item.storageKey, typedBlob) : setMediaBlob(item.storageKey, typedBlob)); }), ); return data.assets; } function safeFileName(value: string) { return value.replace(/[\\/:*?"<>|]/g, "_"); } function fileExtension(mimeType: string, kind: Asset["kind"]) { if (mimeType.includes("png")) return "png"; if (mimeType.includes("jpeg")) return "jpg"; if (mimeType.includes("webp")) return "webp"; if (mimeType.includes("gif")) return "gif"; if (mimeType.includes("mp4")) return "mp4"; if (mimeType.includes("webm")) return "webm"; return kind === "image" ? "png" : "bin"; }