image-list.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import type { FC } from 'react'
  2. import { useState } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import { Loading02, XClose } from '@/app/components/base/icons/src/vender/line/general'
  5. import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
  6. import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
  7. import TooltipPlus from '@/app/components/base/tooltip-plus'
  8. import type { ImageFile } from '@/types/app'
  9. import { TransferMethod } from '@/types/app'
  10. import ImagePreview from '@/app/components/base/image-uploader/image-preview'
  11. type ImageListProps = {
  12. list: ImageFile[]
  13. readonly?: boolean
  14. onRemove?: (imageFileId: string) => void
  15. onReUpload?: (imageFileId: string) => void
  16. onImageLinkLoadSuccess?: (imageFileId: string) => void
  17. onImageLinkLoadError?: (imageFileId: string) => void
  18. }
  19. const ImageList: FC<ImageListProps> = ({
  20. list,
  21. readonly,
  22. onRemove,
  23. onReUpload,
  24. onImageLinkLoadSuccess,
  25. onImageLinkLoadError,
  26. }) => {
  27. const { t } = useTranslation()
  28. const [imagePreviewUrl, setImagePreviewUrl] = useState('')
  29. const handleImageLinkLoadSuccess = (item: ImageFile) => {
  30. if (item.type === TransferMethod.remote_url && onImageLinkLoadSuccess && item.progress !== -1)
  31. onImageLinkLoadSuccess(item._id)
  32. }
  33. const handleImageLinkLoadError = (item: ImageFile) => {
  34. if (item.type === TransferMethod.remote_url && onImageLinkLoadError)
  35. onImageLinkLoadError(item._id)
  36. }
  37. return (
  38. <div className='flex flex-wrap'>
  39. {
  40. list.map(item => (
  41. <div
  42. key={item._id}
  43. className='group relative mr-1 border-[0.5px] border-black/5 rounded-lg'
  44. >
  45. {
  46. item.type === TransferMethod.local_file && item.progress !== 100 && (
  47. <>
  48. <div
  49. className='absolute inset-0 flex items-center justify-center z-[1] bg-black/30'
  50. style={{ left: item.progress > -1 ? `${item.progress}%` : 0 }}
  51. >
  52. {
  53. item.progress === -1 && (
  54. <RefreshCcw01 className='w-5 h-5 text-white' onClick={() => onReUpload && onReUpload(item._id)} />
  55. )
  56. }
  57. </div>
  58. {
  59. item.progress > -1 && (
  60. <span className='absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] text-sm text-white mix-blend-lighten z-[1]'>{item.progress}%</span>
  61. )
  62. }
  63. </>
  64. )
  65. }
  66. {
  67. item.type === TransferMethod.remote_url && item.progress !== 100 && (
  68. <div className={`
  69. absolute inset-0 flex items-center justify-center rounded-lg z-[1] border
  70. ${item.progress === -1 ? 'bg-[#FEF0C7] border-[#DC6803]' : 'bg-black/[0.16] border-transparent'}
  71. `}>
  72. {
  73. item.progress > -1 && (
  74. <Loading02 className='animate-spin w-5 h-5 text-white' />
  75. )
  76. }
  77. {
  78. item.progress === -1 && (
  79. <TooltipPlus popupContent={t('common.imageUploader.pasteImageLinkInvalid')}>
  80. <AlertTriangle className='w-4 h-4 text-[#DC6803]' />
  81. </TooltipPlus>
  82. )
  83. }
  84. </div>
  85. )
  86. }
  87. <img
  88. className='w-16 h-16 rounded-lg object-cover cursor-pointer border-[0.5px] border-black/5'
  89. alt=''
  90. onLoad={() => handleImageLinkLoadSuccess(item)}
  91. onError={() => handleImageLinkLoadError(item)}
  92. src={item.type === TransferMethod.remote_url ? item.url : item.base64Url}
  93. onClick={() => item.progress === 100 && setImagePreviewUrl((item.type === TransferMethod.remote_url ? item.url : item.base64Url) as string)}
  94. />
  95. {
  96. !readonly && (
  97. <div
  98. className={`
  99. absolute z-10 -top-[9px] -right-[9px] items-center justify-center w-[18px] h-[18px]
  100. bg-white hover:bg-gray-50 border-[0.5px] border-black/[0.02] rounded-2xl shadow-lg
  101. cursor-pointer
  102. ${item.progress === -1 ? 'flex' : 'hidden group-hover:flex'}
  103. `}
  104. onClick={() => onRemove && onRemove(item._id)}
  105. >
  106. <XClose className='w-3 h-3 text-gray-500' />
  107. </div>
  108. )
  109. }
  110. </div>
  111. ))
  112. }
  113. {
  114. imagePreviewUrl && (
  115. <ImagePreview
  116. url={imagePreviewUrl}
  117. onCancel={() => setImagePreviewUrl('')}
  118. />
  119. )
  120. }
  121. </div>
  122. )
  123. }
  124. export default ImageList