Files
ExcaliDash/frontend/src/utils/importUtils.ts
T
Zimeng Xiong 0476315322 0.2.1 Release (#32)
* feat(security): implement CSRF protection

* chore: clean up CSRF implementation

  - Remove unused generateCsrfToken export from security.ts
  - Remove redundant /csrf-token path check (GET already exempt)
  - Restore defineConfig wrapper in vitest.config.ts for type safety

* add K8S note in README, fix broken e2e

* feat/upload-bar (#30)

* feat/upload-bar: add a upload bar when user upload file, indicate the upload process

* feat/save-loading-status: add save status when click back button from editor

* fix: address PR review issues in upload and save features

- Replace deprecated substr() with substring() in UploadContext
- Fix broken error handling that checked stale task status
- Fix missing useEffect dependency in UploadStatus
- Fix CSS class conflict in progress bar styling
- Add error recovery for save state in Editor (reset on failure)
- Use .finally() instead of .then() to ensure refresh on upload failure
- Fix inconsistent indentation in UploadContext

* fix e2e tests

---------

Co-authored-by: Zimeng Xiong <zxzimeng@gmail.com>

* chore: pre-release v0.2.1-dev

* Update backend/src/security.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix filename/math random UUID generation

---------

Co-authored-by: AdrianAcala <adrianacala017@gmail.com>
Co-authored-by: adamant368 <60790941+Yiheng-Liu@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-14 11:25:27 -08:00

112 lines
3.5 KiB
TypeScript

import { exportToSvg } from "@excalidraw/excalidraw";
import { api } from "../api";
import { type UploadStatus } from "../context/UploadContext";
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",
},
onUploadProgress: (progressEvent) => {
if (onProgress && progressEvent.total) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
onProgress(fileIndex, 'uploading', percentCompleted);
}
},
});
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 };
};