From c625e3dedea02725fda1333bee8deb0fff7f32ef Mon Sep 17 00:00:00 2001 From: glax Date: Fri, 5 Sep 2025 17:07:42 +0200 Subject: [PATCH] Add ChaptersSection to Manga, add TCheckbox and rework other custom components --- tranga-website/src/App.tsx | 2 +- .../src/Components/Inputs/TButton.tsx | 5 +- .../src/Components/Inputs/TCheckbox.tsx | 35 +++++++++ .../src/Components/Inputs/TInput.tsx | 13 ++-- .../src/Components/Inputs/TProps.ts | 1 - .../Mangas/Detail/ChaptersSection.tsx | 78 +++++++++++++++++++ .../Mangas/Detail/DownloadSection.tsx | 64 ++++++++++++--- .../Components/Mangas/Detail/MangaDetail.tsx | 52 ++----------- .../Settings/ChapterNamingScheme.tsx | 2 +- .../Components/Settings/DownloadLanguage.tsx | 2 +- .../src/Components/Settings/FlareSolverr.tsx | 2 +- .../src/Components/Settings/Maintenance.tsx | 2 +- .../src/Components/Settings/Settings.tsx | 2 +- tranga-website/src/Search.tsx | 2 +- 14 files changed, 186 insertions(+), 76 deletions(-) create mode 100644 tranga-website/src/Components/Inputs/TCheckbox.tsx diff --git a/tranga-website/src/App.tsx b/tranga-website/src/App.tsx index e2a7b53..b95d6ca 100644 --- a/tranga-website/src/App.tsx +++ b/tranga-website/src/App.tsx @@ -70,7 +70,7 @@ export function App() { setOpen={setDownloadDrawerOpen} mangaKey={selectedMangaKey} downloadOpen={downloadSectionOpen}> - removeManga(selectedMangaKey)}> + removeManga(selectedMangaKey)}> Remove diff --git a/tranga-website/src/Components/Inputs/TButton.tsx b/tranga-website/src/Components/Inputs/TButton.tsx index 060e9f3..3ca48de 100644 --- a/tranga-website/src/Components/Inputs/TButton.tsx +++ b/tranga-website/src/Components/Inputs/TButton.tsx @@ -8,9 +8,9 @@ export default function TButton(props: TButtonProps) { const clicked: MouseEventHandler = (e) => { setState(TState.busy); e.preventDefault(); - if (props.completionAction) + if (props.onClick) props - .completionAction(undefined) + .onClick() .then(() => setState(TState.success)) .catch(() => setState(TState.failure)); }; @@ -29,4 +29,5 @@ export default function TButton(props: TButtonProps) { export interface TButtonProps extends TProps { children?: ReactNode; + onClick?: () => Promise; } diff --git a/tranga-website/src/Components/Inputs/TCheckbox.tsx b/tranga-website/src/Components/Inputs/TCheckbox.tsx new file mode 100644 index 0000000..ea4fcce --- /dev/null +++ b/tranga-website/src/Components/Inputs/TCheckbox.tsx @@ -0,0 +1,35 @@ +import { Checkbox } from '@mui/joy'; +import TProps, { TColor, TDisabled, TState } from './TProps.ts'; +import { ChangeEvent, ReactNode, useState } from 'react'; + +export default function TCheckbox(props: TCheckboxProps) { + const [state, setState] = useState(TState.clean); + + const onChange = (e: ChangeEvent) => { + setState(TState.busy); + e.preventDefault(); + if (props.onCheckChanged) + props + .onCheckChanged(e.target.checked) + .then(() => setState(TState.success)) + .catch(() => setState(TState.failure)); + }; + + return ( + + ); +} + +export interface TCheckboxProps extends TProps { + label?: ReactNode; + defaultChecked?: boolean; + onCheckChanged?: (value: boolean) => Promise; +} diff --git a/tranga-website/src/Components/Inputs/TInput.tsx b/tranga-website/src/Components/Inputs/TInput.tsx index aac3afe..bb671b2 100644 --- a/tranga-website/src/Components/Inputs/TInput.tsx +++ b/tranga-website/src/Components/Inputs/TInput.tsx @@ -6,12 +6,10 @@ import './loadingBorder.css'; export default function TInput(props: TInputProps) { const [state, setState] = useState(TState.clean); - const [value, setValue] = useState( + const [value, setValue] = useState(props.defaultValue); + const [initialValue, setInitialValue] = useState( props.defaultValue ); - const [initialValue, setInitialValue] = useState< - string | number | readonly string[] | undefined - >(props.defaultValue); const timerRef = React.useRef>(undefined); @@ -39,9 +37,9 @@ export default function TInput(props: TInputProps) { const submit = () => { setState(TState.busy); clearTimeout(timerRef.current); - if (props.completionAction) + if (props.onSubmit) props - .completionAction(value) + .onSubmit(value) .then(() => { setState(TState.success); setInitialValue(value); @@ -82,9 +80,10 @@ export default function TInput(props: TInputProps) { export interface TInputProps extends TProps { placeholder?: string; - defaultValue?: string | number | readonly string[]; + defaultValue?: string | number; actionDelay?: number; autoSubmit?: boolean; submitButtonHidden?: boolean; submitButtonText?: string; + onSubmit?: (value?: string | number) => Promise; } diff --git a/tranga-website/src/Components/Inputs/TProps.ts b/tranga-website/src/Components/Inputs/TProps.ts index 6354166..0b81dca 100644 --- a/tranga-website/src/Components/Inputs/TProps.ts +++ b/tranga-website/src/Components/Inputs/TProps.ts @@ -34,5 +34,4 @@ export const TColor = (state: TState): ColorPaletteProp => { export default interface TProps { disabled?: boolean; - completionAction?: (value?: string | number | readonly string[]) => Promise; } diff --git a/tranga-website/src/Components/Mangas/Detail/ChaptersSection.tsx b/tranga-website/src/Components/Mangas/Detail/ChaptersSection.tsx index e69de29..3a1e26f 100644 --- a/tranga-website/src/Components/Mangas/Detail/ChaptersSection.tsx +++ b/tranga-website/src/Components/Mangas/Detail/ChaptersSection.tsx @@ -0,0 +1,78 @@ +import { ReactNode, useContext, useEffect, useState } from 'react'; +import { Chapter, Manga, MangaConnector, MangaConnectorId } from '../../../api/data-contracts.ts'; +import { Accordion, AccordionDetails, AccordionSummary, Table, Typography } from '@mui/joy'; +import { ApiContext } from '../../../contexts/ApiContext.tsx'; +import { MangaConnectorContext } from '../../../contexts/MangaConnectorContext.tsx'; +import MangaConnectorIcon from '../MangaConnectorIcon.tsx'; +import TCheckbox from '../../Inputs/TCheckbox.tsx'; + +export default function ChaptersSection(props: ChaptersSectionProps): ReactNode { + const Api = useContext(ApiContext); + const MangaConnectors = useContext(MangaConnectorContext); + + const [chapters, setChapters] = useState([]); + useEffect(() => { + if (!props.manga) return; + Api.mangaChaptersList(props.manga.key).then((data) => { + if (!data.ok) return; + setChapters(data.data); + }); + }, [props]); + + const chapterConnectorCheckbox = (chapter: Chapter, connector: MangaConnector): ReactNode => { + const id = chapter.mangaConnectorIds.find((id) => id.mangaConnectorName == connector.key); + if (!id) return null; + return ( + setDownloadingFrom(id, value)} + defaultChecked={id.useForDownload} + /> + ); + }; + + const setDownloadingFrom = (id: MangaConnectorId, value: boolean): Promise => { + return Promise.reject(); + }; + + return ( + + + Chapters + + + Set source for chapter + + + + + + + {MangaConnectors.map((con) => ( + + ))} + + + + {chapters.map((ch) => ( + + + + + {MangaConnectors.map((con) => ( + + ))} + + ))} + +
VolChTitle + + {con.name} +
{ch.volume}{ch.chapterNumber}{ch.title}{chapterConnectorCheckbox(ch, con)}
+
+
+ ); +} + +export interface ChaptersSectionProps { + manga?: Manga; +} diff --git a/tranga-website/src/Components/Mangas/Detail/DownloadSection.tsx b/tranga-website/src/Components/Mangas/Detail/DownloadSection.tsx index 2cb273d..a3468d2 100644 --- a/tranga-website/src/Components/Mangas/Detail/DownloadSection.tsx +++ b/tranga-website/src/Components/Mangas/Detail/DownloadSection.tsx @@ -11,15 +11,61 @@ import { Stack, Typography, } from '@mui/joy'; -import { ReactNode, useContext } from 'react'; +import { ReactNode, useContext, useEffect, useState } from 'react'; import TButton from '../../Inputs/TButton.tsx'; import MangaConnectorIcon from '../MangaConnectorIcon.tsx'; import { FileLibrary, Manga, MangaConnectorId } from '../../../api/data-contracts.ts'; import { FileLibraryContext } from '../../../contexts/FileLibraryContext.tsx'; +import { ApiContext } from '../../../contexts/ApiContext.tsx'; -export default function DownloadSection(props: DownloadSectionProps): ReactNode { +export function DownloadSection(props: DownloadSectionProps): ReactNode { + const Api = useContext(ApiContext); const Libraries = useContext(FileLibraryContext); + const [manga, setManga] = useState(); + const [library, setLibrary] = useState(); + const [downloadFromMap, setDownloadFromMap] = useState>( + new Map() + ); + + useEffect(() => { + const newMap = new Map(); + setLibrary(Libraries.find((library) => library.key == manga?.fileLibraryId)); + manga?.mangaConnectorIds.forEach((id) => { + newMap.set(id, id.useForDownload); + }); + setDownloadFromMap(newMap); + }, [manga, Libraries]); + + useEffect(() => { + setManga(props.manga); + }, [props]); + + const onLibraryChange = (_: any, value: string | null) => { + setLibrary(Libraries.find((library) => library.key == value)); + }; + + const setDownload = async (): Promise => { + if (!manga) return Promise.reject(); + if (library) { + const s = await Api.mangaChangeLibraryCreate(manga.key, library?.key) + .then((result) => result.ok) + .catch(() => false); + if (!s) return Promise.reject(); + } + for (const kv of downloadFromMap) { + const s = await Api.mangaSetAsDownloadFromCreate( + manga?.key, + kv[0].mangaConnectorName, + kv[1] + ) + .then((result) => result.ok) + .catch(() => false); + if (!s) return Promise.reject(); + } + return Promise.resolve(); + }; + return ( @@ -34,8 +80,8 @@ export default function DownloadSection(props: DownloadSectionProps): ReactNode Select a Library to Download to: