Files
dashfront/src/components/article/ArticlePhoto.jsx
2026-10-05 01:01:03 +02:00

115 lines
3.1 KiB
JavaScript

import { Component } from 'react';
import Box from '@mui/material/Box';
import { thumb } from '../../api';
import ThumbHashImage from './ThumbHashImage';
import {
SCRIM_SOFT,
} from '../../colors';
class ArticlePhoto extends Component {
constructor(props) {
super(props);
this.state = { thumbLoaded: false };
this.handleThumbLoad = this.handleThumbLoad.bind(this);
this.handleThumbError = this.handleThumbError.bind(this);
}
componentDidUpdate(prevProps) {
if (prevProps.photo?.fullHash !== this.props.photo?.fullHash) {
this.setState({ thumbLoaded: false });
}
}
handleThumbLoad() {
this.setState({ thumbLoaded: true });
}
handleThumbError() {
this.setState({ thumbLoaded: false });
}
render() {
const {
photo,
size = 200,
fill = false,
objectFit = fill ? 'cover' : 'contain',
sx,
} = this.props;
const { thumbLoaded } = this.state;
const fullHash = photo?.fullHash;
const hasPreview = photo?.thumbHash != null;
const aspectRatio =
photo?.thumbW != null && photo?.thumbH != null
? `${photo.thumbW} / ${photo.thumbH}`
: '1 / 1';
return (
<Box
sx={{
position: 'relative',
...(fill
? { width: '100%', height: '100%' }
: { width: size, height: size, maxWidth: '100%', flexShrink: 0 }),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: fill ? 0 : 2,
overflow: 'hidden',
bgcolor: SCRIM_SOFT,
...sx,
}}
>
{hasPreview && !thumbLoaded ? (
<Box
sx={
fill || objectFit === 'cover'
? { position: 'absolute', inset: 0 }
: {
position: 'absolute',
maxWidth: '100%',
maxHeight: '100%',
aspectRatio,
width: photo.thumbW >= photo.thumbH ? '100%' : 'auto',
height: photo.thumbH > photo.thumbW ? '100%' : 'auto',
}
}
>
<ThumbHashImage thumbHash={photo.thumbHash} />
</Box>
) : null}
{fullHash ? (
<Box
component="img"
src={thumb.url(fullHash)}
alt=""
onLoad={this.handleThumbLoad}
onError={this.handleThumbError}
sx={
fill || objectFit === 'cover'
? {
display: 'block',
width: '100%',
height: '100%',
objectFit: 'cover',
opacity: thumbLoaded ? 1 : 0,
}
: {
display: 'block',
maxWidth: '100%',
maxHeight: '100%',
width: 'auto',
height: 'auto',
objectFit: 'contain',
opacity: thumbLoaded ? 1 : 0,
}
}
/>
) : null}
</Box>
);
}
}
export default ArticlePhoto;