mirror of
https://github.com/C9Glax/tranga-website.git
synced 2025-09-10 03:48:21 +02:00
Maintenance Settings
This commit is contained in:
32
tranga-website/src/Components/Loading.tsx
Normal file
32
tranga-website/src/Components/Loading.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import {Close, Done} from "@mui/icons-material";
|
||||
import {CircularProgress, ColorPaletteProp} from "@mui/joy";
|
||||
import {ReactNode} from "react";
|
||||
|
||||
export enum LoadingState {
|
||||
none,
|
||||
loading,
|
||||
success,
|
||||
failure
|
||||
}
|
||||
|
||||
export function StateIndicator(state : LoadingState) : ReactNode {
|
||||
switch (state) {
|
||||
case LoadingState.loading:
|
||||
return (<CircularProgress />);
|
||||
case LoadingState.failure:
|
||||
return (<Close />);
|
||||
case LoadingState.success:
|
||||
return (<Done />);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function StateColor(state : LoadingState) : ColorPaletteProp | undefined {
|
||||
switch (state) {
|
||||
case LoadingState.failure:
|
||||
return "danger";
|
||||
case LoadingState.success:
|
||||
return "success";
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
@@ -19,7 +19,6 @@ 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}) {
|
||||
@@ -29,7 +28,7 @@ export function MangaCardFromId({mangaId} : {mangaId: string}) {
|
||||
return <MangaCard manga={manga} />
|
||||
}
|
||||
|
||||
export function MangaCard({manga} : {manga: Manga | undefined}) {
|
||||
export function MangaCard({manga, children} : {manga: Manga | undefined, children? : ReactNode}) {
|
||||
if (manga === undefined)
|
||||
return PlaceHolderCard();
|
||||
|
||||
@@ -47,7 +46,7 @@ export function MangaCard({manga} : {manga: Manga | undefined}) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
<MangaModal manga={manga} open={open} setOpen={setOpen}>
|
||||
<MangaDownloadDialog manga={manga} />
|
||||
{children}
|
||||
</MangaModal>
|
||||
</MangaConnectorBadge>
|
||||
);
|
||||
@@ -72,12 +71,12 @@ export function MangaModal({manga, open, setOpen, children}: {manga: Manga | und
|
||||
{manga?.mangaTags?.map((tag) => <Chip key={tag.tag}>{tag.tag}</Chip>)}
|
||||
{manga?.links?.map((link) => <Chip key={link.key}><Link href={link.linkUrl}>{link.linkProvider}</Link></Chip>)}
|
||||
</Stack>
|
||||
<Box>
|
||||
<Box sx={{flexGrow: 1}}>
|
||||
<MarkdownPreview source={manga?.description} style={{background: "transparent"}}/>
|
||||
</Box>
|
||||
<Stack sx={{justifySelf: "flex-end", alignSelf: "flex-end"}} spacing={2} direction={"row"}>{children}</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
{children}
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
);
|
||||
|
@@ -3,13 +3,25 @@ import {MangaCard} from "./MangaCard.tsx";
|
||||
import {Stack} from "@mui/joy";
|
||||
import "./MangaList.css";
|
||||
import {Manga} from "../../apiClient/data-contracts.ts";
|
||||
import MangaDownloadDialog from "./MangaDownloadDialog.tsx";
|
||||
import MangaMerge from "./MangaMerge.tsx";
|
||||
|
||||
export default function MangaList ({mangas, children} : {mangas: Manga[], children?: ReactNode}) {
|
||||
|
||||
return (
|
||||
<Stack className={"manga-list"} direction={"row"} useFlexGap={true} spacing={2} flexWrap={"wrap"}>
|
||||
<Stack className={"manga-list"} direction={"row"} useFlexGap={true} spacing={2} flexWrap={"wrap"} sx={
|
||||
{
|
||||
mx: 'calc(-1 * var(--ModalDialog-padding))',
|
||||
px: 'var(--ModalDialog-padding)',
|
||||
overflowY: 'scroll'
|
||||
}}>
|
||||
{children}
|
||||
{mangas?.map(manga => <MangaCard key={manga.key} manga={manga} />)}
|
||||
{mangas?.map(manga => (
|
||||
<MangaCard key={manga.key} manga={manga}>
|
||||
<MangaDownloadDialog manga={manga} />
|
||||
<MangaMerge manga={manga} />
|
||||
</MangaCard>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
|
38
tranga-website/src/Components/Mangas/MangaMerge.tsx
Normal file
38
tranga-website/src/Components/Mangas/MangaMerge.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import {ReactNode, useContext, useEffect, useState} from "react";
|
||||
import {Manga} from "../../apiClient/data-contracts.ts";
|
||||
import Drawer from "@mui/joy/Drawer";
|
||||
import ModalClose from "@mui/joy/ModalClose";
|
||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
||||
import {MangaCard} from "./MangaCard.tsx";
|
||||
import {Button} from "@mui/joy";
|
||||
|
||||
export default function ({manga} : {manga : Manga | undefined}) : ReactNode {
|
||||
const Api = useContext(ApiContext);
|
||||
|
||||
const [similar, setSimilar] = useState<Manga[]>([]);
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
|
||||
useEffect(()=> {
|
||||
if (manga === undefined || !open)
|
||||
return;
|
||||
Api.queryMangaSimilarNameList(manga.key as string).then(response => {
|
||||
if (response.ok)
|
||||
Api.mangaWithIDsCreate(response.data).then(response => {
|
||||
if (response.ok)
|
||||
setSimilar(response.data);
|
||||
});
|
||||
});
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={_ => setOpen(true)}>
|
||||
Merge
|
||||
</Button>
|
||||
<Drawer open={open} onClose={() => setOpen(false)} anchor={"bottom"}>
|
||||
<ModalClose />
|
||||
{similar.map(manga => <MangaCard manga={manga}></MangaCard>)}
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
@@ -1,12 +1,15 @@
|
||||
import {Dispatch, KeyboardEventHandler, ReactNode, useContext, useState} from "react";
|
||||
import {
|
||||
Badge, Button,
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardCover,
|
||||
CardCover, Chip,
|
||||
Input,
|
||||
Modal,
|
||||
ModalDialog, Option, Select,
|
||||
ModalDialog,
|
||||
Option,
|
||||
Select,
|
||||
Step,
|
||||
StepIndicator,
|
||||
Stepper,
|
||||
@@ -17,6 +20,7 @@ import {MangaConnectorContext} from "../App.tsx";
|
||||
import {Manga, MangaConnector} from "../apiClient/data-contracts.ts";
|
||||
import MangaList from "./Mangas/MangaList.tsx";
|
||||
import {ApiContext} from "../apiClient/ApiContext.tsx";
|
||||
import {LoadingState, StateColor, StateIndicator} from "./Loading.tsx";
|
||||
|
||||
export default function () : ReactNode {
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -47,24 +51,34 @@ function SearchDialog ({open, setOpen} : {open: boolean, setOpen: Dispatch<boole
|
||||
const [searchTerm, setSearchTerm] = useState<string>();
|
||||
const [searchResults, setSearchResults] = useState<Manga[]>([]);
|
||||
|
||||
const [loadingState, setLoadingState] = useState<LoadingState>(LoadingState.none);
|
||||
|
||||
const doTheSearch = () => {
|
||||
if (searchTerm === undefined || searchTerm.length < 1)
|
||||
return;
|
||||
if (!isUrl(searchTerm) && selectedMangaConnector === undefined)
|
||||
return;
|
||||
|
||||
setLoadingState(LoadingState.loading);
|
||||
|
||||
if (isUrl(searchTerm))
|
||||
Api.searchUrlCreate(searchTerm)
|
||||
.then(response => {
|
||||
if (response.ok)
|
||||
if (response.ok){
|
||||
setSearchResults([response.data]);
|
||||
});
|
||||
setLoadingState(LoadingState.success);
|
||||
}else
|
||||
setLoadingState(LoadingState.failure);
|
||||
}).catch(() => setLoadingState(LoadingState.failure));
|
||||
else
|
||||
Api.searchDetail(selectedMangaConnector!.name, searchTerm)
|
||||
.then(response => {
|
||||
if(response.ok)
|
||||
if(response.ok){
|
||||
setSearchResults(response.data);
|
||||
});
|
||||
setLoadingState(LoadingState.success);
|
||||
}else
|
||||
setLoadingState(LoadingState.failure);
|
||||
}).catch(() => setLoadingState(LoadingState.failure));
|
||||
}
|
||||
|
||||
const isUrl = (url: string) => {
|
||||
@@ -89,7 +103,9 @@ function SearchDialog ({open, setOpen} : {open: boolean, setOpen: Dispatch<boole
|
||||
<Stepper orientation={"vertical"}>
|
||||
<Step indicator={<StepIndicator>1</StepIndicator>}>
|
||||
<Typography>Connector</Typography>
|
||||
<Select onChange={(_, v) => setSelectedMangaConnector(v as MangaConnector)}>
|
||||
<Select
|
||||
disabled={loadingState == LoadingState.loading}
|
||||
onChange={(_, v) => setSelectedMangaConnector(v as MangaConnector)}>
|
||||
{mangaConnectors?.map(con => (
|
||||
<Option value={con}>
|
||||
<Typography><img src={con.iconUrl} style={{maxHeight: "var(--Icon-fontSize)"}} />{con.name}</Typography>
|
||||
@@ -99,13 +115,20 @@ function SearchDialog ({open, setOpen} : {open: boolean, setOpen: Dispatch<boole
|
||||
</Step>
|
||||
<Step indicator={<StepIndicator>2</StepIndicator>}>
|
||||
<Typography>Search</Typography>
|
||||
<Input onKeyDown={keyDownCheck}
|
||||
<Input
|
||||
disabled={loadingState == LoadingState.loading}
|
||||
onKeyDown={keyDownCheck}
|
||||
onChange={(e) => setSearchTerm(e.currentTarget.value)}
|
||||
endDecorator={<Button onClick={doTheSearch}>Search</Button>}
|
||||
endDecorator={<Button
|
||||
disabled={loadingState == LoadingState.loading}
|
||||
onClick={doTheSearch}
|
||||
endDecorator={StateIndicator(loadingState)}
|
||||
color={StateColor(loadingState)}
|
||||
>Search</Button>}
|
||||
/>
|
||||
</Step>
|
||||
<Step indicator={<StepIndicator>3</StepIndicator>}>
|
||||
<Typography>Result</Typography>
|
||||
<Typography>Result <Chip>{searchResults.length}</Chip></Typography>
|
||||
<MangaList mangas={searchResults} />
|
||||
</Step>
|
||||
</Stepper>
|
||||
|
@@ -2,7 +2,6 @@ import {ReactNode, useContext, useState} from "react";
|
||||
import { ApiContext } from "../../apiClient/ApiContext";
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Input,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
@@ -14,14 +13,7 @@ import {
|
||||
} from "@mui/joy";
|
||||
import ModalClose from "@mui/joy/ModalClose";
|
||||
import {GotifyRecord, NtfyRecord, PushoverRecord} from "../../apiClient/data-contracts.ts";
|
||||
import {Close, Done} from "@mui/icons-material";
|
||||
|
||||
enum LoadingState {
|
||||
none,
|
||||
loading,
|
||||
success,
|
||||
failure
|
||||
}
|
||||
import {LoadingState, StateColor, StateIndicator} from "../Loading.tsx";
|
||||
|
||||
export default function ({open, setOpen} : {open: boolean, setOpen: (open: boolean) => void}) {
|
||||
|
||||
@@ -45,28 +37,6 @@ export default function ({open, setOpen} : {open: boolean, setOpen: (open: boole
|
||||
}
|
||||
|
||||
function NotificationConnectorTab({ value, children, add, state }: { value: string, children: ReactNode, add: (data: any) => void, state: LoadingState }) {
|
||||
const StateIndicator = (state : LoadingState) : ReactNode => {
|
||||
switch (state) {
|
||||
case LoadingState.loading:
|
||||
return (<CircularProgress />);
|
||||
case LoadingState.failure:
|
||||
return (<Close />);
|
||||
case LoadingState.success:
|
||||
return (<Done />);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const StateColor = (state : LoadingState) => {
|
||||
switch (state) {
|
||||
case LoadingState.failure:
|
||||
return "danger";
|
||||
case LoadingState.success:
|
||||
return "success";
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const IsLoading = (state : LoadingState) : boolean => state === LoadingState.loading;
|
||||
|
||||
|
33
tranga-website/src/Components/Settings/Maintenance.tsx
Normal file
33
tranga-website/src/Components/Settings/Maintenance.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import {Button} from "@mui/joy";
|
||||
import {SettingsItem} from "./Settings.tsx";
|
||||
import {useContext, useState} from "react";
|
||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
||||
import {LoadingState, StateColor, StateIndicator} from "../Loading.tsx";
|
||||
|
||||
export default function () {
|
||||
const Api = useContext(ApiContext);
|
||||
|
||||
const [unusedMangaState, setUnusedMangaState] = useState(LoadingState.none);
|
||||
const cleanUnusedManga = () => {
|
||||
setUnusedMangaState(LoadingState.loading);
|
||||
Api.maintenanceCleanupNoDownloadMangaCreate()
|
||||
.then(r => {
|
||||
if (r.ok)
|
||||
setUnusedMangaState(LoadingState.success);
|
||||
else
|
||||
setUnusedMangaState(LoadingState.failure);
|
||||
}).catch(_ => setUnusedMangaState(LoadingState.failure));
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<SettingsItem title={"Maintenance"}>
|
||||
<Button
|
||||
disabled={unusedMangaState == LoadingState.loading}
|
||||
color={StateColor(unusedMangaState)}
|
||||
endDecorator={StateIndicator(unusedMangaState)}
|
||||
onClick={cleanUnusedManga}>Cleanup unused Manga</Button>
|
||||
</SettingsItem>
|
||||
);
|
||||
}
|
@@ -19,6 +19,7 @@ import ImageCompression from "./ImageCompression.tsx";
|
||||
import FlareSolverr from "./FlareSolverr.tsx";
|
||||
import DownloadLanguage from "./DownloadLanguage.tsx";
|
||||
import ChapterNamingScheme from "./ChapterNamingScheme.tsx";
|
||||
import Maintenance from "./Maintenance.tsx";
|
||||
|
||||
export const SettingsContext = createContext<TrangaSettings|undefined>(undefined);
|
||||
|
||||
@@ -73,6 +74,7 @@ export default function Settings({setApiUri} : {setApiUri: Dispatch<React.SetSta
|
||||
<DownloadLanguage />
|
||||
<ChapterNamingScheme />
|
||||
<NotificationConnectors />
|
||||
<Maintenance />
|
||||
</AccordionGroup>
|
||||
</DialogContent>
|
||||
</ModalDialog>
|
||||
|
32
tranga-website/src/apiClient/CleanupNoDownloadManga.ts
Normal file
32
tranga-website/src/apiClient/CleanupNoDownloadManga.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { HttpClient, RequestParams } from "./http-client";
|
||||
|
||||
export class CleanupNoDownloadManga<
|
||||
SecurityDataType = unknown,
|
||||
> extends HttpClient<SecurityDataType> {
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Maintenance
|
||||
* @name CleanupNoDownloadMangaCreate
|
||||
* @summary Removes all API.Schema.MangaContext.Manga not marked for Download on any API.MangaConnectors.MangaConnector
|
||||
* @request POST:/CleanupNoDownloadManga
|
||||
*/
|
||||
cleanupNoDownloadMangaCreate = (params: RequestParams = {}) =>
|
||||
this.request<void, string>({
|
||||
path: `/CleanupNoDownloadManga`,
|
||||
method: "POST",
|
||||
...params,
|
||||
});
|
||||
}
|
@@ -204,6 +204,20 @@ export class V2<
|
||||
method: "DELETE",
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Maintenance
|
||||
* @name MaintenanceCleanupNoDownloadMangaCreate
|
||||
* @summary Removes all API.Schema.MangaContext.Manga not marked for Download on any API.MangaConnectors.MangaConnector
|
||||
* @request POST:/v2/Maintenance/CleanupNoDownloadManga
|
||||
*/
|
||||
maintenanceCleanupNoDownloadMangaCreate = (params: RequestParams = {}) =>
|
||||
this.request<void, string>({
|
||||
path: `/v2/Maintenance/CleanupNoDownloadManga`,
|
||||
method: "POST",
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
@@ -255,6 +269,7 @@ export class V2<
|
||||
*
|
||||
* @tags Manga
|
||||
* @name MangaDelete
|
||||
* @summary Delete API.Schema.MangaContext.Manga with MangaId
|
||||
* @request DELETE:/v2/Manga/{MangaId}
|
||||
*/
|
||||
mangaDelete = (mangaId: string, params: RequestParams = {}) =>
|
||||
@@ -863,6 +878,21 @@ export class V2<
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Query
|
||||
* @name QueryMangaSimilarNameList
|
||||
* @summary Returns API.Schema.MangaContext.Manga with names similar to API.Schema.MangaContext.Manga (identified by MangaId
|
||||
* @request GET:/v2/Query/Manga/{MangaId}/SimilarName
|
||||
*/
|
||||
queryMangaSimilarNameList = (mangaId: string, params: RequestParams = {}) =>
|
||||
this.request<string[], ProblemDetails>({
|
||||
path: `/v2/Query/Manga/${mangaId}/SimilarName`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
|
Reference in New Issue
Block a user