/* * 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/. */ import React, { useState, CSSProperties, useEffect } from 'react'; 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'; interface IProps { src: string; alt: string; imgRef?: React.RefObject; spinnerStyle?: SxProps; imgStyle?: CSSProperties; onImageLoad?: () => void; } export function SpinnerImage(props: IProps) { const { src, alt, onImageLoad, imgRef, spinnerStyle, imgStyle } = props; const { t } = useTranslation(); const [imgLoadRetryKey, setImgLoadRetryKey] = useState(0); const [isLoading, setIsLoading] = useState(undefined); const [hasError, setHasError] = useState(false); const updateImageState = (loading: boolean, error: boolean = false) => { setIsLoading(loading); setHasError(error); if (!loading && !error) { onImageLoad?.(); } }; 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) && ( {isLoading && } {hasError && ( <> )} )} {alt} updateImageState(false)} onError={() => updateImageState(false, true)} draggable={false} /> ); } SpinnerImage.defaultProps = { spinnerStyle: {}, imgStyle: {}, onImageLoad: () => {}, imgRef: undefined, };