Reorganize Settings for faster access to individual settings (group them by category)

This commit is contained in:
2025-08-04 11:39:02 +02:00
parent d5975192e8
commit ee1b0536c7
10 changed files with 104 additions and 77 deletions

View File

@@ -1,7 +1,7 @@
import {ReactNode, useContext, useState} from "react";
import { ApiContext } from "../../apiClient/ApiContext";
import {
Button,
Button, Card,
Input,
Modal,
ModalDialog,
@@ -9,30 +9,36 @@ import {
Tab,
TabList,
TabPanel,
Tabs
Tabs, Typography
} from "@mui/joy";
import ModalClose from "@mui/joy/ModalClose";
import {GotifyRecord, NtfyRecord, PushoverRecord} from "../../apiClient/data-contracts.ts";
import {LoadingState, StateColor, StateIndicator} from "../Loading.tsx";
import * as React from "react";
export default function ({open, setOpen} : {open: boolean, setOpen: (open: boolean) => void}) {
export default function () {
const [notificationConnectorsOpen, setNotificationConnectorsOpen] = React.useState(false);
return (
<Modal open={open} onClose={() => setOpen(false)}>
<ModalDialog>
<ModalClose />
<Tabs sx={{width:'95%'}} defaultValue={"gotify"}>
<TabList>
<Tab value={"gotify"}>Gotify</Tab>
<Tab value={"ntfy"}>Ntfy</Tab>
<Tab value={"pushover"}>Pushover</Tab>
</TabList>
<Gotify />
<Ntfy />
<Pushover />
</Tabs>
</ModalDialog>
</Modal>
<Card>
<Typography>Notification Connectors</Typography>
<Button onClick={() => setNotificationConnectorsOpen(true)}>Add</Button>
<Modal open={notificationConnectorsOpen} onClose={() => setNotificationConnectorsOpen(false)}>
<ModalDialog>
<ModalClose />
<Tabs sx={{width:'95%'}} defaultValue={"gotify"}>
<TabList>
<Tab value={"gotify"}>Gotify</Tab>
<Tab value={"ntfy"}>Ntfy</Tab>
<Tab value={"pushover"}>Pushover</Tab>
</TabList>
<Gotify />
<Ntfy />
<Pushover />
</Tabs>
</ModalDialog>
</Modal>
</Card>
);
}

View File

@@ -1,7 +1,7 @@
import {ReactNode, useContext, useState} from "react";
import {SettingsContext, SettingsItem} from "./Settings.tsx";
import {SettingsContext} from "./Settings.tsx";
import {ApiContext} from "../../apiClient/ApiContext.tsx";
import {ColorPaletteProp, Input} from "@mui/joy";
import {Card, ColorPaletteProp, Input, Typography} from "@mui/joy";
import * as React from "react";
import MarkdownPreview from "@uiw/react-markdown-preview";
@@ -27,9 +27,10 @@ export default function () : ReactNode {
}
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"} />
<Card>
<Typography>Chapter Naming Scheme</Typography>
<Input color={scheme} defaultValue={settings?.chapterNamingScheme as string} placeholder={"Scheme"} onChange={schemeChanged} />
</SettingsItem>
<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"} />
</Card>
);
}

View File

@@ -0,0 +1,14 @@
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>
)
}

View File

@@ -1,7 +1,7 @@
import {ReactNode, useContext, useState} from "react";
import {SettingsContext, SettingsItem} from "./Settings.tsx";
import {SettingsContext} from "./Settings.tsx";
import {ApiContext} from "../../apiClient/ApiContext.tsx";
import {ColorPaletteProp, Input} from "@mui/joy";
import {Card, ColorPaletteProp, Input, Typography} from "@mui/joy";
import * as React from "react";
export default function () : ReactNode {
@@ -26,8 +26,9 @@ export default function () : ReactNode {
}
return (
<SettingsItem title={"Download Language"}>
<Card>
<Typography>Download Language</Typography>
<Input color={color} defaultValue={settings?.downloadLanguage as string} placeholder={"Language code (f.e. 'en')"} onChange={languageChanged} />
</SettingsItem>
</Card>
);
}

View File

@@ -1,6 +1,6 @@
import {ReactNode, useContext, useState} from "react";
import {SettingsContext, SettingsItem} from "./Settings.tsx";
import {ColorPaletteProp, Input} from "@mui/joy";
import {ReactNode, useCallback, useContext, useState} from "react";
import {SettingsContext} from "./Settings.tsx";
import {Button, Card, ColorPaletteProp, Input, Typography} from "@mui/joy";
import * as React from "react";
import {ApiContext} from "../../apiClient/ApiContext.tsx";
@@ -8,26 +8,34 @@ export default function () : ReactNode {
const settings = useContext(SettingsContext);
const Api = useContext(ApiContext);
const [uriValue, setUriValue] = React.useState<string>(settings?.flareSolverrUrl as string);
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"));
setUriValue(e.target.value);
}, 1000);
}
const changeUri = useCallback(() => {
Api.settingsFlareSolverrUrlCreate(uriValue)
.then(response => {
if (response.ok)
setUriColor("success");
else
setUriColor("danger");
})
.catch(() => setUriColor("danger"));
}, [uriValue]);
return (
<SettingsItem title={"FlareSolverr"}>
<Input color={uriColor} defaultValue={settings?.flareSolverrUrl as string} type={"url"} placeholder={"URL"} onChange={uriChanged} />
</SettingsItem>
<Card>
<Typography>FlareSolverr</Typography>
<Input color={uriColor} value={uriValue} type={"url"} placeholder={"URL"} onChange={uriChanged}
endDecorator={<Button onClick={changeUri}>Apply</Button>}
/>
</Card>
);
}

View File

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

View File

@@ -19,9 +19,7 @@ export default function () {
}).catch(_ => setUnusedMangaState(LoadingState.failure));
}
return (
<SettingsItem title={"Maintenance"}>
<Button
disabled={unusedMangaState == LoadingState.loading}

View File

@@ -1,16 +0,0 @@
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

@@ -0,0 +1,15 @@
import {SettingsItem} from "./Settings.tsx";
import NotificationConnectors from "./AddNotificationConnector.tsx";
import FlareSolverr from "./FlareSolverr.tsx";
export default function(){
return (
<SettingsItem title={"Services"} direction={"row"}>
<FlareSolverr />
<NotificationConnectors />
</SettingsItem>
);
}

View File

@@ -6,20 +6,17 @@ import {
AccordionSummary, Button, ColorPaletteProp,
DialogContent,
DialogTitle, Input,
Modal, ModalDialog
Modal, ModalDialog, Stack
} from "@mui/joy";
import './Settings.css';
import * as React from "react";
import {createContext, Dispatch, ReactNode, useContext, useEffect, useState} from "react";
import {TrangaSettings} from "../../apiClient/data-contracts.ts";
import {ApiContext} from "../../apiClient/ApiContext.tsx";
import NotificationConnectors from "./NotificationConnectors.tsx";
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";
import Maintenance from "./Maintenance.tsx";
import Services from "./Services.tsx";
import Download from './Download.tsx';
export const SettingsContext = createContext<TrangaSettings|undefined>(undefined);
@@ -69,11 +66,8 @@ export default function Settings({setApiUri} : {setApiUri: Dispatch<React.SetSta
defaultValue={Api.baseUrl}
onChange={apiUriChanged} />
</SettingsItem>
<ImageCompression />
<FlareSolverr />
<DownloadLanguage />
<ChapterNamingScheme />
<NotificationConnectors />
<Download />
<Services />
<Maintenance />
</AccordionGroup>
</DialogContent>
@@ -83,12 +77,17 @@ export default function Settings({setApiUri} : {setApiUri: Dispatch<React.SetSta
);
}
export function SettingsItem({title, children} : {title: string, children: ReactNode}) {
export function SettingsItem({title, children, defaultExpanded, direction} : {title: string, children?: ReactNode, defaultExpanded?: boolean, direction?: "row" | "column"}) {
const [expanded, setExpanded] = React.useState(defaultExpanded??false);
const stackDirection = direction ?? "column";
return (
<Accordion>
<Accordion expanded={expanded} onChange={(_, expanded) => setExpanded(expanded)}>
<AccordionSummary>{title}</AccordionSummary>
<AccordionDetails>
{children}
<Stack direction={stackDirection} spacing={1}>
{children}
</Stack>
</AccordionDetails>
</Accordion>
);