diff --git a/tranga-website/src/App.tsx b/tranga-website/src/App.tsx index ed94916..ee1a9c9 100644 --- a/tranga-website/src/App.tsx +++ b/tranga-website/src/App.tsx @@ -10,21 +10,69 @@ import MangaList from "./Components/MangaList.tsx"; import {MangaConnectorContext} from "./api/Contexts/MangaConnectorContext.tsx"; import IMangaConnector from "./api/types/IMangaConnector.ts"; import {GetAllConnectors} from "./api/MangaConnector.tsx"; +import JobsDrawer from "./Components/Jobs.tsx"; +import {MangaContext} from "./api/Contexts/MangaContext.tsx"; +import IManga from "./api/types/IManga.ts"; +import {GetMangaById} from "./api/Manga.tsx"; +import IChapter from "./api/types/IChapter.ts"; +import {GetChapterFromId} from "./api/Chapter.tsx"; +import {ChapterContext} from "./api/Contexts/ChapterContext.tsx"; export default function App () { const [showSettings, setShowSettings] = useState(false); const [showSearch, setShowSearch] = useState(false); + const [showJobs, setShowJobs] = useState(false); const [apiConnected, setApiConnected] = useState(false); const apiUriStr = localStorage.getItem("apiUri") ?? window.location.href.substring(0, window.location.href.lastIndexOf("/")); const [apiUri, setApiUri] = useState(apiUriStr); + const [mangas, setMangas] = useState([]); + const [chapters, setChapters] = useState([]); useEffect(() => { localStorage.setItem("apiUri", apiUri); }, [apiUri]); + const [mangaPromises, setMangaPromises] = useState(new Map>()); + const GetManga = (mangaId: string) : Promise => { + const promise = mangaPromises.get(mangaId); + if(promise) return promise; + const p = new Promise((resolve, reject) => { + let ret = mangas?.find(m => m.mangaId == mangaId); + if (ret) resolve(ret); + + console.log(`Fetching manga ${mangaId}`); + GetMangaById(apiUri, mangaId).then(manga => { + if(manga && mangas?.find(m => m.mangaId == mangaId) === undefined) + setMangas([...mangas, manga]); + resolve(manga); + }).catch(reject); + }); + setMangaPromises(mangaPromises.set(mangaId, p)); + return p; + } + + const [chapterPromises, setChapterPromises] = useState(new Map>()); + const GetChapter = (chapterId: string) : Promise => { + const promise = chapterPromises.get(chapterId); + if(promise) return promise; + const p = new Promise((resolve, reject) => { + let ret = chapters?.find(c => c.chapterId == chapterId); + if (ret) resolve(ret); + + console.log(`Fetching chapter ${chapterId}`); + GetChapterFromId(apiUri, chapterId).then(chapter => { + if(chapter && chapters?.find(c => c.chapterId == chapterId) === undefined) + setChapters([...chapters, chapter]); + resolve(chapter); + }).catch(reject); + }); + setChapterPromises(chapterPromises.set(chapterId, p)); + return p; + } + const [mangaConnectors, setMangaConnectors] = useState([]); useEffect(() => { @@ -35,18 +83,24 @@ export default function App () { return ( - -
- - - -
- - - - - -
+ + + +
+ + + + +
+ + + + + + +
+
+
); diff --git a/tranga-website/src/Components/Chapter.tsx b/tranga-website/src/Components/Chapter.tsx new file mode 100644 index 0000000..d684833 --- /dev/null +++ b/tranga-website/src/Components/Chapter.tsx @@ -0,0 +1,61 @@ +import {ReactElement, useContext, useState} from "react"; +import IChapter from "../api/types/IChapter.ts"; +import {Card, CardActions, CardContent, Chip, Link, Stack, Tooltip, Typography} from "@mui/joy"; +import {MangaFromId} from "./Manga.tsx"; +import {ChapterContext} from "../api/Contexts/ChapterContext.tsx"; +import { Description } from "@mui/icons-material"; + +export function ChapterFromId({chapterId, children} : { chapterId: string, children?: ReactElement | ReactElement[] | undefined }){ + const chapterContext = useContext(ChapterContext); + + const [chapter, setChapter] = useState(undefined); + chapterContext.GetChapter(chapterId).then(setChapter); + + return ( + chapter === undefined ? + + + + + + + + + + + + {children} + + + + + + : + {children} + ); +} + +export function Chapter({chapter, children} : { chapter: IChapter, children?: ReactElement | ReactElement[] | undefined }){ + return ( + + + + + + + {chapter.title} + Vol. {chapter.volumeNumber} + Ch. {chapter.chapterNumber} + + + + + + {children} + + + + + + ); +} \ No newline at end of file diff --git a/tranga-website/src/Components/Manga.tsx b/tranga-website/src/Components/Manga.tsx index 69d2dd6..57049d0 100644 --- a/tranga-website/src/Components/Manga.tsx +++ b/tranga-website/src/Components/Manga.tsx @@ -1,12 +1,13 @@ import {Badge, Box, Card, CardContent, CardCover, Skeleton, Typography,} from "@mui/joy"; 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 {GetMangaCoverImageUrl} from "../api/Manga.tsx"; import {ApiUriContext, getData} from "../api/fetchApi.tsx"; import {MangaReleaseStatus, ReleaseStatusToPalette} from "../api/types/EnumMangaReleaseStatus.ts"; import {SxProps} from "@mui/joy/styles/types"; import MangaPopup from "./MangaPopup.tsx"; import {MangaConnectorContext} from "../api/Contexts/MangaConnectorContext.tsx"; +import {MangaContext} from "../api/Contexts/MangaContext.tsx"; export const CardWidth = 190; export const CardHeight = 300; @@ -23,21 +24,13 @@ const coverCss : CSSProperties = { } export function MangaFromId({mangaId, children} : { mangaId: string, children?: ReactElement | ReactElement[] | undefined }){ - const [manga, setManga] = useState(); + const mangaContext = useContext(MangaContext); - const apiUri = useContext(ApiUriContext); - - const loadManga = useCallback(() => { - GetMangaById(apiUri, mangaId).then(setManga); - },[apiUri, mangaId]); - - useEffect(() => { - loadManga(); - }, []); + const [manga, setManga] = useState(undefined); + mangaContext.GetManga(mangaId).then(setManga); return ( - <> - {manga === undefined ? + manga === undefined ? } color={ReleaseStatusToPalette(MangaReleaseStatus.Completed)} size={"lg"}> @@ -51,7 +44,7 @@ export function MangaFromId({mangaId, children} : { mangaId: string, children?: - {"x ".repeat(Math.random()*25+5)} + {mangaId.split("").splice(0,mangaId.length/2).join(" ")} @@ -59,8 +52,7 @@ export function MangaFromId({mangaId, children} : { mangaId: string, children?: : - } - + ); } @@ -91,7 +83,8 @@ export function Manga({manga: manga, children} : { manga: IManga, children?: Rea const interactiveElements = ["button", "input", "textarea", "a", "select", "option", "li"]; - const mangaName = manga.name.length > 30 ? manga.name.substring(0, 27) + "..." : manga.name; + const maxLength = 50; + const mangaName = manga.name.length > maxLength ? manga.name.substring(0, maxLength-3) + "..." : manga.name; return ( : manga.mangaConnectorName} color={ReleaseStatusToPalette(manga.releaseStatus)} size={"lg"}> diff --git a/tranga-website/src/api/Contexts/ChapterContext.tsx b/tranga-website/src/api/Contexts/ChapterContext.tsx new file mode 100644 index 0000000..4023eac --- /dev/null +++ b/tranga-website/src/api/Contexts/ChapterContext.tsx @@ -0,0 +1,9 @@ +import {createContext} from "react"; +import IChapter from "../types/IChapter.ts"; + +export const ChapterContext = createContext<{chapters: IChapter[], GetChapter: (chapterId: string) => Promise}>( + { + chapters : [], + GetChapter: _ => Promise.resolve(undefined) + } +); \ No newline at end of file diff --git a/tranga-website/src/api/Contexts/MangaContext.tsx b/tranga-website/src/api/Contexts/MangaContext.tsx new file mode 100644 index 0000000..ec7b19e --- /dev/null +++ b/tranga-website/src/api/Contexts/MangaContext.tsx @@ -0,0 +1,9 @@ +import {createContext} from "react"; +import IManga, {DefaultManga} from "../types/IManga.ts"; + +export const MangaContext = createContext<{mangas: IManga[], GetManga: (mangaId: string) => Promise}>( + { + mangas : [], + GetManga: _ => Promise.resolve(DefaultManga) + } +); \ No newline at end of file