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}>
|
<TrackerMangaCardLink url={manga.trackingUrl}>
|
||||||
<SpinnerImage
|
<SpinnerImage
|
||||||
|
useFetchApi={false}
|
||||||
alt={manga.title}
|
alt={manga.title}
|
||||||
src={manga.coverUrl}
|
src={manga.coverUrl}
|
||||||
spinnerStyle={{ width: '100%', height: '100%' }}
|
spinnerStyle={{ width: '100%', height: '100%' }}
|
||||||
|
|||||||
@@ -27,10 +27,12 @@ interface IProps {
|
|||||||
imgStyle?: CSSProperties;
|
imgStyle?: CSSProperties;
|
||||||
|
|
||||||
onImageLoad?: () => void;
|
onImageLoad?: () => void;
|
||||||
|
|
||||||
|
useFetchApi?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTMLImageElement | null>) => {
|
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();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -55,8 +57,7 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
|
|||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
let tmpImageSourceUrl: string;
|
const imageRequest = requestManager.requestImage(src, Priority.HIGH, useFetchApi);
|
||||||
const imageRequest = requestManager.requestImage(src, Priority.HIGH);
|
|
||||||
let cacheTimeout: NodeJS.Timeout;
|
let cacheTimeout: NodeJS.Timeout;
|
||||||
|
|
||||||
const fetchImage = async () => {
|
const fetchImage = async () => {
|
||||||
@@ -66,7 +67,6 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
|
|||||||
|
|
||||||
updateImageState(false);
|
updateImageState(false);
|
||||||
setImageSourceUrl(image);
|
setImageSourceUrl(image);
|
||||||
tmpImageSourceUrl = image;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkCache = await Promise.race([
|
const checkCache = await Promise.race([
|
||||||
@@ -94,9 +94,7 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
|
|||||||
fetchImage().catch(defaultPromiseErrorHandler);
|
fetchImage().catch(defaultPromiseErrorHandler);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (tmpImageSourceUrl) {
|
imageRequest.cleanup();
|
||||||
URL.revokeObjectURL(tmpImageSourceUrl);
|
|
||||||
}
|
|
||||||
clearTimeout(cacheTimeout);
|
clearTimeout(cacheTimeout);
|
||||||
imageRequest.abortRequest(new Error('Component was unmounted'));
|
imageRequest.abortRequest(new Error('Component was unmounted'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -280,6 +280,7 @@ import {
|
|||||||
TRACKER_LOGOUT,
|
TRACKER_LOGOUT,
|
||||||
TRACKER_UPDATE_BIND,
|
TRACKER_UPDATE_BIND,
|
||||||
} from '@/lib/graphql/mutations/TrackerMutation.ts';
|
} from '@/lib/graphql/mutations/TrackerMutation.ts';
|
||||||
|
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
|
||||||
|
|
||||||
enum GQLMethod {
|
enum GQLMethod {
|
||||||
QUERY = 'QUERY',
|
QUERY = 'QUERY',
|
||||||
@@ -330,6 +331,8 @@ type SubscriptionHookOptions<Data = any, Variables extends OperationVariables =
|
|||||||
|
|
||||||
type AbortableRequest = { abortRequest: AbortController['abort'] };
|
type AbortableRequest = { abortRequest: AbortController['abort'] };
|
||||||
|
|
||||||
|
type ImageRequest = { response: Promise<string>; cleanup: () => void } & AbortableRequest;
|
||||||
|
|
||||||
export type AbortabaleApolloQueryResponse<Data = any> = {
|
export type AbortabaleApolloQueryResponse<Data = any> = {
|
||||||
response: Promise<ApolloQueryResult<Data>>;
|
response: Promise<ApolloQueryResult<Data>>;
|
||||||
} & AbortableRequest;
|
} & AbortableRequest;
|
||||||
@@ -778,6 +781,39 @@ export class RequestManager {
|
|||||||
return `${this.getValidUrlFor(imageUrl, apiVersion)}`;
|
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.
|
* 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 imageUrl = await imageRequest.response
|
||||||
*
|
*
|
||||||
* const img = new Image();
|
* const img = new Image();
|
||||||
* img.onLoad = () => URL.revokeObjectURL(imageUrl);
|
* img.onLoad = () => imageRequest.cleanup();
|
||||||
* img.src = imageUrl;
|
* 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 { abortRequest, signal } = this.createAbortController();
|
||||||
const response = this.imageQueue.enqueue(
|
const response = this.imageQueue.enqueue(
|
||||||
url,
|
url,
|
||||||
@@ -806,11 +843,26 @@ export class RequestManager {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((data) => data.blob())
|
.then((data) => data.blob())
|
||||||
.then((data) => URL.createObjectURL(data)),
|
.then((data) => URL.createObjectURL(data))
|
||||||
|
.then((imageUrl) => {
|
||||||
|
objectUrl = imageUrl;
|
||||||
|
return imageUrl;
|
||||||
|
}),
|
||||||
priority,
|
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>(
|
private doRequest<Data, Variables extends OperationVariables = OperationVariables>(
|
||||||
|
|||||||
Reference in New Issue
Block a user