Files
suwayomi-material-you-webui/src/components/util/SpinnerImage.tsx

102 lines
3.6 KiB
TypeScript
Raw Normal View History

2021-05-28 19:37:26 +04:30
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
2021-05-28 19:37:26 +04:30
import React, { useState, CSSProperties, useEffect } from 'react';
2021-09-09 17:51:22 +04:30
import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
import { Theme, SxProps, Stack, Button } from '@mui/material';
import BrokenImageIcon from '@mui/icons-material/BrokenImage';
import RefreshIcon from '@mui/icons-material/Refresh';
import { useTranslation } from 'react-i18next';
2021-05-28 19:37:26 +04:30
interface IProps {
src: string;
alt: string;
2021-05-28 19:37:26 +04:30
imgRef?: React.RefObject<HTMLImageElement>;
2021-05-28 19:37:26 +04:30
spinnerStyle?: SxProps<Theme>;
imgStyle?: CSSProperties;
2021-05-28 19:37:26 +04:30
onImageLoad?: () => void;
2021-05-28 19:37:26 +04:30
}
2023-10-28 00:32:02 +02:00
export function SpinnerImage(props: IProps) {
const { src, alt, onImageLoad, imgRef, spinnerStyle, imgStyle } = props;
2021-05-28 19:37:26 +04:30
const { t } = useTranslation();
const [imgLoadRetryKey, setImgLoadRetryKey] = useState(0);
const [isLoading, setIsLoading] = useState<boolean | undefined>(undefined);
const [hasError, setHasError] = useState(false);
2021-05-28 19:37:26 +04:30
const updateImageState = (loading: boolean, error: boolean = false) => {
setIsLoading(loading);
setHasError(error);
if (!loading && !error) {
onImageLoad?.();
}
};
2021-05-28 19:37:26 +04:30
useEffect(() => {
// only activate the loading state in case the image has not been cached yet.
// otherwise, the loading placeholder will always be visible before the actual image is shown, which looks like flickering
const timeout = setTimeout(() => setIsLoading((prevState) => (prevState === undefined ? true : prevState)), 1);
return () => clearTimeout(timeout);
}, []);
return (
<>
{(isLoading || hasError) && (
<Box sx={spinnerStyle}>
<Stack height="100%" alignItems="center" justifyContent="center">
{isLoading && <CircularProgress thickness={5} />}
{hasError && (
<>
<BrokenImageIcon />
<Button
startIcon={<RefreshIcon />}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
setIsLoading(true);
setHasError(false);
setImgLoadRetryKey((prevState) => (prevState + 1) % 100);
}}
size="large"
>
{t('global.button.retry')}
</Button>
</>
)}
</Stack>
</Box>
)}
<img
key={`${src}_${imgLoadRetryKey}`}
style={{ ...imgStyle, display: isLoading || hasError ? 'none' : imgStyle?.display }}
ref={imgRef}
src={src}
alt={alt}
onLoad={() => updateImageState(false)}
onError={() => updateImageState(false, true)}
draggable={false}
/>
</>
);
2021-05-28 19:37:26 +04:30
}
SpinnerImage.defaultProps = {
2021-10-31 21:02:45 +03:30
spinnerStyle: {},
imgStyle: {},
2021-05-28 19:37:26 +04:30
onImageLoad: () => {},
imgRef: undefined,
};