diff options
Diffstat (limited to 'lib/dolce/utils/upload-with-progress.ts')
| -rw-r--r-- | lib/dolce/utils/upload-with-progress.ts | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/lib/dolce/utils/upload-with-progress.ts b/lib/dolce/utils/upload-with-progress.ts new file mode 100644 index 00000000..8e36afe4 --- /dev/null +++ b/lib/dolce/utils/upload-with-progress.ts @@ -0,0 +1,128 @@ +/** + * XMLHttpRequest를 사용하여 파일 업로드 진행도 추적 + */ +export interface UploadProgressCallback { + onProgress: (fileIndex: number, progress: number) => void; + onFileComplete: (fileIndex: number) => void; + onFileError: (fileIndex: number, error: string) => void; +} + +export interface UploadFilesWithProgressOptions { + uploadId: string; + userId: string; + files: File[]; + callbacks: UploadProgressCallback; +} + +export interface UploadResult { + success: boolean; + uploadedCount?: number; + error?: string; +} + +/** + * 진행도 추적을 지원하는 파일 업로드 함수 + */ +export async function uploadFilesWithProgress({ + uploadId, + userId, + files, + callbacks, +}: UploadFilesWithProgressOptions): Promise<UploadResult> { + return new Promise((resolve) => { + const formData = new FormData(); + formData.append("uploadId", uploadId); + formData.append("userId", userId); + formData.append("fileCount", String(files.length)); + + files.forEach((file, index) => { + formData.append(`file_${index}`, file); + }); + + const xhr = new XMLHttpRequest(); + + // 전체 업로드 진행도 (단순화: 전체 진행도를 각 파일에 분배) + xhr.upload.addEventListener("progress", (event) => { + if (event.lengthComputable) { + const totalProgress = (event.loaded / event.total) * 100; + + // 현재 업로드 중인 파일 인덱스 추정 + const filesCompleted = Math.floor((totalProgress / 100) * files.length); + const currentFileIndex = Math.min(filesCompleted, files.length - 1); + + // 각 파일별 진행도 계산 + files.forEach((_, index) => { + if (index < filesCompleted) { + callbacks.onProgress(index, 100); + callbacks.onFileComplete(index); + } else if (index === currentFileIndex) { + const fileProgress = ((totalProgress / 100) * files.length - filesCompleted) * 100; + callbacks.onProgress(index, Math.min(fileProgress, 99)); + } else { + callbacks.onProgress(index, 0); + } + }); + } + }); + + xhr.addEventListener("load", () => { + if (xhr.status >= 200 && xhr.status < 300) { + try { + const response = JSON.parse(xhr.responseText); + + // 모든 파일 완료 처리 + files.forEach((_, index) => { + callbacks.onProgress(index, 100); + callbacks.onFileComplete(index); + }); + + resolve(response); + } catch (error) { + const errorMsg = "응답 파싱 실패"; + files.forEach((_, index) => { + callbacks.onFileError(index, errorMsg); + }); + resolve({ + success: false, + error: errorMsg, + }); + } + } else { + const errorMsg = `업로드 실패: ${xhr.status} ${xhr.statusText}`; + files.forEach((_, index) => { + callbacks.onFileError(index, errorMsg); + }); + resolve({ + success: false, + error: errorMsg, + }); + } + }); + + xhr.addEventListener("error", () => { + const errorMsg = "네트워크 오류"; + files.forEach((_, index) => { + callbacks.onFileError(index, errorMsg); + }); + resolve({ + success: false, + error: errorMsg, + }); + }); + + xhr.addEventListener("abort", () => { + const errorMsg = "업로드가 취소되었습니다"; + files.forEach((_, index) => { + callbacks.onFileError(index, errorMsg); + }); + resolve({ + success: false, + error: errorMsg, + }); + }); + + xhr.open("POST", "/api/dolce/upload-files"); + xhr.send(formData); + }); +} + |
