MangaList

This commit is contained in:
glax 2025-04-01 19:05:57 +02:00
parent 63b220bd79
commit 9e0eb0262e
5 changed files with 112 additions and 47 deletions

View File

@ -6,6 +6,7 @@ import {Badge, Button} from "@mui/joy";
import {useState} from "react"; import {useState} from "react";
import {ApiUriContext} from "./api/fetchApi.tsx"; import {ApiUriContext} from "./api/fetchApi.tsx";
import Search from './Components/Search.tsx'; import Search from './Components/Search.tsx';
import MangaList from "./Components/MangaList.tsx";
export default function App () { export default function App () {
@ -27,7 +28,7 @@ export default function App () {
<Settings open={showSettings} setOpen={setShowSettings} setApiUri={setApiUri} setConnected={setApiConnected} /> <Settings open={showSettings} setOpen={setShowSettings} setApiUri={setApiUri} setConnected={setApiConnected} />
<Search open={showSearch} setOpen={setShowSearch} /> <Search open={showSearch} setOpen={setShowSearch} />
<Sheet className={"app-content"}> <Sheet className={"app-content"}>
<MangaList />
</Sheet> </Sheet>
</Sheet> </Sheet>
</ApiUriContext.Provider> </ApiUriContext.Provider>

View File

@ -13,7 +13,7 @@ import {
} from "@mui/joy"; } from "@mui/joy";
import IManga, {DefaultManga} from "../api/types/IManga.ts"; import IManga, {DefaultManga} from "../api/types/IManga.ts";
import {ReactElement, useCallback, useContext, useEffect, useState} from "react"; import {ReactElement, useCallback, useContext, useEffect, useState} from "react";
import {GetLatestChapterAvailable, GetMangaCoverImageUrl, SetIgnoreThreshold} from "../api/Manga.tsx"; import {GetLatestChapterAvailable, GetMangaById, GetMangaCoverImageUrl, SetIgnoreThreshold} from "../api/Manga.tsx";
import {ApiUriContext} from "../api/fetchApi.tsx"; import {ApiUriContext} from "../api/fetchApi.tsx";
import AuthorTag from "./AuthorTag.tsx"; import AuthorTag from "./AuthorTag.tsx";
import LinkTag from "./LinkTag.tsx"; import LinkTag from "./LinkTag.tsx";
@ -22,8 +22,27 @@ import IChapter from "../api/types/IChapter.ts";
import MarkdownPreview from "@uiw/react-markdown-preview"; import MarkdownPreview from "@uiw/react-markdown-preview";
import {SxProps} from "@mui/joy/styles/types"; import {SxProps} from "@mui/joy/styles/types";
export function Manga({manga, children} : { manga: IManga | undefined, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined }) { export function MangaFromId({mangaId, children} : { mangaId: string, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined }){
const [manga, setManga] = useState(DefaultManga);
const [loading, setLoading] = useState(true);
const apiUri = useContext(ApiUriContext);
const loadManga = useCallback(() => {
setLoading(true);
GetMangaById(apiUri, mangaId).then(setManga).finally(() => setLoading(false));
},[apiUri, mangaId]);
useEffect(() => {
loadManga();
}, []);
return <Manga manga={manga} loading={loading} children={children} />
}
export function Manga({manga, children, loading} : { manga: IManga | undefined, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined, loading?: boolean}) {
const useManga = manga ?? DefaultManga; const useManga = manga ?? DefaultManga;
loading = loading ?? false;
const apiUri = useContext(ApiUriContext); const apiUri = useContext(ApiUriContext);
@ -79,19 +98,25 @@ export function Manga({manga, children} : { manga: IManga | undefined, children?
}}/> }}/>
<CardContent sx={{display: "flex", alignItems: "center", flexFlow: "row nowrap"}}> <CardContent sx={{display: "flex", alignItems: "center", flexFlow: "row nowrap"}}>
<Box sx={sideSx}> <Box sx={sideSx}>
<Link href={useManga.websiteUrl} level={"h1"} sx={{height:"min-content",width:"fit-content",color:"white",margin:"0 0 0 10px"}}> <Skeleton loading={loading}>
{useManga.name} <Link href={useManga.websiteUrl} level={"h1"} sx={{height:"min-content",width:"fit-content",color:"white",margin:"0 0 0 10px"}}>
</Link> {useManga.name}
</Link>
</Skeleton>
</Box> </Box>
{ {
expanded ? expanded ?
<Box sx={sideSx}> <Box sx={sideSx}>
<Stack direction={"row"} flexWrap={"wrap"} spacing={0.5}> <Skeleton loading={loading} variant={"text"} level={"title-lg"}>
{useManga.authorIds.map(authorId => <AuthorTag key={authorId} authorId={authorId} color={"success"} />)} <Stack direction={"row"} flexWrap={"wrap"} spacing={0.5}>
{useManga.tags.map(tag => <Chip key={tag} variant={"outlined"} size={"md"} color={"primary"}>{tag}</Chip>)} {useManga.authorIds.map(authorId => <AuthorTag key={authorId} authorId={authorId} color={"success"} />)}
{useManga.linkIds.map(linkId => <LinkTag key={linkId} linkId={linkId} color={"danger"} />)} {useManga.tags.map(tag => <Chip key={tag} variant={"outlined"} size={"md"} color={"primary"}>{tag}</Chip>)}
</Stack> {useManga.linkIds.map(linkId => <LinkTag key={linkId} linkId={linkId} color={"danger"} />)}
<MarkdownPreview source={useManga.description} style={{backgroundColor: "transparent", color: "black"}} /> </Stack>
</Skeleton>
<Skeleton loading={loading} sx={{maxHeight:"300px"}}>
<MarkdownPreview source={useManga.description} style={{backgroundColor: "transparent", color: "black"}} />
</Skeleton>
</Box> </Box>
: null : null
} }
@ -99,30 +124,32 @@ export function Manga({manga, children} : { manga: IManga | undefined, children?
{ {
expanded ? expanded ?
<CardActions sx={{justifyContent:"space-between"}}> <CardActions sx={{justifyContent:"space-between"}}>
<Input <Skeleton loading={loading} sx={{maxHeight: "30px", maxWidth:"calc(100% - 40px)"}}>
type={"number"} <Input
placeholder={"0.0"} type={"number"}
startDecorator={ placeholder={"0.0"}
<> startDecorator={
{ <>
updatingThreshold ? {
<CircularProgress color={"primary"} size={"sm"} /> updatingThreshold ?
: <Typography>Ch.</Typography> <CircularProgress color={"primary"} size={"sm"} />
: <Typography>Ch.</Typography>
}
</>
} }
</> endDecorator={
} <Typography>
endDecorator={ <Skeleton loading={maxChapterLoading}>
<Typography> /{mangaMaxChapter?.chapterNumber??"Load Failed"}
<Skeleton loading={maxChapterLoading}> </Skeleton>
/{mangaMaxChapter?.chapterNumber??"Load Failed"} </Typography>
</Skeleton> }
</Typography> sx={{width:"min-content"}}
} size={"md"}
sx={{width:"min-content"}} onChange={(e) => updateIgnoreThreshhold(e.currentTarget.valueAsNumber)}
size={"md"} />
onChange={(e) => updateIgnoreThreshhold(e.currentTarget.valueAsNumber)} {children}
/> </Skeleton>
{children}
</CardActions> </CardActions>
: null : null
} }

View File

@ -0,0 +1,36 @@
import {Button, Stack} from "@mui/joy";
import {useCallback, useContext, useEffect, useState} from "react";
import {ApiUriContext} from "../api/fetchApi.tsx";
import {DeleteJob, GetJobsWithType} from "../api/Job.tsx";
import {JobType} from "../api/types/Jobs/IJob.ts";
import IDownloadAvailableChaptersJob from "../api/types/Jobs/IDownloadAvailableChaptersJob.ts";
import {MangaFromId} from "./Manga.tsx";
import { Remove } from "@mui/icons-material";
export default function MangaList(){
const apiUri = useContext(ApiUriContext);
const [jobList, setJobList] = useState<IDownloadAvailableChaptersJob[]>([]);
const getJobList = useCallback(() => {
GetJobsWithType(apiUri, JobType.DownloadAvailableChaptersJob).then((jl) => setJobList(jl as IDownloadAvailableChaptersJob[]));
},[apiUri]);
const deleteJob = useCallback((jobId: string) => {
DeleteJob(apiUri, jobId).finally(() => getJobList());
},[apiUri]);
useEffect(() => {
getJobList();
}, [apiUri]);
return(
<Stack direction="row" spacing={1}>
{jobList.map((job) => (
<MangaFromId key={job.mangaId} mangaId={job.mangaId}>
<Button color={"danger"} endDecorator={<Remove />} onClick={() => deleteJob(job.jobId)}>Delete</Button>
</MangaFromId>
))}
</Stack>
);
}

View File

@ -149,6 +149,7 @@ export default function Search({open, setOpen}:{open:boolean, setOpen:React.Disp
placeholder={"Select Library"} placeholder={"Select Library"}
defaultValue={""} defaultValue={""}
startDecorator={<LibraryBooks />} startDecorator={<LibraryBooks />}
value={selectedLibraryId}
onChange={(_e, newValue) => setSelectedLibraryId(newValue!)}> onChange={(_e, newValue) => setSelectedLibraryId(newValue!)}>
{localLibrariesLoading ? {localLibrariesLoading ?
<Option value={""} disabled>Loading <CircularProgress color={"primary"} size={"sm"} /></Option> <Option value={""} disabled>Loading <CircularProgress color={"primary"} size={"sm"} /></Option>

View File

@ -19,19 +19,19 @@ export default interface IManga{
} }
export const DefaultManga : IManga = { export const DefaultManga : IManga = {
mangaId: "MangaId", mangaId: "Loading",
idOnConnectorSite: "ID", idOnConnectorSite: "Loading",
name: "TestManga", name: "Loading",
description: "Wow so much text, very cool", description: "Loading",
websiteUrl: "https://realsite.realdomain", websiteUrl: "",
year: 1999, year: 1999,
originalLanguage: "lindtChoccy", originalLanguage: "en",
releaseStatus: MangaReleaseStatus.Continuing, releaseStatus: MangaReleaseStatus.Continuing,
folderName: "uhhh", folderName: "Loading",
ignoreChapterBefore: 0, ignoreChapterBefore: 0,
mangaConnectorId: "MangaDex", mangaConnectorId: "Loading",
authorIds: ["We got", "Authors"], authorIds: ["Loading"],
tags: ["And we", "got Tags"], tags: ["Loading"],
linkIds: ["And most", "definitely", "links"], linkIds: ["Loading"],
altTitleIds: ["But not alt-titles."], altTitleIds: ["Loading"],
} }