mirror of
https://github.com/C9Glax/tranga-website.git
synced 2025-05-07 15:42:10 +02:00
Compare commits
10 Commits
6d402357e7
...
10f48af9fa
Author | SHA1 | Date | |
---|---|---|---|
10f48af9fa | |||
5e95c34306 | |||
66dcb2f1e6 | |||
19e033995d | |||
99265bacb2 | |||
5385dfd918 | |||
d6e4d1d27f | |||
288cd77049 | |||
e605578a34 | |||
6340c5ad03 |
@ -2,7 +2,7 @@ import Sheet from '@mui/joy/Sheet';
|
|||||||
import './App.css'
|
import './App.css'
|
||||||
import Settings from "./Settings.tsx";
|
import Settings from "./Settings.tsx";
|
||||||
import Header from "./Header.tsx";
|
import Header from "./Header.tsx";
|
||||||
import {Badge, Button} from "@mui/joy";
|
import {Badge, Box, Button, Card, CardContent, CardCover, Typography} from "@mui/joy";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {ApiUriContext} from "./api/fetchApi.tsx";
|
import {ApiUriContext} from "./api/fetchApi.tsx";
|
||||||
import Search from './Components/Search.tsx';
|
import Search from './Components/Search.tsx';
|
||||||
@ -23,12 +23,29 @@ export default function App () {
|
|||||||
<Badge color={"danger"} invisible={apiConnected} badgeContent={"!"}>
|
<Badge color={"danger"} invisible={apiConnected} badgeContent={"!"}>
|
||||||
<Button onClick={() => setShowSettings(true)}>Settings</Button>
|
<Button onClick={() => setShowSettings(true)}>Settings</Button>
|
||||||
</Badge>
|
</Badge>
|
||||||
<Button onClick={() => setShowSearch(true)}>Search</Button>
|
|
||||||
</Header>
|
</Header>
|
||||||
<Settings open={showSettings} setOpen={setShowSettings} setApiUri={setApiUri} setConnected={setApiConnected} />
|
<Settings open={showSettings} setOpen={setShowSettings} setApiUri={setApiUri} setConnected={setApiConnected} />
|
||||||
<Search open={showSearch} setOpen={setShowSearch} />
|
<Search open={showSearch} setOpen={setShowSearch} />
|
||||||
<Sheet className={"app-content"}>
|
<Sheet className={"app-content"}>
|
||||||
<MangaList />
|
<MangaList connected={apiConnected}>
|
||||||
|
<Badge invisible>
|
||||||
|
<Card onClick={() => setShowSearch(true)} sx={{height:"fit-content",width:"fit-content"}}>
|
||||||
|
<CardCover sx={{margin:"var(--Card-padding)"}}>
|
||||||
|
<img src={"/blahaj.png"} style={{height:"400px", width:"300px"}} />
|
||||||
|
</CardCover>
|
||||||
|
<CardCover sx={{
|
||||||
|
background: 'rgba(234, 119, 246, 0.14)',
|
||||||
|
backdropFilter: 'blur(6.9px)',
|
||||||
|
webkitBackdropFilter: 'blur(6.9px)',
|
||||||
|
}}/>
|
||||||
|
<CardContent>
|
||||||
|
<Box style={{height:"400px", width:"300px"}} >
|
||||||
|
<Typography level={"h1"}>Search</Typography>
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Badge>
|
||||||
|
</MangaList>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
</ApiUriContext.Provider>
|
</ApiUriContext.Provider>
|
||||||
|
@ -16,9 +16,9 @@ export default function LinkTag({linkId, color} : { linkId: string | undefined,
|
|||||||
}, [linkId]);
|
}, [linkId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Chip variant={"outlined"} size={"md"} color={color??"primary"}>
|
<Chip variant={"soft"} size={"sm"} color={color??"primary"}>
|
||||||
<Skeleton variant={"text"} loading={loading}>
|
<Skeleton variant={"text"} loading={loading}>
|
||||||
<Link href={link?.linkUrl}>{link?.linkProvider??"Load Failed"}</Link>
|
<Link sx={{textDecoration:"underline"}} level={"body-xs"} href={link?.linkUrl}>{link?.linkProvider??"Load Failed"}</Link>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</Chip>
|
</Chip>
|
||||||
);
|
);
|
||||||
|
@ -108,14 +108,14 @@ export function Manga({manga, children, loading} : { manga: IManga | undefined,
|
|||||||
expanded ?
|
expanded ?
|
||||||
<Box sx={sideSx}>
|
<Box sx={sideSx}>
|
||||||
<Skeleton loading={loading} variant={"text"} level={"title-lg"}>
|
<Skeleton loading={loading} variant={"text"} level={"title-lg"}>
|
||||||
<Stack direction={"row"} flexWrap={"wrap"} spacing={0.5}>
|
<Stack direction={"row"} flexWrap={"wrap"} spacing={0.5} sx={{maxHeight:"75px", overflowY:"auto", scrollbarWidth: "thin"}}>
|
||||||
{useManga.authorIds.map(authorId => <AuthorTag key={authorId} authorId={authorId} color={"success"} />)}
|
{useManga.authorIds.map(authorId => <AuthorTag key={authorId} authorId={authorId} color={"success"} />)}
|
||||||
{useManga.tags.map(tag => <Chip key={tag} variant={"outlined"} size={"md"} color={"primary"}>{tag}</Chip>)}
|
{useManga.tags.map(tag => <Chip key={tag} variant={"soft"} size={"md"} color={"primary"}>{tag}</Chip>)}
|
||||||
{useManga.linkIds.map(linkId => <LinkTag key={linkId} linkId={linkId} color={"danger"} />)}
|
{useManga.linkIds.map(linkId => <LinkTag key={linkId} linkId={linkId} color={"warning"} />)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<Skeleton loading={loading} sx={{maxHeight:"300px"}}>
|
<Skeleton loading={loading} sx={{maxHeight:"300px"}}>
|
||||||
<MarkdownPreview source={useManga.description} style={{backgroundColor: "transparent", color: "black"}} />
|
<MarkdownPreview source={useManga.description} style={{backgroundColor: "transparent", color: "black", maxHeight:"310px", overflowY:"auto", marginTop:"10px", scrollbarWidth: "thin"}} />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</Box>
|
</Box>
|
||||||
: null
|
: null
|
||||||
|
@ -6,13 +6,16 @@ import {JobType} from "../api/types/Jobs/IJob.ts";
|
|||||||
import IDownloadAvailableChaptersJob from "../api/types/Jobs/IDownloadAvailableChaptersJob.ts";
|
import IDownloadAvailableChaptersJob from "../api/types/Jobs/IDownloadAvailableChaptersJob.ts";
|
||||||
import {MangaFromId} from "./Manga.tsx";
|
import {MangaFromId} from "./Manga.tsx";
|
||||||
import { Remove } from "@mui/icons-material";
|
import { Remove } from "@mui/icons-material";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
export default function MangaList(){
|
export default function MangaList({connected, children}: {connected: boolean, children?: React.ReactNode} ){
|
||||||
const apiUri = useContext(ApiUriContext);
|
const apiUri = useContext(ApiUriContext);
|
||||||
|
|
||||||
const [jobList, setJobList] = useState<IDownloadAvailableChaptersJob[]>([]);
|
const [jobList, setJobList] = useState<IDownloadAvailableChaptersJob[]>([]);
|
||||||
|
|
||||||
const getJobList = useCallback(() => {
|
const getJobList = useCallback(() => {
|
||||||
|
if(!connected)
|
||||||
|
return;
|
||||||
GetJobsWithType(apiUri, JobType.DownloadAvailableChaptersJob).then((jl) => setJobList(jl as IDownloadAvailableChaptersJob[]));
|
GetJobsWithType(apiUri, JobType.DownloadAvailableChaptersJob).then((jl) => setJobList(jl as IDownloadAvailableChaptersJob[]));
|
||||||
},[apiUri]);
|
},[apiUri]);
|
||||||
|
|
||||||
@ -24,8 +27,21 @@ export default function MangaList(){
|
|||||||
getJobList();
|
getJobList();
|
||||||
}, [apiUri]);
|
}, [apiUri]);
|
||||||
|
|
||||||
|
const timerRef = React.useRef<ReturnType<typeof setInterval>>(undefined);
|
||||||
|
useEffect(() => {
|
||||||
|
if(!connected){
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
timerRef.current = setInterval(() => {
|
||||||
|
getJobList();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}, [connected,]);
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<Stack direction="row" spacing={1}>
|
<Stack direction="row" spacing={1}>
|
||||||
|
{children}
|
||||||
{jobList.map((job) => (
|
{jobList.map((job) => (
|
||||||
<MangaFromId key={job.mangaId} mangaId={job.mangaId}>
|
<MangaFromId key={job.mangaId} mangaId={job.mangaId}>
|
||||||
<Button color={"danger"} endDecorator={<Remove />} onClick={() => deleteJob(job.jobId)}>Delete</Button>
|
<Button color={"danger"} endDecorator={<Remove />} onClick={() => deleteJob(job.jobId)}>Delete</Button>
|
||||||
|
@ -48,6 +48,7 @@ export default function Search({open, setOpen}:{open:boolean, setOpen:React.Disp
|
|||||||
const [resultsLoading, setResultsLoading] = useState<boolean>(false);
|
const [resultsLoading, setResultsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const StartSearch = useCallback((mangaConnector : IMangaConnector | undefined, value: string)=>{
|
const StartSearch = useCallback((mangaConnector : IMangaConnector | undefined, value: string)=>{
|
||||||
|
setStep(3);
|
||||||
if(mangaConnector === undefined)
|
if(mangaConnector === undefined)
|
||||||
return;
|
return;
|
||||||
setResults([]);
|
setResults([]);
|
||||||
@ -92,19 +93,21 @@ export default function Search({open, setOpen}:{open:boolean, setOpen:React.Disp
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return (
|
return (
|
||||||
<Drawer size={"lg"} anchor={"right"} open={open} onClose={() => {
|
<Drawer size={"lg"} anchor={"right"} open={open} onClose={() => {
|
||||||
setStep(2);
|
if(step > 2)
|
||||||
|
setStep(2);
|
||||||
setResults([]);
|
setResults([]);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}>
|
}}>
|
||||||
<ModalClose />
|
<ModalClose />
|
||||||
<Stepper orientation={"vertical"} sx={{ height: '100%', width: "calc(100% - 80px)", margin:"40px"}}>
|
<Stepper orientation={"vertical"} sx={{ height: '100%', width: "calc(100% - 80px)", margin:"40px"}}>
|
||||||
<Step indicator={
|
<Step indicator={
|
||||||
<StepIndicator variant="solid" color="primary">
|
<StepIndicator variant={step==1?"solid":"outlined"} color={mangaConnectors.length < 1 ? "danger" : "primary"}>
|
||||||
1
|
1
|
||||||
</StepIndicator>}>
|
</StepIndicator>}>
|
||||||
<Skeleton loading={mangaConnectorsLoading}>
|
<Skeleton loading={mangaConnectorsLoading}>
|
||||||
<Select
|
<Select
|
||||||
disabled={mangaConnectorsLoading || resultsLoading}
|
color={mangaConnectors.length < 1 ? "danger" : "neutral"}
|
||||||
|
disabled={mangaConnectorsLoading || resultsLoading || mangaConnectors.length < 1}
|
||||||
placeholder={"Select Connector"}
|
placeholder={"Select Connector"}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
listbox: {
|
listbox: {
|
||||||
@ -119,13 +122,13 @@ export default function Search({open, setOpen}:{open:boolean, setOpen:React.Disp
|
|||||||
setStep(2);
|
setStep(2);
|
||||||
setSelectedMangaConnector(mangaConnectors.find((o) => o.name === newValue));
|
setSelectedMangaConnector(mangaConnectors.find((o) => o.name === newValue));
|
||||||
}}
|
}}
|
||||||
endDecorator={<Chip size={"sm"} color={"primary"}>{mangaConnectors.length}</Chip>}>
|
endDecorator={<Chip size={"sm"} color={mangaConnectors.length < 1 ? "danger" : "primary"}>{mangaConnectors.length}</Chip>}>
|
||||||
{mangaConnectors?.map((connector: IMangaConnector) => ConnectorOption(connector))}
|
{mangaConnectors?.map((connector: IMangaConnector) => ConnectorOption(connector))}
|
||||||
</Select>
|
</Select>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</Step>
|
</Step>
|
||||||
<Step indicator={
|
<Step indicator={
|
||||||
<StepIndicator variant="solid" color="primary">
|
<StepIndicator variant={step==2?"solid":"outlined"} color="primary">
|
||||||
2
|
2
|
||||||
</StepIndicator>}>
|
</StepIndicator>}>
|
||||||
<Input disabled={step < 2 || resultsLoading} placeholder={"Name or Url " + (selectedMangaConnector ? selectedMangaConnector.baseUris[0] : "")} onKeyDown={(e) => {
|
<Input disabled={step < 2 || resultsLoading} placeholder={"Name or Url " + (selectedMangaConnector ? selectedMangaConnector.baseUris[0] : "")} onKeyDown={(e) => {
|
||||||
@ -137,7 +140,7 @@ export default function Search({open, setOpen}:{open:boolean, setOpen:React.Disp
|
|||||||
}}/>
|
}}/>
|
||||||
</Step>
|
</Step>
|
||||||
<Step indicator={
|
<Step indicator={
|
||||||
<StepIndicator variant="solid" color="primary">
|
<StepIndicator variant={step==3?"solid":"outlined"} color="primary">
|
||||||
3
|
3
|
||||||
</StepIndicator>}>
|
</StepIndicator>}>
|
||||||
<Typography endDecorator={<Chip size={"sm"} color={"primary"}>{results.length}</Chip>}>Results</Typography>
|
<Typography endDecorator={<Chip size={"sm"} color={"primary"}>{results.length}</Chip>}>Results</Typography>
|
||||||
|
53
tranga-website/src/Components/Settings/AprilFoolsMode.tsx
Normal file
53
tranga-website/src/Components/Settings/AprilFoolsMode.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import IBackendSettings from "../../api/types/IBackendSettings.ts";
|
||||||
|
import {useCallback, useContext, useState} from "react";
|
||||||
|
import {ApiUriContext} from "../../api/fetchApi.tsx";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
|
ColorPaletteProp,
|
||||||
|
Switch,
|
||||||
|
Typography
|
||||||
|
} from "@mui/joy";
|
||||||
|
import * as React from "react";
|
||||||
|
import {UpdateAprilFoolsToggle} from "../../api/BackendSettings.tsx";
|
||||||
|
|
||||||
|
export default function ImageProcessing({backendSettings}: {backendSettings?: IBackendSettings}) {
|
||||||
|
const apiUri = useContext(ApiUriContext);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [color, setColor] = useState<ColorPaletteProp>("neutral");
|
||||||
|
|
||||||
|
const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
|
const valueChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setColor("warning");
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
console.log(e);
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
UpdateAprilFoolsMode(e.target.checked);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateAprilFoolsMode = useCallback((value: boolean) => {
|
||||||
|
UpdateAprilFoolsToggle(apiUri, value)
|
||||||
|
.then(() => setColor("success"))
|
||||||
|
.catch(() => setColor("danger"))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [apiUri]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary>April Fools Mode</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Typography endDecorator={
|
||||||
|
<Switch disabled={backendSettings === undefined || loading}
|
||||||
|
onChange={valueChanged}
|
||||||
|
color={color}
|
||||||
|
defaultChecked={backendSettings?.aprilFoolsMode} />
|
||||||
|
}>
|
||||||
|
Toggle
|
||||||
|
</Typography>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
import IBackendSettings from "../../api/types/IBackendSettings";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary, Chip,
|
||||||
|
CircularProgress,
|
||||||
|
ColorPaletteProp,
|
||||||
|
Divider,
|
||||||
|
Input,
|
||||||
|
Stack, Tooltip, Typography
|
||||||
|
} from "@mui/joy";
|
||||||
|
import {KeyboardEventHandler, useCallback, useContext, useState} from "react";
|
||||||
|
import {ApiUriContext} from "../../api/fetchApi.tsx";
|
||||||
|
import {UpdateChapterNamingScheme} from "../../api/BackendSettings.tsx";
|
||||||
|
|
||||||
|
export default function ChapterNamingScheme({backendSettings}: {backendSettings?: IBackendSettings}) {
|
||||||
|
const apiUri = useContext(ApiUriContext);
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [value, setValue] = useState<string>("");
|
||||||
|
const [color, setColor] = useState<ColorPaletteProp>("neutral");
|
||||||
|
|
||||||
|
const keyDown : KeyboardEventHandler<HTMLInputElement> = useCallback((e) => {
|
||||||
|
if(e.key === "Enter") {
|
||||||
|
setLoading(true);
|
||||||
|
UpdateChapterNamingScheme(apiUri, value)
|
||||||
|
.then(() => setColor("success"))
|
||||||
|
.catch(() => setColor("danger"))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}
|
||||||
|
}, [apiUri])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary>Chapter Naming Scheme</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Input disabled={backendSettings === undefined || loading}
|
||||||
|
placeholder={"Chapter Naming Scheme"}
|
||||||
|
defaultValue={backendSettings?.chapterNamingScheme}
|
||||||
|
onKeyDown={keyDown}
|
||||||
|
onChange={e => setValue(e.target.value)}
|
||||||
|
color={color}
|
||||||
|
endDecorator={(loading ? <CircularProgress color={"primary"} size={"sm"} /> : null)}
|
||||||
|
/>
|
||||||
|
<Typography level={"title-sm"}>Placeholders:</Typography>
|
||||||
|
<Stack direction="row" spacing={1} divider={<Divider />}>
|
||||||
|
<Tooltip arrow placement="bottom" size="md" variant="outlined"
|
||||||
|
title={"Manga Title"} >
|
||||||
|
<Chip color={"primary"}>%M</Chip>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip arrow placement="bottom" size="md" variant="outlined"
|
||||||
|
title={"Volume Number"} >
|
||||||
|
<Chip color={"primary"}>%V</Chip>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip arrow placement="bottom" size="md" variant="outlined"
|
||||||
|
title={"Chapter Number"} >
|
||||||
|
<Chip color={"primary"}>%C</Chip>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip arrow placement="bottom" size="md" variant="outlined"
|
||||||
|
title={"Chapter Title"} >
|
||||||
|
<Chip color={"primary"}>%T</Chip>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip arrow placement="bottom" size="md" variant="outlined"
|
||||||
|
title={"Year"} >
|
||||||
|
<Chip color={"primary"}>%Y</Chip>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip arrow placement="bottom" size="md" variant="outlined"
|
||||||
|
title={"First Author"} >
|
||||||
|
<Chip color={"primary"}>%A</Chip>
|
||||||
|
</Tooltip>
|
||||||
|
</Stack>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
114
tranga-website/src/Components/Settings/ImageProcessing.tsx
Normal file
114
tranga-website/src/Components/Settings/ImageProcessing.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import IBackendSettings from "../../api/types/IBackendSettings.ts";
|
||||||
|
import {useCallback, useContext, useState} from "react";
|
||||||
|
import {ApiUriContext} from "../../api/fetchApi.tsx";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
|
ColorPaletteProp,
|
||||||
|
Input,
|
||||||
|
Switch,
|
||||||
|
Typography
|
||||||
|
} from "@mui/joy";
|
||||||
|
import * as React from "react";
|
||||||
|
import {UpdateBWImageToggle, UpdateImageCompressionValue} from "../../api/BackendSettings.tsx";
|
||||||
|
|
||||||
|
export default function ImageProcessing({backendSettings}: {backendSettings?: IBackendSettings}) {
|
||||||
|
const apiUri = useContext(ApiUriContext);
|
||||||
|
|
||||||
|
const [loadingBw, setLoadingBw] = useState<boolean>(false);
|
||||||
|
const [bwInputColor, setBwInputcolor] = useState<ColorPaletteProp>("neutral");
|
||||||
|
|
||||||
|
const timerRefBw = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
|
const bwChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setBwInputcolor("warning");
|
||||||
|
clearTimeout(timerRefBw.current);
|
||||||
|
console.log(e);
|
||||||
|
timerRefBw.current = setTimeout(() => {
|
||||||
|
UpdateBw(e.target.checked);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateBw = useCallback((value: boolean) => {
|
||||||
|
UpdateBWImageToggle(apiUri, value)
|
||||||
|
.then(() => setBwInputcolor("success"))
|
||||||
|
.catch(() => setBwInputcolor("danger"))
|
||||||
|
.finally(() => setLoadingBw(false));
|
||||||
|
}, [apiUri]);
|
||||||
|
|
||||||
|
const [loadingCompression, setLoadingCompression] = useState<boolean>(false);
|
||||||
|
const [compressionInputColor, setCompressionInputColor] = useState<ColorPaletteProp>("neutral");
|
||||||
|
const [compressionEnabled, setCompressionEnabled] = useState<boolean>((backendSettings?.compression??100) < 100);
|
||||||
|
const [compressionValue, setCompressionValue] = useState<number|undefined>(backendSettings?.compression);
|
||||||
|
|
||||||
|
const timerRefCompression = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
|
const compressionLevelChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setCompressionInputColor("warning");
|
||||||
|
setCompressionValue(Number.parseInt(e.target.value));
|
||||||
|
clearTimeout(timerRefCompression.current);
|
||||||
|
|
||||||
|
console.log(e);
|
||||||
|
timerRefCompression.current = setTimeout(() => {
|
||||||
|
UpdateCompressionLevel(Number.parseInt(e.target.value));
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const compressionEnableChanged = (e : React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setCompressionInputColor("warning");
|
||||||
|
setCompressionEnabled(e.target.checked);
|
||||||
|
clearTimeout(timerRefCompression.current);
|
||||||
|
timerRefCompression.current = setTimeout(() => {
|
||||||
|
UpdateCompressionLevel(e.target.checked ? compressionValue! : 100);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateCompressionLevel = useCallback((value: number)=> {
|
||||||
|
setLoadingCompression(true);
|
||||||
|
UpdateImageCompressionValue(apiUri, value)
|
||||||
|
.then(() => {
|
||||||
|
setCompressionInputColor("success");
|
||||||
|
setCompressionValue(value);
|
||||||
|
})
|
||||||
|
.catch(() => setCompressionInputColor("danger"))
|
||||||
|
.finally(() => setLoadingCompression(false));
|
||||||
|
}, [apiUri]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary>Image Processing</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Typography endDecorator={
|
||||||
|
<Switch disabled={backendSettings === undefined || loadingBw}
|
||||||
|
onChange={bwChanged}
|
||||||
|
color={bwInputColor}
|
||||||
|
defaultChecked={backendSettings?.bwImages} />
|
||||||
|
}>
|
||||||
|
Black and White Images
|
||||||
|
</Typography>
|
||||||
|
<Typography endDecorator={
|
||||||
|
<Switch disabled={backendSettings === undefined || loadingCompression}
|
||||||
|
onChange={compressionEnableChanged}
|
||||||
|
color={compressionInputColor}
|
||||||
|
defaultChecked={compressionEnabled} endDecorator={
|
||||||
|
<Input
|
||||||
|
defaultValue={backendSettings?.compression}
|
||||||
|
disabled={!compressionEnabled || loadingCompression}
|
||||||
|
onChange={compressionLevelChanged}
|
||||||
|
color={compressionInputColor}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if(e.key === "Enter") {
|
||||||
|
clearTimeout(timerRefCompression.current);
|
||||||
|
// @ts-ignore
|
||||||
|
UpdateCompressionLevel(Number.parseInt(e.target.value));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={{width:"70px"}}
|
||||||
|
/>
|
||||||
|
} />
|
||||||
|
}>
|
||||||
|
Image Compression
|
||||||
|
</Typography>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
81
tranga-website/src/Components/Settings/RequestLimits.tsx
Normal file
81
tranga-website/src/Components/Settings/RequestLimits.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import IBackendSettings from "../../api/types/IBackendSettings.ts";
|
||||||
|
import {useCallback, useContext, useState} from "react";
|
||||||
|
import {ApiUriContext} from "../../api/fetchApi.tsx";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
|
Button,
|
||||||
|
ColorPaletteProp,
|
||||||
|
Input,
|
||||||
|
Stack,
|
||||||
|
Typography
|
||||||
|
} from "@mui/joy";
|
||||||
|
import {RequestLimitType} from "../../api/types/EnumRequestLimitType.ts";
|
||||||
|
import {ResetRequestLimit, ResetRequestLimits, UpdateRequestLimit} from "../../api/BackendSettings.tsx";
|
||||||
|
import {Restore} from "@mui/icons-material";
|
||||||
|
|
||||||
|
export default function RequestLimits({backendSettings}: {backendSettings?: IBackendSettings}) {
|
||||||
|
const apiUri = useContext(ApiUriContext);
|
||||||
|
|
||||||
|
const [color, setColor] = useState<ColorPaletteProp>("neutral");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const Update = useCallback((target: HTMLInputElement, limit: RequestLimitType) => {
|
||||||
|
setLoading(true);
|
||||||
|
UpdateRequestLimit(apiUri, limit, Number.parseInt(target.value))
|
||||||
|
.then(() => setColor("success"))
|
||||||
|
.catch(() => setColor("danger"))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
},[apiUri])
|
||||||
|
|
||||||
|
const Reset = useCallback((limit: RequestLimitType) => {
|
||||||
|
setLoading(true);
|
||||||
|
ResetRequestLimit(apiUri, limit)
|
||||||
|
.then(() => setColor("success"))
|
||||||
|
.catch(() => setColor("danger"))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [apiUri]);
|
||||||
|
|
||||||
|
const ResetAll = useCallback(() => {
|
||||||
|
setLoading(true);
|
||||||
|
ResetRequestLimits(apiUri)
|
||||||
|
.then(() => setColor("success"))
|
||||||
|
.catch(() => setColor("danger"))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [apiUri]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary>Request Limits</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Stack spacing={1} direction="column">
|
||||||
|
<Button loading={backendSettings === undefined} onClick={ResetAll} size={"sm"} variant={"outlined"} endDecorator={<Restore />} color={"warning"}>Reset all</Button>
|
||||||
|
<Item type={RequestLimitType.Default} color={color} backendSettings={backendSettings} loading={loading} Reset={Reset} Update={Update} />
|
||||||
|
<Item type={RequestLimitType.MangaInfo} color={color} backendSettings={backendSettings} loading={loading} Reset={Reset} Update={Update} />
|
||||||
|
<Item type={RequestLimitType.MangaImage} color={color} backendSettings={backendSettings} loading={loading} Reset={Reset} Update={Update} />
|
||||||
|
<Item type={RequestLimitType.MangaDexFeed} color={color} backendSettings={backendSettings} loading={loading} Reset={Reset} Update={Update} />
|
||||||
|
<Item type={RequestLimitType.MangaDexImage} color={color} backendSettings={backendSettings} loading={loading} Reset={Reset} Update={Update} />
|
||||||
|
</Stack>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Item({type, color, loading, backendSettings, Reset, Update}:
|
||||||
|
{type: RequestLimitType, color: ColorPaletteProp, loading: boolean, backendSettings: IBackendSettings | undefined, Reset: (x: RequestLimitType) => void, Update: (a: HTMLInputElement, x: RequestLimitType) => void}) {
|
||||||
|
return (
|
||||||
|
<Input slotProps={{input: {min: 0, max: 360}}}
|
||||||
|
color={color}
|
||||||
|
startDecorator={<Typography sx={{width:"140px"}}>{type}</Typography>}
|
||||||
|
endDecorator={<Button onClick={() => Reset(type)}>Reset</Button>}
|
||||||
|
disabled={loading} type={"number"}
|
||||||
|
defaultValue={backendSettings?.requestLimits[type]}
|
||||||
|
placeholder={"Default"}
|
||||||
|
required
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if(e.key == "Enter")
|
||||||
|
Update(e.target as HTMLInputElement, type);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -1,8 +1,15 @@
|
|||||||
import IBackendSettings from "../../api/types/IBackendSettings";
|
import IBackendSettings from "../../api/types/IBackendSettings";
|
||||||
import {Accordion, AccordionDetails, AccordionSummary, CircularProgress, ColorPaletteProp, Input} from "@mui/joy";
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
|
Button,
|
||||||
|
ColorPaletteProp,
|
||||||
|
Input
|
||||||
|
} from "@mui/joy";
|
||||||
import {KeyboardEventHandler, useCallback, useContext, useState} from "react";
|
import {KeyboardEventHandler, useCallback, useContext, useState} from "react";
|
||||||
import {ApiUriContext} from "../../api/fetchApi.tsx";
|
import {ApiUriContext} from "../../api/fetchApi.tsx";
|
||||||
import {UpdateUserAgent} from "../../api/BackendSettings.tsx";
|
import {ResetUserAgent, UpdateUserAgent} from "../../api/BackendSettings.tsx";
|
||||||
|
|
||||||
export default function UserAgent({backendSettings}: {backendSettings?: IBackendSettings}) {
|
export default function UserAgent({backendSettings}: {backendSettings?: IBackendSettings}) {
|
||||||
const apiUri = useContext(ApiUriContext);
|
const apiUri = useContext(ApiUriContext);
|
||||||
@ -20,6 +27,14 @@ export default function UserAgent({backendSettings}: {backendSettings?: IBackend
|
|||||||
}
|
}
|
||||||
}, [apiUri])
|
}, [apiUri])
|
||||||
|
|
||||||
|
const Reset = useCallback(() => {
|
||||||
|
setLoading(true);
|
||||||
|
ResetUserAgent(apiUri)
|
||||||
|
.then(() => setColor("success"))
|
||||||
|
.catch(() => setColor("danger"))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [apiUri]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary>UserAgent</AccordionSummary>
|
<AccordionSummary>UserAgent</AccordionSummary>
|
||||||
@ -30,7 +45,7 @@ export default function UserAgent({backendSettings}: {backendSettings?: IBackend
|
|||||||
onKeyDown={keyDown}
|
onKeyDown={keyDown}
|
||||||
onChange={e => setValue(e.target.value)}
|
onChange={e => setValue(e.target.value)}
|
||||||
color={color}
|
color={color}
|
||||||
endDecorator={(loading ? <CircularProgress color={"primary"} size={"sm"} /> : null)}
|
endDecorator={<Button onClick={Reset} loading={loading}>Reset</Button>}
|
||||||
/>
|
/>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
@ -15,6 +15,10 @@ import {ApiUriContext} from "./api/fetchApi.tsx";
|
|||||||
import IBackendSettings from "./api/types/IBackendSettings.ts";
|
import IBackendSettings from "./api/types/IBackendSettings.ts";
|
||||||
import { GetSettings } from './api/BackendSettings.tsx';
|
import { GetSettings } from './api/BackendSettings.tsx';
|
||||||
import UserAgent from "./Components/Settings/UserAgent.tsx";
|
import UserAgent from "./Components/Settings/UserAgent.tsx";
|
||||||
|
import ImageProcessing from "./Components/Settings/ImageProcessing.tsx";
|
||||||
|
import ChapterNamingScheme from "./Components/Settings/ChapterNamingScheme.tsx";
|
||||||
|
import AprilFoolsMode from './Components/Settings/AprilFoolsMode.tsx';
|
||||||
|
import RequestLimits from "./Components/Settings/RequestLimits.tsx";
|
||||||
|
|
||||||
const checkConnection = async (apiUri: string): Promise<boolean> =>{
|
const checkConnection = async (apiUri: string): Promise<boolean> =>{
|
||||||
return fetch(`${apiUri}/swagger/v2/swagger.json`,
|
return fetch(`${apiUri}/swagger/v2/swagger.json`,
|
||||||
@ -101,6 +105,10 @@ export default function Settings({open, setOpen, setApiUri, setConnected}:{open:
|
|||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<UserAgent backendSettings={backendSettings} />
|
<UserAgent backendSettings={backendSettings} />
|
||||||
|
<ImageProcessing backendSettings={backendSettings} />
|
||||||
|
<ChapterNamingScheme backendSettings={backendSettings} />
|
||||||
|
<AprilFoolsMode backendSettings={backendSettings} />
|
||||||
|
<RequestLimits backendSettings={backendSettings} />
|
||||||
</AccordionGroup>
|
</AccordionGroup>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user