Add option to use img tag to fetch images
MangaUpdates covers could not be fetched via the fetch api because there were cors issues
This commit is contained in:
@@ -145,6 +145,7 @@ export const TrackerMangaCard = ({
|
||||
>
|
||||
<TrackerMangaCardLink url={manga.trackingUrl}>
|
||||
<SpinnerImage
|
||||
useFetchApi={false}
|
||||
alt={manga.title}
|
||||
src={manga.coverUrl}
|
||||
spinnerStyle={{ width: '100%', height: '100%' }}
|
||||
|
||||
@@ -27,10 +27,12 @@ interface IProps {
|
||||
imgStyle?: CSSProperties;
|
||||
|
||||
onImageLoad?: () => void;
|
||||
|
||||
useFetchApi?: boolean;
|
||||
}
|
||||
|
||||
export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTMLImageElement | null>) => {
|
||||
const { src, alt, onImageLoad, spinnerStyle: { small, ...spinnerStyle } = {}, imgStyle } = props;
|
||||
const { useFetchApi, src, alt, onImageLoad, spinnerStyle: { small, ...spinnerStyle } = {}, imgStyle } = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -55,8 +57,7 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
|
||||
return () => {};
|
||||
}
|
||||
|
||||
let tmpImageSourceUrl: string;
|
||||
const imageRequest = requestManager.requestImage(src, Priority.HIGH);
|
||||
const imageRequest = requestManager.requestImage(src, Priority.HIGH, useFetchApi);
|
||||
let cacheTimeout: NodeJS.Timeout;
|
||||
|
||||
const fetchImage = async () => {
|
||||
@@ -66,7 +67,6 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
|
||||
|
||||
updateImageState(false);
|
||||
setImageSourceUrl(image);
|
||||
tmpImageSourceUrl = image;
|
||||
};
|
||||
|
||||
const checkCache = await Promise.race([
|
||||
@@ -94,9 +94,7 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
|
||||
fetchImage().catch(defaultPromiseErrorHandler);
|
||||
|
||||
return () => {
|
||||
if (tmpImageSourceUrl) {
|
||||
URL.revokeObjectURL(tmpImageSourceUrl);
|
||||
}
|
||||
imageRequest.cleanup();
|
||||
clearTimeout(cacheTimeout);
|
||||
imageRequest.abortRequest(new Error('Component was unmounted'));
|
||||
};
|
||||
|
||||
@@ -280,6 +280,7 @@ import {
|
||||
TRACKER_LOGOUT,
|
||||
TRACKER_UPDATE_BIND,
|
||||
} from '@/lib/graphql/mutations/TrackerMutation.ts';
|
||||
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
|
||||
|
||||
enum GQLMethod {
|
||||
QUERY = 'QUERY',
|
||||
@@ -330,6 +331,8 @@ type SubscriptionHookOptions<Data = any, Variables extends OperationVariables =
|
||||
|
||||
type AbortableRequest = { abortRequest: AbortController['abort'] };
|
||||
|
||||
type ImageRequest = { response: Promise<string>; cleanup: () => void } & AbortableRequest;
|
||||
|
||||
export type AbortabaleApolloQueryResponse<Data = any> = {
|
||||
response: Promise<ApolloQueryResult<Data>>;
|
||||
} & AbortableRequest;
|
||||
@@ -778,6 +781,39 @@ export class RequestManager {
|
||||
return `${this.getValidUrlFor(imageUrl, apiVersion)}`;
|
||||
}
|
||||
|
||||
private fetchImageViaTag(url: string, priority?: QueuePriority): ImageRequest {
|
||||
const imgRequest = new ControlledPromise<string>();
|
||||
imgRequest.promise.catch(defaultPromiseErrorHandler(`fetchImageViaTag(${url})`));
|
||||
|
||||
const img = new Image();
|
||||
const abortRequest = (reason?: any) => {
|
||||
img.src = '';
|
||||
img.onload = null;
|
||||
img.onerror = null;
|
||||
img.onabort = null;
|
||||
imgRequest.reject(reason);
|
||||
};
|
||||
|
||||
const response = this.imageQueue.enqueue(
|
||||
url,
|
||||
async () => {
|
||||
// throws error in case request was already aborted
|
||||
await Promise.race([imgRequest.promise, Promise.resolve()]);
|
||||
|
||||
img.src = url;
|
||||
|
||||
img.onload = () => imgRequest.resolve(url);
|
||||
img.onerror = (error) => imgRequest.reject(error);
|
||||
img.onabort = (error) => imgRequest.reject(error);
|
||||
|
||||
return imgRequest.promise;
|
||||
},
|
||||
priority,
|
||||
);
|
||||
|
||||
return { response, abortRequest, cleanup: () => {} };
|
||||
}
|
||||
|
||||
/**
|
||||
* After the image has been handled, {@see URL#revokeObjectURL} has to be called.
|
||||
*
|
||||
@@ -787,11 +823,12 @@ export class RequestManager {
|
||||
* const imageUrl = await imageRequest.response
|
||||
*
|
||||
* const img = new Image();
|
||||
* img.onLoad = () => URL.revokeObjectURL(imageUrl);
|
||||
* img.onLoad = () => imageRequest.cleanup();
|
||||
* img.src = imageUrl;
|
||||
*
|
||||
*/
|
||||
public requestImage(url: string, priority?: QueuePriority): { response: Promise<string> } & AbortableRequest {
|
||||
private fetchImageViaFetchApi(url: string, priority?: QueuePriority): ImageRequest {
|
||||
let objectUrl: string = '';
|
||||
const { abortRequest, signal } = this.createAbortController();
|
||||
const response = this.imageQueue.enqueue(
|
||||
url,
|
||||
@@ -806,11 +843,26 @@ export class RequestManager {
|
||||
},
|
||||
})
|
||||
.then((data) => data.blob())
|
||||
.then((data) => URL.createObjectURL(data)),
|
||||
.then((data) => URL.createObjectURL(data))
|
||||
.then((imageUrl) => {
|
||||
objectUrl = imageUrl;
|
||||
return imageUrl;
|
||||
}),
|
||||
priority,
|
||||
);
|
||||
|
||||
return { response, abortRequest };
|
||||
return { response, abortRequest, cleanup: () => URL.revokeObjectURL(objectUrl) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure to call "cleanup" once the image is not needed anymore (only required if fetched via "fetch api")
|
||||
*/
|
||||
public requestImage(url: string, priority?: QueuePriority, useFetchApi: boolean = true): ImageRequest {
|
||||
if (useFetchApi) {
|
||||
return this.fetchImageViaFetchApi(url, priority);
|
||||
}
|
||||
|
||||
return this.fetchImageViaTag(url, priority);
|
||||
}
|
||||
|
||||
private doRequest<Data, Variables extends OperationVariables = OperationVariables>(
|
||||
|
||||
Reference in New Issue
Block a user