fetchApi dont throw rejected promises

This commit is contained in:
glax 2025-05-18 19:49:43 +02:00
parent a0d15e08a7
commit f391ace9b2
22 changed files with 109 additions and 59 deletions

View File

@ -5,7 +5,7 @@ import {
CardContent, CardCover,
Link,
} from "@mui/joy";
import IManga, {DefaultManga} from "../api/types/IManga.ts";
import IManga from "../api/types/IManga.ts";
import {CSSProperties, ReactElement, useCallback, useContext, useEffect, useRef, useState} from "react";
import {GetMangaById, GetMangaCoverImageUrl} from "../api/Manga.tsx";
import {ApiUriContext, getData} from "../api/fetchApi.tsx";
@ -16,7 +16,7 @@ import IMangaConnector from "../api/types/IMangaConnector.ts";
import {GetConnector} from "../api/MangaConnector.tsx";
export function MangaFromId({mangaId, children} : { mangaId: string, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined }){
const [manga, setManga] = useState(DefaultManga);
const [manga, setManga] = useState<IManga>();
const [loading, setLoading] = useState(true);
const apiUri = useContext(ApiUriContext);
@ -32,7 +32,7 @@ export function MangaFromId({mangaId, children} : { mangaId: string, children?:
return (
<>
{loading ? <></> : <Manga manga={manga} children={children} /> }
{loading || manga === undefined ? <></> : <Manga manga={manga} children={children} /> }
</>
);
}

View File

@ -35,21 +35,23 @@ export default function MangaList({connected, children}: {connected: boolean, ch
const timerRef = React.useRef<ReturnType<typeof setInterval>>(undefined);
const updateTimer = () => {
if(!connected){
console.debug("Clear timer");
clearTimeout(timerRef.current);
return;
}else{
console.debug("Add timer");
timerRef.current = setInterval(() => {
if(timerRef.current === undefined) {
console.log("Added timer!");
getJobList();
}, 2000);
timerRef.current = setInterval(() => {
getJobList();
}, 2000);
}
}
}
return(
<Stack direction="row" spacing={1} flexWrap={"wrap"}>
{children}
{jobList.map((job) => (
{jobList?.map((job) => (
<MangaFromId key={job.mangaId} mangaId={job.mangaId}>
<Button color={"danger"} endDecorator={<Remove />} onClick={() => deleteJob(job.jobId)}>Delete</Button>
</MangaFromId>

View File

@ -35,7 +35,7 @@ export default function Search({open, setOpen}:{open:boolean, setOpen:React.Disp
const [step, setStep] = useState<number>(1);
const apiUri = useContext(ApiUriContext);
const [mangaConnectors, setMangaConnectors] = useState<IMangaConnector[]>([]);
const [mangaConnectors, setMangaConnectors] = useState<IMangaConnector[]>();
const [mangaConnectorsLoading, setMangaConnectorsLoading] = useState<boolean>(true);
const [selectedMangaConnector, setSelectedMangaConnector] = useState<IMangaConnector>();
@ -83,7 +83,7 @@ export default function Search({open, setOpen}:{open:boolean, setOpen:React.Disp
return (
<React.Fragment>
<ListItemDecorator>
<Avatar size="sm" src={mangaConnectors.find((o) => o.name === option.value)?.iconUrl} />
<Avatar size="sm" src={mangaConnectors?.find((o) => o.name === option.value)?.iconUrl} />
</ListItemDecorator>
{option.label}
</React.Fragment>
@ -101,13 +101,13 @@ export default function Search({open, setOpen}:{open:boolean, setOpen:React.Disp
<ModalClose />
<Stepper orientation={"vertical"} sx={{ height: '100%', width: "calc(100% - 80px)", margin:"40px"}}>
<Step indicator={
<StepIndicator variant={step==1?"solid":"outlined"} color={mangaConnectors.length < 1 ? "danger" : "primary"}>
<StepIndicator variant={step==1?"solid":"outlined"} color={mangaConnectors?.length??0 < 1 ? "danger" : "primary"}>
1
</StepIndicator>}>
<Skeleton loading={mangaConnectorsLoading}>
<Select
color={mangaConnectors.length < 1 ? "danger" : "neutral"}
disabled={mangaConnectorsLoading || resultsLoading || mangaConnectors.length < 1}
color={mangaConnectors?.length??0 < 1 ? "danger" : "neutral"}
disabled={mangaConnectorsLoading || resultsLoading || mangaConnectors?.length == null || mangaConnectors.length < 1}
placeholder={"Select Connector"}
slotProps={{
listbox: {
@ -120,9 +120,9 @@ export default function Search({open, setOpen}:{open:boolean, setOpen:React.Disp
renderValue={renderValue}
onChange={(_e, newValue) => {
setStep(2);
setSelectedMangaConnector(mangaConnectors.find((o) => o.name === newValue));
setSelectedMangaConnector(mangaConnectors?.find((o) => o.name === newValue));
}}
endDecorator={<Chip size={"sm"} color={mangaConnectors.length < 1 ? "danger" : "primary"}>{mangaConnectors.length}</Chip>}>
endDecorator={<Chip size={"sm"} color={mangaConnectors?.length??0 < 1 ? "danger" : "primary"}>{mangaConnectors?.length}</Chip>}>
{mangaConnectors?.map((connector: IMangaConnector) => ConnectorOption(connector))}
</Select>
</Skeleton>

View File

@ -0,0 +1,4 @@
import {createContext} from "react";
import IMangaConnector from "../types/IMangaConnector.ts";
export const MangaConnectorContext = createContext<IMangaConnector[]>([]);

View File

@ -88,10 +88,10 @@ export const CreateUpdateAllMetadataJob = async (apiUri: string) : Promise<strin
return await putData(`${apiUri}/v2/Job/UpdateAllMetadataJob`, {}) as Promise<string[]>;
}
export const StartJob = async (apiUri: string, jobId: string) : Promise<object> => {
export const StartJob = async (apiUri: string, jobId: string) : Promise<object | undefined> => {
return await postData(`${apiUri}/v2/Job/${jobId}/Start`, {});
}
export const StopJob = async (apiUri: string, jobId: string) : Promise<object> => {
export const StopJob = async (apiUri: string, jobId: string) : Promise<object | undefined> => {
return await postData(`${apiUri}/v2/Job/${jobId}/Stop`, {});
}

View File

@ -18,14 +18,14 @@ export const DeleteLibrary = async (apiUri: string, libraryId: string) : Promis
return await deleteData(`${apiUri}/v2/LocalLibraries/${libraryId}`);
}
export const ChangeLibraryPath = async (apiUri: string, libraryId: string, newPath: string) : Promise<object> => {
export const ChangeLibraryPath = async (apiUri: string, libraryId: string, newPath: string) : Promise<object | undefined> => {
return await patchData(`${apiUri}/v2/LocalLibraries/${libraryId}/ChangeBasePath`, newPath);
}
export const ChangeLibraryName = async (apiUri: string, libraryId: string, newName: string) : Promise<object> => {
export const ChangeLibraryName = async (apiUri: string, libraryId: string, newName: string) : Promise<object | undefined> => {
return await patchData(`${apiUri}/v2/LocalLibraries/${libraryId}/ChangeName`, newName);
}
export const UpdateLibrary = async (apiUri: string, libraryId: string, record: INewLibraryRecord) : Promise<object> => {
export const UpdateLibrary = async (apiUri: string, libraryId: string, record: INewLibraryRecord) : Promise<object | undefined> => {
return await patchData(`${apiUri}/v2/LocalLibraries/${libraryId}`, record);
}

View File

@ -1,5 +1,5 @@
import {deleteData, getData, patchData, postData} from './fetchApi.tsx';
import IManga from "./types/IManga.ts";
import IManga, {DefaultManga} from "./types/IManga.ts";
import IChapter from "./types/IChapter.ts";
export const GetAllManga = async (apiUri: string) : Promise<IManga[]> => {
@ -15,63 +15,83 @@ export const GetMangaWithIds = async (apiUri: string, mangaIds: string[]) : Prom
export const GetMangaById = async (apiUri: string, mangaId: string) : Promise<IManga> => {
if(mangaId === undefined || mangaId === null || mangaId.length < 1)
return Promise.reject("mangaId was not provided");
if(mangaId === DefaultManga.mangaId)
return Promise.reject("Default Manga was requested");
return await getData(`${apiUri}/v2/Manga/${mangaId}`) as Promise<IManga>;
}
export const DeleteManga = async (apiUri: string, mangaId: string) : Promise<void> => {
if(mangaId === undefined || mangaId === null || mangaId.length < 1)
return Promise.reject("mangaId was not provided");
if(mangaId === DefaultManga.mangaId)
return Promise.reject("Default Manga was requested");
return await deleteData(`${apiUri}/v2/Manga/${mangaId}`);
}
export const GetMangaCoverImageUrl = (apiUri: string, mangaId: string, ref: HTMLImageElement | undefined | null) : string => {
if(ref == null || ref == undefined)
return `${apiUri}/v2/Manga/${mangaId}/Cover?width=64&height=64`;
if(mangaId === DefaultManga.mangaId)
return "/blahaj.png";
return `${apiUri}/v2/Manga/${mangaId}/Cover?width=${ref.clientWidth}&height=${ref.clientHeight}`;
}
export const GetChapters = async (apiUri: string, mangaId: string) : Promise<IChapter[]> => {
if(mangaId === undefined || mangaId === null || mangaId.length < 1)
return Promise.reject("mangaId was not provided");
if(mangaId === DefaultManga.mangaId)
return Promise.reject("Default Manga was requested");
return await getData(`${apiUri}/v2/Manga/${mangaId}/Chapters`) as Promise<IChapter[]>;
}
export const GetDownloadedChapters = async (apiUri: string, mangaId: string) : Promise<IChapter[]> => {
if(mangaId === undefined || mangaId === null || mangaId.length < 1)
return Promise.reject("mangaId was not provided");
if(mangaId === DefaultManga.mangaId)
return Promise.reject("Default Manga was requested");
return await getData(`${apiUri}/v2/Manga/${mangaId}/Chapters/Downloaded`) as Promise<IChapter[]>;
}
export const GetNotDownloadedChapters = async (apiUri: string, mangaId: string) : Promise<IChapter[]> => {
if(mangaId === undefined || mangaId === null || mangaId.length < 1)
return Promise.reject("mangaId was not provided");
if(mangaId === DefaultManga.mangaId)
return Promise.reject("Default Manga was requested");
return await getData(`${apiUri}/v2/Manga/${mangaId}/Chapters/NotDownloaded`) as Promise<IChapter[]>;
}
export const GetLatestChapterAvailable = async (apiUri: string, mangaId: string) : Promise<IChapter> => {
if(mangaId === undefined || mangaId === null || mangaId.length < 1)
return Promise.reject("mangaId was not provided");
if(mangaId === DefaultManga.mangaId)
return Promise.reject("Default Manga was requested");
return await getData(`${apiUri}/v2/Manga/${mangaId}/Chapter/LatestAvailable`) as Promise<IChapter>;
}
export const GetLatestChapterDownloaded = async (apiUri: string, mangaId: string) : Promise<IChapter> => {
if(mangaId === undefined || mangaId === null || mangaId.length < 1)
return Promise.reject("mangaId was not provided");
if(mangaId === DefaultManga.mangaId)
return Promise.reject("Default Manga was requested");
return await getData(`${apiUri}/v2/Manga/${mangaId}/Chapter/LatestDownloaded`) as Promise<IChapter>;
}
export const SetIgnoreThreshold = async (apiUri: string, mangaId: string, chapterThreshold: number) : Promise<object> => {
export const SetIgnoreThreshold = async (apiUri: string, mangaId: string, chapterThreshold: number) : Promise<object | undefined> => {
if(mangaId === undefined || mangaId === null || mangaId.length < 1)
return Promise.reject("mangaId was not provided");
if(chapterThreshold === undefined || chapterThreshold === null)
return Promise.reject("chapterThreshold was not provided");
if(mangaId === DefaultManga.mangaId)
return Promise.reject("Default Manga was requested");
return await patchData(`${apiUri}/v2/Manga/${mangaId}/IgnoreChaptersBefore`, chapterThreshold);
}
export const MoveFolder = async (apiUri: string, mangaId: string, newPath: string) : Promise<object> => {
export const MoveFolder = async (apiUri: string, mangaId: string, newPath: string) : Promise<object | undefined> => {
if(mangaId === undefined || mangaId === null || mangaId.length < 1)
return Promise.reject("mangaId was not provided");
if(newPath === undefined || newPath === null || newPath.length < 1)
return Promise.reject("newPath was not provided");
if(mangaId === DefaultManga.mangaId)
return Promise.reject("Default Manga was requested");
return await postData(`${apiUri}/v2/Manga/{MangaId}/MoveFolder`, {newPath});
}

View File

@ -17,7 +17,7 @@ export const GetDisabledConnectors = async (apiUri: string) : Promise<IMangaCon
return await getData(`${apiUri}/v2/MangaConnector/disabled`) as Promise<IMangaConnector[]>
}
export const SetConnectorEnabled = async (apiUri: string, connectorName: string, enabled: boolean) : Promise<object> => {
export const SetConnectorEnabled = async (apiUri: string, connectorName: string, enabled: boolean) : Promise<object | undefined> => {
if(connectorName === undefined || connectorName === null || connectorName.length < 1)
return Promise.reject("connectorName was not provided");
if(enabled === undefined || enabled === null)

View File

@ -2,24 +2,33 @@ import {createContext} from "react";
export const ApiUriContext = createContext<string>("");
export function getData(uri: string) : Promise<object> {
return makeRequest("GET", uri, null) as Promise<object>;
export function getData(uri: string) : Promise<object | undefined> {
return makeRequestWrapper("GET", uri, null);
}
export function postData(uri: string, content: object | string | number | boolean) : Promise<object> {
return makeRequest("POST", uri, content) as Promise<object>;
export function postData(uri: string, content: object | string | number | boolean) : Promise<object | undefined> {
return makeRequestWrapper("POST", uri, content);
}
export function deleteData(uri: string) : Promise<void> {
return makeRequest("DELETE", uri, null) as Promise<void>;
return makeRequestWrapper("DELETE", uri, null) as Promise<void>;
}
export function patchData(uri: string, content: object | string | number | boolean) : Promise<object> {
return makeRequest("patch", uri, content) as Promise<object>;
export function patchData(uri: string, content: object | string | number | boolean) : Promise<object | undefined> {
return makeRequestWrapper("patch", uri, content);
}
export function putData(uri: string, content: object | string | number | boolean) : Promise<object> {
return makeRequest("PUT", uri, content) as Promise<object>;
export function putData(uri: string, content: object | string | number | boolean) : Promise<object | undefined> {
return makeRequestWrapper("PUT", uri, content);
}
function makeRequestWrapper(method: string, uri: string, content: object | string | number | null | boolean) : Promise<object | undefined>{
return makeRequest(method, uri, content)
.then((result) => result as Promise<object>)
.catch((e) => {
console.warn(e);
return Promise.resolve(undefined);
});
}
let currentlyRequestedEndpoints: string[] = [];

View File

@ -1,5 +1,5 @@
import IJob from "./IJob";
import IJobWithMangaId from "./IJobWithMangaId.ts";
export default interface IDownloadAvailableChaptersJob extends IJobWithMangaId {
export default interface IDownloadAvailableChaptersJob extends IJob {
mangaId: string;
}

View File

@ -1,5 +1,5 @@
import IJob from "./IJob";
import IJobWithMangaId from "./IJobWithMangaId.ts";
export default interface IDownloadMangaCoverJob extends IJobWithMangaId {
export default interface IDownloadMangaCoverJob extends IJob {
mangaId: string;
}

View File

@ -1,5 +1,5 @@
import IJob from "./IJob";
import IJobWithChapterId from "./IJobWithChapterId.tsx";
export default interface IDownloadSingleChapterJob extends IJobWithChapterId {
export default interface IDownloadSingleChapterJob extends IJob {
chapterId: string;
}

View File

@ -18,7 +18,8 @@ export enum JobType {
RetrieveChaptersJob = "RetrieveChaptersJob",
UpdateChaptersDownloadedJob = "UpdateChaptersDownloadedJob",
MoveMangaLibraryJob = "MoveMangaLibraryJob",
UpdateSingleChapterDownloadedJob = "UpdateSingleChapterDownloadedJob"
UpdateSingleChapterDownloadedJob = "UpdateSingleChapterDownloadedJob",
UpdateCoverJob = "UpdateCoverJob"
}
export enum JobState {

View File

@ -0,0 +1,5 @@
import IJob from "./IJob.ts";
export default interface IJobWithChapterId extends IJob {
chapterId: string;
}

View File

@ -0,0 +1,5 @@
import IJob from "./IJob.ts";
export default interface IJobWithMangaId extends IJob {
mangaId: string;
}

View File

@ -1,6 +1,5 @@
import IJob from "./IJob";
import IJobWithMangaId from "./IJobWithMangaId.ts";
export default interface IMoveMangaLibraryJob extends IJob {
MangaId: string;
export default interface IMoveMangaLibraryJob extends IJobWithMangaId {
ToLibraryId: string;
}

View File

@ -1,5 +1,5 @@
import IJob from "./IJob";
import IJobWithMangaId from "./IJobWithMangaId.ts";
export default interface IRetrieveChaptersJob extends IJobWithMangaId {
export default interface IRetrieveChaptersJob extends IJob {
mangaId: string;
}

View File

@ -0,0 +1,5 @@
import IJobWithMangaId from "./IJobWithMangaId.ts";
export default interface IUpdateChaptersDownloadedJob extends IJobWithMangaId {
}

View File

@ -0,0 +1,5 @@
import IJobWithMangaId from "./IJobWithMangaId.ts";
export default interface IUpdateCoverJob extends IJobWithMangaId {
}

View File

@ -1,5 +0,0 @@
import IJob from "./IJob";
export default interface IUpdateFilesDownloadedJob extends IJob {
mangaId: string;
}

View File

@ -1,5 +0,0 @@
import IJob from "./IJob";
export default interface IUpdateMetadataJob extends IJob {
mangaId: string;
}

View File

@ -0,0 +1,5 @@
import IJobWithChapterId from "./IJobWithChapterId.tsx";
export default interface IUpdateChaptersDownloadedJob extends IJobWithChapterId {
}