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 (
)
}
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)