hooks.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import type { ClipboardEvent } from 'react'
  2. import {
  3. useCallback,
  4. useState,
  5. } from 'react'
  6. import { useParams } from 'next/navigation'
  7. import produce from 'immer'
  8. import { v4 as uuid4 } from 'uuid'
  9. import { useTranslation } from 'react-i18next'
  10. import type { FileEntity } from './types'
  11. import { useFileStore } from './store'
  12. import {
  13. fileUpload,
  14. getSupportFileType,
  15. isAllowedFileExtension,
  16. } from './utils'
  17. import { FILE_SIZE_LIMIT } from './constants'
  18. import { useToastContext } from '@/app/components/base/toast'
  19. import { TransferMethod } from '@/types/app'
  20. import { SupportUploadFileTypes } from '@/app/components/workflow/types'
  21. import type { FileUpload } from '@/app/components/base/features/types'
  22. import { formatFileSize } from '@/utils/format'
  23. import { fetchRemoteFileInfo } from '@/service/common'
  24. export const useFile = (fileConfig: FileUpload) => {
  25. const { t } = useTranslation()
  26. const { notify } = useToastContext()
  27. const fileStore = useFileStore()
  28. const params = useParams()
  29. const handleAddFile = useCallback((newFile: FileEntity) => {
  30. const {
  31. files,
  32. setFiles,
  33. } = fileStore.getState()
  34. const newFiles = produce(files, (draft) => {
  35. draft.push(newFile)
  36. })
  37. setFiles(newFiles)
  38. }, [fileStore])
  39. const handleUpdateFile = useCallback((newFile: FileEntity) => {
  40. const {
  41. files,
  42. setFiles,
  43. } = fileStore.getState()
  44. const newFiles = produce(files, (draft) => {
  45. const index = draft.findIndex(file => file.id === newFile.id)
  46. if (index > -1)
  47. draft[index] = newFile
  48. })
  49. setFiles(newFiles)
  50. }, [fileStore])
  51. const handleRemoveFile = useCallback((fileId: string) => {
  52. const {
  53. files,
  54. setFiles,
  55. } = fileStore.getState()
  56. const newFiles = files.filter(file => file.id !== fileId)
  57. setFiles(newFiles)
  58. }, [fileStore])
  59. const handleReUploadFile = useCallback((fileId: string) => {
  60. const {
  61. files,
  62. setFiles,
  63. } = fileStore.getState()
  64. const index = files.findIndex(file => file.id === fileId)
  65. if (index > -1) {
  66. const uploadingFile = files[index]
  67. const newFiles = produce(files, (draft) => {
  68. draft[index].progress = 0
  69. })
  70. setFiles(newFiles)
  71. fileUpload({
  72. file: uploadingFile.originalFile!,
  73. onProgressCallback: (progress) => {
  74. handleUpdateFile({ ...uploadingFile, progress })
  75. },
  76. onSuccessCallback: (res) => {
  77. handleUpdateFile({ ...uploadingFile, uploadedId: res.id, progress: 100 })
  78. },
  79. onErrorCallback: () => {
  80. notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerUploadError') })
  81. handleUpdateFile({ ...uploadingFile, progress: -1 })
  82. },
  83. }, !!params.token)
  84. }
  85. }, [fileStore, notify, t, handleUpdateFile, params])
  86. const handleLoadFileFromLink = useCallback((url: string) => {
  87. const allowedFileTypes = fileConfig.allowed_file_types
  88. const uploadingFile = {
  89. id: uuid4(),
  90. name: url,
  91. type: '',
  92. size: 0,
  93. progress: 0,
  94. transferMethod: TransferMethod.remote_url,
  95. supportFileType: '',
  96. url,
  97. }
  98. handleAddFile(uploadingFile)
  99. fetchRemoteFileInfo(url).then((res) => {
  100. const newFile = {
  101. ...uploadingFile,
  102. type: res.file_type,
  103. size: res.file_length,
  104. progress: 100,
  105. supportFileType: getSupportFileType(url, res.file_type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)),
  106. }
  107. handleUpdateFile(newFile)
  108. }).catch(() => {
  109. notify({ type: 'error', message: t('common.fileUploader.pasteFileLinkInvalid') })
  110. handleRemoveFile(uploadingFile.id)
  111. })
  112. }, [handleAddFile, handleUpdateFile, notify, t, handleRemoveFile, fileConfig?.allowed_file_types])
  113. const handleLoadFileFromLinkSuccess = useCallback(() => { }, [])
  114. const handleLoadFileFromLinkError = useCallback(() => { }, [])
  115. const handleClearFiles = useCallback(() => {
  116. const {
  117. setFiles,
  118. } = fileStore.getState()
  119. setFiles([])
  120. }, [fileStore])
  121. const handleLocalFileUpload = useCallback((file: File) => {
  122. if (!isAllowedFileExtension(file.name, file.type, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) {
  123. notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') })
  124. return
  125. }
  126. if (file.size > FILE_SIZE_LIMIT) {
  127. notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerLimit', { size: formatFileSize(FILE_SIZE_LIMIT) }) })
  128. return
  129. }
  130. const reader = new FileReader()
  131. const isImage = file.type.startsWith('image')
  132. const allowedFileTypes = fileConfig.allowed_file_types
  133. reader.addEventListener(
  134. 'load',
  135. () => {
  136. const uploadingFile = {
  137. id: uuid4(),
  138. name: file.name,
  139. type: file.type,
  140. size: file.size,
  141. progress: 0,
  142. transferMethod: TransferMethod.local_file,
  143. supportFileType: getSupportFileType(file.name, file.type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)),
  144. originalFile: file,
  145. base64Url: isImage ? reader.result as string : '',
  146. }
  147. handleAddFile(uploadingFile)
  148. fileUpload({
  149. file: uploadingFile.originalFile,
  150. onProgressCallback: (progress) => {
  151. handleUpdateFile({ ...uploadingFile, progress })
  152. },
  153. onSuccessCallback: (res) => {
  154. handleUpdateFile({ ...uploadingFile, uploadedId: res.id, progress: 100 })
  155. },
  156. onErrorCallback: () => {
  157. notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerUploadError') })
  158. handleUpdateFile({ ...uploadingFile, progress: -1 })
  159. },
  160. }, !!params.token)
  161. },
  162. false,
  163. )
  164. reader.addEventListener(
  165. 'error',
  166. () => {
  167. notify({ type: 'error', message: t('common.fileUploader.uploadFromComputerReadError') })
  168. },
  169. false,
  170. )
  171. reader.readAsDataURL(file)
  172. }, [notify, t, handleAddFile, handleUpdateFile, params.token, fileConfig?.allowed_file_types, fileConfig?.allowed_file_extensions])
  173. const handleClipboardPasteFile = useCallback((e: ClipboardEvent<HTMLTextAreaElement>) => {
  174. const file = e.clipboardData?.files[0]
  175. if (file) {
  176. e.preventDefault()
  177. handleLocalFileUpload(file)
  178. }
  179. }, [handleLocalFileUpload])
  180. const [isDragActive, setIsDragActive] = useState(false)
  181. const handleDragFileEnter = useCallback((e: React.DragEvent<HTMLElement>) => {
  182. e.preventDefault()
  183. e.stopPropagation()
  184. setIsDragActive(true)
  185. }, [])
  186. const handleDragFileOver = useCallback((e: React.DragEvent<HTMLElement>) => {
  187. e.preventDefault()
  188. e.stopPropagation()
  189. }, [])
  190. const handleDragFileLeave = useCallback((e: React.DragEvent<HTMLElement>) => {
  191. e.preventDefault()
  192. e.stopPropagation()
  193. setIsDragActive(false)
  194. }, [])
  195. const handleDropFile = useCallback((e: React.DragEvent<HTMLElement>) => {
  196. e.preventDefault()
  197. e.stopPropagation()
  198. setIsDragActive(false)
  199. const file = e.dataTransfer.files[0]
  200. if (file)
  201. handleLocalFileUpload(file)
  202. }, [handleLocalFileUpload])
  203. return {
  204. handleAddFile,
  205. handleUpdateFile,
  206. handleRemoveFile,
  207. handleReUploadFile,
  208. handleLoadFileFromLink,
  209. handleLoadFileFromLinkSuccess,
  210. handleLoadFileFromLinkError,
  211. handleClearFiles,
  212. handleLocalFileUpload,
  213. handleClipboardPasteFile,
  214. isDragActive,
  215. handleDragFileEnter,
  216. handleDragFileOver,
  217. handleDragFileLeave,
  218. handleDropFile,
  219. }
  220. }