feat(ui): add smooth image transitions to album and artist artwork (#4120)

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan Quintão
2025-05-26 08:57:37 -04:00
committed by GitHub
parent 5c4fbdb7c1
commit d26e2e29a6
6 changed files with 174 additions and 33 deletions
+45 -4
View File
@@ -38,11 +38,22 @@ const useStyles = makeStyles(
height: '12rem',
borderRadius: '6em',
cursor: 'pointer',
backgroundColor: 'transparent',
transition: 'opacity 0.3s ease-in-out',
objectFit: 'cover',
},
coverLoading: {
opacity: 0.5,
},
artistImage: {
maxHeight: '12rem',
minHeight: '12rem',
width: '12rem',
minWidth: '12rem',
backgroundColor: 'inherit',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: 'none',
},
artistDetail: {
@@ -73,8 +84,31 @@ const DesktopArtistDetails = ({ artistInfo, record, biography }) => {
const classes = useStyles()
const title = record.name
const [isLightboxOpen, setLightboxOpen] = React.useState(false)
const [imageLoading, setImageLoading] = React.useState(false)
const [imageError, setImageError] = React.useState(false)
// Reset image state when artist changes
React.useEffect(() => {
setImageLoading(true)
setImageError(false)
}, [record.id])
const handleImageLoad = React.useCallback(() => {
setImageLoading(false)
setImageError(false)
}, [])
const handleImageError = React.useCallback(() => {
setImageLoading(false)
setImageError(true)
}, [])
const handleOpenLightbox = React.useCallback(() => {
if (!imageError) {
setLightboxOpen(true)
}
}, [imageError])
const handleOpenLightbox = React.useCallback(() => setLightboxOpen(true), [])
const handleCloseLightbox = React.useCallback(
() => setLightboxOpen(false),
[],
@@ -86,10 +120,17 @@ const DesktopArtistDetails = ({ artistInfo, record, biography }) => {
<Card className={classes.artistImage}>
{artistInfo && (
<CardMedia
className={classes.cover}
image={subsonic.getCoverArtUrl(record, 300)}
key={record.id}
component="img"
src={subsonic.getCoverArtUrl(record, 300)}
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
onClick={handleOpenLightbox}
onLoad={handleImageLoad}
onError={handleImageError}
title={title}
style={{
cursor: imageError ? 'default' : 'pointer',
}}
/>
)}
</Card>
@@ -140,7 +181,7 @@ const DesktopArtistDetails = ({ artistInfo, record, biography }) => {
)}
</Typography>
</div>
{isLightboxOpen && (
{isLightboxOpen && !imageError && (
<Lightbox
imagePadding={50}
animationDuration={200}
+40 -4
View File
@@ -50,6 +50,12 @@ const useStyles = makeStyles(
width: 151,
boxShadow: '0px 0px 6px 0px #565656',
borderRadius: '5px',
backgroundColor: 'transparent',
transition: 'opacity 0.3s ease-in-out',
objectFit: 'cover',
},
coverLoading: {
opacity: 0.5,
},
artistImage: {
marginLeft: '1em',
@@ -81,8 +87,31 @@ const MobileArtistDetails = ({ artistInfo, biography, record }) => {
const classes = useStyles({ img, expanded })
const title = record.name
const [isLightboxOpen, setLightboxOpen] = React.useState(false)
const [imageLoading, setImageLoading] = React.useState(false)
const [imageError, setImageError] = React.useState(false)
// Reset image state when artist changes
React.useEffect(() => {
setImageLoading(true)
setImageError(false)
}, [record.id])
const handleImageLoad = React.useCallback(() => {
setImageLoading(false)
setImageError(false)
}, [])
const handleImageError = React.useCallback(() => {
setImageLoading(false)
setImageError(true)
}, [])
const handleOpenLightbox = React.useCallback(() => {
if (!imageError) {
setLightboxOpen(true)
}
}, [imageError])
const handleOpenLightbox = React.useCallback(() => setLightboxOpen(true), [])
const handleCloseLightbox = React.useCallback(
() => setLightboxOpen(false),
[],
@@ -95,10 +124,17 @@ const MobileArtistDetails = ({ artistInfo, biography, record }) => {
<Card className={classes.artistImage}>
{artistInfo && (
<CardMedia
className={classes.cover}
image={subsonic.getCoverArtUrl(record, 300)}
key={record.id}
component="img"
src={subsonic.getCoverArtUrl(record, 300)}
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
onClick={handleOpenLightbox}
onLoad={handleImageLoad}
onError={handleImageError}
title={title}
style={{
cursor: imageError ? 'default' : 'pointer',
}}
/>
)}
</Card>
@@ -136,7 +172,7 @@ const MobileArtistDetails = ({ artistInfo, biography, record }) => {
</Typography>
</Collapse>
</div>
{isLightboxOpen && (
{isLightboxOpen && !imageError && (
<Lightbox
imagePadding={50}
animationDuration={200}