mirror of
https://github.com/C9Glax/tranga-website.git
synced 2025-09-10 11:58:20 +02:00
Use new API layout with MinimalManga
This commit is contained in:
@@ -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<string, Promise<Manga | undefined>> = new Map();
|
||||
const getManga = async (key: string): Promise<Manga | undefined> => {
|
||||
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<Manga | undefined> => {
|
||||
return Api.mangaDetail(key).then((response) => {
|
||||
if (response.ok) {
|
||||
manga.push(response.data);
|
||||
return response.data;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
};
|
||||
|
||||
export const MangaConnectorContext = createContext<MangaConnector[]>([]);
|
||||
export const MangaContext = createContext<Manga[]>([]);
|
||||
export const MangaContext = createContext<{
|
||||
getManga: (key: string) => Promise<Manga | undefined>;
|
||||
}>({
|
||||
getManga,
|
||||
});
|
||||
export const FileLibraryContext = createContext<FileLibrary[]>([]);
|
||||
|
||||
export default function App() {
|
||||
const apiUriStr =
|
||||
localStorage.getItem("apiUri") ??
|
||||
window.location.href.substring(0, window.location.href.lastIndexOf("/")) +
|
||||
"/api";
|
||||
const [apiUri, setApiUri] = useState<string>(apiUriStr);
|
||||
const [Api, setApi] = useState<V2>(
|
||||
new V2({
|
||||
baseUrl: apiUri,
|
||||
}),
|
||||
);
|
||||
const updateApiUri = (uri: string) => {
|
||||
localStorage.setItem("apiUri", uri);
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
const [mangaConnectors, setMangaConnectors] = useState<MangaConnector[]>([]);
|
||||
const [manga, setManga] = useState<Manga[]>([]);
|
||||
const [downloadingManga, setDownloadingManga] = useState<MinimalManga[]>([]);
|
||||
const [fileLibraries, setFileLibraries] = useState<FileLibrary[]>([]);
|
||||
|
||||
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 (
|
||||
<ApiContext.Provider value={Api}>
|
||||
<FileLibraryContext value={fileLibraries}>
|
||||
<MangaConnectorContext.Provider value={mangaConnectors}>
|
||||
<MangaContext.Provider value={manga}>
|
||||
<MangaContext.Provider value={{ getManga }}>
|
||||
{Api ? (
|
||||
<Sheet className={"app"}>
|
||||
<Header>
|
||||
<Settings setApiUri={setApiUri} />
|
||||
<Settings setApiUri={updateApiUri} />
|
||||
<Workers />
|
||||
</Header>
|
||||
<Sheet className={"app-content"}>
|
||||
<MangaList mangas={manga}>
|
||||
<MangaList manga={downloadingManga}>
|
||||
<Search />
|
||||
</MangaList>
|
||||
</Sheet>
|
||||
|
@@ -57,7 +57,12 @@ export default function MangaConnectorLink({
|
||||
}
|
||||
>
|
||||
<Link href={MangaConnectorId.websiteUrl as string}>
|
||||
<img ref={imageRef} src={mangaConnector?.iconUrl} style={imageStyle} />
|
||||
<img
|
||||
ref={imageRef}
|
||||
src={mangaConnector?.iconUrl}
|
||||
style={imageStyle}
|
||||
className={"manga-card-badge-icon"}
|
||||
/>
|
||||
{printName ? <Typography>{mangaConnector?.name}</Typography> : null}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
|
@@ -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 <MangaCard manga={manga} />;
|
||||
}
|
||||
|
||||
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({
|
||||
<MangaConnectorBadge manga={manga}>
|
||||
<Card className={"manga-card"} onClick={() => setOpen(true)}>
|
||||
<CardCover className={"manga-cover"}>
|
||||
<MangaCover manga={manga} />
|
||||
<MangaCover mangaId={manga?.key} />
|
||||
</CardCover>
|
||||
<CardCover className={"manga-cover-blur"} />
|
||||
<CardContent className={"manga-content"}>
|
||||
<Typography level={"title-lg"}>{manga?.name}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<MangaModal manga={manga} open={open} setOpen={setOpen}>
|
||||
<MangaModal minimalManga={manga} open={open} setOpen={setOpen}>
|
||||
{children}
|
||||
</MangaModal>
|
||||
</MangaConnectorBadge>
|
||||
@@ -65,16 +59,22 @@ export function MangaCard({
|
||||
}
|
||||
|
||||
export function MangaModal({
|
||||
manga,
|
||||
minimalManga,
|
||||
open,
|
||||
setOpen,
|
||||
children,
|
||||
}: {
|
||||
manga: Manga | undefined;
|
||||
minimalManga: MinimalManga;
|
||||
open: boolean;
|
||||
setOpen: Dispatch<SetStateAction<boolean>>;
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
const { getManga } = useContext(MangaContext);
|
||||
const [manga, setManga] = useState<Manga>();
|
||||
useEffect(() => {
|
||||
getManga(minimalManga.key).then(setManga);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={() => setOpen(false)} className={"manga-modal"}>
|
||||
<ModalDialog style={{ width: "100%" }}>
|
||||
@@ -89,12 +89,12 @@ export function MangaModal({
|
||||
}
|
||||
>
|
||||
<Typography level={"h4"} width={"fit-content"}>
|
||||
{manga?.name}
|
||||
{manga?.name ?? minimalManga.name}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<Box key={"Cover"} className={"manga-card"}>
|
||||
<MangaCover manga={manga} />
|
||||
<MangaCover mangaId={minimalManga.key} />
|
||||
</Box>
|
||||
<Stack
|
||||
key={"Description"}
|
||||
@@ -125,8 +125,12 @@ export function MangaModal({
|
||||
</Stack>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<MarkdownPreview
|
||||
source={manga?.description}
|
||||
style={{ background: "transparent" }}
|
||||
source={manga?.description ?? "Loading..."}
|
||||
style={{
|
||||
background: "transparent",
|
||||
maxHeight: "50vh",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Stack
|
||||
@@ -134,7 +138,7 @@ export function MangaModal({
|
||||
spacing={2}
|
||||
direction={"row"}
|
||||
>
|
||||
{children}
|
||||
{manga ? children : null}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -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 (
|
||||
|
@@ -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<any, any> | ReactElement<any, any>[] | undefined;
|
||||
}) {
|
||||
return (
|
||||
<Badge
|
||||
badgeContent={manga.mangaConnectorIdsIds?.map((id) => (
|
||||
<MangaConnectorLinkFromId key={id} MangaConnectorIdId={id} />
|
||||
badgeContent={manga.mangaConnectorIds?.map((id) => (
|
||||
<MangaConnectorLink key={id.key} MangaConnectorId={id} />
|
||||
))}
|
||||
>
|
||||
{children}
|
||||
|
@@ -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<Manga>();
|
||||
useEffect(() => {
|
||||
getManga(mangaId).then(setManga);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -32,7 +37,7 @@ function DownloadDrawer({
|
||||
open,
|
||||
setOpen,
|
||||
}: {
|
||||
manga: Manga;
|
||||
manga: Manga | undefined;
|
||||
open: boolean;
|
||||
setOpen: Dispatch<boolean>;
|
||||
}): 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({
|
||||
<Select
|
||||
placeholder={"Library"}
|
||||
onChange={onLibraryChange}
|
||||
value={manga.libraryId}
|
||||
value={manga?.libraryId}
|
||||
>
|
||||
{fileLibraries?.map((library) => (
|
||||
<Option value={library.key} key={library.key}>
|
||||
@@ -63,7 +69,7 @@ function DownloadDrawer({
|
||||
</Select>
|
||||
<Typography>Download from:</Typography>
|
||||
<Stack>
|
||||
{manga.mangaConnectorIdsIds?.map((id) => (
|
||||
{manga?.mangaConnectorIdsIds?.map((id) => (
|
||||
<DownloadCheckBox key={id} mangaConnectorIdId={id} />
|
||||
))}
|
||||
</Stack>
|
||||
|
@@ -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) => (
|
||||
<MangaCard key={manga.key} manga={manga}>
|
||||
<MangaDownloadDialog manga={manga} />
|
||||
<MangaMerge manga={manga} />
|
||||
{manga?.map((minimalManga) => (
|
||||
<MangaCard key={minimalManga.key} manga={minimalManga}>
|
||||
<MangaDownloadDialog mangaId={minimalManga.key} />
|
||||
<MangaMerge manga={minimalManga} />
|
||||
</MangaCard>
|
||||
))}
|
||||
</Stack>
|
||||
|
@@ -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<Manga[]>([]);
|
||||
const [similar, setSimilar] = useState<Manga[]>();
|
||||
const [open, setOpen] = useState<boolean>(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"}
|
||||
>
|
||||
<ModalClose />
|
||||
<Typography>Merge targets: {similar?.length ?? 0}</Typography>
|
||||
<Stack direction={"row"} spacing={2} flexWrap={"wrap"} useFlexGap>
|
||||
{similar.map((similarManga) => (
|
||||
<MangaCard manga={similarManga}>
|
||||
<ConfirmationModal
|
||||
manga={manga as Manga}
|
||||
similarManga={similarManga}
|
||||
exit={() => exit(similarManga)}
|
||||
/>
|
||||
</MangaCard>
|
||||
))}
|
||||
{similar
|
||||
? similar?.map((similarManga) => (
|
||||
<MangaCard manga={similarManga}>
|
||||
<ConfirmationModal
|
||||
manga={manga as Manga}
|
||||
similarManga={similarManga}
|
||||
exit={() => exit(similarManga)}
|
||||
/>
|
||||
</MangaCard>
|
||||
))
|
||||
: "Loading..."}
|
||||
</Stack>
|
||||
</Drawer>
|
||||
</>
|
||||
|
@@ -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<string>();
|
||||
const [searchResults, setSearchResults] = useState<Manga[]>([]);
|
||||
const [searchResults, setSearchResults] = useState<MinimalManga[]>([]);
|
||||
|
||||
const [loadingState, setLoadingState] = useState<LoadingState>(
|
||||
LoadingState.none,
|
||||
@@ -166,7 +166,7 @@ function SearchDialog({
|
||||
<Typography>
|
||||
Result <Chip>{searchResults.length}</Chip>
|
||||
</Typography>
|
||||
<MangaList mangas={searchResults} />
|
||||
<MangaList manga={searchResults} />
|
||||
</Step>
|
||||
</Stepper>
|
||||
</ModalDialog>
|
||||
|
@@ -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 (
|
||||
<SettingsItem title={"Download"}>
|
||||
<ImageCompression />
|
||||
<DownloadLanguage />
|
||||
<ChapterNamingScheme />
|
||||
</SettingsItem>
|
||||
)
|
||||
export default function () {
|
||||
return (
|
||||
<SettingsItem title={"Download"}>
|
||||
<ImageCompression />
|
||||
<DownloadLanguage />
|
||||
<ChapterNamingScheme />
|
||||
</SettingsItem>
|
||||
);
|
||||
}
|
@@ -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<boolean>}) {
|
||||
return (
|
||||
<Modal open={open} onClose={() => setOpen(false)}>
|
||||
<ModalDialog>
|
||||
<ModalClose />
|
||||
<Tabs sx={{width:'95%'}} defaultValue={"komga"}>
|
||||
<TabList>
|
||||
<Tab value={"komga"}>Komga</Tab>
|
||||
<Tab value={"kavita"}>Kavita</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
);
|
||||
export default function ({
|
||||
open,
|
||||
setOpen,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: Dispatch<boolean>;
|
||||
}) {
|
||||
return (
|
||||
<Modal open={open} onClose={() => setOpen(false)}>
|
||||
<ModalDialog>
|
||||
<ModalClose />
|
||||
<Tabs sx={{ width: "95%" }} defaultValue={"komga"}>
|
||||
<TabList>
|
||||
<Tab value={"komga"}>Komga</Tab>
|
||||
<Tab value={"kavita"}>Kavita</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
);
|
||||
}
|
@@ -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<boolean>(false);
|
||||
|
||||
const [addDialogOpen, setAddDialogOpen] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Typography>Library Connectors</Typography>
|
||||
<Button onClick={() => setAddDialogOpen(true)}>Add</Button>
|
||||
<ListLibraryConnectors />
|
||||
<AddLibraryConnector open={addDialogOpen} setOpen={() => setAddDialogOpen(false)} />
|
||||
</Card>
|
||||
);
|
||||
return (
|
||||
<Card>
|
||||
<Typography>Library Connectors</Typography>
|
||||
<Button onClick={() => setAddDialogOpen(true)}>Add</Button>
|
||||
<ListLibraryConnectors />
|
||||
<AddLibraryConnector
|
||||
open={addDialogOpen}
|
||||
setOpen={() => setAddDialogOpen(false)}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
@@ -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<LibraryConnector[]>([]);
|
||||
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 (
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{libraryConnectors.map(c => <LibraryConnectorItem key={c.key} connector={c} />)}
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{libraryConnectors.map((c) => (
|
||||
<LibraryConnectorItem key={c.key} connector={c} />
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function LibraryConnectorItem({connector} : {connector: LibraryConnector}){
|
||||
return (
|
||||
<Card>
|
||||
<Chip>{connector.libraryType}</Chip>
|
||||
<Input disabled value={connector.baseUrl} />
|
||||
</Card>
|
||||
);
|
||||
function LibraryConnectorItem({ connector }: { connector: LibraryConnector }) {
|
||||
return (
|
||||
<Card>
|
||||
<Chip>{connector.libraryType}</Chip>
|
||||
<Input disabled value={connector.baseUrl} />
|
||||
</Card>
|
||||
);
|
||||
}
|
@@ -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<NotificationConnector[]>([]);
|
||||
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 (
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{notificationConnectors.map(c => <NotificationConnectorItem key={c.name} connector={c} />)}
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{notificationConnectors.map((c) => (
|
||||
<NotificationConnectorItem key={c.name} connector={c} />
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function NotificationConnectorItem({connector} : {connector: NotificationConnector}){
|
||||
return (
|
||||
<Card>
|
||||
<Typography left={"h2"}>{connector.name}</Typography>
|
||||
<Input disabled startDecorator={<Chip>{connector.httpMethod}</Chip>} value={connector.url} />
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Header</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(connector.headers).map(x =>
|
||||
<tr key={x[0]}>
|
||||
<td>{x[0]}</td>
|
||||
<td>{[x[1]]}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
<Textarea disabled value={connector.body}/>
|
||||
</Card>
|
||||
);
|
||||
function NotificationConnectorItem({
|
||||
connector,
|
||||
}: {
|
||||
connector: NotificationConnector;
|
||||
}) {
|
||||
return (
|
||||
<Card>
|
||||
<Typography left={"h2"}>{connector.name}</Typography>
|
||||
<Input
|
||||
disabled
|
||||
startDecorator={<Chip>{connector.httpMethod}</Chip>}
|
||||
value={connector.url}
|
||||
/>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Header</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(connector.headers).map((x) => (
|
||||
<tr key={x[0]}>
|
||||
<td>{x[0]}</td>
|
||||
<td>{[x[1]]}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
<Textarea disabled value={connector.body} />
|
||||
</Card>
|
||||
);
|
||||
}
|
@@ -1,15 +1,12 @@
|
||||
import {SettingsItem} from "./Settings.tsx";
|
||||
import { SettingsItem } from "./Settings.tsx";
|
||||
import FlareSolverr from "./FlareSolverr.tsx";
|
||||
import LibraryConnectors from "./LibraryConnectors/LibraryConnectors.tsx";
|
||||
|
||||
export default function(){
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<SettingsItem title={"Services"}>
|
||||
<FlareSolverr />
|
||||
<LibraryConnectors />
|
||||
</SettingsItem>
|
||||
);
|
||||
export default function () {
|
||||
return (
|
||||
<SettingsItem title={"Services"}>
|
||||
<FlareSolverr />
|
||||
<LibraryConnectors />
|
||||
</SettingsItem>
|
||||
);
|
||||
}
|
@@ -16,7 +16,6 @@ import "./Settings.css";
|
||||
import * as React from "react";
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useEffect,
|
||||
@@ -38,7 +37,7 @@ export const SettingsContext = createContext<TrangaSettings | undefined>(
|
||||
export default function Settings({
|
||||
setApiUri,
|
||||
}: {
|
||||
setApiUri: Dispatch<React.SetStateAction<string>>;
|
||||
setApiUri: (uri: string) => void;
|
||||
}) {
|
||||
const Api = useContext(ApiContext);
|
||||
const [settings, setSettings] = useState<TrangaSettings>();
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Dispatch, ReactNode, useContext, useState } from "react";
|
||||
import { Dispatch, ReactNode, useContext, useEffect, useState } from "react";
|
||||
import Drawer from "@mui/joy/Drawer";
|
||||
import { Button, Option, Select, Table } from "@mui/joy";
|
||||
import { BaseWorker } from "../../apiClient/data-contracts.ts";
|
||||
@@ -10,11 +10,14 @@ export default function (): ReactNode {
|
||||
|
||||
const [workers, setWorkers] = useState<BaseWorker[]>([]);
|
||||
const Api = useContext(ApiContext);
|
||||
Api.workerList().then((response) => {
|
||||
if (response.ok) {
|
||||
setWorkers(response.data);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
Api.workerList().then((response) => {
|
||||
if (response.ok) {
|
||||
setWorkers(response.data);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -52,7 +55,7 @@ function WorkerDrawer({
|
||||
<tbody>
|
||||
{workers.map((worker) => {
|
||||
return (
|
||||
<tr>
|
||||
<tr key={worker.key}>
|
||||
<td>{worker.key}</td>
|
||||
<td>{worker.allDependenciesFulfilled ? "yes" : "no"}</td>
|
||||
<td>
|
||||
|
@@ -1,8 +1,6 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
// noinspection JSValidateJSDoc,JSUnusedGlobalSymbols
|
||||
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
@@ -25,6 +23,7 @@ import {
|
||||
MangaMangaConnectorId,
|
||||
MetadataEntry,
|
||||
MetadataSearchResult,
|
||||
MinimalManga,
|
||||
NotificationConnector,
|
||||
NtfyRecord,
|
||||
ProblemDetails,
|
||||
@@ -230,7 +229,7 @@ export class V2<
|
||||
* @request GET:/v2/Manga
|
||||
*/
|
||||
mangaList = (params: RequestParams = {}) =>
|
||||
this.request<Manga[], void>({
|
||||
this.request<MinimalManga[], void>({
|
||||
path: `/v2/Manga`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
@@ -260,7 +259,7 @@ export class V2<
|
||||
* @request GET:/v2/Manga/Downloading
|
||||
*/
|
||||
mangaDownloadingList = (params: RequestParams = {}) =>
|
||||
this.request<Manga[], void>({
|
||||
this.request<MinimalManga[], void>({
|
||||
path: `/v2/Manga/Downloading`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
@@ -943,7 +942,7 @@ export class V2<
|
||||
query: string,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<Manga[], ProblemDetails | void>({
|
||||
this.request<MinimalManga[], ProblemDetails | void>({
|
||||
path: `/v2/Search/${mangaConnectorName}/${query}`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
@@ -958,7 +957,7 @@ export class V2<
|
||||
* @request POST:/v2/Search/Url
|
||||
*/
|
||||
searchUrlCreate = (data: string, params: RequestParams = {}) =>
|
||||
this.request<Manga, void | ProblemDetails>({
|
||||
this.request<MinimalManga, void | ProblemDetails>({
|
||||
path: `/v2/Search/Url`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
|
@@ -331,6 +331,20 @@ export interface MetadataSearchResult {
|
||||
coverUrl?: string | null;
|
||||
}
|
||||
|
||||
export interface MinimalManga {
|
||||
/**
|
||||
* @minLength 16
|
||||
* @maxLength 64
|
||||
*/
|
||||
key: string;
|
||||
/** @minLength 1 */
|
||||
name: string;
|
||||
/** @minLength 1 */
|
||||
description: string;
|
||||
releaseStatus: MangaReleaseStatus;
|
||||
mangaConnectorIds?: MangaMangaConnectorId[] | null;
|
||||
}
|
||||
|
||||
export interface NotificationConnector {
|
||||
/**
|
||||
* @minLength 0
|
||||
|
Reference in New Issue
Block a user