|
import type { ClipboardEvent } from 'react' |
|
import { |
|
useCallback, |
|
useState, |
|
} from 'react' |
|
import { useParams } from 'next/navigation' |
|
import produce from 'immer' |
|
import { v4 as uuid4 } from 'uuid' |
|
import { useTranslation } from 'react-i18next' |
|
import type { FileEntity } from './types' |
|
import { useFileStore } from './store' |
|
import { |
|
fileUpload, |
|
getSupportFileType, |
|
isAllowedFileExtension, |
|
} from './utils' |
|
import { |
|
AUDIO_SIZE_LIMIT, |
|
FILE_SIZE_LIMIT, |
|
IMG_SIZE_LIMIT, |
|
MAX_FILE_UPLOAD_LIMIT, |
|
VIDEO_SIZE_LIMIT, |
|
} from '@/app/components/base/file-uploader/constants' |
|
import { useToastContext } from '@/app/components/base/toast' |
|
import { TransferMethod } from '@/types/app' |
|
import { SupportUploadFileTypes } from '@/app/components/workflow/types' |
|
import type { FileUpload } from '@/app/components/base/features/types' |
|
import { formatFileSize } from '@/utils/format' |
|
import { uploadRemoteFileInfo } from '@/service/common' |
|
import type { FileUploadConfigResponse } from '@/models/common' |
|
|
|
export const useFileSizeLimit = (fileUploadConfig?: FileUploadConfigResponse) => { |
|
const imgSizeLimit = Number(fileUploadConfig?.image_file_size_limit) * 1024 * 1024 || IMG_SIZE_LIMIT |
|
const docSizeLimit = Number(fileUploadConfig?.file_size_limit) * 1024 * 1024 || FILE_SIZE_LIMIT |
|
const audioSizeLimit = Number(fileUploadConfig?.audio_file_size_limit) * 1024 * 1024 || AUDIO_SIZE_LIMIT |
|
const videoSizeLimit = Number(fileUploadConfig?.video_file_size_limit) * 1024 * 1024 || VIDEO_SIZE_LIMIT |
|
const maxFileUploadLimit = Number(fileUploadConfig?.workflow_file_upload_limit) || MAX_FILE_UPLOAD_LIMIT |
|
|
|
return { |
|
imgSizeLimit, |
|
docSizeLimit, |
|
audioSizeLimit, |
|
videoSizeLimit, |
|
maxFileUploadLimit, |
|
} |
|
} |
|
|
|
export const useFile = (fileConfig: FileUpload) => { |
|
const { t } = useTranslation() |
|
const { notify } = useToastContext() |
|
const fileStore = useFileStore() |
|
const params = useParams() |
|
const { imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit } = useFileSizeLimit(fileConfig.fileUploadConfig) |
|
|
|
const checkSizeLimit = useCallback((fileType: string, fileSize: number) => { |
|
switch (fileType) { |
|
case SupportUploadFileTypes.image: { |
|
if (fileSize > imgSizeLimit) { |
|
notify({ |
|
type: 'error', |
|
message: t('common.fileUploader.uploadFromComputerLimit', { |
|
type: SupportUploadFileTypes.image, |
|
size: formatFileSize(imgSizeLimit), |
|
}), |
|
}) |
|
return false |
|
} |
|
return true |
|
} |
|
case SupportUploadFileTypes.document: { |
|
if (fileSize > docSizeLimit) { |
|
notify({ |
|
type: 'error', |
|
message: t('common.fileUploader.uploadFromComputerLimit', { |
|
type: SupportUploadFileTypes.document, |
|
size: formatFileSize(docSizeLimit), |
|
}), |
|
}) |
|
return false |
|
} |
|
return true |
|
} |
|
case SupportUploadFileTypes.audio: { |
|
if (fileSize > audioSizeLimit) { |
|
notify({ |
|
type: 'error', |
|
message: t('common.fileUploader.uploadFromComputerLimit', { |
|
type: SupportUploadFileTypes.audio, |
|
size: formatFileSize(audioSizeLimit), |
|
}), |
|
}) |
|
return false |
|
} |
|
return true |
|
} |
|
case SupportUploadFileTypes.video: { |
|
if (fileSize > videoSizeLimit) { |
|
notify({ |
|
type: 'error', |
|
message: t('common.fileUploader.uploadFromComputerLimit', { |
|
type: SupportUploadFileTypes.video, |
|
size: formatFileSize(videoSizeLimit), |
|
}), |
|
}) |
|
return false |
|
} |
|
return true |
|
} |
|
case SupportUploadFileTypes.custom: { |
|
if (fileSize > docSizeLimit) { |
|
notify({ |
|
type: 'error', |
|
message: t('common.fileUploader.uploadFromComputerLimit', { |
|
type: SupportUploadFileTypes.document, |
|
size: formatFileSize(docSizeLimit), |
|
}), |
|
}) |
|
return false |
|
} |
|
return true |
|
} |
|
default: { |
|
return true |
|
} |
|
} |
|
}, [audioSizeLimit, docSizeLimit, imgSizeLimit, notify, t, videoSizeLimit]) |
|
|
|
const handleAddFile = useCallback((newFile: FileEntity) => { |
|
const { |
|
files, |
|
setFiles, |
|
} = fileStore.getState() |
|
|
|
const newFiles = produce(files, (draft) => { |
|
draft.push(newFile) |
|
}) |
|
setFiles(newFiles) |
|
}, [fileStore]) |
|
|
|
const handleUpdateFile = useCallback((newFile: FileEntity) => { |
|
const { |
|
files, |
|
setFiles, |
|
} = fileStore.getState() |
|
|
|
const newFiles = produce(files, (draft) => { |
|
const index = draft.findIndex(file => file.id === newFile.id) |
|
|
|
if (index > -1) |
|
draft[index] = newFile |
|
}) |
|
setFiles(newFiles) |
|
}, [fileStore]) |
|
|
|
const handleRemoveFile = useCallback((fileId: string) => { |
|
const { |
|
files, |
|
setFiles, |
|
} = fileStore.getState() |
|
|
|
const newFiles = files.filter(file => file.id !== fileId) |
|
setFiles(newFiles) |
|
}, [fileStore]) |
|
|
|
const handleReUploadFile = useCallback((fileId: string) => { |
|
const { |
|
files, |
|
setFiles, |
|
} = fileStore.getState() |
|
const index = files.findIndex(file => file.id === fileId) |
|
|
|
if (index > -1) { |
|
const uploadingFile = files[index] |
|
const newFiles = produce(files, (draft) => { |
|
draft[index].progress = 0 |
|
}) |
|
setFiles(newFiles) |
|
fileUpload({ |
|
file: uploadingFile.originalFile!, |
|
onProgressCallback: (progress) => { |
|
handleUpdateFile({ ...uploadingFile, progress }) |
|
}, |
|
onSuccessCallback: (res) => { |
|
handleUpdateFile({ ...uploadingFile, uploadedId: res.id, progress: 100 }) |
|
}, |
|
onErrorCallback: () => { |
|
notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerUploadError') }) |
|
handleUpdateFile({ ...uploadingFile, progress: -1 }) |
|
}, |
|
}, !!params.token) |
|
} |
|
}, [fileStore, notify, t, handleUpdateFile, params]) |
|
|
|
const startProgressTimer = useCallback((fileId: string) => { |
|
const timer = setInterval(() => { |
|
const files = fileStore.getState().files |
|
const file = files.find(file => file.id === fileId) |
|
|
|
if (file && file.progress < 80 && file.progress >= 0) |
|
handleUpdateFile({ ...file, progress: file.progress + 20 }) |
|
else |
|
clearTimeout(timer) |
|
}, 200) |
|
}, [fileStore, handleUpdateFile]) |
|
const handleLoadFileFromLink = useCallback((url: string) => { |
|
const allowedFileTypes = fileConfig.allowed_file_types |
|
|
|
const uploadingFile = { |
|
id: uuid4(), |
|
name: url, |
|
type: '', |
|
size: 0, |
|
progress: 0, |
|
transferMethod: TransferMethod.local_file, |
|
supportFileType: '', |
|
url, |
|
isRemote: true, |
|
} |
|
handleAddFile(uploadingFile) |
|
startProgressTimer(uploadingFile.id) |
|
|
|
uploadRemoteFileInfo(url, !!params.token).then((res) => { |
|
const newFile = { |
|
...uploadingFile, |
|
type: res.mime_type, |
|
size: res.size, |
|
progress: 100, |
|
supportFileType: getSupportFileType(res.name, res.mime_type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)), |
|
uploadedId: res.id, |
|
url: res.url, |
|
} |
|
if (!isAllowedFileExtension(res.name, res.mime_type, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) { |
|
notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') }) |
|
handleRemoveFile(uploadingFile.id) |
|
} |
|
if (!checkSizeLimit(newFile.supportFileType, newFile.size)) |
|
handleRemoveFile(uploadingFile.id) |
|
else |
|
handleUpdateFile(newFile) |
|
}).catch(() => { |
|
notify({ type: 'error', message: t('common.fileUploader.pasteFileLinkInvalid') }) |
|
handleRemoveFile(uploadingFile.id) |
|
}) |
|
}, [checkSizeLimit, handleAddFile, handleUpdateFile, notify, t, handleRemoveFile, fileConfig?.allowed_file_types, fileConfig.allowed_file_extensions, startProgressTimer]) |
|
|
|
const handleLoadFileFromLinkSuccess = useCallback(() => { }, []) |
|
|
|
const handleLoadFileFromLinkError = useCallback(() => { }, []) |
|
|
|
const handleClearFiles = useCallback(() => { |
|
const { |
|
setFiles, |
|
} = fileStore.getState() |
|
setFiles([]) |
|
}, [fileStore]) |
|
|
|
const handleLocalFileUpload = useCallback((file: File) => { |
|
if (!isAllowedFileExtension(file.name, file.type, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) { |
|
notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') }) |
|
return |
|
} |
|
const allowedFileTypes = fileConfig.allowed_file_types |
|
const fileType = getSupportFileType(file.name, file.type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)) |
|
if (!checkSizeLimit(fileType, file.size)) |
|
return |
|
|
|
const reader = new FileReader() |
|
const isImage = file.type.startsWith('image') |
|
|
|
reader.addEventListener( |
|
'load', |
|
() => { |
|
const uploadingFile = { |
|
id: uuid4(), |
|
name: file.name, |
|
type: file.type, |
|
size: file.size, |
|
progress: 0, |
|
transferMethod: TransferMethod.local_file, |
|
supportFileType: getSupportFileType(file.name, file.type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)), |
|
originalFile: file, |
|
base64Url: isImage ? reader.result as string : '', |
|
} |
|
handleAddFile(uploadingFile) |
|
fileUpload({ |
|
file: uploadingFile.originalFile, |
|
onProgressCallback: (progress) => { |
|
handleUpdateFile({ ...uploadingFile, progress }) |
|
}, |
|
onSuccessCallback: (res) => { |
|
handleUpdateFile({ ...uploadingFile, uploadedId: res.id, progress: 100 }) |
|
}, |
|
onErrorCallback: () => { |
|
notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerUploadError') }) |
|
handleUpdateFile({ ...uploadingFile, progress: -1 }) |
|
}, |
|
}, !!params.token) |
|
}, |
|
false, |
|
) |
|
reader.addEventListener( |
|
'error', |
|
() => { |
|
notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerReadError') }) |
|
}, |
|
false, |
|
) |
|
reader.readAsDataURL(file) |
|
}, [checkSizeLimit, notify, t, handleAddFile, handleUpdateFile, params.token, fileConfig?.allowed_file_types, fileConfig?.allowed_file_extensions]) |
|
|
|
const handleClipboardPasteFile = useCallback((e: ClipboardEvent<HTMLTextAreaElement>) => { |
|
const file = e.clipboardData?.files[0] |
|
if (file) { |
|
e.preventDefault() |
|
handleLocalFileUpload(file) |
|
} |
|
}, [handleLocalFileUpload]) |
|
|
|
const [isDragActive, setIsDragActive] = useState(false) |
|
const handleDragFileEnter = useCallback((e: React.DragEvent<HTMLElement>) => { |
|
e.preventDefault() |
|
e.stopPropagation() |
|
setIsDragActive(true) |
|
}, []) |
|
|
|
const handleDragFileOver = useCallback((e: React.DragEvent<HTMLElement>) => { |
|
e.preventDefault() |
|
e.stopPropagation() |
|
}, []) |
|
|
|
const handleDragFileLeave = useCallback((e: React.DragEvent<HTMLElement>) => { |
|
e.preventDefault() |
|
e.stopPropagation() |
|
setIsDragActive(false) |
|
}, []) |
|
|
|
const handleDropFile = useCallback((e: React.DragEvent<HTMLElement>) => { |
|
e.preventDefault() |
|
e.stopPropagation() |
|
setIsDragActive(false) |
|
|
|
const file = e.dataTransfer.files[0] |
|
|
|
if (file) |
|
handleLocalFileUpload(file) |
|
}, [handleLocalFileUpload]) |
|
|
|
return { |
|
handleAddFile, |
|
handleUpdateFile, |
|
handleRemoveFile, |
|
handleReUploadFile, |
|
handleLoadFileFromLink, |
|
handleLoadFileFromLinkSuccess, |
|
handleLoadFileFromLinkError, |
|
handleClearFiles, |
|
handleLocalFileUpload, |
|
handleClipboardPasteFile, |
|
isDragActive, |
|
handleDragFileEnter, |
|
handleDragFileOver, |
|
handleDragFileLeave, |
|
handleDropFile, |
|
} |
|
} |
|
|