"use client"; import localforage from "localforage"; import { nanoid } from "nanoid"; import { readImageMeta } from "@/lib/image-utils"; export type UploadedImage = { url: string; storageKey: string; width: number; height: number; bytes: number; mimeType: string; }; const store = localforage.createInstance({ name: "infinite-canvas", storeName: "image_files" }); const objectUrls = new Map(); export async function uploadImage(input: string | Blob): Promise { const blob = typeof input === "string" ? await (await fetch(input)).blob() : input; const storageKey = `image:${nanoid()}`; await store.setItem(storageKey, blob); const url = URL.createObjectURL(blob); objectUrls.set(storageKey, url); const meta = await readImageMeta(url); return { url, storageKey, width: meta.width, height: meta.height, bytes: blob.size, mimeType: blob.type || meta.mimeType }; } export async function resolveImageUrl(storageKey?: string, fallback = "") { if (!storageKey) return fallback; const cached = objectUrls.get(storageKey); if (cached) return cached; const blob = await store.getItem(storageKey); if (!blob) return fallback; const url = URL.createObjectURL(blob); objectUrls.set(storageKey, url); return url; } export async function imageToDataUrl(image: { url?: string; dataUrl?: string; storageKey?: string }) { const url = image.dataUrl || await resolveImageUrl(image.storageKey, image.url || ""); if (!url || url.startsWith("data:")) return url; return blobToDataUrl(await (await fetch(url)).blob()); } export async function deleteStoredImages(keys: Iterable) { await Promise.all(Array.from(new Set(keys)).map(async (key) => { const url = objectUrls.get(key); if (url) URL.revokeObjectURL(url); objectUrls.delete(key); await store.removeItem(key); })); } export async function cleanupUnusedImages(usedData: unknown) { const usedKeys = collectImageStorageKeys(usedData); const unused: string[] = []; await store.iterate((_value, key) => { if (!usedKeys.has(key)) unused.push(key); }); await deleteStoredImages(unused); } export function collectImageStorageKeys(value: unknown, keys = new Set()) { if (!value || typeof value !== "object") return keys; if ("storageKey" in value && typeof value.storageKey === "string" && value.storageKey.startsWith("image:")) keys.add(value.storageKey); Object.values(value).forEach((item) => Array.isArray(item) ? item.forEach((child) => collectImageStorageKeys(child, keys)) : collectImageStorageKeys(item, keys)); return keys; } function blobToDataUrl(blob: Blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(String(reader.result || "")); reader.onerror = () => reject(new Error("读取图片失败")); reader.readAsDataURL(blob); }); }