import { DragHandler } from "./drag-drop.util"; import { FileUploadState, FileUploadError, FileUploadEvent, FileUploadErrorReason } from "../types"; import { NileFileUpload } from "../nile-file-upload"; import { RemoveFileDetail } from "../../nile-file-preview/types"; import { ErrorType } from "../types"; export const setUpDragHandler = ( nileFileUpload: NileFileUpload, dragHandler: DragHandler, ): void => { dragHandler.setIsDisabled(() => { return nileFileUpload.state === FileUploadState.DISABLED; }); dragHandler.onStateChange((newState: FileUploadState) => { nileFileUpload.state = newState; }); dragHandler.onFileDrop((files: File[]) => { if (nileFileUpload.state === FileUploadState.DISABLED) return; nileFileUpload.errorMessage = ''; const maxSize = parseSize(nileFileUpload.size); const uploadedFiles = nileFileUpload.uploadedFiles; if (!nileFileUpload.allowMultiple && (uploadedFiles.length + files.length > 1)) { nileFileUpload.errorMessage = FileUploadError.MULTIPLE_NOT_ALLOWED; nileFileUpload.requestUpdate(); return; } let sizeExceeded = false; let duplicatesFound = false; const newFiles: File[] = []; const rejectedFiles: File[] = []; for (const file of files) { if (file.size > maxSize) { sizeExceeded = true; rejectedFiles.push(file); continue; } if ( !nileFileUpload.allowDuplicates && uploadedFiles.some( (uploaded) => uploaded.name === file.name && uploaded.size === file.size && uploaded.type === file.type ) ) { duplicatesFound = true; rejectedFiles.push(file); continue; } if(!rejectedFiles.includes(file)) { newFiles.push(file); } } if (sizeExceeded) { nileFileUpload.errorMessage = FileUploadError.SIZE_LIMIT_EXCEEDED; const sizeExceededFiles = rejectedFiles.filter(file => file.size > maxSize); nileFileUpload.dispatchEvent(new CustomEvent(FileUploadEvent.NILE_ERROR, { detail: { files: sizeExceededFiles, type: FileUploadErrorReason.SIZE_LIMIT_EXCEEDED, errorType: ErrorType.VALIDATION, message: FileUploadError.SIZE_LIMIT_EXCEEDED, }, bubbles: true, composed: true, })); nileFileUpload.dispatchEvent(new CustomEvent(FileUploadEvent.NILE_SIZE_EXCEED_FILES, { detail: { files: rejectedFiles } })); nileFileUpload.requestUpdate(); } if (duplicatesFound && newFiles.length === 0) { nileFileUpload.errorMessage = FileUploadError.DUPLICATES_NOT_ALLOWED; nileFileUpload.requestUpdate(); } let updatedFiles = []; if(nileFileUpload.allowedTypes.length > 0) { for (const file of newFiles) { if(nileFileUpload.allowedTypes.includes(file.type)) { updatedFiles.push(file); } else { nileFileUpload.errorMessage = FileUploadError.INVALID_FORMAT; } } } if(updatedFiles.length === 0) { updatedFiles = newFiles; } nileFileUpload.uploadedFiles = [...uploadedFiles, ...updatedFiles]; nileFileUpload.requestUpdate(); }); dragHandler.setErrorMessage((errorMessage: string) => { nileFileUpload.errorMessage = errorMessage; }); }; export const addGlobalListeners = (dragHandler: DragHandler): void => { document.addEventListener('dragover', dragHandler.preventDragOver, { passive: false }); document.addEventListener('drop', dragHandler.preventDrop, { passive: false }); } export const addInternalListeners = (nileFileUpload: NileFileUpload, dragHandler: DragHandler, uploadRequests: any): void => { nileFileUpload.addEventListener('mouseenter', (e) => handleHoverIn(nileFileUpload.setState, nileFileUpload.state)); nileFileUpload.addEventListener('mouseleave', (e) => handleHoverOut(nileFileUpload.setState, nileFileUpload.state)); nileFileUpload.addEventListener('dragenter', (e) => dragHandler.dragEnter(e)); nileFileUpload.addEventListener('dragleave', (e) => dragHandler.dragLeave(e)); nileFileUpload.addEventListener('dragover', (e) => dragHandler.dragOver(e)); nileFileUpload.addEventListener('drop', (e) => dragHandler.drop(e)); const clickHandler = nileFileUpload.browseFiles.bind(nileFileUpload); const observer = new ResizeObserver(entries => { for (const entry of entries) { if (entry.contentRect.width < 400) { nileFileUpload.horizontalDiv.addEventListener('click', clickHandler); } else { nileFileUpload.horizontalDiv.removeEventListener('click', clickHandler); } } }); observer.observe(nileFileUpload.horizontalDiv); cancelFileUpload(nileFileUpload, uploadRequests); removeFile(nileFileUpload.uploadedFiles, nileFileUpload); } export function handleHoverIn( setState: (state: FileUploadState) => void, currentState: FileUploadState ): void { if (currentState === FileUploadState.DEFAULT) { setState(FileUploadState.HOVER); } } export function handleHoverOut( setState: (state: FileUploadState) => void, currentState: FileUploadState ): void { if (currentState === FileUploadState.HOVER) { setState(FileUploadState.DEFAULT); } } export function uploadFiles(nileFileUpload: NileFileUpload) { const { uploadedFiles, fileUploadUrl, uploadRequests } = nileFileUpload; if (!fileUploadUrl || !uploadedFiles?.length) return; for (const file of uploadedFiles) { if (uploadRequests.has(file)) continue; if (nileFileUpload.doNotUpload.includes(file)) continue; if((file as any).errorMessage) continue; const formData = new FormData(); formData.append('file', file); const xhr = new XMLHttpRequest(); uploadRequests.set(file, xhr); nileFileUpload.emit(FileUploadEvent.NILE_UPLOADING); xhr.upload.onprogress = (e) => { const percent = Math.floor((e.loaded / e.total) * 100); nileFileUpload.dispatchEvent(new CustomEvent(FileUploadEvent.NILE_UPLOAD_PROGRESS, { detail: { file, progress: percent }, bubbles: true, composed: true, })); }; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { nileFileUpload.dispatchEvent(new CustomEvent(FileUploadEvent.NILE_UPLOAD_SUCCESS, { detail: { file, response: xhr.response }, bubbles: true, composed: true, })); } else { nileFileUpload.errorMessage = FileUploadError.SERVER_SIDE_ERROR; nileFileUpload.dispatchEvent(new CustomEvent(FileUploadEvent.SERVER_SIDE_ERROR, { detail: { file, status: xhr.status, response: xhr.response } })); nileFileUpload.dispatchEvent(new CustomEvent(FileUploadEvent.NILE_ERROR, { detail: { type: FileUploadErrorReason.SERVER_SIDE_ERROR, file, status: xhr?.status || 0, response: xhr?.response, message: FileUploadError.SERVER_SIDE_ERROR, errorType: ErrorType.SERVER, }, bubbles: true, composed: true, })); nileFileUpload.requestUpdate(); } }; xhr.onerror = () => { uploadRequests.delete(file); nileFileUpload.doNotUpload.push(file); nileFileUpload.errorMessage = FileUploadError.NETWORK_ERROR; nileFileUpload.dispatchEvent(new CustomEvent(FileUploadEvent.NILE_ERROR, { detail: { type: FileUploadErrorReason.NETWORK_ERROR, file, status: xhr.status, response: xhr.response, errorType: ErrorType.NETWORK, message: FileUploadError.NETWORK_ERROR, }, bubbles: true, composed: true, })); nileFileUpload.dispatchEvent(new CustomEvent(FileUploadEvent.NILE_NETWORK_ERROR, { detail: { file, status: xhr.status, response: xhr.response }, bubbles: true, composed: true, })); }; xhr.open('POST', fileUploadUrl); xhr.send(formData); } } export const cancelFileUpload = (nileFileUpload: NileFileUpload, uploadRequests: Map) => { document.addEventListener(FileUploadEvent.NILE_CANCEL_UPLOAD, (e: CustomEvent) => { const fileToCancel = e.detail.file; const xhr = uploadRequests.get(fileToCancel); if (xhr) { xhr.abort(); } uploadRequests.delete(fileToCancel); nileFileUpload.uploadedFiles = nileFileUpload.uploadedFiles.filter(file => file !== fileToCancel); nileFileUpload.dispatchEvent(new CustomEvent(FileUploadEvent.NILE_ERROR, { detail: { type: FileUploadErrorReason.UPLOAD_CANCELLED, file: fileToCancel, message: FileUploadError.UPLOAD_CANCELLED, errorType: ErrorType.UPLOAD_CANCELLED, }, bubbles: true, composed: true, })); nileFileUpload.dispatchEvent(new CustomEvent(FileUploadEvent.NILE_UPLOAD_CANCELLED, { detail: { file: fileToCancel } })); }); }; function parseSize(sizeStr: string): number { const units = { B: 1, KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3 }; const num = parseFloat(sizeStr); const unit = sizeStr.replace(/[0-9.\s]/g, '').toUpperCase(); const multiplier = units[unit as keyof typeof units] || 1; return num * multiplier; } export const preventDefaultAndStopPropagation = (event: Event | DragEvent): void => { event.preventDefault?.(); event.stopPropagation(); } export const removeFile = (uploadedFiles: File[], nileFileUpload: NileFileUpload) => { document.addEventListener('nile-remove', (e: CustomEvent) => { const { value } = e.detail; uploadedFiles = uploadedFiles.filter(file => file !== value); nileFileUpload.uploadedFiles = uploadedFiles; nileFileUpload.errorMessage = ''; nileFileUpload.requestUpdate(); }); } export const truncateString = (nileFileUpload: NileFileUpload): void => { if(nileFileUpload.variant === 'vertical' && nileFileUpload.errorMessage.length > 34) { nileFileUpload.isStringTruncated = true; nileFileUpload.requestUpdate(); } else if(nileFileUpload.variant === 'horizontal' && nileFileUpload.errorMessage.length > 96) { nileFileUpload.isStringTruncated = true; nileFileUpload.requestUpdate(); } }