From cd566f01e1567086c23127ee466e16d65f3620ec Mon Sep 17 00:00:00 2001 From: glax Date: Tue, 22 Jul 2025 16:08:16 +0200 Subject: [PATCH] Download Dialog --- tranga-website/src/App.tsx | 33 ++++-- .../src/Components/MangaConnectorLink.tsx | 41 +++++++ .../src/Components/Mangas/MangaCard.tsx | 14 ++- .../Components/Mangas/MangaConnectorBadge.tsx | 17 +-- .../Components/Mangas/MangaDownloadDialog.tsx | 47 ++++++++ .../src/Components/Settings/Settings.tsx | 63 ++++++----- tranga-website/src/apiClient/V2.ts | 103 ++++++++++++------ .../src/apiClient/data-contracts.ts | 53 +++++++++ 8 files changed, 281 insertions(+), 90 deletions(-) create mode 100644 tranga-website/src/Components/MangaConnectorLink.tsx create mode 100644 tranga-website/src/Components/Mangas/MangaDownloadDialog.tsx diff --git a/tranga-website/src/App.tsx b/tranga-website/src/App.tsx index a9b5806..fb9fb4e 100644 --- a/tranga-website/src/App.tsx +++ b/tranga-website/src/App.tsx @@ -6,10 +6,11 @@ import {createContext, useEffect, useState} from "react"; import {V2} from "./apiClient/V2.ts"; import { ApiContext } from './apiClient/ApiContext.tsx'; import MangaList from "./Components/Mangas/MangaList.tsx"; -import {Manga, MangaConnector} from "./apiClient/data-contracts.ts"; +import {FileLibrary, Manga, MangaConnector} from "./apiClient/data-contracts.ts"; export const MangaConnectorContext = createContext([]); export const MangaContext = createContext([]); +export const FileLibraryContext = createContext([]); export default function App () { const apiUriStr = localStorage.getItem("apiUri") ?? window.location.href.substring(0, window.location.href.lastIndexOf("/")) + "/api"; @@ -18,6 +19,7 @@ export default function App () { const [mangaConnectors, setMangaConnectors] = useState([]); const [manga, setManga] = useState([]); + const [fileLibraries, setFileLibraries] = useState([]); useEffect(() => { Api.mangaConnectorList().then(response => { @@ -25,6 +27,11 @@ export default function App () { setMangaConnectors(response.data); }); + Api.fileLibraryList().then(response => { + if (response.ok) + setFileLibraries(response.data); + }) + Api.mangaList().then(response => { if (!response.ok) { @@ -48,18 +55,20 @@ export default function App () { return ( - - - -
- -
- - + + + + +
+ +
+ + +
-
-
-
+ + +
); } diff --git a/tranga-website/src/Components/MangaConnectorLink.tsx b/tranga-website/src/Components/MangaConnectorLink.tsx new file mode 100644 index 0000000..63e8b28 --- /dev/null +++ b/tranga-website/src/Components/MangaConnectorLink.tsx @@ -0,0 +1,41 @@ +import {CSSProperties, ReactNode, useContext, useEffect, useRef, useState} from "react"; +import {ChapterMangaConnectorId, MangaConnector, MangaMangaConnectorId} from "../apiClient/data-contracts.ts"; +import {Link, Tooltip, Typography} from "@mui/joy"; +import {MangaConnectorContext} from "../App.tsx"; +import {ApiContext} from "../apiClient/ApiContext.tsx"; + +export default function MangaConnectorLink({MangaConnectorId, imageStyle} : {MangaConnectorId : MangaMangaConnectorId | ChapterMangaConnectorId, imageStyle? : CSSProperties}) : ReactNode{ + const mangaConnectorContext = useContext(MangaConnectorContext); + const [mangaConnector, setMangaConnector] = useState(mangaConnectorContext?.find(c => c.name == MangaConnectorId.mangaConnectorName)); + const imageRef = useRef(null); + + useEffect(() => { + const connector = mangaConnectorContext?.find(c => c.name == MangaConnectorId.mangaConnectorName); + setMangaConnector(connector); + if (imageRef?.current != null) + imageRef.current.setHTMLUnsafe(""); + }, []); + + return ( + {MangaConnectorId.mangaConnectorName}: {MangaConnectorId.websiteUrl}}> + + + + + ); +} + +export function MangaConnectorLinkFromId({MangaConnectorIdId} : {MangaConnectorIdId: string}) : ReactNode { + const Api = useContext(ApiContext); + + const [node, setNode] = useState(null); + + useEffect(() => { + Api.queryMangaMangaConnectorIdDetail(MangaConnectorIdId).then(response => { + if (response.ok) + setNode(); + }); + }, []); + + return node; +} \ No newline at end of file diff --git a/tranga-website/src/Components/Mangas/MangaCard.tsx b/tranga-website/src/Components/Mangas/MangaCard.tsx index ada27c0..c4a971b 100644 --- a/tranga-website/src/Components/Mangas/MangaCard.tsx +++ b/tranga-website/src/Components/Mangas/MangaCard.tsx @@ -12,13 +12,15 @@ import { Typography } from "@mui/joy"; import {Manga} from "../../apiClient/data-contracts.ts"; -import {Dispatch, SetStateAction, useContext, useState} from "react"; +import {Dispatch, ReactNode, SetStateAction, useContext, useState} from "react"; import "./MangaCard.css"; import MangaConnectorBadge from "./MangaConnectorBadge.tsx"; import ModalClose from "@mui/joy/ModalClose"; import {ApiContext} from "../../apiClient/ApiContext.tsx"; import MarkdownPreview from '@uiw/react-markdown-preview'; import {MangaContext} from "../../App.tsx"; +import MangaDownloadDialog from "./MangaDownloadDialog.tsx"; +import {MangaConnectorLinkFromId} from "../MangaConnectorLink.tsx"; export function MangaCardFromId({mangaId} : {mangaId: string}) { const mangas = useContext(MangaContext); @@ -44,12 +46,14 @@ export function MangaCard({manga} : {manga: Manga | undefined}) { {manga?.name} - + + + ); } -function MangaModal({manga, open, setOpen}: {manga: Manga | undefined, open: boolean, setOpen: Dispatch>}) { +export function MangaModal({manga, open, setOpen, children}: {manga: Manga | undefined, open: boolean, setOpen: Dispatch>, children?: ReactNode}) { return ( setOpen(false)} className={"manga-modal"}> @@ -64,14 +68,16 @@ function MangaModal({manga, open, setOpen}: {manga: Manga | undefined, open: boo + {manga?.mangaConnectorIdsIds?.map((idid) => )} {manga?.mangaTags?.map((tag) => {tag.tag})} {manga?.links?.map((link) => {link.linkProvider})} - + + {children} ); diff --git a/tranga-website/src/Components/Mangas/MangaConnectorBadge.tsx b/tranga-website/src/Components/Mangas/MangaConnectorBadge.tsx index b63d34a..978e252 100644 --- a/tranga-website/src/Components/Mangas/MangaConnectorBadge.tsx +++ b/tranga-website/src/Components/Mangas/MangaConnectorBadge.tsx @@ -1,20 +1,13 @@ import { Badge } from "@mui/joy"; -import {Manga, MangaConnector} from "../../apiClient/data-contracts.ts"; -import {ReactElement, useContext, useEffect, useState} from "react"; -import {MangaConnectorContext} from "../../App.tsx"; +import {Manga} from "../../apiClient/data-contracts.ts"; +import {ReactElement} from "react"; import "./MangaCard.css" +import {MangaConnectorLinkFromId} from "../MangaConnectorLink.tsx"; export default function MangaConnectorBadge ({manga, children} : {manga: Manga, children? : ReactElement | ReactElement[] | undefined}) { - const context = useContext(MangaConnectorContext); - const [connectors, setConnectors] = useState([]); - - useEffect(() => { - if (context) - setConnectors(context.filter(con => Object.keys(manga.idsOnMangaConnectors??[]).find(name => con.name == name))); - }, []); - + return ( - )}> + )}> {children} ); diff --git a/tranga-website/src/Components/Mangas/MangaDownloadDialog.tsx b/tranga-website/src/Components/Mangas/MangaDownloadDialog.tsx new file mode 100644 index 0000000..7157be5 --- /dev/null +++ b/tranga-website/src/Components/Mangas/MangaDownloadDialog.tsx @@ -0,0 +1,47 @@ +import {Manga} from "../../apiClient/data-contracts.ts"; +import {Dispatch, ReactNode, useContext, useState} from "react"; +import {Button, Checkbox, Option, Select, Stack, Typography} from "@mui/joy"; +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"; + +export default function ({manga} : {manga: Manga}) : ReactNode{ + const [open, setOpen] = useState(false); + + return ( + <> + + + + ); +} + +function DownloadDrawer({manga, open, setOpen}: {manga: Manga, open: boolean, setOpen: Dispatch}): ReactNode { + const fileLibraries = useContext(FileLibraryContext); + + return ( + setOpen(false)}> + + + Download to Library: + + Download from: + + {manga.mangaConnectorIdsIds?.map(id => )} + + + + ); +} + +function DownloadCheckBox({mangaConnectorIdId} : {mangaConnectorIdId : string}) : ReactNode { + return ( + } /> + ); +} \ No newline at end of file diff --git a/tranga-website/src/Components/Settings/Settings.tsx b/tranga-website/src/Components/Settings/Settings.tsx index 663ab17..75ad178 100644 --- a/tranga-website/src/Components/Settings/Settings.tsx +++ b/tranga-website/src/Components/Settings/Settings.tsx @@ -1,4 +1,3 @@ -import Drawer from '@mui/joy/Drawer'; import ModalClose from '@mui/joy/ModalClose'; import { Accordion, @@ -7,7 +6,7 @@ import { AccordionSummary, Button, ColorPaletteProp, DialogContent, DialogTitle, Input, - Link, Stack + Link, Modal, ModalDialog, Stack } from "@mui/joy"; import './Settings.css'; import * as React from "react"; @@ -16,6 +15,7 @@ import {Article} from '@mui/icons-material'; import {TrangaSettings} from "../../apiClient/data-contracts.ts"; import {ApiContext} from "../../apiClient/ApiContext.tsx"; import NotificationConnectors from "./AddNotificationConnector.tsx"; +import {SxProps} from "@mui/joy/styles/types"; export const SettingsContext = createContext({}); @@ -46,35 +46,42 @@ export default function Settings({setApiUri} : {setApiUri: Dispatch - setOpen(false)}> - - Settings - - - setApiUriAccordionOpen(expanded)}> - ApiUri - - - - - - - - - - -
Swagger Doc - - - + setOpen(false)}> + + + Settings + + + setApiUriAccordionOpen(expanded)}> + ApiUri + + + + + + + + + + +
Swagger Doc + + + + ); } \ No newline at end of file diff --git a/tranga-website/src/apiClient/V2.ts b/tranga-website/src/apiClient/V2.ts index b6b22a1..17974f6 100644 --- a/tranga-website/src/apiClient/V2.ts +++ b/tranga-website/src/apiClient/V2.ts @@ -14,11 +14,13 @@ import { Author, BaseWorker, Chapter, + ChapterMangaConnectorId, FileLibrary, GotifyRecord, LibraryConnector, Manga, MangaConnector, + MangaMangaConnectorId, MetadataEntry, MetadataSearchResult, NotificationConnector, @@ -471,6 +473,36 @@ export class V2< format: "json", ...params, }); + /** + * No description + * + * @tags Manga + * @name MangaWithAuthorIdDetail + * @summary Returns all API.Schema.MangaContext.Manga which where Authored by API.Schema.MangaContext.Author with AuthorId + * @request GET:/v2/Manga/WithAuthorId/{AuthorId} + */ + mangaWithAuthorIdDetail = (authorId: string, params: RequestParams = {}) => + this.request({ + path: `/v2/Manga/WithAuthorId/${authorId}`, + method: "GET", + format: "json", + ...params, + }); + /** + * No description + * + * @tags Manga + * @name MangaWithTagDetail + * @summary Returns all API.Schema.MangaContext.Manga with !:Tag + * @request GET:/v2/Manga/WithTag/{Tag} + */ + mangaWithTagDetail = (tag: string, params: RequestParams = {}) => + this.request({ + path: `/v2/Manga/WithTag/${tag}`, + method: "GET", + format: "json", + ...params, + }); /** * No description * @@ -783,39 +815,6 @@ export class V2< format: "json", ...params, }); - /** - * No description - * - * @tags Query - * @name QueryMangasWithAuthorIdDetail - * @summary Returns all API.Schema.MangaContext.Manga which where Authored by API.Schema.MangaContext.Author with AuthorId - * @request GET:/v2/Query/Mangas/WithAuthorId/{AuthorId} - */ - queryMangasWithAuthorIdDetail = ( - authorId: string, - params: RequestParams = {}, - ) => - this.request({ - path: `/v2/Query/Mangas/WithAuthorId/${authorId}`, - method: "GET", - format: "json", - ...params, - }); - /** - * No description - * - * @tags Query - * @name QueryMangasWithTagDetail - * @summary Returns all API.Schema.MangaContext.Manga with !:Tag - * @request GET:/v2/Query/Mangas/WithTag/{Tag} - */ - queryMangasWithTagDetail = (tag: string, params: RequestParams = {}) => - this.request({ - path: `/v2/Query/Mangas/WithTag/${tag}`, - method: "GET", - format: "json", - ...params, - }); /** * No description * @@ -825,12 +824,48 @@ export class V2< * @request GET:/v2/Query/Chapter/{ChapterId} */ queryChapterDetail = (chapterId: string, params: RequestParams = {}) => - this.request({ + this.request({ path: `/v2/Query/Chapter/${chapterId}`, method: "GET", format: "json", ...params, }); + /** + * No description + * + * @tags Query + * @name QueryMangaMangaConnectorIdDetail + * @summary Returns the API.Schema.MangaContext.MangaConnectorId`1 with API.Schema.MangaContext.MangaConnectorId`1.Key + * @request GET:/v2/Query/Manga/MangaConnectorId/{MangaConnectorIdId} + */ + queryMangaMangaConnectorIdDetail = ( + mangaConnectorIdId: string, + params: RequestParams = {}, + ) => + this.request({ + path: `/v2/Query/Manga/MangaConnectorId/${mangaConnectorIdId}`, + method: "GET", + format: "json", + ...params, + }); + /** + * No description + * + * @tags Query + * @name QueryChapterMangaConnectorIdDetail + * @summary Returns the API.Schema.MangaContext.MangaConnectorId`1 with API.Schema.MangaContext.MangaConnectorId`1.Key + * @request GET:/v2/Query/chapter/MangaConnectorId/{MangaConnectorIdId} + */ + queryChapterMangaConnectorIdDetail = ( + mangaConnectorIdId: string, + params: RequestParams = {}, + ) => + this.request({ + path: `/v2/Query/chapter/MangaConnectorId/${mangaConnectorIdId}`, + method: "GET", + format: "json", + ...params, + }); /** * No description * diff --git a/tranga-website/src/apiClient/data-contracts.ts b/tranga-website/src/apiClient/data-contracts.ts index 77791de..3b1b5a0 100644 --- a/tranga-website/src/apiClient/data-contracts.ts +++ b/tranga-website/src/apiClient/data-contracts.ts @@ -106,6 +106,32 @@ export interface Chapter { key?: string | null; } +export interface ChapterMangaConnectorId { + /** + * @minLength 0 + * @maxLength 64 + */ + objId: string; + /** + * @minLength 0 + * @maxLength 32 + */ + mangaConnectorName: string; + /** + * @minLength 0 + * @maxLength 256 + */ + idOnConnectorSite: string; + /** + * @format uri + * @minLength 0 + * @maxLength 512 + */ + websiteUrl?: string | null; + useForDownload?: boolean; + key?: string | null; +} + export interface FileLibrary { /** * @minLength 0 @@ -193,6 +219,7 @@ export interface Manga { originalLanguage?: string | null; chapterIds?: string[] | null; idsOnMangaConnectors?: Record; + mangaConnectorIdsIds?: string[] | null; key?: string | null; } @@ -220,6 +247,32 @@ export interface MangaConnector { enabled: boolean; } +export interface MangaMangaConnectorId { + /** + * @minLength 0 + * @maxLength 64 + */ + objId: string; + /** + * @minLength 0 + * @maxLength 32 + */ + mangaConnectorName: string; + /** + * @minLength 0 + * @maxLength 256 + */ + idOnConnectorSite: string; + /** + * @format uri + * @minLength 0 + * @maxLength 512 + */ + websiteUrl?: string | null; + useForDownload?: boolean; + key?: string | null; +} + export interface MangaTag { /** * @minLength 0