file-item.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {
  2. RiCloseLine,
  3. RiDownloadLine,
  4. } from '@remixicon/react'
  5. import { useState } from 'react'
  6. import {
  7. downloadFile,
  8. fileIsUploaded,
  9. getFileAppearanceType,
  10. getFileExtension,
  11. } from '../utils'
  12. import FileTypeIcon from '../file-type-icon'
  13. import type { FileEntity } from '../types'
  14. import cn from '@/utils/classnames'
  15. import { formatFileSize } from '@/utils/format'
  16. import ProgressCircle from '@/app/components/base/progress-bar/progress-circle'
  17. import { ReplayLine } from '@/app/components/base/icons/src/vender/other'
  18. import ActionButton from '@/app/components/base/action-button'
  19. import Button from '@/app/components/base/button'
  20. import PdfPreview from '@/app/components/base/file-uploader/dynamic-pdf-preview'
  21. import AudioPreview from '@/app/components/base/file-uploader/audio-preview'
  22. import VideoPreview from '@/app/components/base/file-uploader/video-preview'
  23. type FileItemProps = {
  24. file: FileEntity
  25. showDeleteAction?: boolean
  26. showDownloadAction?: boolean
  27. canPreview?: boolean
  28. onRemove?: (fileId: string) => void
  29. onReUpload?: (fileId: string) => void
  30. }
  31. const FileItem = ({
  32. file,
  33. showDeleteAction,
  34. showDownloadAction = true,
  35. onRemove,
  36. onReUpload,
  37. canPreview,
  38. }: FileItemProps) => {
  39. const { id, name, type, progress, url, base64Url, isRemote } = file
  40. const [previewUrl, setPreviewUrl] = useState('')
  41. const ext = getFileExtension(name, type, isRemote)
  42. const uploadError = progress === -1
  43. let tmp_preview_url = url || base64Url
  44. if (!tmp_preview_url && file?.originalFile)
  45. tmp_preview_url = URL.createObjectURL(file.originalFile.slice()).toString()
  46. return (
  47. <>
  48. <div
  49. className={cn(
  50. 'group/file-item relative p-2 w-[144px] h-[68px] rounded-lg border-[0.5px] border-components-panel-border bg-components-card-bg shadow-xs',
  51. !uploadError && 'hover:bg-components-card-bg-alt',
  52. uploadError && 'border border-state-destructive-border bg-state-destructive-hover',
  53. uploadError && 'hover:border-[0.5px] hover:border-state-destructive-border bg-state-destructive-hover-alt',
  54. )}
  55. >
  56. {
  57. showDeleteAction && (
  58. <Button
  59. className='hidden group-hover/file-item:flex absolute -right-1.5 -top-1.5 p-0 w-5 h-5 rounded-full z-[11]'
  60. onClick={() => onRemove?.(id)}
  61. >
  62. <RiCloseLine className='w-4 h-4 text-components-button-secondary-text' />
  63. </Button>
  64. )
  65. }
  66. <div
  67. className='mb-1 h-8 line-clamp-2 system-xs-medium text-text-tertiary break-all cursor-pointer'
  68. title={name}
  69. onClick={() => canPreview && setPreviewUrl(tmp_preview_url || '')}
  70. >
  71. {name}
  72. </div>
  73. <div className='relative flex items-center justify-between'>
  74. <div className='flex items-center system-2xs-medium-uppercase text-text-tertiary'>
  75. <FileTypeIcon
  76. size='sm'
  77. type={getFileAppearanceType(name, type)}
  78. className='mr-1'
  79. />
  80. {
  81. ext && (
  82. <>
  83. {ext}
  84. <div className='mx-1'>·</div>
  85. </>
  86. )
  87. }
  88. {
  89. !!file.size && formatFileSize(file.size)
  90. }
  91. </div>
  92. {
  93. showDownloadAction && tmp_preview_url && (
  94. <ActionButton
  95. size='m'
  96. className='hidden group-hover/file-item:flex absolute -right-1 -top-1'
  97. onClick={(e) => {
  98. e.stopPropagation()
  99. downloadFile(tmp_preview_url || '', name)
  100. }}
  101. >
  102. <RiDownloadLine className='w-3.5 h-3.5 text-text-tertiary' />
  103. </ActionButton>
  104. )
  105. }
  106. {
  107. progress >= 0 && !fileIsUploaded(file) && (
  108. <ProgressCircle
  109. percentage={progress}
  110. size={12}
  111. className='shrink-0'
  112. />
  113. )
  114. }
  115. {
  116. uploadError && (
  117. <ReplayLine
  118. className='w-4 h-4 text-text-tertiary'
  119. onClick={() => onReUpload?.(id)}
  120. />
  121. )
  122. }
  123. </div>
  124. </div>
  125. {
  126. type.split('/')[0] === 'audio' && canPreview && previewUrl && (
  127. <AudioPreview
  128. title={name}
  129. url={previewUrl}
  130. onCancel={() => setPreviewUrl('')}
  131. />
  132. )
  133. }
  134. {
  135. type.split('/')[0] === 'video' && canPreview && previewUrl && (
  136. <VideoPreview
  137. title={name}
  138. url={previewUrl}
  139. onCancel={() => setPreviewUrl('')}
  140. />
  141. )
  142. }
  143. {
  144. type.split('/')[1] === 'pdf' && canPreview && previewUrl && (
  145. <PdfPreview url={previewUrl} onCancel={() => { setPreviewUrl('') }} />
  146. )
  147. }
  148. </>
  149. )
  150. }
  151. export default FileItem