Feature/support xlsx (#311)
This commit is contained in:
@@ -1,19 +1,18 @@
|
||||
'use client'
|
||||
import React, { useState, useRef, useEffect, useCallback } from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { File as FileEntity } from '@/models/datasets'
|
||||
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'
|
||||
|
||||
import cn from 'classnames'
|
||||
import s from './index.module.css'
|
||||
|
||||
type IFileUploaderProps = {
|
||||
file?: FileEntity;
|
||||
onFileUpdate: (file?: FileEntity) => void;
|
||||
file?: FileEntity
|
||||
onFileUpdate: (file?: FileEntity) => void
|
||||
}
|
||||
|
||||
const ACCEPTS = [
|
||||
@@ -23,9 +22,12 @@ const ACCEPTS = [
|
||||
'.md',
|
||||
'.markdown',
|
||||
'.txt',
|
||||
'.xls',
|
||||
'.xlsx',
|
||||
'.csv',
|
||||
]
|
||||
|
||||
const MAX_SIZE = 15 * 1024 *1024
|
||||
const MAX_SIZE = 15 * 1024 * 1024
|
||||
|
||||
const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -39,6 +41,84 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
|
||||
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()
|
||||
@@ -57,27 +137,26 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setDragging(false)
|
||||
if (!e.dataTransfer) {
|
||||
if (!e.dataTransfer)
|
||||
return
|
||||
}
|
||||
|
||||
const files = [...e.dataTransfer.files]
|
||||
if (files.length > 1) {
|
||||
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.count') })
|
||||
return;
|
||||
return
|
||||
}
|
||||
onFileUpdate()
|
||||
fileUpload(files[0])
|
||||
}
|
||||
|
||||
const selectHandle = () => {
|
||||
if (fileUploader.current) {
|
||||
fileUploader.current.click();
|
||||
}
|
||||
if (fileUploader.current)
|
||||
fileUploader.current.click()
|
||||
}
|
||||
const removeFile = () => {
|
||||
if (fileUploader.current) {
|
||||
if (fileUploader.current)
|
||||
fileUploader.current.value = ''
|
||||
}
|
||||
|
||||
setCurrentFile(undefined)
|
||||
onFileUpdate()
|
||||
}
|
||||
@@ -86,96 +165,17 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
|
||||
onFileUpdate()
|
||||
fileUpload(currentFile)
|
||||
}
|
||||
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') })
|
||||
return
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dropRef.current?.addEventListener('dragenter', handleDragEnter);
|
||||
dropRef.current?.addEventListener('dragover', handleDragOver);
|
||||
dropRef.current?.addEventListener('dragleave', handleDragLeave);
|
||||
dropRef.current?.addEventListener('drop', handleDrop);
|
||||
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);
|
||||
dropRef.current?.removeEventListener('dragenter', handleDragEnter)
|
||||
dropRef.current?.removeEventListener('dragover', handleDragOver)
|
||||
dropRef.current?.removeEventListener('dragleave', handleDragLeave)
|
||||
dropRef.current?.removeEventListener('drop', handleDrop)
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -202,7 +202,7 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
|
||||
{currentFile && (
|
||||
<div className={cn(s.file, uploading && s.uploading)}>
|
||||
{uploading && (
|
||||
<div className={s.progressbar} style={{ width: `${percent}%`}}/>
|
||||
<div className={s.progressbar} style={{ width: `${percent}%` }}/>
|
||||
)}
|
||||
<div className={cn(s.fileIcon, s[getFileType(currentFile)])}/>
|
||||
<div className={s.fileInfo}>
|
||||
@@ -264,4 +264,4 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default FileUploader;
|
||||
export default FileUploader
|
||||
|
Reference in New Issue
Block a user