import { useEffect, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
import { makeStyles } from '@material-ui/core/styles'
import config from '../config'
import subsonic from '../subsonic'
import { useImageUrl } from './useImageUrl'
import { ThumbHashCanvas } from './ThumbHashCanvas'
const fadeMs = 150
const useStyles = makeStyles({
root: {
position: 'relative',
display: 'inline-flex',
overflow: 'hidden',
},
fill: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
},
img: {
opacity: 0,
transition: `opacity ${fadeMs}ms ease-out`,
'@media (prefers-reduced-motion: reduce)': { transition: 'none' },
},
imgVisible: { opacity: 1 },
imgInstant: { opacity: 1, transition: 'none' },
})
export const Artwork = ({
record,
size = config.uiCoverArtSize,
square = false,
fit = 'cover',
className,
title,
onClick,
}) => {
const classes = useStyles()
const url = record ? subsonic.getCoverArtUrl(record, size, square) : ''
const { imgUrl, fromCache } = useImageUrl(url)
const [decoded, setDecoded] = useState(false)
const [faded, setFaded] = useState(false)
const frame = useRef(0)
useEffect(() => {
setDecoded(false)
setFaded(false)
return () => cancelAnimationFrame(frame.current)
}, [url])
const handleLoad = () => {
frame.current = requestAnimationFrame(() => {
frame.current = requestAnimationFrame(() => setDecoded(true))
})
}
useEffect(() => {
if (!decoded || faded) return undefined
const timer = setTimeout(() => setFaded(true), fadeMs)
return () => clearTimeout(timer)
}, [decoded, faded])
if (!record) return null
const instant = fromCache
const showPlaceholder = !!record.thumbHash && !instant && !faded
const effectiveFit = square ? 'contain' : fit
const ratio = record.imageWidth / record.imageHeight
const handleClick = imgUrl && onClick ? onClick : undefined
return (
<div
className={clsx(classes.root, className)}
onClick={handleClick}
style={{ cursor: handleClick ? 'pointer' : 'default' }}
>
{showPlaceholder && (
<ThumbHashCanvas
hash={record.thumbHash}
ratio={ratio}
fit={effectiveFit}
className={classes.fill}
/>
)}
{imgUrl && (
<img
src={imgUrl}
alt={title}
title={title}
className={clsx(
classes.fill,
classes.img,
instant && classes.imgInstant,
decoded && classes.imgVisible,
)}
style={{ objectFit: effectiveFit }}
onLoad={handleLoad}
/>
)}
</div>
)
}
Artwork.propTypes = {
record: PropTypes.object,
size: PropTypes.number,
square: PropTypes.bool,
fit: PropTypes.oneOf(['cover', 'contain']),
className: PropTypes.string,
title: PropTypes.string,
onClick: PropTypes.func,
}