mirror of
https://github.com/C9Glax/tranga-website.git
synced 2025-09-10 03:48:21 +02:00
Download Dialog
This commit is contained in:
@@ -8,6 +8,7 @@ import MangaProvider from './contexts/MangaContext.tsx'
|
||||
import MangaList from './Components/Mangas/MangaList.tsx'
|
||||
import {Search} from './Search.tsx'
|
||||
import MangaConnectorProvider from './contexts/MangaConnectorContext.tsx'
|
||||
import LibraryProvider from "./contexts/FileLibraryContext.tsx";
|
||||
|
||||
export default function App() {
|
||||
const [apiUri, setApiUri] = useState<string>(
|
||||
@@ -27,17 +28,19 @@ export default function App() {
|
||||
return (
|
||||
<ApiProvider apiConfig={apiConfig}>
|
||||
<MangaConnectorProvider>
|
||||
<MangaProvider>
|
||||
<Sheet className={'app'}>
|
||||
<Header>
|
||||
<Settings setApiUri={setApiUri} />
|
||||
</Header>
|
||||
<Sheet className={'app-content'}>
|
||||
<MangaList openSearch={() => setSearchOpen(true)} />
|
||||
<Search open={searchOpen} setOpen={setSearchOpen} />
|
||||
<LibraryProvider>
|
||||
<MangaProvider>
|
||||
<Sheet className={'app'}>
|
||||
<Header>
|
||||
<Settings setApiUri={setApiUri} />
|
||||
</Header>
|
||||
<Sheet className={'app-content'}>
|
||||
<MangaList openSearch={() => setSearchOpen(true)} />
|
||||
<Search open={searchOpen} setOpen={setSearchOpen} />
|
||||
</Sheet>
|
||||
</Sheet>
|
||||
</Sheet>
|
||||
</MangaProvider>
|
||||
</MangaProvider>
|
||||
</LibraryProvider>
|
||||
</MangaConnectorProvider>
|
||||
</ApiProvider>
|
||||
)
|
||||
|
@@ -35,6 +35,6 @@ export const TColor = (state: TState): ColorPaletteProp => {
|
||||
export default interface TProps {
|
||||
disabled?: boolean
|
||||
completionAction?: (
|
||||
value: string | number | readonly string[] | undefined
|
||||
value?: string | number | readonly string[]
|
||||
) => Promise<void>
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ export default function MangaDetail (props: MangaDetailProps) : ReactNode {
|
||||
<img src={manga ? `${Api.baseUrl}/v2/Manga/${manga.key}/Cover` : '/blahaj.png'} />
|
||||
</CardCover>
|
||||
</Card>
|
||||
<Stack direction={'column'} gap={2} sx={{maxWidth: 'calc(100% - 230px)', margin: '0 5px'}}>
|
||||
<Stack direction={'column'} gap={2} sx={{maxWidth: 'calc(100% - 230px)', margin: '5px'}}>
|
||||
<Stack direction={'row'} gap={0.5} flexWrap={'wrap'}>
|
||||
{manga?.tags.map(tag => <Chip key={tag} size={"sm"} sx={{backgroundColor: theme.palette.primary.plainColor}}>{tag}</Chip>)}
|
||||
{manga?.authors.map(author => <Chip key={author.key} size={'sm'} sx={{backgroundColor: theme.palette.success.plainColor}}>{author.name}</Chip> )}
|
||||
@@ -41,7 +41,7 @@ export default function MangaDetail (props: MangaDetailProps) : ReactNode {
|
||||
</Stack>
|
||||
<MarkdownPreview source={manga?.description} style={{backgroundColor: "transparent", color: theme.palette.text.primary, overflowY: "auto"}}/>
|
||||
</Stack>
|
||||
<Stack sx={{width: '100%'}} flexWrap={'nowrap'} gap={1}>
|
||||
<Stack sx={{flexGrow: 1, flexBasis: 0, margin: '5px 0', alignItems: 'flex-end'}} flexWrap={'nowrap'} gap={1}>
|
||||
{props.actions}
|
||||
</Stack>
|
||||
</div>
|
||||
|
91
tranga-website/src/MangaDownloadDrawer.tsx
Normal file
91
tranga-website/src/MangaDownloadDrawer.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import {Dispatch, ReactNode, useContext, useEffect, useState} from "react";
|
||||
import {Box, Card, Checkbox, Drawer, List, ListItem, Option, Select, Stack, Typography} from "@mui/joy";
|
||||
import ModalClose from "@mui/joy/ModalClose";
|
||||
import {Manga, MangaConnectorId} from "./api/data-contracts.ts";
|
||||
import {ApiContext} from "./contexts/ApiContext.tsx";
|
||||
import {MangaContext} from "./contexts/MangaContext.tsx";
|
||||
import {FileLibraryContext} from "./contexts/FileLibraryContext.tsx";
|
||||
import MangaConnectorIcon from "./Components/Mangas/MangaConnectorIcon.tsx";
|
||||
import TButton from "./Components/Inputs/TButton.tsx";
|
||||
|
||||
export default function MangaDownloadDrawer (props: MangaDownloadDrawerProps) : ReactNode{
|
||||
const Api = useContext(ApiContext);
|
||||
const Manga = useContext(MangaContext);
|
||||
const Libraries = useContext(FileLibraryContext);
|
||||
|
||||
const [manga, setManga] = useState<Manga | undefined>(props.manga)
|
||||
const [downloadFromMap, setDownloadFromMap] = useState<Map<MangaConnectorId, boolean>>(new Map())
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.open) return;
|
||||
if (!props.mangaKey) return;
|
||||
if (props.manga != undefined) return;
|
||||
Manga.GetManga(props.mangaKey).then(setManga);
|
||||
}, [Api, props]);
|
||||
|
||||
useEffect(() => {
|
||||
const newMap = new Map();
|
||||
manga?.mangaConnectorIds.forEach(id => {
|
||||
newMap.set(id, id.useForDownload);
|
||||
})
|
||||
setDownloadFromMap(newMap);
|
||||
}, [manga]);
|
||||
|
||||
const setDownload = () : Promise<void> => {
|
||||
if (!manga) return Promise.reject();
|
||||
downloadFromMap.forEach(async (download, id) => {
|
||||
const result = await Api.mangaSetAsDownloadFromCreate(manga?.key, id.mangaConnectorName, download);
|
||||
if (!result.ok)
|
||||
return Promise.reject();
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={props.open}
|
||||
onClose={() => props.setOpen(false)}
|
||||
anchor="left"
|
||||
size="md">
|
||||
<Card sx={{flexGrow: 1, margin: '10px'}}>
|
||||
<ModalClose />
|
||||
<Typography level={"h3"}>Download</Typography>
|
||||
<Typography level={"h4"}>{manga?.name}</Typography>
|
||||
<Stack direction={'column'} gap={2} sx={{flexBasis: 0}}>
|
||||
<Box>
|
||||
<Typography>Select a Library to Download to:</Typography>
|
||||
<Select placeholder={"Select a Library"}>
|
||||
{Libraries.map(l => <Option key={l.key} value={l.key}>{l.libraryName} ({l.basePath})</Option>)}
|
||||
</Select>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>Select which connectors you want to download this Manga from:</Typography>
|
||||
<List>
|
||||
{manga?.mangaConnectorIds.map(id => (
|
||||
<ListItem key={id.key}>
|
||||
<Checkbox
|
||||
defaultChecked={id.useForDownload}
|
||||
onChange={(c) => downloadFromMap.set(id, c.target.checked)}
|
||||
label={
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: 5}}>
|
||||
<MangaConnectorIcon mangaConnectorName={id.mangaConnectorName} />
|
||||
<Typography>{id.mangaConnectorName}</Typography>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
<TButton completionAction={setDownload}>Download All</TButton>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
export interface MangaDownloadDrawerProps {
|
||||
manga?: Manga;
|
||||
mangaKey?: string;
|
||||
open: boolean;
|
||||
setOpen: Dispatch<boolean>;
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
import {Dispatch, ReactNode, useContext, useEffect, useState} from 'react'
|
||||
import {
|
||||
Button,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemDecorator,
|
||||
@@ -18,6 +19,7 @@ import { ApiContext } from './contexts/ApiContext.tsx'
|
||||
import { MangaCardList } from './Components/Mangas/MangaList.tsx'
|
||||
import {MangaConnector, MinimalManga} from './api/data-contracts.ts'
|
||||
import MangaDetail from "./MangaDetail.tsx";
|
||||
import MangaDownloadDrawer from "./MangaDownloadDrawer.tsx";
|
||||
|
||||
export function Search(props: SearchModalProps): ReactNode {
|
||||
const Api = useContext(ApiContext)
|
||||
@@ -64,12 +66,18 @@ export function Search(props: SearchModalProps): ReactNode {
|
||||
|
||||
const [selectedManga, setSelectedManga] = useState<MinimalManga | undefined>(undefined);
|
||||
const [mangaDetailOpen, setMangaDetailOpen] = useState(false);
|
||||
const [mangaDownloadDrawerOpen, setMangaDownloadDrawerOpen] = useState(false);
|
||||
|
||||
function openMangaDetail(manga: MinimalManga) {
|
||||
setSelectedManga(manga);
|
||||
setMangaDetailOpen(true);
|
||||
}
|
||||
|
||||
function openMangaDownloadDrawer() {
|
||||
setMangaDetailOpen(false);
|
||||
setMangaDownloadDrawerOpen(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={props.open} onClose={() => props.setOpen(false)}>
|
||||
<ModalDialog sx={{width: '90vw'}}>
|
||||
@@ -120,7 +128,10 @@ export function Search(props: SearchModalProps): ReactNode {
|
||||
</Step>
|
||||
</Stepper>
|
||||
<MangaCardList manga={searchResults} mangaOnClick={openMangaDetail}/>
|
||||
<MangaDetail mangaKey={selectedManga?.key} open={mangaDetailOpen} setOpen={setMangaDetailOpen} />
|
||||
<MangaDetail mangaKey={selectedManga?.key} open={mangaDetailOpen} setOpen={setMangaDetailOpen} actions={[
|
||||
<Button onClick={openMangaDownloadDrawer}>Download</Button>
|
||||
]} />
|
||||
<MangaDownloadDrawer open={mangaDownloadDrawerOpen} setOpen={setMangaDownloadDrawerOpen} mangaKey={selectedManga?.key} />
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
)
|
||||
|
29
tranga-website/src/contexts/FileLibraryContext.tsx
Normal file
29
tranga-website/src/contexts/FileLibraryContext.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {FileLibrary} from '../api/data-contracts.ts'
|
||||
import { ApiContext } from './ApiContext.tsx'
|
||||
|
||||
export const FileLibraryContext = createContext<FileLibrary[]>([]);
|
||||
|
||||
export default function LibraryProvider({ children }: { children: ReactNode }) : ReactNode {
|
||||
const Api = useContext(ApiContext)
|
||||
|
||||
const [state, setState] = useState<FileLibrary[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
Api.fileLibraryList().then((result) => {
|
||||
if (result.ok) {
|
||||
setState(result.data)
|
||||
}
|
||||
})
|
||||
}, [Api])
|
||||
|
||||
return (
|
||||
<FileLibraryContext value={state}>{children}</FileLibraryContext>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user