'use client' import React, { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import cn from 'classnames' import s from './index.module.css' import type { File as FileEntity } from '@/models/datasets' import { ToastContext } from '@/app/components/base/toast' import Button from '@/app/components/base/button' import { upload } from '@/service/base' type IFileUploaderProps = { file?: FileEntity onFileUpdate: (file?: FileEntity) => void } const ACCEPTS = [ '.pdf', '.html', '.htm', '.md', '.markdown', '.txt', // '.xls', '.xlsx', '.csv', ] const MAX_SIZE = 15 * 1024 * 1024 const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) const [dragging, setDragging] = useState(false) const dropRef = useRef(null) const dragRef = useRef(null) const fileUploader = useRef(null) const uploadPromise = useRef(null) const [currentFile, setCurrentFile] = useState() const [uploading, setUploading] = useState(false) const [percent, setPercent] = useState(0) // utils const getFileType = (currentFile: File) => { if (!currentFile) return '' const arr = currentFile.name.split('.') return arr[arr.length - 1] } const getFileName = (name: string) => { const arr = name.split('.') return arr.slice(0, -1).join() } const getFileSize = (size: number) => { if (size / 1024 < 10) return `${(size / 1024).toFixed(2)}KB` return `${(size / 1024 / 1024).toFixed(2)}MB` } const isValid = (file: File) => { const { size } = file const ext = `.${getFileType(file)}` const isValidType = ACCEPTS.includes(ext) if (!isValidType) notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.typeError') }) const isValidSize = size <= MAX_SIZE if (!isValidSize) notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.size') }) return isValidType && isValidSize } const onProgress = useCallback((e: ProgressEvent) => { if (e.lengthComputable) { const percent = Math.floor(e.loaded / e.total * 100) setPercent(percent) } }, [setPercent]) const abort = () => { const currentXHR = uploadPromise.current currentXHR.abort() } const fileUpload = async (file?: File) => { if (!file) return if (!isValid(file)) return setCurrentFile(file) setUploading(true) const formData = new FormData() formData.append('file', file) // store for abort const currentXHR = new XMLHttpRequest() uploadPromise.current = currentXHR try { const result = await upload({ xhr: currentXHR, data: formData, onprogress: onProgress, }) as FileEntity onFileUpdate(result) setUploading(false) } catch (xhr: any) { setUploading(false) // abort handle if (xhr.readyState === 0 && xhr.status === 0) { if (fileUploader.current) fileUploader.current.value = '' setCurrentFile(undefined) return } notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.failed') }) } } const handleDragEnter = (e: DragEvent) => { e.preventDefault() e.stopPropagation() e.target !== dragRef.current && setDragging(true) } const handleDragOver = (e: DragEvent) => { e.preventDefault() e.stopPropagation() } const handleDragLeave = (e: DragEvent) => { e.preventDefault() e.stopPropagation() e.target === dragRef.current && setDragging(false) } const handleDrop = (e: DragEvent) => { e.preventDefault() e.stopPropagation() setDragging(false) if (!e.dataTransfer) return const files = [...e.dataTransfer.files] if (files.length > 1) { notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.count') }) return } onFileUpdate() fileUpload(files[0]) } const selectHandle = () => { if (fileUploader.current) fileUploader.current.click() } const removeFile = () => { if (fileUploader.current) fileUploader.current.value = '' setCurrentFile(undefined) onFileUpdate() } const fileChangeHandle = (e: React.ChangeEvent) => { const currentFile = e.target.files?.[0] onFileUpdate() fileUpload(currentFile) } useEffect(() => { dropRef.current?.addEventListener('dragenter', handleDragEnter) dropRef.current?.addEventListener('dragover', handleDragOver) dropRef.current?.addEventListener('dragleave', handleDragLeave) dropRef.current?.addEventListener('drop', handleDrop) return () => { dropRef.current?.removeEventListener('dragenter', handleDragEnter) dropRef.current?.removeEventListener('dragover', handleDragOver) dropRef.current?.removeEventListener('dragleave', handleDragLeave) dropRef.current?.removeEventListener('drop', handleDrop) } }, []) return (
{t('datasetCreation.stepOne.uploader.title')}
{!currentFile && !file && (
{t('datasetCreation.stepOne.uploader.button')} {dragging &&
}
)}
{currentFile && (
{uploading && (
)}
{getFileName(currentFile.name)} {`.${getFileType(currentFile)}`}
{getFileSize(currentFile.size)}
{uploading && ( <>
{`${percent}%`}
)} {!uploading && ( <>
)}
)} {!currentFile && file && (
{getFileName(file.name)} {`.${file.extension}`}
{getFileSize(file.size)}
)}
{t('datasetCreation.stepOne.uploader.tip')}
) } export default FileUploader