feat(assets): 使用 file-saver 库优化文件下载功能
- 引入 file-saver 库替代手动创建下载链接的方式 - 在 asset-transfer.ts 中使用 saveAs 替代 URL.createObjectURL 和手动点击链接 - 在 canvas-export.ts 中使用 saveAs 替代原有的文件下载逻辑 - 更新 package.json 添加 file-saver 依赖 - 更新 bun.lock 文件同步依赖变更
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
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";
|
||||
@@ -36,12 +38,7 @@ export async function exportAssets(assets: Asset[]) {
|
||||
|
||||
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]);
|
||||
const url = URL.createObjectURL(zip);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = "我的素材.zip";
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
saveAs(zip, "我的素材.zip");
|
||||
}
|
||||
|
||||
export async function readAssetPackage(file: File) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { Copy, Download, PencilLine, Search, Trash2, Upload } from "lucide-react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { App, Button, Card, Drawer, Empty, Form, Image, Input, Modal, Pagination, Select, Space, Tag, Typography } from "antd";
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import { useCopyText } from "@/hooks/use-copy-text";
|
||||
import { formatBytes, readFileAsDataUrl } from "@/lib/image-utils";
|
||||
@@ -149,10 +150,7 @@ export default function AssetsPage() {
|
||||
|
||||
const downloadImage = (asset: Asset) => {
|
||||
if (asset.kind !== "image" && asset.kind !== "video") return;
|
||||
const link = document.createElement("a");
|
||||
link.href = asset.kind === "video" ? asset.data.url : asset.data.dataUrl;
|
||||
link.download = `${asset.title || "asset"}.${asset.data.mimeType.split("/")[1] || "png"}`;
|
||||
link.click();
|
||||
saveAs(asset.kind === "video" ? asset.data.url : asset.data.dataUrl, `${asset.title || "asset"}.${asset.data.mimeType.split("/")[1] || "png"}`);
|
||||
};
|
||||
|
||||
const exportAllAssets = async () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } fr
|
||||
import type { ChangeEvent as ReactChangeEvent, DragEvent as ReactDragEvent, MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { Home, ImageIcon, Images, List, Menu, MessageSquare, Plus, Redo2, Settings2, Trash2, Undo2, Upload, Video } from "lucide-react";
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import { requestEdit, requestGeneration, requestImageQuestion } from "@/services/api/image";
|
||||
import { requestVideoGeneration } from "@/services/api/video";
|
||||
@@ -1317,10 +1318,7 @@ function InfiniteCanvasPage() {
|
||||
|
||||
const downloadNodeImage = useCallback((node: CanvasNodeData) => {
|
||||
if ((node.type !== CanvasNodeType.Image && node.type !== CanvasNodeType.Video) || !node.metadata?.content) return;
|
||||
const link = document.createElement("a");
|
||||
link.href = node.metadata.content;
|
||||
link.download = `canvas-${node.type}-${node.id}.${node.type === CanvasNodeType.Video ? "mp4" : imageExtension(node.metadata.content)}`;
|
||||
link.click();
|
||||
saveAs(node.metadata.content, `canvas-${node.type}-${node.id}.${node.type === CanvasNodeType.Video ? "mp4" : imageExtension(node.metadata.content)}`);
|
||||
}, []);
|
||||
|
||||
const saveNodeAsset = useCallback(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import { createZip } from "@/lib/zip";
|
||||
import { getMediaBlob } from "@/services/file-storage";
|
||||
import { getImageBlob } from "@/services/image-storage";
|
||||
@@ -24,12 +26,7 @@ export async function exportCanvasProjects(projects: CanvasProject[], fileName =
|
||||
|
||||
const data: CanvasExportFile = { app: "infinite-canvas", version: 3, exportedAt: new Date().toISOString(), projects: exportedProjects };
|
||||
const zip = await createZip([{ name: "projects.json", data: JSON.stringify(data, null, 2) }, ...zipFiles]);
|
||||
const url = URL.createObjectURL(zip);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `${safeFileName(fileName)}.zip`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
saveAs(zip, `${safeFileName(fileName)}.zip`);
|
||||
}
|
||||
|
||||
function collectStorageKeys(value: unknown, keys = new Set<string>()) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { BookOpen, CheckSquare, ClipboardPaste, Download, FolderPlus, History, I
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { App, Button, Checkbox, Drawer, Empty, Image, Input, Modal, Tag, Typography } from "antd";
|
||||
import localforage from "localforage";
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import { ImageSettingsPanel } from "@/components/image-settings-panel";
|
||||
import { ModelPicker } from "@/components/model-picker";
|
||||
@@ -177,10 +178,7 @@ export default function ImagePage() {
|
||||
};
|
||||
|
||||
const downloadImage = (image: GeneratedImage, index: number) => {
|
||||
const link = document.createElement("a");
|
||||
link.href = image.dataUrl;
|
||||
link.download = `image-${index + 1}.png`;
|
||||
link.click();
|
||||
saveAs(image.dataUrl, `image-${index + 1}.png`);
|
||||
};
|
||||
|
||||
const addResultToReferences = async (image: GeneratedImage, index: number) => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { App, Button, Checkbox, Drawer, Empty, Input, Modal, Tag, Typography } from "antd";
|
||||
import localforage from "localforage";
|
||||
import { nanoid } from "nanoid";
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import { AssetPickerModal, type InsertAssetPayload } from "@/app/(user)/canvas/components/asset-picker-modal";
|
||||
import { ModelPicker } from "@/components/model-picker";
|
||||
@@ -181,10 +182,7 @@ export default function VideoPage() {
|
||||
};
|
||||
|
||||
const downloadVideo = (video: GeneratedVideo) => {
|
||||
const link = document.createElement("a");
|
||||
link.href = video.url;
|
||||
link.download = "video.mp4";
|
||||
link.click();
|
||||
saveAs(video.url, "video.mp4");
|
||||
};
|
||||
|
||||
const saveResultToAssets = (video: GeneratedVideo) => {
|
||||
|
||||
Reference in New Issue
Block a user