Feature/request manager make requests abortable (#316)

* Make requests abortable

* Make requests abortable - Fix usage
This commit is contained in:
schroda
2023-05-23 13:27:38 +02:00
committed by GitHub
parent 95ceac335b
commit 0d36d2dcfb
11 changed files with 126 additions and 90 deletions

View File

@@ -71,7 +71,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
[key]: value, [key]: value,
lastPageRead: key === 'read' ? 0 : undefined, lastPageRead: key === 'read' ? 0 : undefined,
}) })
.then(() => triggerChaptersUpdate()); .response.then(() => triggerChaptersUpdate());
}; };
const downloadChapter = () => { const downloadChapter = () => {
@@ -82,7 +82,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
const deleteChapter = () => { const deleteChapter = () => {
requestManager requestManager
.removeChapterFromDownloadQueue(chapter.mangaId, chapter.index) .removeChapterFromDownloadQueue(chapter.mangaId, chapter.index)
.then(() => triggerChaptersUpdate()); .response.then(() => triggerChaptersUpdate());
handleClose(); handleClose();
}; };

View File

@@ -153,7 +153,7 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
let actionPromise: Promise<any>; let actionPromise: Promise<any>;
if (action === 'download') { if (action === 'download') {
actionPromise = requestManager.addChaptersToDownloadQueue(chapterIds); actionPromise = requestManager.addChaptersToDownloadQueue(chapterIds).response;
} else { } else {
const change: BatchChaptersChange = {}; const change: BatchChaptersChange = {};
@@ -165,7 +165,7 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
change.lastPageRead = 0; change.lastPageRead = 0;
} }
actionPromise = requestManager.updateChapters(chapterIds, change); actionPromise = requestManager.updateChapters(chapterIds, change).response;
} }
actionPromise actionPromise

View File

@@ -145,12 +145,12 @@ const MangaDetails: React.FC<IProps> = ({ manga }) => {
const addToLibrary = () => { const addToLibrary = () => {
mutate(`/api/v1/manga/${manga.id}`, { ...manga, inLibrary: true }, { revalidate: false }); mutate(`/api/v1/manga/${manga.id}`, { ...manga, inLibrary: true }, { revalidate: false });
requestManager.addMangaToLibrary(manga.id).then(() => mutate(`/api/v1/manga/${manga.id}`)); requestManager.addMangaToLibrary(manga.id).response.then(() => mutate(`/api/v1/manga/${manga.id}`));
}; };
const removeFromLibrary = () => { const removeFromLibrary = () => {
mutate(`/api/v1/manga/${manga.id}`, { ...manga, inLibrary: false }, { revalidate: false }); mutate(`/api/v1/manga/${manga.id}`, { ...manga, inLibrary: false }, { revalidate: false });
requestManager.removeMangaFromLibrary(manga.id).then(() => mutate(`/api/v1/manga/${manga.id}`)); requestManager.removeMangaFromLibrary(manga.id).response.then(() => mutate(`/api/v1/manga/${manga.id}`));
}; };
return ( return (

View File

@@ -56,7 +56,7 @@ export default function CategorySelect(props: IProps) {
(checked (checked
? requestManager.addMangaToCategory(mangaId, categoryId) ? requestManager.addMangaToCategory(mangaId, categoryId)
: requestManager.removeMangaFromCategory(mangaId, categoryId) : requestManager.removeMangaFromCategory(mangaId, categoryId)
).then(() => mutate()); ).response.then(() => mutate());
}; };
return ( return (

View File

@@ -52,6 +52,11 @@ type CustomSWROptions<Data> = {
type SWROptions<Data = any, Error = any> = SWRConfiguration<Data, Error> & CustomSWROptions<Data>; type SWROptions<Data = any, Error = any> = SWRConfiguration<Data, Error> & CustomSWROptions<Data>;
type SWRInfiniteOptions<Data = any, Error = any> = SWRInfiniteConfiguration<Data, Error> & CustomSWROptions<Data>; type SWRInfiniteOptions<Data = any, Error = any> = SWRInfiniteConfiguration<Data, Error> & CustomSWROptions<Data>;
type AbortableRequest = { abortRequest: AbortController['abort'] };
type AbortableAxiosResponse<Data = any> = { response: Promise<AxiosResponse<Data>> } & AbortableRequest;
type AbortableSWRResponse<Data = any, Error = any> = SWRResponse<Data, Error> & AbortableRequest;
type AbortableSWRInfiniteResponse<Data = any, Error = any> = SWRInfiniteResponse<Data, Error> & AbortableRequest;
// the following endpoints have not been implemented: // the following endpoints have not been implemented:
// - PUT /api/v1/manga/{mangaId}/chapter/{chapterIndex} - modify chapter # PATCH endpoint used instead // - PUT /api/v1/manga/{mangaId}/chapter/{chapterIndex} - modify chapter # PATCH endpoint used instead
// - POST /api/v1/backup/import - import backup # "import backup file" endpoint used instead // - POST /api/v1/backup/import - import backup # "import backup file" endpoint used instead
@@ -175,7 +180,7 @@ export class RequestManager {
* In case "formData" is passed, "data" gets ignored. * In case "formData" is passed, "data" gets ignored.
*/ */
private doRequest< private doRequest<
Result extends Promise<AxiosResponse> | SWRResponse | SWRInfiniteResponse, Result extends AbortableAxiosResponse | AbortableSWRResponse | AbortableSWRInfiniteResponse,
OptionsSWR extends SWROptions | SWRInfiniteOptions, OptionsSWR extends SWROptions | SWRInfiniteOptions,
>( >(
httpMethod: HttpMethodType, httpMethod: HttpMethodType,
@@ -205,54 +210,80 @@ export class RequestManager {
}); });
} }
const abortController = new AbortController();
const abortRequest = (reason?: any): void => {
if (!abortController.signal.aborted) {
abortController.abort(reason);
}
};
const axiosOptionsWithAbortController = { ...axiosOptions, signal: abortController.signal };
switch (httpMethod) { switch (httpMethod) {
case HttpMethod.SWR_GET: case HttpMethod.SWR_GET:
return this.useSwr(url, HttpMethod.GET, { axiosOptions, swrOptions }) as Result; return {
...(this.useSwr(url, HttpMethod.GET, { axiosOptions, swrOptions }) as Result),
abortRequest,
};
case HttpMethod.SWR_GET_INFINITE: case HttpMethod.SWR_GET_INFINITE:
// throw TypeError in case options aren't correctly passed // throw TypeError in case options aren't correctly passed
return this.useSwrInfinite(swrOptions!.getEndpoint!, HttpMethod.GET, { return {
axiosOptions, ...(this.useSwrInfinite(swrOptions!.getEndpoint!, HttpMethod.GET, {
swrOptions, axiosOptions: axiosOptionsWithAbortController,
}) as Result; swrOptions,
}) as Result),
abortRequest,
};
case SWRHttpMethod.SWR_POST_INFINITE: case SWRHttpMethod.SWR_POST_INFINITE:
return this.useSwrInfinite(swrOptions!.getEndpoint!, HttpMethod.POST, { return {
data, ...(this.useSwrInfinite(swrOptions!.getEndpoint!, HttpMethod.POST, {
axiosOptions, data,
swrOptions, axiosOptions: axiosOptionsWithAbortController,
}) as Result; swrOptions,
}) as Result),
abortRequest,
};
case HttpMethod.SWR_POST: case HttpMethod.SWR_POST:
return this.useSwr(url, HttpMethod.POST, { data, axiosOptions, swrOptions }) as Result; return {
...(this.useSwr(url, HttpMethod.POST, {
data,
axiosOptions: axiosOptionsWithAbortController,
swrOptions,
}) as Result),
controller: abortController,
};
default: default:
return this.restClient.fetcher(url, { return {
data, response: this.restClient.fetcher(url, {
httpMethod, data,
config: axiosOptions, httpMethod,
checkResponseIsJson: false, config: axiosOptionsWithAbortController,
}) as Result; checkResponseIsJson: false,
}),
abortRequest,
} as Result;
} }
} }
public useGetGlobalMeta(swrOptions?: SWROptions<Metadata>): SWRResponse<Metadata> { public useGetGlobalMeta(swrOptions?: SWROptions<Metadata>): AbortableSWRResponse<Metadata> {
return this.doRequest(HttpMethod.SWR_GET, 'meta', { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, 'meta', { swrOptions });
} }
public setGlobalMetadata(key: string, value: any): Promise<AxiosResponse> { public setGlobalMetadata(key: string, value: any): AbortableAxiosResponse {
return this.doRequest(HttpMethod.PATCH, 'meta', { formData: { key, value } }); return this.doRequest(HttpMethod.PATCH, 'meta', { formData: { key, value } });
} }
public useGetAbout(swrOptions?: SWROptions<IAbout>): SWRResponse<IAbout> { public useGetAbout(swrOptions?: SWROptions<IAbout>): AbortableSWRResponse<IAbout> {
return this.doRequest(HttpMethod.SWR_GET, 'settings/about', { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, 'settings/about', { swrOptions });
} }
public useCheckForUpdate(swrOptions?: SWROptions<UpdateCheck[]>): SWRResponse<UpdateCheck[]> { public useCheckForUpdate(swrOptions?: SWROptions<UpdateCheck[]>): AbortableSWRResponse<UpdateCheck[]> {
return this.doRequest(HttpMethod.SWR_GET, 'settings/check-update', { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, 'settings/check-update', { swrOptions });
} }
public useGetExtensionList(swrOptions?: SWROptions<IExtension[]>): SWRResponse<IExtension[]> { public useGetExtensionList(swrOptions?: SWROptions<IExtension[]>): AbortableSWRResponse<IExtension[]> {
return this.doRequest(HttpMethod.SWR_GET, 'extension/list', { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, 'extension/list', { swrOptions });
} }
public installExtension(extension: string | File): Promise<AxiosResponse> { public installExtension(extension: string | File): AbortableAxiosResponse {
if (typeof extension === 'string') { if (typeof extension === 'string') {
return this.doRequest(HttpMethod.GET, `extension/install/${extension}`); return this.doRequest(HttpMethod.GET, `extension/install/${extension}`);
} }
@@ -260,11 +291,11 @@ export class RequestManager {
return this.doRequest(HttpMethod.POST, `extension/install`, { formData: { file: extension } }); return this.doRequest(HttpMethod.POST, `extension/install`, { formData: { file: extension } });
} }
public updateExtension(extension: string): Promise<AxiosResponse> { public updateExtension(extension: string): AbortableAxiosResponse {
return this.doRequest(HttpMethod.GET, `extension/update/${extension}`); return this.doRequest(HttpMethod.GET, `extension/update/${extension}`);
} }
public uninstallExtension(extension: string): Promise<AxiosResponse> { public uninstallExtension(extension: string): AbortableAxiosResponse {
return this.doRequest(HttpMethod.GET, `extension/uninstall/${extension}`); return this.doRequest(HttpMethod.GET, `extension/uninstall/${extension}`);
} }
@@ -272,11 +303,11 @@ export class RequestManager {
return this.getValidImgUrlFor(`extension/icon/${extension}`); return this.getValidImgUrlFor(`extension/icon/${extension}`);
} }
public useGetSourceList(swrOptions?: SWROptions<ISource[]>): SWRResponse<ISource[]> { public useGetSourceList(swrOptions?: SWROptions<ISource[]>): AbortableSWRResponse<ISource[]> {
return this.doRequest(HttpMethod.SWR_GET, 'source/list', { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, 'source/list', { swrOptions });
} }
public useGetSource(sourceId: string, swrOptions?: SWROptions<ISource>): SWRResponse<ISource> { public useGetSource(sourceId: string, swrOptions?: SWROptions<ISource>): AbortableSWRResponse<ISource> {
return this.doRequest(HttpMethod.SWR_GET, `source/${sourceId}`, { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, `source/${sourceId}`, { swrOptions });
} }
@@ -284,7 +315,7 @@ export class RequestManager {
sourceId: string, sourceId: string,
initialPages?: number, initialPages?: number,
swrOptions?: SWRInfiniteOptions<PaginatedMangaList>, swrOptions?: SWRInfiniteOptions<PaginatedMangaList>,
): SWRInfiniteResponse<PaginatedMangaList> { ): AbortableSWRInfiniteResponse<PaginatedMangaList> {
return this.doRequest(SWRHttpMethod.SWR_GET_INFINITE, '', { return this.doRequest(SWRHttpMethod.SWR_GET_INFINITE, '', {
swrOptions: { swrOptions: {
getEndpoint: (page, previousData) => getEndpoint: (page, previousData) =>
@@ -299,7 +330,7 @@ export class RequestManager {
sourceId: string, sourceId: string,
initialPages?: number, initialPages?: number,
swrOptions?: SWRInfiniteOptions<PaginatedMangaList>, swrOptions?: SWRInfiniteOptions<PaginatedMangaList>,
): SWRInfiniteResponse<PaginatedMangaList> { ): AbortableSWRInfiniteResponse<PaginatedMangaList> {
return this.doRequest(SWRHttpMethod.SWR_GET_INFINITE, '', { return this.doRequest(SWRHttpMethod.SWR_GET_INFINITE, '', {
swrOptions: { swrOptions: {
getEndpoint: (page, previousData) => getEndpoint: (page, previousData) =>
@@ -313,11 +344,11 @@ export class RequestManager {
public useGetSourcePreferences( public useGetSourcePreferences(
sourceId: string, sourceId: string,
swrOptions?: SWROptions<SourcePreferences[]>, swrOptions?: SWROptions<SourcePreferences[]>,
): SWRResponse<SourcePreferences[]> { ): AbortableSWRResponse<SourcePreferences[]> {
return this.doRequest(HttpMethod.SWR_GET, `source/${sourceId}/preferences`, { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, `source/${sourceId}/preferences`, { swrOptions });
} }
public setSourcePreferences(sourceId: string, position: number, value: string): Promise<AxiosResponse> { public setSourcePreferences(sourceId: string, position: number, value: string): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, `source/${sourceId}/preferences`, { data: { position, value } }); return this.doRequest(HttpMethod.POST, `source/${sourceId}/preferences`, { data: { position, value } });
} }
@@ -325,15 +356,15 @@ export class RequestManager {
sourceId: string, sourceId: string,
reset?: boolean, reset?: boolean,
swrOptions?: SWROptions<ISourceFilters[]>, swrOptions?: SWROptions<ISourceFilters[]>,
): SWRResponse<ISourceFilters[]> { ): AbortableSWRResponse<ISourceFilters[]> {
return this.doRequest(HttpMethod.SWR_GET, `source/${sourceId}/filters`, { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, `source/${sourceId}/filters`, { swrOptions });
} }
public setSourceFilters(sourceId: string, filters: { position: number; state: string }[]): Promise<AxiosResponse> { public setSourceFilters(sourceId: string, filters: { position: number; state: string }[]): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, `source/${sourceId}/filters`, { data: filters }); return this.doRequest(HttpMethod.POST, `source/${sourceId}/filters`, { data: filters });
} }
public resetSourceFilters(sourceId: string): Promise<AxiosResponse> { public resetSourceFilters(sourceId: string): AbortableAxiosResponse {
return this.doRequest(HttpMethod.GET, `source/${sourceId}/filters?reset=true`); return this.doRequest(HttpMethod.GET, `source/${sourceId}/filters?reset=true`);
} }
@@ -342,7 +373,7 @@ export class RequestManager {
searchTerm: string, searchTerm: string,
initialPages?: number, initialPages?: number,
swrOptions?: SWRInfiniteOptions<SourceSearchResult>, swrOptions?: SWRInfiniteOptions<SourceSearchResult>,
): SWRInfiniteResponse<SourceSearchResult> { ): AbortableSWRInfiniteResponse<SourceSearchResult> {
return this.doRequest(HttpMethod.SWR_GET_INFINITE, '', { return this.doRequest(HttpMethod.SWR_GET_INFINITE, '', {
swrOptions: { swrOptions: {
getEndpoint: (page, previousData) => getEndpoint: (page, previousData) =>
@@ -361,7 +392,7 @@ export class RequestManager {
filters: { position: number; state: string }[], filters: { position: number; state: string }[],
initialPages?: number, initialPages?: number,
swrOptions?: SWRInfiniteOptions<SourceSearchResult>, swrOptions?: SWRInfiniteOptions<SourceSearchResult>,
): SWRInfiniteResponse<SourceSearchResult> { ): AbortableSWRInfiniteResponse<SourceSearchResult> {
return this.doRequest(HttpMethod.SWR_POST_INFINITE, '', { return this.doRequest(HttpMethod.SWR_POST_INFINITE, '', {
data: { searchTerm, filter: filters }, data: { searchTerm, filter: filters },
swrOptions: { swrOptions: {
@@ -378,7 +409,7 @@ export class RequestManager {
public useGetManga( public useGetManga(
mangaId: number | string, mangaId: number | string,
{ doOnlineFetch, ...swrOptions }: SWROptions<IManga> & RequestOption = {}, { doOnlineFetch, ...swrOptions }: SWROptions<IManga> & RequestOption = {},
): SWRResponse<IManga> { ): AbortableSWRResponse<IManga> {
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : ''; const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}${onlineFetch}`, { return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}${onlineFetch}`, {
swrOptions, swrOptions,
@@ -388,7 +419,7 @@ export class RequestManager {
public useGetFullManga( public useGetFullManga(
mangaId: number | string, mangaId: number | string,
{ doOnlineFetch, ...swrOptions }: SWROptions<IManga> & RequestOption = {}, { doOnlineFetch, ...swrOptions }: SWROptions<IManga> & RequestOption = {},
): SWRResponse<IManga> { ): AbortableSWRResponse<IManga> {
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : ''; const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/full${onlineFetch}`, { return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/full${onlineFetch}`, {
swrOptions, swrOptions,
@@ -399,34 +430,37 @@ export class RequestManager {
return this.getValidImgUrlFor(`manga/${mangaId}/thumbnail`); return this.getValidImgUrlFor(`manga/${mangaId}/thumbnail`);
} }
public useGetMangaCategories(mangaId: number, swrOptions?: SWROptions<ICategory[]>): SWRResponse<ICategory[]> { public useGetMangaCategories(
mangaId: number,
swrOptions?: SWROptions<ICategory[]>,
): AbortableSWRResponse<ICategory[]> {
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/category`, { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/category`, { swrOptions });
} }
public addMangaToCategory(mangaId: number, categoryId: number): Promise<AxiosResponse> { public addMangaToCategory(mangaId: number, categoryId: number): AbortableAxiosResponse {
return this.doRequest(HttpMethod.GET, `manga/${mangaId}/category/${categoryId}`); return this.doRequest(HttpMethod.GET, `manga/${mangaId}/category/${categoryId}`);
} }
public removeMangaFromCategory(mangaId: number, categoryId: number): Promise<AxiosResponse> { public removeMangaFromCategory(mangaId: number, categoryId: number): AbortableAxiosResponse {
return this.doRequest(HttpMethod.DELETE, `manga/${mangaId}/category/${categoryId}`); return this.doRequest(HttpMethod.DELETE, `manga/${mangaId}/category/${categoryId}`);
} }
public addMangaToLibrary(mangaId: number | string): Promise<AxiosResponse> { public addMangaToLibrary(mangaId: number | string): AbortableAxiosResponse {
return this.doRequest(HttpMethod.GET, `manga/${mangaId}/library`); return this.doRequest(HttpMethod.GET, `manga/${mangaId}/library`);
} }
public removeMangaFromLibrary(mangaId: number | string): Promise<AxiosResponse> { public removeMangaFromLibrary(mangaId: number | string): AbortableAxiosResponse {
return this.doRequest(HttpMethod.DELETE, `manga/${mangaId}/library`); return this.doRequest(HttpMethod.DELETE, `manga/${mangaId}/library`);
} }
public setMangaMeta(mangaId: number, key: string, value: any): Promise<AxiosResponse> { public setMangaMeta(mangaId: number, key: string, value: any): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, `manga/${mangaId}/meta`, { formData: { key, value } }); return this.doRequest(HttpMethod.POST, `manga/${mangaId}/meta`, { formData: { key, value } });
} }
public useGetMangaChapters( public useGetMangaChapters(
mangaId: number | string, mangaId: number | string,
{ doOnlineFetch, ...swrOptions }: SWROptions<IChapter[]> & RequestOption = {}, { doOnlineFetch, ...swrOptions }: SWROptions<IChapter[]> & RequestOption = {},
): SWRResponse<IChapter[]> { ): AbortableSWRResponse<IChapter[]> {
const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : ''; const onlineFetch = doOnlineFetch ? '?onlineFetch=true' : '';
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/chapters${onlineFetch}`, { return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/chapters${onlineFetch}`, {
swrOptions, swrOptions,
@@ -443,7 +477,7 @@ export class RequestManager {
| { chapterIds?: number[]; chapterIndexes: number[] } | { chapterIds?: number[]; chapterIndexes: number[] }
| { chapterIds: number[]; chapterIndexes?: number[] } | { chapterIds: number[]; chapterIndexes?: number[] }
) & { change: BatchChaptersChange }, ) & { change: BatchChaptersChange },
): Promise<AxiosResponse> { ): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, `manga/${mangaId}/chapter/batch`, { return this.doRequest(HttpMethod.POST, `manga/${mangaId}/chapter/batch`, {
data: { data: {
chapterIds, chapterIds,
@@ -457,13 +491,13 @@ export class RequestManager {
mangaId: number | string, mangaId: number | string,
chapterIndex: number | string, chapterIndex: number | string,
swrOptions?: SWROptions<IChapter>, swrOptions?: SWROptions<IChapter>,
): SWRResponse<IChapter> { ): AbortableSWRResponse<IChapter> {
return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/chapter/${chapterIndex}`, { return this.doRequest(HttpMethod.SWR_GET, `manga/${mangaId}/chapter/${chapterIndex}`, {
swrOptions, swrOptions,
}); });
} }
public deleteDownloadedChapter(mangaId: number | string, chapterIndex: number | string): Promise<AxiosResponse> { public deleteDownloadedChapter(mangaId: number | string, chapterIndex: number | string): AbortableAxiosResponse {
return this.doRequest(HttpMethod.DELETE, `manga/${mangaId}/chapter/${chapterIndex}`); return this.doRequest(HttpMethod.DELETE, `manga/${mangaId}/chapter/${chapterIndex}`);
} }
@@ -471,7 +505,7 @@ export class RequestManager {
mangaId: number | string, mangaId: number | string,
chapterIndex: number | string, chapterIndex: number | string,
change: { read?: boolean; bookmarked?: boolean; markPrevRead?: boolean; lastPageRead?: number } = {}, change: { read?: boolean; bookmarked?: boolean; markPrevRead?: boolean; lastPageRead?: number } = {},
): Promise<AxiosResponse> { ): AbortableAxiosResponse {
return this.doRequest(HttpMethod.PATCH, `manga/${mangaId}/chapter/${chapterIndex}`, { formData: change }); return this.doRequest(HttpMethod.PATCH, `manga/${mangaId}/chapter/${chapterIndex}`, { formData: change });
} }
@@ -480,7 +514,7 @@ export class RequestManager {
chapterIndex: number | string, chapterIndex: number | string,
key: string, key: string,
value: any, value: any,
): Promise<AxiosResponse> { ): AbortableAxiosResponse {
return this.doRequest(HttpMethod.PATCH, `manga/${mangaId}/chapter/${chapterIndex}/meta`, { return this.doRequest(HttpMethod.PATCH, `manga/${mangaId}/chapter/${chapterIndex}/meta`, {
formData: { key, value }, formData: { key, value },
}); });
@@ -493,51 +527,51 @@ export class RequestManager {
); );
} }
public updateChapters(chapterIds: number[], change: BatchChaptersChange): Promise<AxiosResponse> { public updateChapters(chapterIds: number[], change: BatchChaptersChange): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, `chapter/batch`, { data: { chapterIds, change } }); return this.doRequest(HttpMethod.POST, `chapter/batch`, { data: { chapterIds, change } });
} }
public useGetCategories(swrOptions?: SWROptions<ICategory[]>): SWRResponse<ICategory[]> { public useGetCategories(swrOptions?: SWROptions<ICategory[]>): AbortableSWRResponse<ICategory[]> {
return this.doRequest(HttpMethod.SWR_GET, `category`, { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, `category`, { swrOptions });
} }
public createCategory(name: string): Promise<AxiosResponse> { public createCategory(name: string): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, `category`, { formData: { name } }); return this.doRequest(HttpMethod.POST, `category`, { formData: { name } });
} }
public reorderCategory(currentPosition: number, newPosition: number): Promise<AxiosResponse> { public reorderCategory(currentPosition: number, newPosition: number): AbortableAxiosResponse {
return this.doRequest(HttpMethod.PATCH, `category/reorder`, { return this.doRequest(HttpMethod.PATCH, `category/reorder`, {
formData: { from: currentPosition, to: newPosition }, formData: { from: currentPosition, to: newPosition },
}); });
} }
public useGetCategoryMangas(categoryId: number, swrOptions?: SWROptions<IManga[]>): SWRResponse<IManga[]> { public useGetCategoryMangas(categoryId: number, swrOptions?: SWROptions<IManga[]>): AbortableSWRResponse<IManga[]> {
return this.doRequest(HttpMethod.SWR_GET, `category/${categoryId}`, { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, `category/${categoryId}`, { swrOptions });
} }
public deleteCategory(categoryId: number): Promise<AxiosResponse> { public deleteCategory(categoryId: number): AbortableAxiosResponse {
return this.doRequest(HttpMethod.DELETE, `category/${categoryId}`); return this.doRequest(HttpMethod.DELETE, `category/${categoryId}`);
} }
public updateCategory( public updateCategory(
categoryId: number, categoryId: number,
change: { name?: string; default?: boolean; includeInUpdate?: IncludeInGlobalUpdate } = {}, change: { name?: string; default?: boolean; includeInUpdate?: IncludeInGlobalUpdate } = {},
): Promise<AxiosResponse> { ): AbortableAxiosResponse {
return this.doRequest(HttpMethod.PATCH, `category/${categoryId}`, { formData: change }); return this.doRequest(HttpMethod.PATCH, `category/${categoryId}`, { formData: change });
} }
public setCategoryMeta(categoryId: number, key: string, value: any): Promise<AxiosResponse> { public setCategoryMeta(categoryId: number, key: string, value: any): AbortableAxiosResponse {
return this.doRequest(HttpMethod.PATCH, `category/${categoryId}`, { formData: { key, value } }); return this.doRequest(HttpMethod.PATCH, `category/${categoryId}`, { formData: { key, value } });
} }
public restoreBackupFile(file: File): Promise<AxiosResponse> { public restoreBackupFile(file: File): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, 'backup/import/file', { formData: { 'backup.proto.gz': file } }); return this.doRequest(HttpMethod.POST, 'backup/import/file', { formData: { 'backup.proto.gz': file } });
} }
public useValidateBackupFile( public useValidateBackupFile(
file: File, file: File,
swrOptions?: SWROptions<BackupValidationResult>, swrOptions?: SWROptions<BackupValidationResult>,
): SWRResponse<BackupValidationResult> { ): AbortableSWRResponse<BackupValidationResult> {
return this.doRequest(HttpMethod.SWR_POST, 'backup/validate/file', { return this.doRequest(HttpMethod.SWR_POST, 'backup/validate/file', {
formData: { 'backup.proto.gz': file }, formData: { 'backup.proto.gz': file },
swrOptions, swrOptions,
@@ -548,26 +582,26 @@ export class RequestManager {
return this.getValidUrlFor('backup/export/file'); return this.getValidUrlFor('backup/export/file');
} }
public startDownloads(): Promise<AxiosResponse> { public startDownloads(): AbortableAxiosResponse {
return this.doRequest(HttpMethod.GET, 'downloads/start'); return this.doRequest(HttpMethod.GET, 'downloads/start');
} }
public stopDownloads(): Promise<AxiosResponse> { public stopDownloads(): AbortableAxiosResponse {
return this.doRequest(HttpMethod.GET, 'downloads/stop'); return this.doRequest(HttpMethod.GET, 'downloads/stop');
} }
public clearDownloads(): Promise<AxiosResponse> { public clearDownloads(): AbortableAxiosResponse {
return this.doRequest(HttpMethod.GET, 'downloads/clear'); return this.doRequest(HttpMethod.GET, 'downloads/clear');
} }
public addChapterToDownloadQueue(mangaId: number | string, chapterIndex: number | string): Promise<AxiosResponse> { public addChapterToDownloadQueue(mangaId: number | string, chapterIndex: number | string): AbortableAxiosResponse {
return this.doRequest(HttpMethod.GET, `download/${mangaId}/chapter/${chapterIndex}`); return this.doRequest(HttpMethod.GET, `download/${mangaId}/chapter/${chapterIndex}`);
} }
public removeChapterFromDownloadQueue( public removeChapterFromDownloadQueue(
mangaId: number | string, mangaId: number | string,
chapterIndex: number | string, chapterIndex: number | string,
): Promise<AxiosResponse> { ): AbortableAxiosResponse {
return this.doRequest(HttpMethod.DELETE, `download/${mangaId}/chapter/${chapterIndex}`); return this.doRequest(HttpMethod.DELETE, `download/${mangaId}/chapter/${chapterIndex}`);
} }
@@ -575,22 +609,22 @@ export class RequestManager {
mangaId: number | string, mangaId: number | string,
chapterIndex: number | string, chapterIndex: number | string,
position: number, position: number,
): Promise<AxiosResponse> { ): AbortableAxiosResponse {
return this.doRequest(HttpMethod.PATCH, `download/${mangaId}/chapter/${chapterIndex}/reorder/${position}`); return this.doRequest(HttpMethod.PATCH, `download/${mangaId}/chapter/${chapterIndex}/reorder/${position}`);
} }
public addChaptersToDownloadQueue(chapterIds: number[]): Promise<AxiosResponse> { public addChaptersToDownloadQueue(chapterIds: number[]): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, 'download/batch', { data: { chapterIds } }); return this.doRequest(HttpMethod.POST, 'download/batch', { data: { chapterIds } });
} }
public removeChaptersFromDownloadQueue(chapterIds: number[]): Promise<AxiosResponse> { public removeChaptersFromDownloadQueue(chapterIds: number[]): AbortableAxiosResponse {
return this.doRequest(HttpMethod.DELETE, 'download/batch', { data: { chapterIds } }); return this.doRequest(HttpMethod.DELETE, 'download/batch', { data: { chapterIds } });
} }
public useGetRecentlyUpdatedChapters( public useGetRecentlyUpdatedChapters(
initialPages?: number, initialPages?: number,
swrOptions?: SWRInfiniteOptions<PaginatedList<IMangaChapter>>, swrOptions?: SWRInfiniteOptions<PaginatedList<IMangaChapter>>,
): SWRInfiniteResponse<PaginatedList<IMangaChapter>> { ): AbortableSWRInfiniteResponse<PaginatedList<IMangaChapter>> {
return this.doRequest(HttpMethod.SWR_GET_INFINITE, '', { return this.doRequest(HttpMethod.SWR_GET_INFINITE, '', {
swrOptions: { swrOptions: {
getEndpoint: (page, previousData) => getEndpoint: (page, previousData) =>
@@ -601,15 +635,15 @@ export class RequestManager {
}); });
} }
public startGlobalUpdate(categoryId?: number): Promise<AxiosResponse> { public startGlobalUpdate(categoryId?: number): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, 'update/fetch', { formData: { categoryId } }); return this.doRequest(HttpMethod.POST, 'update/fetch', { formData: { categoryId } });
} }
public resetGlobalUpdate(): Promise<AxiosResponse> { public resetGlobalUpdate(): AbortableAxiosResponse {
return this.doRequest(HttpMethod.POST, 'update/reset'); return this.doRequest(HttpMethod.POST, 'update/reset');
} }
public useGetGlobalUpdateSummary(swrOptions?: SWROptions<IUpdateStatus>): SWRResponse<IUpdateStatus> { public useGetGlobalUpdateSummary(swrOptions?: SWROptions<IUpdateStatus>): AbortableSWRResponse<IUpdateStatus> {
return this.doRequest(HttpMethod.SWR_GET, 'update/summary', { swrOptions }); return this.doRequest(HttpMethod.SWR_GET, 'update/summary', { swrOptions });
} }
} }

View File

@@ -85,7 +85,7 @@ const DownloadQueue: React.FC = () => {
return; return;
} }
requestManager.startDownloads().catch(() => {}); requestManager.startDownloads().response.catch(() => {});
}; };
return ( return (

View File

@@ -135,7 +135,7 @@ export default function MangaExtensions() {
makeToast(t('extension.label.installing_file'), 'info'); makeToast(t('extension.label.installing_file'), 'info');
requestManager requestManager
.installExtension(file) .installExtension(file)
.then(() => { .response.then(() => {
makeToast(t('extension.label.installed_successfully'), 'success'); makeToast(t('extension.label.installed_successfully'), 'success');
mutate(); mutate();
}) })

View File

@@ -57,7 +57,9 @@ export default function SourceConfigure() {
}; };
const updateValue = (position: number) => (value: any) => { const updateValue = (position: number) => (value: any) => {
requestManager.setSourcePreferences(sourceId, position, convertToString(position, value)).then(() => mutate()); requestManager
.setSourcePreferences(sourceId, position, convertToString(position, value))
.response.then(() => mutate());
}; };
return ( return (

View File

@@ -108,7 +108,7 @@ export default function SourceMangas({ popular }: { popular: boolean }) {
}; };
}), }),
) )
.then(() => { .response.then(() => {
setTriggerUpdate(0); setTriggerUpdate(0);
makeFilters(); makeFilters();
}); });
@@ -128,7 +128,7 @@ export default function SourceMangas({ popular }: { popular: boolean }) {
setNoreset(undefined); setNoreset(undefined);
setReset(1); setReset(1);
} else if (Noreset === undefined) { } else if (Noreset === undefined) {
requestManager.resetSourceFilters(sourceId).then(() => { requestManager.resetSourceFilters(sourceId).response.then(() => {
makeFilters(); makeFilters();
setSearch(false); setSearch(false);
if (reset === 1) { if (reset === 1) {

View File

@@ -30,7 +30,7 @@ export default function Backup() {
makeToast(t('settings.backup.label.restoring_backup'), 'info'); makeToast(t('settings.backup.label.restoring_backup'), 'info');
requestManager requestManager
.restoreBackupFile(file) .restoreBackupFile(file)
.then(() => makeToast(t('settings.backup.label.restored_backup'), 'success')) .response.then(() => makeToast(t('settings.backup.label.restored_backup'), 'success'))
.catch(() => makeToast(t('settings.backup.label.backup_restore_failed'), 'error')); .catch(() => makeToast(t('settings.backup.label.backup_restore_failed'), 'error'));
} else if (file.name.toLowerCase().endsWith('json')) { } else if (file.name.toLowerCase().endsWith('json')) {
makeToast(t('settings.backup.label.legacy_backup_unsupported'), 'error'); makeToast(t('settings.backup.label.legacy_backup_unsupported'), 'error');

View File

@@ -79,7 +79,7 @@ export default function Categories() {
newData.splice(to, 0, removed); newData.splice(to, 0, removed);
mutate(newData, { revalidate: false }); mutate(newData, { revalidate: false });
requestManager.reorderCategory(from + 1, to + 1).finally(() => mutate()); requestManager.reorderCategory(from + 1, to + 1).response.finally(() => mutate());
}; };
const onDragEnd = (result: DropResult) => { const onDragEnd = (result: DropResult) => {
@@ -117,18 +117,18 @@ export default function Categories() {
setDialogOpen(false); setDialogOpen(false);
if (categoryToEdit === -1) { if (categoryToEdit === -1) {
requestManager.createCategory(dialogName).finally(() => mutate()); requestManager.createCategory(dialogName).response.finally(() => mutate());
} else { } else {
const category = categories[categoryToEdit]; const category = categories[categoryToEdit];
requestManager requestManager
.updateCategory(category.id, { name: dialogName, default: dialogDefault }) .updateCategory(category.id, { name: dialogName, default: dialogDefault })
.finally(() => mutate()); .response.finally(() => mutate());
} }
}; };
const deleteCategory = (index: number) => { const deleteCategory = (index: number) => {
const category = categories[index]; const category = categories[index];
requestManager.deleteCategory(category.id).finally(() => mutate()); requestManager.deleteCategory(category.id).response.finally(() => mutate());
}; };
return ( return (