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 MangaList from './Components/Mangas/MangaList.tsx'
|
||||||
import {Search} from './Search.tsx'
|
import {Search} from './Search.tsx'
|
||||||
import MangaConnectorProvider from './contexts/MangaConnectorContext.tsx'
|
import MangaConnectorProvider from './contexts/MangaConnectorContext.tsx'
|
||||||
|
import LibraryProvider from "./contexts/FileLibraryContext.tsx";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [apiUri, setApiUri] = useState<string>(
|
const [apiUri, setApiUri] = useState<string>(
|
||||||
@@ -27,17 +28,19 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<ApiProvider apiConfig={apiConfig}>
|
<ApiProvider apiConfig={apiConfig}>
|
||||||
<MangaConnectorProvider>
|
<MangaConnectorProvider>
|
||||||
<MangaProvider>
|
<LibraryProvider>
|
||||||
<Sheet className={'app'}>
|
<MangaProvider>
|
||||||
<Header>
|
<Sheet className={'app'}>
|
||||||
<Settings setApiUri={setApiUri} />
|
<Header>
|
||||||
</Header>
|
<Settings setApiUri={setApiUri} />
|
||||||
<Sheet className={'app-content'}>
|
</Header>
|
||||||
<MangaList openSearch={() => setSearchOpen(true)} />
|
<Sheet className={'app-content'}>
|
||||||
<Search open={searchOpen} setOpen={setSearchOpen} />
|
<MangaList openSearch={() => setSearchOpen(true)} />
|
||||||
|
<Search open={searchOpen} setOpen={setSearchOpen} />
|
||||||
|
</Sheet>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
</Sheet>
|
</MangaProvider>
|
||||||
</MangaProvider>
|
</LibraryProvider>
|
||||||
</MangaConnectorProvider>
|
</MangaConnectorProvider>
|
||||||
</ApiProvider>
|
</ApiProvider>
|
||||||
)
|
)
|
||||||
|
@@ -35,6 +35,6 @@ export const TColor = (state: TState): ColorPaletteProp => {
|
|||||||
export default interface TProps {
|
export default interface TProps {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
completionAction?: (
|
completionAction?: (
|
||||||
value: string | number | readonly string[] | undefined
|
value?: string | number | readonly string[]
|
||||||
) => Promise<void>
|
) => 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'} />
|
<img src={manga ? `${Api.baseUrl}/v2/Manga/${manga.key}/Cover` : '/blahaj.png'} />
|
||||||
</CardCover>
|
</CardCover>
|
||||||
</Card>
|
</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'}>
|
<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?.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> )}
|
{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>
|
</Stack>
|
||||||
<MarkdownPreview source={manga?.description} style={{backgroundColor: "transparent", color: theme.palette.text.primary, overflowY: "auto"}}/>
|
<MarkdownPreview source={manga?.description} style={{backgroundColor: "transparent", color: theme.palette.text.primary, overflowY: "auto"}}/>
|
||||||
</Stack>
|
</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}
|
{props.actions}
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</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 {Dispatch, ReactNode, useContext, useEffect, useState} from 'react'
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemDecorator,
|
ListItemDecorator,
|
||||||
@@ -18,6 +19,7 @@ import { ApiContext } from './contexts/ApiContext.tsx'
|
|||||||
import { MangaCardList } from './Components/Mangas/MangaList.tsx'
|
import { MangaCardList } from './Components/Mangas/MangaList.tsx'
|
||||||
import {MangaConnector, MinimalManga} from './api/data-contracts.ts'
|
import {MangaConnector, MinimalManga} from './api/data-contracts.ts'
|
||||||
import MangaDetail from "./MangaDetail.tsx";
|
import MangaDetail from "./MangaDetail.tsx";
|
||||||
|
import MangaDownloadDrawer from "./MangaDownloadDrawer.tsx";
|
||||||
|
|
||||||
export function Search(props: SearchModalProps): ReactNode {
|
export function Search(props: SearchModalProps): ReactNode {
|
||||||
const Api = useContext(ApiContext)
|
const Api = useContext(ApiContext)
|
||||||
@@ -64,11 +66,17 @@ export function Search(props: SearchModalProps): ReactNode {
|
|||||||
|
|
||||||
const [selectedManga, setSelectedManga] = useState<MinimalManga | undefined>(undefined);
|
const [selectedManga, setSelectedManga] = useState<MinimalManga | undefined>(undefined);
|
||||||
const [mangaDetailOpen, setMangaDetailOpen] = useState(false);
|
const [mangaDetailOpen, setMangaDetailOpen] = useState(false);
|
||||||
|
const [mangaDownloadDrawerOpen, setMangaDownloadDrawerOpen] = useState(false);
|
||||||
|
|
||||||
function openMangaDetail(manga: MinimalManga) {
|
function openMangaDetail(manga: MinimalManga) {
|
||||||
setSelectedManga(manga);
|
setSelectedManga(manga);
|
||||||
setMangaDetailOpen(true);
|
setMangaDetailOpen(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openMangaDownloadDrawer() {
|
||||||
|
setMangaDetailOpen(false);
|
||||||
|
setMangaDownloadDrawerOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={props.open} onClose={() => props.setOpen(false)}>
|
<Modal open={props.open} onClose={() => props.setOpen(false)}>
|
||||||
@@ -120,7 +128,10 @@ export function Search(props: SearchModalProps): ReactNode {
|
|||||||
</Step>
|
</Step>
|
||||||
</Stepper>
|
</Stepper>
|
||||||
<MangaCardList manga={searchResults} mangaOnClick={openMangaDetail}/>
|
<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>
|
</ModalDialog>
|
||||||
</Modal>
|
</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