import { useState, useEffect, useContext, useRef, memo } from 'react' import { Flex, Card } from 'antd' import { FileOutlined } from '@ant-design/icons' import { decode } from 'blurhash' import PropTypes from 'prop-types' import { ApiServerContext } from '../context/ApiServerContext' const getFileId = (file) => file?._id || (typeof file === 'string' ? file : null) const getBlurHash = (file) => file?.metaData?.blurHash || null const Thumbnail = function Thumbnail({ file, size = 64, alt = '', fallback = null, style = {}, className }) { const { fetchFileThumbnail } = useContext(ApiServerContext) const fetchFileThumbnailRef = useRef(fetchFileThumbnail) fetchFileThumbnailRef.current = fetchFileThumbnail const fileId = getFileId(file) const blurHash = getBlurHash(file) const [thumbnailUrl, setThumbnailUrl] = useState(null) const [blurHashUrl, setBlurHashUrl] = useState(null) const thumbnailUrlRef = useRef(null) const loadedKeyRef = useRef(null) const imageStyle = { width: size, height: size, borderRadius: '5px', objectFit: 'cover', ...style } const cardStyle = { borderRadius: '5px', width: size, height: size, ...style } useEffect(() => { if (!blurHash) { setBlurHashUrl(null) return } try { const pixels = decode(blurHash, 32, 32) const canvas = document.createElement('canvas') canvas.width = 32 canvas.height = 32 const ctx = canvas.getContext('2d') const imageData = ctx.createImageData(32, 32) imageData.data.set(pixels) ctx.putImageData(imageData, 0, 0) setBlurHashUrl(canvas.toDataURL()) } catch { setBlurHashUrl(null) } }, [blurHash]) useEffect(() => { if (!fileId) { if (thumbnailUrlRef.current) { URL.revokeObjectURL(thumbnailUrlRef.current) thumbnailUrlRef.current = null } loadedKeyRef.current = null setThumbnailUrl(null) return } const loadKey = `${fileId}-${size}` if (loadedKeyRef.current === loadKey && thumbnailUrlRef.current) { return } let cancelled = false const fileObject = typeof file === 'object' && file !== null ? file : { _id: fileId } const loadThumbnail = async () => { try { const fileURL = await fetchFileThumbnailRef.current(fileObject, size) if (!cancelled) { if (thumbnailUrlRef.current) { URL.revokeObjectURL(thumbnailUrlRef.current) } thumbnailUrlRef.current = fileURL || null loadedKeyRef.current = loadKey setThumbnailUrl(fileURL || null) } else if (fileURL) { URL.revokeObjectURL(fileURL) } } catch { if (!cancelled) { setThumbnailUrl(null) } } } loadThumbnail() return () => { cancelled = true } }, [file, fileId, size]) useEffect(() => { return () => { if (thumbnailUrlRef.current) { URL.revokeObjectURL(thumbnailUrlRef.current) thumbnailUrlRef.current = null } loadedKeyRef.current = null } }, []) if (thumbnailUrl) { return ( {alt} ) } if (fileId && blurHashUrl) { return ( ) } return ( {fallback || ( )} ) } Thumbnail.propTypes = { file: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), size: PropTypes.oneOf([64, 128, 256]), alt: PropTypes.string, fallback: PropTypes.node, style: PropTypes.object, className: PropTypes.string } const areEqual = (prevProps, nextProps) => { return ( getFileId(prevProps.file) === getFileId(nextProps.file) && getBlurHash(prevProps.file) === getBlurHash(nextProps.file) && prevProps.size === nextProps.size && prevProps.alt === nextProps.alt && JSON.stringify(prevProps.style) === JSON.stringify(nextProps.style) ) } export default memo(Thumbnail, areEqual)