mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-10 14:22:46 +02:00
2759cfee97
This reverts commit 4008c8dfdf.
160 lines
4.6 KiB
TypeScript
160 lines
4.6 KiB
TypeScript
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
|
import { ImageRequest } from '/@/shared/types/domain-types';
|
|
|
|
type FetchPriority = 'auto' | 'high' | 'low';
|
|
|
|
interface NativeImageState {
|
|
displaySrc?: string;
|
|
status: 'error' | 'idle' | 'loaded' | 'loading';
|
|
}
|
|
|
|
interface UseNativeImageArgs {
|
|
enabled: boolean;
|
|
fetchPriority?: FetchPriority;
|
|
onFetchError?: () => void;
|
|
request?: ImageRequest | null;
|
|
}
|
|
|
|
export function useNativeImage({
|
|
enabled,
|
|
fetchPriority,
|
|
onFetchError,
|
|
request,
|
|
}: UseNativeImageArgs) {
|
|
const abortControllerRef = useRef<AbortController | null>(null);
|
|
const loadedRequestSignatureRef = useRef<null | string>(null);
|
|
const objectUrlRef = useRef<null | string>(null);
|
|
const onFetchErrorRef = useRef(onFetchError);
|
|
const [state, setState] = useState<NativeImageState>({ status: 'idle' });
|
|
|
|
const requestSignature = useMemo(() => {
|
|
if (!request) {
|
|
return null;
|
|
}
|
|
|
|
return JSON.stringify({
|
|
cacheKey: request.cacheKey,
|
|
credentials: request.credentials,
|
|
headers: request.headers,
|
|
url: request.url,
|
|
});
|
|
}, [request]);
|
|
|
|
onFetchErrorRef.current = onFetchError;
|
|
|
|
useEffect(() => {
|
|
const abortCurrentRequest = () => {
|
|
abortControllerRef.current?.abort();
|
|
abortControllerRef.current = null;
|
|
};
|
|
|
|
const revokeObjectUrl = () => {
|
|
if (!objectUrlRef.current) {
|
|
return;
|
|
}
|
|
|
|
URL.revokeObjectURL(objectUrlRef.current);
|
|
objectUrlRef.current = null;
|
|
loadedRequestSignatureRef.current = null;
|
|
};
|
|
|
|
if (!request || !requestSignature) {
|
|
abortCurrentRequest();
|
|
revokeObjectUrl();
|
|
setState({ status: 'idle' });
|
|
return;
|
|
}
|
|
|
|
if (!enabled) {
|
|
abortCurrentRequest();
|
|
setState((currentState) =>
|
|
currentState.displaySrc
|
|
? { ...currentState, status: 'loaded' }
|
|
: { status: 'idle' },
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (loadedRequestSignatureRef.current === requestSignature && objectUrlRef.current) {
|
|
setState({ displaySrc: objectUrlRef.current, status: 'loaded' });
|
|
return;
|
|
}
|
|
|
|
abortCurrentRequest();
|
|
revokeObjectUrl();
|
|
setState({ status: 'loading' });
|
|
|
|
const abortController = new AbortController();
|
|
abortControllerRef.current = abortController;
|
|
|
|
void (async () => {
|
|
try {
|
|
const init = {
|
|
credentials: request.credentials,
|
|
headers: request.headers,
|
|
signal: abortController.signal,
|
|
} as RequestInit & { priority?: FetchPriority };
|
|
|
|
if (fetchPriority) {
|
|
init.priority = fetchPriority;
|
|
}
|
|
|
|
const response = await fetch(request.url, init);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to load image: ${response.status}`);
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
|
|
if (abortController.signal.aborted) {
|
|
return;
|
|
}
|
|
|
|
const objectUrl = URL.createObjectURL(blob);
|
|
objectUrlRef.current = objectUrl;
|
|
loadedRequestSignatureRef.current = requestSignature;
|
|
setState({ displaySrc: objectUrl, status: 'loaded' });
|
|
} catch {
|
|
if (abortController.signal.aborted) {
|
|
return;
|
|
}
|
|
|
|
revokeObjectUrl();
|
|
setState({ status: 'error' });
|
|
onFetchErrorRef.current?.();
|
|
} finally {
|
|
if (abortControllerRef.current === abortController) {
|
|
abortControllerRef.current = null;
|
|
}
|
|
}
|
|
})();
|
|
|
|
return () => {
|
|
abortController.abort();
|
|
|
|
if (abortControllerRef.current === abortController) {
|
|
abortControllerRef.current = null;
|
|
}
|
|
};
|
|
}, [enabled, fetchPriority, request, requestSignature]);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
abortControllerRef.current?.abort();
|
|
|
|
if (objectUrlRef.current) {
|
|
URL.revokeObjectURL(objectUrlRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
return {
|
|
displaySrc: state.displaySrc,
|
|
isError: state.status === 'error',
|
|
isLoaded: state.status === 'loaded',
|
|
isLoading: state.status === 'loading',
|
|
};
|
|
}
|