This commit is contained in:
2025-07-22 16:54:15 +02:00
parent 476762a30f
commit 8d6ef2388c
6 changed files with 165 additions and 25 deletions

View File

@@ -0,0 +1,35 @@
import {ReactNode, useContext, useState} from "react";
import {SettingsContext, SettingsItem} from "./Settings.tsx";
import {ApiContext} from "../../apiClient/ApiContext.tsx";
import {ColorPaletteProp, Input} from "@mui/joy";
import * as React from "react";
import MarkdownPreview from "@uiw/react-markdown-preview";
export default function () : ReactNode {
const settings = useContext(SettingsContext);
const Api = useContext(ApiContext);
const [scheme, setScheme] = useState<ColorPaletteProp>("neutral");
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
const schemeChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
clearTimeout(timerRef.current);
setScheme("warning");
timerRef.current = setTimeout(() => {
Api.settingsChapterNamingSchemePartialUpdate(e.target.value)
.then(response => {
if (response.ok)
setScheme("success");
else
setScheme("danger");
})
.catch(() => setScheme("danger"));
}, 1000);
}
return (
<SettingsItem title={"Chapter Naming Scheme"}>
<MarkdownPreview style={{backgroundColor: "transparent"}} source={"Placeholders:\n * %M Obj Name\n * %V Volume\n * %C Chapter\n * %T Title\n * %A Author (first in list)\n * %I Chapter Internal ID\n * %i Obj Internal ID\n * %Y Year (Obj)\n *\n * ?_(...) replace _ with a value from above:\n * Everything inside the braces will only be added if the value of %_ is not null"} />
<Input color={scheme} value={settings?.chapterNamingScheme as string} placeholder={"Scheme"} onChange={schemeChanged} />
</SettingsItem>
);
}

View File

@@ -0,0 +1,33 @@
import {ReactNode, useContext, useState} from "react";
import {SettingsContext, SettingsItem} from "./Settings.tsx";
import {ApiContext} from "../../apiClient/ApiContext.tsx";
import {ColorPaletteProp, Input} from "@mui/joy";
import * as React from "react";
export default function () : ReactNode {
const settings = useContext(SettingsContext);
const Api = useContext(ApiContext);
const [color, setColor] = useState<ColorPaletteProp>("neutral");
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
const languageChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
clearTimeout(timerRef.current);
setColor("warning");
timerRef.current = setTimeout(() => {
Api.settingsDownloadLanguagePartialUpdate(e.target.value)
.then(response => {
if (response.ok)
setColor("success");
else
setColor("danger");
})
.catch(() => setColor("danger"));
}, 1000);
}
return (
<SettingsItem title={"Download Language"}>
<Input color={color} value={settings?.downloadLanguage as string} placeholder={"Language code (f.e. 'en')"} onChange={languageChanged} />
</SettingsItem>
);
}

View File

@@ -0,0 +1,33 @@
import {ReactNode, useContext, useState} from "react";
import {SettingsContext, SettingsItem} from "./Settings.tsx";
import {ColorPaletteProp, Input} from "@mui/joy";
import * as React from "react";
import {ApiContext} from "../../apiClient/ApiContext.tsx";
export default function () : ReactNode {
const settings = useContext(SettingsContext);
const Api = useContext(ApiContext);
const [uriColor, setUriColor] = useState<ColorPaletteProp>("neutral");
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
const uriChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
clearTimeout(timerRef.current);
setUriColor("warning");
timerRef.current = setTimeout(() => {
Api.settingsFlareSolverrUrlCreate(e.target.value)
.then(response => {
if (response.ok)
setUriColor("success");
else
setUriColor("danger");
})
.catch(() => setUriColor("danger"));
}, 1000);
}
return (
<SettingsItem title={"FlareSolverr"}>
<Input color={uriColor} value={settings?.flareSolverrUrl as string} type={"url"} placeholder={"URL"} onChange={uriChanged} />
</SettingsItem>
);
}

View File

@@ -0,0 +1,13 @@
import {ReactNode, useContext} from "react";
import {SettingsContext, SettingsItem} from "./Settings.tsx";
import {Slider} from "@mui/joy";
export default function () : ReactNode {
const settings = useContext(SettingsContext);
return (
<SettingsItem title={"Image Compression"}>
<Slider sx={{marginTop: "20px"}} valueLabelDisplay={"auto"} defaultValue={settings?.imageCompression}></Slider>
</SettingsItem>
);
}

View File

@@ -0,0 +1,16 @@
import {ReactNode} from "react";
import {SettingsItem} from "./Settings.tsx";
import {Button} from "@mui/joy";
import NotificationConnectors from "./AddNotificationConnector.tsx";
import * as React from "react";
export default function () : ReactNode {
const [notificationConnectorsOpen, setNotificationConnectorsOpen] = React.useState(false);
return (
<SettingsItem title={"Notification Connectors"}>
<Button onClick={() => setNotificationConnectorsOpen(true)}>Add Notification Connector</Button>
<NotificationConnectors open={notificationConnectorsOpen} setOpen={setNotificationConnectorsOpen} />
</SettingsItem>
);
}

View File

@@ -10,28 +10,31 @@ import {
} from "@mui/joy"; } from "@mui/joy";
import './Settings.css'; import './Settings.css';
import * as React from "react"; import * as React from "react";
import {createContext, Dispatch, useContext, useEffect, useState} from "react"; import {createContext, Dispatch, ReactNode, useContext, useEffect, useState} from "react";
import {Article} from '@mui/icons-material'; import {Article} from '@mui/icons-material';
import {TrangaSettings} from "../../apiClient/data-contracts.ts"; import {TrangaSettings} from "../../apiClient/data-contracts.ts";
import {ApiContext} from "../../apiClient/ApiContext.tsx"; import {ApiContext} from "../../apiClient/ApiContext.tsx";
import NotificationConnectors from "./AddNotificationConnector.tsx"; import NotificationConnectors from "./NotificationConnectors.tsx";
import {SxProps} from "@mui/joy/styles/types"; import {SxProps} from "@mui/joy/styles/types";
import ImageCompression from "./ImageCompression.tsx";
import FlareSolverr from "./FlareSolverr.tsx";
import DownloadLanguage from "./DownloadLanguage.tsx";
import ChapterNamingScheme from "./ChapterNamingScheme.tsx";
export const SettingsContext = createContext<TrangaSettings>({}); export const SettingsContext = createContext<TrangaSettings|undefined>(undefined);
export default function Settings({setApiUri} : {setApiUri: Dispatch<React.SetStateAction<string>>}) { export default function Settings({setApiUri} : {setApiUri: Dispatch<React.SetStateAction<string>>}) {
const Api = useContext(ApiContext); const Api = useContext(ApiContext);
const [settings, setSettings] = useState<TrangaSettings>({}); const [settings, setSettings] = useState<TrangaSettings>();
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [apiUriColor, setApiUriColor] = useState<ColorPaletteProp>("neutral"); const [apiUriColor, setApiUriColor] = useState<ColorPaletteProp>("neutral");
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined); const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
const [apiUriAccordionOpen, setApiUriAccordionOpen] = React.useState(true);
useEffect(() => { useEffect(() => {
Api.settingsList().then((response) => { Api.settingsList().then((response) => {
setSettings(response.data) setSettings(response.data);
}); });
}, []); }, []);
@@ -44,15 +47,13 @@ export default function Settings({setApiUri} : {setApiUri: Dispatch<React.SetSta
}, 1000); }, 1000);
} }
const [notificationConnectorsOpen, setNotificationConnectorsOpen] = React.useState(false);
const ModalStyle : SxProps = { const ModalStyle : SxProps = {
width: "80%", width: "80%",
height: "80%" height: "80%"
} }
return ( return (
<SettingsContext value={settings}> <SettingsContext.Provider value={settings}>
<Button onClick={() => setOpen(true)}>Settings</Button> <Button onClick={() => setOpen(true)}>Settings</Button>
<Modal open={open} onClose={() => setOpen(false)}> <Modal open={open} onClose={() => setOpen(false)}>
<ModalDialog sx={ModalStyle}> <ModalDialog sx={ModalStyle}>
@@ -60,21 +61,19 @@ export default function Settings({setApiUri} : {setApiUri: Dispatch<React.SetSta
<DialogTitle>Settings</DialogTitle> <DialogTitle>Settings</DialogTitle>
<DialogContent> <DialogContent>
<AccordionGroup> <AccordionGroup>
<Accordion expanded={apiUriAccordionOpen} onChange={(_e, expanded) => setApiUriAccordionOpen(expanded)}> <SettingsItem title={"ApiUri"}>
<AccordionSummary>ApiUri</AccordionSummary> <Input
<AccordionDetails> color={apiUriColor}
<Input placeholder={"http(s)://"}
color={apiUriColor} type={"url"}
placeholder={"http(s)://"} defaultValue={Api.baseUrl}
type={"url"} onChange={apiUriChanged} />
defaultValue={Api.baseUrl} </SettingsItem>
onChange={apiUriChanged} /> <ImageCompression />
</AccordionDetails> <FlareSolverr />
</Accordion> <DownloadLanguage />
<Accordion> <ChapterNamingScheme />
<Button onClick={() => setNotificationConnectorsOpen(true)}>Add Notification Connector</Button> <NotificationConnectors />
<NotificationConnectors open={notificationConnectorsOpen} setOpen={setNotificationConnectorsOpen} />
</Accordion>
</AccordionGroup> </AccordionGroup>
<Stack spacing={2} direction="row"> <Stack spacing={2} direction="row">
<Link target={"_blank"} href={Api.baseUrl + "/swagger"}><Article />Swagger Doc</Link> <Link target={"_blank"} href={Api.baseUrl + "/swagger"}><Article />Swagger Doc</Link>
@@ -82,6 +81,17 @@ export default function Settings({setApiUri} : {setApiUri: Dispatch<React.SetSta
</DialogContent> </DialogContent>
</ModalDialog> </ModalDialog>
</Modal> </Modal>
</SettingsContext> </SettingsContext.Provider>
);
}
export function SettingsItem({title, children} : {title: string, children: ReactNode}) {
return (
<Accordion>
<AccordionSummary>{title}</AccordionSummary>
<AccordionDetails>
{children}
</AccordionDetails>
</Accordion>
); );
} }