image-list.tsx 4.8 KB

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