From 9846ffb78996567b45996076256937f9881157ab Mon Sep 17 00:00:00 2001 From: glax Date: Tue, 2 Sep 2025 00:46:57 +0200 Subject: [PATCH] Use new API layout with MinimalManga --- tranga-website/src/App.tsx | 71 +++++++----- .../src/Components/MangaConnectorLink.tsx | 7 +- .../src/Components/Mangas/MangaCard.tsx | 46 ++++---- .../Components/Mangas/MangaConnectorBadge.tsx | 10 +- .../Components/Mangas/MangaDownloadDialog.tsx | 18 ++- .../src/Components/Mangas/MangaList.tsx | 14 +-- .../src/Components/Mangas/MangaMerge.tsx | 33 +++--- tranga-website/src/Components/Search.tsx | 6 +- .../src/Components/Settings/Download.tsx | 20 ++-- .../LibraryConnectors/AddLibraryConnector.tsx | 40 ++++--- .../LibraryConnectors/LibraryConnectors.tsx | 30 ++--- .../ListLibraryConnectors.tsx | 61 +++++----- .../ListNotificationConnector.tsx | 109 ++++++++++-------- .../src/Components/Settings/Services.tsx | 21 ++-- .../src/Components/Settings/Settings.tsx | 3 +- .../src/Components/WorkerModal/Workers.tsx | 17 +-- tranga-website/src/apiClient/V2.ts | 11 +- .../src/apiClient/data-contracts.ts | 14 +++ 18 files changed, 306 insertions(+), 225 deletions(-) diff --git a/tranga-website/src/App.tsx b/tranga-website/src/App.tsx index b3ba0cf..e9ab1f8 100644 --- a/tranga-website/src/App.tsx +++ b/tranga-website/src/App.tsx @@ -10,29 +10,56 @@ import { FileLibrary, Manga, MangaConnector, + MinimalManga, } from "./apiClient/data-contracts.ts"; import Search from "./Components/Search.tsx"; import { Typography } from "@mui/joy"; import Workers from "./Components/WorkerModal/Workers.tsx"; +const apiUri = + localStorage.getItem("apiUri") ?? + window.location.href.substring(0, window.location.href.lastIndexOf("/")) + + "/api"; +localStorage.setItem("apiUri", apiUri); +const Api = new V2({ baseUrl: apiUri }); + +const manga: Manga[] = []; +const promises: Map> = new Map(); +const getManga = async (key: string): Promise => { + let result = manga.find((m) => m.key === key); + if (result) return result; + if (promises.has(key)) return promises.get(key); + const newPromise = retrieveManga(key); + promises.set(key, newPromise); + return newPromise; +}; + +const retrieveManga = async (key: string): Promise => { + return Api.mangaDetail(key).then((response) => { + if (response.ok) { + manga.push(response.data); + return response.data; + } + return undefined; + }); +}; + export const MangaConnectorContext = createContext([]); -export const MangaContext = createContext([]); +export const MangaContext = createContext<{ + getManga: (key: string) => Promise; +}>({ + getManga, +}); export const FileLibraryContext = createContext([]); -export default function App() { - const apiUriStr = - localStorage.getItem("apiUri") ?? - window.location.href.substring(0, window.location.href.lastIndexOf("/")) + - "/api"; - const [apiUri, setApiUri] = useState(apiUriStr); - const [Api, setApi] = useState( - new V2({ - baseUrl: apiUri, - }), - ); +const updateApiUri = (uri: string) => { + localStorage.setItem("apiUri", uri); + window.location.reload(); +}; +export default function App() { const [mangaConnectors, setMangaConnectors] = useState([]); - const [manga, setManga] = useState([]); + const [downloadingManga, setDownloadingManga] = useState([]); const [fileLibraries, setFileLibraries] = useState([]); useEffect(() => { @@ -45,33 +72,23 @@ export default function App() { }); Api.mangaDownloadingList().then((response) => { - if (response.ok) setManga(response.data); + if (response.ok) setDownloadingManga(response.data); }); }, []); - useEffect(() => { - localStorage.setItem("apiUri", apiUri); - if (Api.baseUrl != apiUri) - setApi( - new V2({ - baseUrl: apiUri, - }), - ); - }, [apiUri]); - return ( - + {Api ? (
- +
- + diff --git a/tranga-website/src/Components/MangaConnectorLink.tsx b/tranga-website/src/Components/MangaConnectorLink.tsx index ea837b1..810797b 100644 --- a/tranga-website/src/Components/MangaConnectorLink.tsx +++ b/tranga-website/src/Components/MangaConnectorLink.tsx @@ -57,7 +57,12 @@ export default function MangaConnectorLink({ } > - + {printName ? {mangaConnector?.name} : null} diff --git a/tranga-website/src/Components/Mangas/MangaCard.tsx b/tranga-website/src/Components/Mangas/MangaCard.tsx index b18a8cb..dd46493 100644 --- a/tranga-website/src/Components/Mangas/MangaCard.tsx +++ b/tranga-website/src/Components/Mangas/MangaCard.tsx @@ -12,12 +12,13 @@ import { Tooltip, Typography, } from "@mui/joy"; -import { Manga } from "../../apiClient/data-contracts.ts"; +import { Manga, MinimalManga } from "../../apiClient/data-contracts.ts"; import { Dispatch, ReactNode, SetStateAction, useContext, + useEffect, useState, } from "react"; import "./MangaCard.css"; @@ -28,18 +29,11 @@ import MarkdownPreview from "@uiw/react-markdown-preview"; import { MangaContext } from "../../App.tsx"; import { MangaConnectorLinkFromId } from "../MangaConnectorLink.tsx"; -export function MangaCardFromId({ mangaId }: { mangaId: string }) { - const mangas = useContext(MangaContext); - const manga = mangas.find((manga) => manga.key === mangaId); - - return ; -} - export function MangaCard({ manga, children, }: { - manga: Manga | undefined; + manga: MinimalManga | undefined; children?: ReactNode; }) { if (manga === undefined) return PlaceHolderCard(); @@ -50,14 +44,14 @@ export function MangaCard({ setOpen(true)}> - + {manga?.name} - + {children} @@ -65,16 +59,22 @@ export function MangaCard({ } export function MangaModal({ - manga, + minimalManga, open, setOpen, children, }: { - manga: Manga | undefined; + minimalManga: MinimalManga; open: boolean; setOpen: Dispatch>; children?: ReactNode; }) { + const { getManga } = useContext(MangaContext); + const [manga, setManga] = useState(); + useEffect(() => { + getManga(minimalManga.key).then(setManga); + }, []); + return ( setOpen(false)} className={"manga-modal"}> @@ -89,12 +89,12 @@ export function MangaModal({ } > - {manga?.name} + {manga?.name ?? minimalManga.name} - + - {children} + {manga ? children : null} @@ -156,10 +160,10 @@ function PlaceHolderCard() { ); } -function MangaCover({ manga }: { manga: Manga | undefined }) { +function MangaCover({ mangaId }: { mangaId?: string }) { const api = useContext(ApiContext); - const uri = manga - ? `${api.baseUrl}/v2/Manga/${manga?.key}/Cover` + const uri = mangaId + ? `${api.baseUrl}/v2/Manga/${mangaId}/Cover` : "blahaj.png"; return ( diff --git a/tranga-website/src/Components/Mangas/MangaConnectorBadge.tsx b/tranga-website/src/Components/Mangas/MangaConnectorBadge.tsx index 85c17b8..3c39d8d 100644 --- a/tranga-website/src/Components/Mangas/MangaConnectorBadge.tsx +++ b/tranga-website/src/Components/Mangas/MangaConnectorBadge.tsx @@ -1,20 +1,20 @@ import { Badge } from "@mui/joy"; -import { Manga } from "../../apiClient/data-contracts.ts"; +import { MinimalManga } from "../../apiClient/data-contracts.ts"; import { ReactElement } from "react"; import "./MangaCard.css"; -import { MangaConnectorLinkFromId } from "../MangaConnectorLink.tsx"; +import MangaConnectorLink from "../MangaConnectorLink.tsx"; export default function MangaConnectorBadge({ manga, children, }: { - manga: Manga; + manga: MinimalManga; children?: ReactElement | ReactElement[] | undefined; }) { return ( ( - + badgeContent={manga.mangaConnectorIds?.map((id) => ( + ))} > {children} diff --git a/tranga-website/src/Components/Mangas/MangaDownloadDialog.tsx b/tranga-website/src/Components/Mangas/MangaDownloadDialog.tsx index f2127c6..336cb69 100644 --- a/tranga-website/src/Components/Mangas/MangaDownloadDialog.tsx +++ b/tranga-website/src/Components/Mangas/MangaDownloadDialog.tsx @@ -12,12 +12,17 @@ import Drawer from "@mui/joy/Drawer"; import ModalClose from "@mui/joy/ModalClose"; import { MangaConnectorLinkFromId } from "../MangaConnectorLink.tsx"; import Sheet from "@mui/joy/Sheet"; -import { FileLibraryContext } from "../../App.tsx"; +import { FileLibraryContext, MangaContext } from "../../App.tsx"; import { ApiContext } from "../../apiClient/ApiContext.tsx"; import { LoadingState, StateIndicator } from "../Loading.tsx"; -export default function ({ manga }: { manga: Manga }): ReactNode { +export default function ({ mangaId }: { mangaId: string }): ReactNode { const [open, setOpen] = useState(false); + const { getManga } = useContext(MangaContext); + const [manga, setManga] = useState(); + useEffect(() => { + getManga(mangaId).then(setManga); + }, []); return ( <> @@ -32,7 +37,7 @@ function DownloadDrawer({ open, setOpen, }: { - manga: Manga; + manga: Manga | undefined; open: boolean; setOpen: Dispatch; }): ReactNode { @@ -40,7 +45,8 @@ function DownloadDrawer({ const Api = useContext(ApiContext); const onLibraryChange = (_: any, value: {} | null) => { - if (value === undefined) return; + if (!value) return; + if (!manga) return; Api.mangaChangeLibraryCreate(manga.key as string, value as string); }; @@ -52,7 +58,7 @@ function DownloadDrawer({ Download from: - {manga.mangaConnectorIdsIds?.map((id) => ( + {manga?.mangaConnectorIdsIds?.map((id) => ( ))} diff --git a/tranga-website/src/Components/Mangas/MangaList.tsx b/tranga-website/src/Components/Mangas/MangaList.tsx index 8a0f050..60ce9cf 100644 --- a/tranga-website/src/Components/Mangas/MangaList.tsx +++ b/tranga-website/src/Components/Mangas/MangaList.tsx @@ -2,15 +2,15 @@ import { ReactNode } from "react"; import { MangaCard } from "./MangaCard.tsx"; import { Stack } from "@mui/joy"; import "./MangaList.css"; -import { Manga } from "../../apiClient/data-contracts.ts"; +import { MinimalManga } from "../../apiClient/data-contracts.ts"; import MangaDownloadDialog from "./MangaDownloadDialog.tsx"; import MangaMerge from "./MangaMerge.tsx"; export default function MangaList({ - mangas, + manga, children, }: { - mangas: Manga[]; + manga: MinimalManga[]; children?: ReactNode; }) { return ( @@ -27,10 +27,10 @@ export default function MangaList({ }} > {children} - {mangas?.map((manga) => ( - - - + {manga?.map((minimalManga) => ( + + + ))} diff --git a/tranga-website/src/Components/Mangas/MangaMerge.tsx b/tranga-website/src/Components/Mangas/MangaMerge.tsx index ea8e367..fde086c 100644 --- a/tranga-website/src/Components/Mangas/MangaMerge.tsx +++ b/tranga-website/src/Components/Mangas/MangaMerge.tsx @@ -1,5 +1,5 @@ import { ReactNode, useContext, useEffect, useState } from "react"; -import { Manga } from "../../apiClient/data-contracts.ts"; +import { Manga, MinimalManga } from "../../apiClient/data-contracts.ts"; import Drawer from "@mui/joy/Drawer"; import ModalClose from "@mui/joy/ModalClose"; import { ApiContext } from "../../apiClient/ApiContext.tsx"; @@ -16,10 +16,14 @@ import { import { KeyboardDoubleArrowRight, Warning } from "@mui/icons-material"; import { LoadingState, StateIndicator } from "../Loading.tsx"; -export default function ({ manga }: { manga: Manga | undefined }): ReactNode { +export default function ({ + manga, +}: { + manga: MinimalManga | undefined; +}): ReactNode { const Api = useContext(ApiContext); - const [similar, setSimilar] = useState([]); + const [similar, setSimilar] = useState(); const [open, setOpen] = useState(false); useEffect(() => { @@ -34,7 +38,7 @@ export default function ({ manga }: { manga: Manga | undefined }): ReactNode { const exit = (manga: Manga) => { setOpen(false); - setSimilar(similar.filter((m) => m.key != manga.key)); + setSimilar(similar?.filter((m) => m.key != manga.key)); }; return ( @@ -47,16 +51,19 @@ export default function ({ manga }: { manga: Manga | undefined }): ReactNode { anchor={"bottom"} > + Merge targets: {similar?.length ?? 0} - {similar.map((similarManga) => ( - - exit(similarManga)} - /> - - ))} + {similar + ? similar?.map((similarManga) => ( + + exit(similarManga)} + /> + + )) + : "Loading..."} diff --git a/tranga-website/src/Components/Search.tsx b/tranga-website/src/Components/Search.tsx index 5086c88..87db434 100644 --- a/tranga-website/src/Components/Search.tsx +++ b/tranga-website/src/Components/Search.tsx @@ -24,7 +24,7 @@ import { } from "@mui/joy"; import ModalClose from "@mui/joy/ModalClose"; import { MangaConnectorContext } from "../App.tsx"; -import { Manga, MangaConnector } from "../apiClient/data-contracts.ts"; +import { MangaConnector, MinimalManga } from "../apiClient/data-contracts.ts"; import MangaList from "./Mangas/MangaList.tsx"; import { ApiContext } from "../apiClient/ApiContext.tsx"; import { LoadingState, StateColor, StateIndicator } from "./Loading.tsx"; @@ -67,7 +67,7 @@ function SearchDialog({ MangaConnector | undefined >(undefined); const [searchTerm, setSearchTerm] = useState(); - const [searchResults, setSearchResults] = useState([]); + const [searchResults, setSearchResults] = useState([]); const [loadingState, setLoadingState] = useState( LoadingState.none, @@ -166,7 +166,7 @@ function SearchDialog({ Result {searchResults.length} - + diff --git a/tranga-website/src/Components/Settings/Download.tsx b/tranga-website/src/Components/Settings/Download.tsx index 4cbd799..014fdad 100644 --- a/tranga-website/src/Components/Settings/Download.tsx +++ b/tranga-website/src/Components/Settings/Download.tsx @@ -1,14 +1,14 @@ -import {SettingsItem} from "./Settings.tsx"; +import { SettingsItem } from "./Settings.tsx"; import ImageCompression from "./ImageCompression.tsx"; import DownloadLanguage from "./DownloadLanguage.tsx"; import ChapterNamingScheme from "./ChapterNamingScheme.tsx"; -export default function (){ - return ( - - - - - - ) -} \ No newline at end of file +export default function () { + return ( + + + + + + ); +} diff --git a/tranga-website/src/Components/Settings/LibraryConnectors/AddLibraryConnector.tsx b/tranga-website/src/Components/Settings/LibraryConnectors/AddLibraryConnector.tsx index 26c8a07..3750e24 100644 --- a/tranga-website/src/Components/Settings/LibraryConnectors/AddLibraryConnector.tsx +++ b/tranga-website/src/Components/Settings/LibraryConnectors/AddLibraryConnector.tsx @@ -1,19 +1,25 @@ -import {Modal, ModalDialog, Tab, TabList, Tabs} from "@mui/joy"; +import { Modal, ModalDialog, Tab, TabList, Tabs } from "@mui/joy"; import ModalClose from "@mui/joy/ModalClose"; -import {Dispatch} from "react"; +import { Dispatch } from "react"; -export default function ({open, setOpen} : {open: boolean, setOpen: Dispatch}) { - return ( - setOpen(false)}> - - - - - Komga - Kavita - - - - - ); -} \ No newline at end of file +export default function ({ + open, + setOpen, +}: { + open: boolean; + setOpen: Dispatch; +}) { + return ( + setOpen(false)}> + + + + + Komga + Kavita + + + + + ); +} diff --git a/tranga-website/src/Components/Settings/LibraryConnectors/LibraryConnectors.tsx b/tranga-website/src/Components/Settings/LibraryConnectors/LibraryConnectors.tsx index c4f1051..ab0a48a 100644 --- a/tranga-website/src/Components/Settings/LibraryConnectors/LibraryConnectors.tsx +++ b/tranga-website/src/Components/Settings/LibraryConnectors/LibraryConnectors.tsx @@ -1,18 +1,20 @@ -import {Button, Card, Typography} from "@mui/joy"; -import {useState} from "react"; +import { Button, Card, Typography } from "@mui/joy"; +import { useState } from "react"; import ListLibraryConnectors from "./ListLibraryConnectors.tsx"; import AddLibraryConnector from "./AddLibraryConnector.tsx"; -export default function (){ +export default function () { + const [addDialogOpen, setAddDialogOpen] = useState(false); - const [addDialogOpen, setAddDialogOpen] = useState(false); - - return ( - - Library Connectors - - - setAddDialogOpen(false)} /> - - ); -} \ No newline at end of file + return ( + + Library Connectors + + + setAddDialogOpen(false)} + /> + + ); +} diff --git a/tranga-website/src/Components/Settings/LibraryConnectors/ListLibraryConnectors.tsx b/tranga-website/src/Components/Settings/LibraryConnectors/ListLibraryConnectors.tsx index c63053f..009c0f3 100644 --- a/tranga-website/src/Components/Settings/LibraryConnectors/ListLibraryConnectors.tsx +++ b/tranga-website/src/Components/Settings/LibraryConnectors/ListLibraryConnectors.tsx @@ -1,35 +1,38 @@ -import {useContext, useEffect, useState} from "react"; -import {ApiContext} from "../../../apiClient/ApiContext.tsx"; -import {LibraryConnector} from "../../../apiClient/data-contracts.ts"; -import {Card, Chip, Input, Stack} from "@mui/joy"; +import { useContext, useEffect, useState } from "react"; +import { ApiContext } from "../../../apiClient/ApiContext.tsx"; +import { LibraryConnector } from "../../../apiClient/data-contracts.ts"; +import { Card, Chip, Input, Stack } from "@mui/joy"; -export default function (){ - const Api = useContext(ApiContext); - const [libraryConnectors, setLibraryConnectors] = useState([]); +export default function () { + const Api = useContext(ApiContext); + const [libraryConnectors, setLibraryConnectors] = useState< + LibraryConnector[] + >([]); - useEffect(() => { - getConnectors(); - }, []); + useEffect(() => { + getConnectors(); + }, []); - const getConnectors = () => { - Api.libraryConnectorList().then(r => { - if(r.ok) - setLibraryConnectors(r.data); - }) - }; + const getConnectors = () => { + Api.libraryConnectorList().then((r) => { + if (r.ok) setLibraryConnectors(r.data); + }); + }; - return ( - - {libraryConnectors.map(c => )} - - ); + return ( + + {libraryConnectors.map((c) => ( + + ))} + + ); } -function LibraryConnectorItem({connector} : {connector: LibraryConnector}){ - return ( - - {connector.libraryType} - - - ); -} \ No newline at end of file +function LibraryConnectorItem({ connector }: { connector: LibraryConnector }) { + return ( + + {connector.libraryType} + + + ); +} diff --git a/tranga-website/src/Components/Settings/NotificationConnectors/ListNotificationConnector.tsx b/tranga-website/src/Components/Settings/NotificationConnectors/ListNotificationConnector.tsx index af50e68..ecc499d 100644 --- a/tranga-website/src/Components/Settings/NotificationConnectors/ListNotificationConnector.tsx +++ b/tranga-website/src/Components/Settings/NotificationConnectors/ListNotificationConnector.tsx @@ -1,52 +1,71 @@ -import {ApiContext} from "../../../apiClient/ApiContext.tsx"; -import {useContext, useEffect, useState} from "react"; +import { ApiContext } from "../../../apiClient/ApiContext.tsx"; +import { useContext, useEffect, useState } from "react"; import { NotificationConnector } from "../../../apiClient/data-contracts.ts"; -import {Card, Chip, Input, Stack, Table, Textarea, Typography} from "@mui/joy"; +import { + Card, + Chip, + Input, + Stack, + Table, + Textarea, + Typography, +} from "@mui/joy"; -export default function (){ - const Api = useContext(ApiContext); - const [notificationConnectors, setNotificationConnectors] = useState([]); +export default function () { + const Api = useContext(ApiContext); + const [notificationConnectors, setNotificationConnectors] = useState< + NotificationConnector[] + >([]); - useEffect(() => { - getConnectors(); - }, []); + useEffect(() => { + getConnectors(); + }, []); - const getConnectors = () => { - Api.notificationConnectorList().then(r => { - if(r.ok) - setNotificationConnectors(r.data); - }) - }; + const getConnectors = () => { + Api.notificationConnectorList().then((r) => { + if (r.ok) setNotificationConnectors(r.data); + }); + }; - return ( - - {notificationConnectors.map(c => )} - - ); + return ( + + {notificationConnectors.map((c) => ( + + ))} + + ); } -function NotificationConnectorItem({connector} : {connector: NotificationConnector}){ - return ( - - {connector.name} - {connector.httpMethod}} value={connector.url} /> - - - - - - - - - {Object.entries(connector.headers).map(x => - - - - - )} - -
HeaderValue
{x[0]}{[x[1]]}
-