Files
ExcaliDash/frontend/src/utils/importUtils.ts
T
2026-01-19 00:07:27 -08:00

113 lines
3.4 KiB
TypeScript

import { exportToSvg } from "@excalidraw/excalidraw";
import { api } from "../api";
import { type UploadStatus } from "../context/UploadContext";
export type ImportResult = {
success: number;
failed: number;
errors: string[];
};
export const importDrawings = async (
files: File[],
targetCollectionId: string | null,
onSuccess?: () => void | Promise<void>,
onProgress?: (
fileIndex: number,
status: UploadStatus,
progress: number,
error?: string
) => void
) => {
const drawingFiles = files.filter(
(f) => f.name.endsWith(".json") || f.name.endsWith(".excalidraw")
);
if (drawingFiles.length === 0) {
return { success: 0, failed: 0, errors: ["No supported files found."] };
}
let successCount = 0;
let failCount = 0;
const errors: string[] = [];
// Build a map from drawingFile index to original file index for progress reporting
const originalIndexMap = new Map<number, number>();
drawingFiles.forEach((df, i) => {
const originalIndex = files.indexOf(df);
originalIndexMap.set(i, originalIndex);
});
// We process files in parallel (Promise.all) but we could limit concurrency if needed.
// For now, full parallel is fine as browser limits connection count anyway.
await Promise.all(
drawingFiles.map(async (file, drawingIndex) => {
const fileIndex = originalIndexMap.get(drawingIndex) ?? drawingIndex;
try {
if (onProgress) onProgress(fileIndex, 'processing', 0); // Parsing phase
const text = await file.text();
const data = JSON.parse(text);
if (!data.elements || !data.appState) {
throw new Error(`Invalid file structure: ${file.name}`);
}
const svg = await exportToSvg({
elements: data.elements,
appState: {
...data.appState,
exportBackground: true,
viewBackgroundColor: data.appState.viewBackgroundColor || "#ffffff",
},
files: data.files || {},
exportPadding: 10,
});
const payload = {
name: file.name.replace(/\.(json|excalidraw)$/, ""),
elements: data.elements,
appState: data.appState,
files: data.files || null,
collectionId: targetCollectionId,
createdAt: data.createdAt || Date.now(),
updatedAt: data.updatedAt || Date.now(),
preview: svg.outerHTML,
};
if (onProgress) onProgress(fileIndex, 'uploading', 0);
await api.post("/drawings", payload, {
headers: {
// Backend uses this header to apply stricter validation for imported files.
"X-Imported-File": "true",
"Content-Type": "application/json",
},
});
if (onProgress) onProgress(fileIndex, 'uploading', 100);
if (onProgress) onProgress(fileIndex, 'success', 100);
successCount++;
} catch (err: any) {
console.error(`Failed to import ${file.name}:`, err);
failCount++;
const errorMessage =
err?.response?.data?.message ||
err?.response?.data?.error ||
err?.message ||
"Upload failed";
errors.push(`${file.name}: ${errorMessage}`);
if (onProgress) onProgress(fileIndex, 'error', 0, errorMessage);
}
})
);
if (successCount > 0 && onSuccess) {
await onSuccess();
}
return { success: successCount, failed: failCount, errors };
};