Remove "forwardRef" usage
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useState, useEffect, forwardRef, ForwardedRef, useCallback, useRef } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef, Ref } from 'react';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Box from '@mui/material/Box';
|
||||
import Stack from '@mui/material/Stack';
|
||||
@@ -41,185 +41,185 @@ export interface SpinnerImageProps {
|
||||
priority?: Priority;
|
||||
|
||||
retryKeyPrefix?: string;
|
||||
|
||||
ref?: Ref<HTMLImageElement | HTMLDivElement | null>;
|
||||
}
|
||||
|
||||
export const SpinnerImage = forwardRef(
|
||||
(props: SpinnerImageProps, imgRef: ForwardedRef<HTMLImageElement | HTMLDivElement | null>) => {
|
||||
const {
|
||||
shouldLoad = true,
|
||||
export const SpinnerImage = ({ ref, ...props }: SpinnerImageProps) => {
|
||||
const {
|
||||
shouldLoad = true,
|
||||
shouldDecode,
|
||||
useFetchApi,
|
||||
disableCors,
|
||||
src,
|
||||
alt,
|
||||
onLoad,
|
||||
onError,
|
||||
spinnerStyle: { small, ...spinnerStyle } = {},
|
||||
imgStyle,
|
||||
hideImgStyle,
|
||||
priority,
|
||||
retryKeyPrefix,
|
||||
} = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const loadingIndicatorRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const showMissingImageIcon = !src.length;
|
||||
|
||||
const [imageSourceUrl, setImageSourceUrl] = useState<string>();
|
||||
const [imgLoadRetryKey, setImgLoadRetryKey] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState<boolean>();
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const updateImageState = (loading: boolean, error: boolean = false, aborted: boolean = false) => {
|
||||
setIsLoading(loading);
|
||||
setHasError(error);
|
||||
|
||||
if (error && !loading && !aborted) {
|
||||
onError?.();
|
||||
}
|
||||
|
||||
if (!loading && !error && !aborted) {
|
||||
onLoad?.();
|
||||
}
|
||||
};
|
||||
|
||||
useIntersectionObserver(
|
||||
loadingIndicatorRef,
|
||||
useCallback((entries) => setIsVisible(entries[0].isIntersecting), []),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (showMissingImageIcon || !shouldLoad) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const imageRequest = requestManager.requestImage(src, {
|
||||
priority,
|
||||
shouldDecode,
|
||||
useFetchApi,
|
||||
disableCors,
|
||||
src,
|
||||
alt,
|
||||
onLoad,
|
||||
onError,
|
||||
spinnerStyle: { small, ...spinnerStyle } = {},
|
||||
imgStyle,
|
||||
hideImgStyle,
|
||||
priority,
|
||||
retryKeyPrefix,
|
||||
} = props;
|
||||
});
|
||||
let cacheTimeout: NodeJS.Timeout;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const fetchImage = async () => {
|
||||
try {
|
||||
const updateImage = async () => {
|
||||
const image = await imageRequest.response;
|
||||
|
||||
const loadingIndicatorRef = useRef<HTMLDivElement | null>(null);
|
||||
updateImageState(false);
|
||||
setImageSourceUrl(image);
|
||||
};
|
||||
|
||||
const showMissingImageIcon = !src.length;
|
||||
const checkCache = await Promise.race([
|
||||
imageRequest.response,
|
||||
new Promise((resolve) => {
|
||||
cacheTimeout = setTimeout(resolve, 50);
|
||||
}),
|
||||
]);
|
||||
const isImageCached = !!checkCache;
|
||||
|
||||
const [imageSourceUrl, setImageSourceUrl] = useState<string>();
|
||||
const [imgLoadRetryKey, setImgLoadRetryKey] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState<boolean>();
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
if (isImageCached) {
|
||||
await updateImage();
|
||||
return;
|
||||
}
|
||||
|
||||
const updateImageState = (loading: boolean, error: boolean = false, aborted: boolean = false) => {
|
||||
setIsLoading(loading);
|
||||
setHasError(error);
|
||||
|
||||
if (error && !loading && !aborted) {
|
||||
onError?.();
|
||||
}
|
||||
|
||||
if (!loading && !error && !aborted) {
|
||||
onLoad?.();
|
||||
updateImageState(true);
|
||||
await updateImage();
|
||||
} catch (e) {
|
||||
const wasAborted =
|
||||
e instanceof Error && (e.name === 'AbortError' || e.message === 'Component was unmounted');
|
||||
updateImageState(false, !wasAborted, wasAborted);
|
||||
}
|
||||
};
|
||||
|
||||
useIntersectionObserver(
|
||||
loadingIndicatorRef,
|
||||
useCallback((entries) => setIsVisible(entries[0].isIntersecting), []),
|
||||
);
|
||||
fetchImage().catch(() => {});
|
||||
|
||||
useEffect(() => {
|
||||
if (showMissingImageIcon || !shouldLoad) {
|
||||
return () => {};
|
||||
}
|
||||
return () => {
|
||||
imageRequest.cleanup();
|
||||
clearTimeout(cacheTimeout);
|
||||
imageRequest.abortRequest(new Error('Component was unmounted'));
|
||||
};
|
||||
}, [src, imgLoadRetryKey, retryKeyPrefix, showMissingImageIcon, shouldLoad]);
|
||||
|
||||
const imageRequest = requestManager.requestImage(src, {
|
||||
priority,
|
||||
shouldDecode,
|
||||
useFetchApi,
|
||||
disableCors,
|
||||
});
|
||||
let cacheTimeout: NodeJS.Timeout;
|
||||
|
||||
const fetchImage = async () => {
|
||||
try {
|
||||
const updateImage = async () => {
|
||||
const image = await imageRequest.response;
|
||||
|
||||
updateImageState(false);
|
||||
setImageSourceUrl(image);
|
||||
};
|
||||
|
||||
const checkCache = await Promise.race([
|
||||
imageRequest.response,
|
||||
new Promise((resolve) => {
|
||||
cacheTimeout = setTimeout(resolve, 50);
|
||||
}),
|
||||
]);
|
||||
const isImageCached = !!checkCache;
|
||||
|
||||
if (isImageCached) {
|
||||
await updateImage();
|
||||
return;
|
||||
}
|
||||
|
||||
updateImageState(true);
|
||||
await updateImage();
|
||||
} catch (e) {
|
||||
const wasAborted =
|
||||
e instanceof Error && (e.name === 'AbortError' || e.message === 'Component was unmounted');
|
||||
updateImageState(false, !wasAborted, wasAborted);
|
||||
}
|
||||
};
|
||||
|
||||
fetchImage().catch(() => {});
|
||||
|
||||
return () => {
|
||||
imageRequest.cleanup();
|
||||
clearTimeout(cacheTimeout);
|
||||
imageRequest.abortRequest(new Error('Component was unmounted'));
|
||||
};
|
||||
}, [src, imgLoadRetryKey, retryKeyPrefix, showMissingImageIcon, shouldLoad]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showMissingImageIcon ? (
|
||||
<Stack
|
||||
ref={imgRef}
|
||||
sx={{
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: (theme) => theme.palette.background.default,
|
||||
...spinnerStyle,
|
||||
}}
|
||||
>
|
||||
<ImageIcon fontSize="large" />
|
||||
</Stack>
|
||||
) : (
|
||||
<Box
|
||||
component="img"
|
||||
key={`${src}_${imgLoadRetryKey}_${retryKeyPrefix}`}
|
||||
sx={[
|
||||
...(Array.isArray(imgStyle) ? (imgStyle ?? []) : [imgStyle]),
|
||||
applyStyles(!imageSourceUrl || isLoading || hasError, {
|
||||
...hideImgStyle,
|
||||
...applyStyles(!hideImgStyle, {
|
||||
display: 'none',
|
||||
}),
|
||||
return (
|
||||
<>
|
||||
{showMissingImageIcon ? (
|
||||
<Stack
|
||||
ref={ref}
|
||||
sx={{
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: (theme) => theme.palette.background.default,
|
||||
...spinnerStyle,
|
||||
}}
|
||||
>
|
||||
<ImageIcon fontSize="large" />
|
||||
</Stack>
|
||||
) : (
|
||||
<Box
|
||||
component="img"
|
||||
key={`${src}_${imgLoadRetryKey}_${retryKeyPrefix}`}
|
||||
sx={[
|
||||
...(Array.isArray(imgStyle) ? (imgStyle ?? []) : [imgStyle]),
|
||||
applyStyles(!imageSourceUrl || isLoading || hasError, {
|
||||
...hideImgStyle,
|
||||
...applyStyles(!hideImgStyle, {
|
||||
display: 'none',
|
||||
}),
|
||||
]}
|
||||
ref={imgRef}
|
||||
crossOrigin={disableCors ? undefined : 'anonymous'}
|
||||
src={imageSourceUrl}
|
||||
alt={alt}
|
||||
draggable={false}
|
||||
/>
|
||||
)}
|
||||
}),
|
||||
]}
|
||||
ref={ref}
|
||||
crossOrigin={disableCors ? undefined : 'anonymous'}
|
||||
src={imageSourceUrl}
|
||||
alt={alt}
|
||||
draggable={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(isLoading || (src && !imageSourceUrl) || hasError) && (
|
||||
{(isLoading || (src && !imageSourceUrl) || hasError) && (
|
||||
<Stack
|
||||
ref={loadingIndicatorRef}
|
||||
sx={{
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
...spinnerStyle,
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
ref={loadingIndicatorRef}
|
||||
sx={{
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
...spinnerStyle,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
sx={{
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{isVisible && (isLoading || (src && !imageSourceUrl && !hasError)) && (
|
||||
<CircularProgress thickness={5} />
|
||||
)}
|
||||
{hasError && isLoading === false && (
|
||||
<>
|
||||
<BrokenImageIcon />
|
||||
<Button
|
||||
startIcon={!small && <RefreshIcon />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setImgLoadRetryKey((prevState) => (prevState + 1) % 100);
|
||||
}}
|
||||
size={small ? 'small' : 'large'}
|
||||
>
|
||||
{small ? <RefreshIcon /> : t('global.button.retry')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
{isVisible && (isLoading || (src && !imageSourceUrl && !hasError)) && (
|
||||
<CircularProgress thickness={5} />
|
||||
)}
|
||||
{hasError && isLoading === false && (
|
||||
<>
|
||||
<BrokenImageIcon />
|
||||
<Button
|
||||
startIcon={!small && <RefreshIcon />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setImgLoadRetryKey((prevState) => (prevState + 1) % 100);
|
||||
}}
|
||||
size={small ? 'small' : 'large'}
|
||||
>
|
||||
{small ? <RefreshIcon /> : t('global.button.retry')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user