lint and prettier

This commit is contained in:
2025-09-04 21:48:05 +02:00
parent 81bde5c099
commit 19920fb507
10 changed files with 329 additions and 161 deletions

View File

@@ -6,9 +6,9 @@ import { useEffect, useState } from 'react'
import { ApiConfig } from './api/http-client.ts' import { ApiConfig } from './api/http-client.ts'
import MangaProvider from './contexts/MangaContext.tsx' 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"; import LibraryProvider from './contexts/FileLibraryContext.tsx'
export default function App() { export default function App() {
const [apiUri, setApiUri] = useState<string>( const [apiUri, setApiUri] = useState<string>(
@@ -35,8 +35,13 @@ export default function App() {
<Settings setApiUri={setApiUri} /> <Settings setApiUri={setApiUri} />
</Header> </Header>
<Sheet className={'app-content'}> <Sheet className={'app-content'}>
<MangaList openSearch={() => setSearchOpen(true)} /> <MangaList
<Search open={searchOpen} setOpen={setSearchOpen} /> openSearch={() => setSearchOpen(true)}
/>
<Search
open={searchOpen}
setOpen={setSearchOpen}
/>
</Sheet> </Sheet>
</Sheet> </Sheet>
</MangaProvider> </MangaProvider>

View File

@@ -1,10 +1,13 @@
@keyframes spin { @keyframes spin {
from {transform: translate(-50%, -50%) rotate(0);} from {
to {transform: translate(-50%, -50%) rotate(360deg);} transform: translate(-50%, -50%) rotate(0);
}
to {
transform: translate(-50%, -50%) rotate(360deg);
}
} }
.t-loadable[aria-disabled='true'] {
.t-loadable[aria-disabled="true"] {
--border-radius: 5px; --border-radius: 5px;
--border-size: 2px; --border-size: 2px;
--border-bg: conic-gradient(red, yellow, lime, aqua, blue, magenta, red); --border-bg: conic-gradient(red, yellow, lime, aqua, blue, magenta, red);
@@ -29,10 +32,10 @@
z-index: -1 !important; z-index: -1 !important;
animation: spin 5s linear infinite; animation: spin 5s linear infinite;
} }
&--reverse::before{ &--reverse::before {
animation-direction: reverse; animation-direction: reverse;
} }
&::after{ &::after {
content: ''; content: '';
position: absolute; position: absolute;
inset: var(--border-size); inset: var(--border-size);
@@ -42,10 +45,10 @@
} }
} }
.t-loadable[aria-disabled="true"]:has(input)::after { .t-loadable[aria-disabled='true']:has(input)::after {
background: black; background: black;
} }
.t-loadable[aria-disabled="true"]:is(button) { .t-loadable[aria-disabled='true']:is(button) {
color: var(--joy-palette-text-primary); color: var(--joy-palette-text-primary);
} }

View File

@@ -13,7 +13,7 @@
.manga-card-cover-blur { .manga-card-cover-blur {
background: linear-gradient( background: linear-gradient(
150deg, 150deg,
rgba(245, 169, 184, 0.80) 50%, rgba(245, 169, 184, 0.8) 50%,
rgba(91, 206, 250, 0.3) rgba(91, 206, 250, 0.3)
); );
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);

View File

@@ -13,18 +13,26 @@ import { Manga, MinimalManga } from '../../api/data-contracts.ts'
import { ApiContext } from '../../contexts/ApiContext.tsx' import { ApiContext } from '../../contexts/ApiContext.tsx'
export default function MangaCard(props: MangaCardProps): ReactNode { export default function MangaCard(props: MangaCardProps): ReactNode {
const Api = useContext(ApiContext); const Api = useContext(ApiContext)
return ( return (
<Badge <Badge
badgeContent={props.manga?.mangaConnectorIds.map((id) => ( badgeContent={props.manga?.mangaConnectorIds.map((id) => (
<MangaConnectorIcon mangaConnectorName={id.mangaConnectorName} /> <MangaConnectorIcon
mangaConnectorName={id.mangaConnectorName}
/>
))} ))}
className={'manga-card-badge'} className={'manga-card-badge'}
> >
<Card className={'manga-card'} onClick={props.onClick}> <Card className={'manga-card'} onClick={props.onClick}>
<CardCover className={'manga-card-cover'}> <CardCover className={'manga-card-cover'}>
<img src={props.manga && props.manga.key != "Search" ? `${Api.baseUrl}/v2/Manga/${props.manga?.key}/Cover` : '/blahaj.png'} /> <img
src={
props.manga && props.manga.key != 'Search'
? `${Api.baseUrl}/v2/Manga/${props.manga?.key}/Cover`
: '/blahaj.png'
}
/>
</CardCover> </CardCover>
<CardCover className={'manga-card-cover-blur'} /> <CardCover className={'manga-card-cover-blur'} />
<CardContent className={'manga-card-content'}> <CardContent className={'manga-card-content'}>

View File

@@ -14,20 +14,20 @@ export default function MangaConnectorIcon({
const [connector, setConnector] = useState<MangaConnector | undefined>( const [connector, setConnector] = useState<MangaConnector | undefined>(
mangaConnector mangaConnector
); )
useEffect(() => { useEffect(() => {
if (mangaConnector) { if (mangaConnector) {
setConnector(mangaConnector) setConnector(mangaConnector)
return; return
} }
if (!mangaConnectorName) return; if (!mangaConnectorName) return
Api.mangaConnectorDetail(mangaConnectorName).then((result) => { Api.mangaConnectorDetail(mangaConnectorName).then((result) => {
if (result.ok) { if (result.ok) {
setConnector(result.data) setConnector(result.data)
} }
}); })
}, [Api, mangaConnectorName, mangaConnector]); }, [Api, mangaConnectorName, mangaConnector])
return ( return (
<Tooltip title={connector?.name ?? 'loading'}> <Tooltip title={connector?.name ?? 'loading'}>
@@ -35,7 +35,7 @@ export default function MangaConnectorIcon({
src={connector?.iconUrl ?? '/blahaj.png'} src={connector?.iconUrl ?? '/blahaj.png'}
width={'25px'} width={'25px'}
height={'25px'} height={'25px'}
style={{borderRadius: '100%'}} style={{ borderRadius: '100%' }}
/> />
</Tooltip> </Tooltip>
) )

View File

@@ -1,6 +1,6 @@
import { Stack } from '@mui/joy' import { Stack } from '@mui/joy'
import './MangaList.css' import './MangaList.css'
import {Dispatch, ReactNode, useContext, useEffect, useState} from 'react' import { Dispatch, ReactNode, useContext, useEffect, useState } from 'react'
import { import {
Manga, Manga,
MangaReleaseStatus, MangaReleaseStatus,
@@ -56,7 +56,13 @@ export function MangaCardList(props: MangaCardListProps): ReactNode {
> >
{props.children} {props.children}
{props.manga.map((m) => ( {props.manga.map((m) => (
<MangaCard key={m.key} manga={m} onClick={() => { if(props.mangaOnClick) props.mangaOnClick(m); } } /> <MangaCard
key={m.key}
manga={m}
onClick={() => {
if (props.mangaOnClick) props.mangaOnClick(m)
}}
/>
))} ))}
</Stack> </Stack>
) )

View File

@@ -1,59 +1,135 @@
import {Manga} from "./api/data-contracts.ts"; import { Manga } from './api/data-contracts.ts'
import {Dispatch, ReactNode, useContext, useEffect, useState} from "react"; import { Dispatch, ReactNode, useContext, useEffect, useState } from 'react'
import {Card, CardCover, Chip, Modal, ModalDialog, Stack, Typography, useTheme} from "@mui/joy"; import {
import ModalClose from "@mui/joy/ModalClose"; Card,
import {ApiContext} from "./contexts/ApiContext.tsx"; CardCover,
import {MangaContext} from "./contexts/MangaContext.tsx"; Chip,
Modal,
ModalDialog,
Stack,
Typography,
useTheme,
} from '@mui/joy'
import ModalClose from '@mui/joy/ModalClose'
import { ApiContext } from './contexts/ApiContext.tsx'
import { MangaContext } from './contexts/MangaContext.tsx'
import './Components/Mangas/MangaCard.css' import './Components/Mangas/MangaCard.css'
import MarkdownPreview from "@uiw/react-markdown-preview"; import MarkdownPreview from '@uiw/react-markdown-preview'
export default function MangaDetail (props: MangaDetailProps) : ReactNode { export default function MangaDetail(props: MangaDetailProps): ReactNode {
const Api = useContext(ApiContext); const Api = useContext(ApiContext)
const Manga = useContext(MangaContext); const Manga = useContext(MangaContext)
const [manga, setManga] = useState<Manga | undefined>(props.manga) const [manga, setManga] = useState<Manga | undefined>(props.manga)
useEffect(() => { useEffect(() => {
if (!props.open) return; if (!props.open) return
if (!props.mangaKey) return; if (!props.mangaKey) return
if (props.manga != undefined) return; if (props.manga != undefined) return
Manga.GetManga(props.mangaKey).then(setManga); Manga.GetManga(props.mangaKey).then(setManga)
}, [Api, props]); }, [Api, Manga, props])
const theme = useTheme(); const theme = useTheme()
return ( return (
<Modal open={props.open} onClose={() => props.setOpen(false)}> <Modal open={props.open} onClose={() => props.setOpen(false)}>
<ModalDialog> <ModalDialog>
<ModalClose /> <ModalClose />
<div style={{display: 'flex', flexWrap: 'wrap', flexDirection: 'row'}}> <div
<Typography level={"h3"} sx={{width: '100%'}}>{manga?.name}</Typography> style={{
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'row',
}}
>
<Typography level={'h3'} sx={{ width: '100%' }}>
{manga?.name}
</Typography>
<Card className={'manga-card'}> <Card className={'manga-card'}>
<CardCover className={'manga-card-cover'}> <CardCover className={'manga-card-cover'}>
<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: '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) => (
{manga?.authors.map(author => <Chip key={author.key} size={'sm'} sx={{backgroundColor: theme.palette.success.plainColor}}>{author.name}</Chip> )} <Chip
{manga?.links.map(link => <Chip key={link.provider} size={"sm"} sx={{backgroundColor: theme.palette.neutral.plainColor}}><a href={link.url}>{link.provider}</a></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?.links.map((link) => (
<Chip
key={link.provider}
size={'sm'}
sx={{
backgroundColor:
theme.palette.neutral.plainColor,
}}
>
<a href={link.url}>{link.provider}</a>
</Chip>
))}
</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={{flexGrow: 1, flexBasis: 0, margin: '5px 0', alignItems: 'flex-end'}} 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>
</ModalDialog> </ModalDialog>
</Modal> </Modal>
); )
} }
export interface MangaDetailProps { export interface MangaDetailProps {
manga?: Manga; manga?: Manga
mangaKey?: string; mangaKey?: string
open: boolean; open: boolean
setOpen: Dispatch<boolean>; setOpen: Dispatch<boolean>
actions?: ReactNode[]; actions?: ReactNode[]
} }

View File

@@ -1,82 +1,130 @@
import {Dispatch, ReactNode, useContext, useEffect, useState} from "react"; import { Dispatch, ReactNode, useContext, useEffect, useState } from 'react'
import {Box, Card, Checkbox, Drawer, List, ListItem, Option, Select, Stack, Typography} from "@mui/joy"; import {
import ModalClose from "@mui/joy/ModalClose"; Box,
import {Manga, MangaConnectorId} from "./api/data-contracts.ts"; Card,
import {ApiContext} from "./contexts/ApiContext.tsx"; Checkbox,
import {MangaContext} from "./contexts/MangaContext.tsx"; Drawer,
import {FileLibraryContext} from "./contexts/FileLibraryContext.tsx"; List,
import MangaConnectorIcon from "./Components/Mangas/MangaConnectorIcon.tsx"; ListItem,
import TButton from "./Components/Inputs/TButton.tsx"; 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{ export default function MangaDownloadDrawer(
const Api = useContext(ApiContext); props: MangaDownloadDrawerProps
const Manga = useContext(MangaContext); ): ReactNode {
const Libraries = useContext(FileLibraryContext); const Api = useContext(ApiContext)
const Manga = useContext(MangaContext)
const Libraries = useContext(FileLibraryContext)
const [manga, setManga] = useState<Manga | undefined>(props.manga) const [manga, setManga] = useState<Manga | undefined>(props.manga)
const [downloadFromMap, setDownloadFromMap] = useState<Map<MangaConnectorId, boolean>>(new Map()) const [downloadFromMap, setDownloadFromMap] = useState<
Map<MangaConnectorId, boolean>
>(new Map())
useEffect(() => { useEffect(() => {
if (!props.open) return; if (!props.open) return
if (!props.mangaKey) return; if (!props.mangaKey) return
if (props.manga != undefined) return; if (props.manga != undefined) return
Manga.GetManga(props.mangaKey).then(setManga); Manga.GetManga(props.mangaKey).then(setManga)
}, [Api, props]); }, [Api, Manga, props])
useEffect(() => { useEffect(() => {
const newMap = new Map(); const newMap = new Map()
manga?.mangaConnectorIds.forEach(id => { manga?.mangaConnectorIds.forEach((id) => {
newMap.set(id, id.useForDownload); newMap.set(id, id.useForDownload)
}) })
setDownloadFromMap(newMap); setDownloadFromMap(newMap)
}, [manga]); }, [manga])
const setDownload = () : Promise<void> => { const setDownload = (): Promise<void> => {
if (!manga) return Promise.reject(); if (!manga) return Promise.reject()
downloadFromMap.forEach(async (download, id) => { downloadFromMap.forEach(async (download, id) => {
const result = await Api.mangaSetAsDownloadFromCreate(manga?.key, id.mangaConnectorName, download); const result = await Api.mangaSetAsDownloadFromCreate(
if (!result.ok) manga?.key,
return Promise.reject(); id.mangaConnectorName,
}); download
return Promise.resolve(); )
if (!result.ok) return Promise.reject()
})
return Promise.resolve()
} }
return ( return (
<Drawer open={props.open} <Drawer
onClose={() => props.setOpen(false)} open={props.open}
anchor="left" onClose={() => props.setOpen(false)}
size="md"> anchor="left"
<Card sx={{flexGrow: 1, margin: '10px'}}> size="md"
>
<Card sx={{ flexGrow: 1, margin: '10px' }}>
<ModalClose /> <ModalClose />
<Typography level={"h3"}>Download</Typography> <Typography level={'h3'}>Download</Typography>
<Typography level={"h4"}>{manga?.name}</Typography> <Typography level={'h4'}>{manga?.name}</Typography>
<Stack direction={'column'} gap={2} sx={{flexBasis: 0}}> <Stack direction={'column'} gap={2} sx={{ flexBasis: 0 }}>
<Box> <Box>
<Typography>Select a Library to Download to:</Typography> <Typography>
<Select placeholder={"Select a Library"}> Select a Library to Download to:
{Libraries.map(l => <Option key={l.key} value={l.key}>{l.libraryName} ({l.basePath})</Option>)} </Typography>
<Select placeholder={'Select a Library'}>
{Libraries.map((l) => (
<Option key={l.key} value={l.key}>
{l.libraryName} ({l.basePath})
</Option>
))}
</Select> </Select>
</Box> </Box>
<Box> <Box>
<Typography>Select which connectors you want to download this Manga from:</Typography> <Typography>
Select which connectors you want to download this
Manga from:
</Typography>
<List> <List>
{manga?.mangaConnectorIds.map(id => ( {manga?.mangaConnectorIds.map((id) => (
<ListItem key={id.key}> <ListItem key={id.key}>
<Checkbox <Checkbox
defaultChecked={id.useForDownload} defaultChecked={id.useForDownload}
onChange={(c) => downloadFromMap.set(id, c.target.checked)} onChange={(c) =>
downloadFromMap.set(
id,
c.target.checked
)
}
label={ label={
<div style={{display: 'flex', alignItems: 'center', gap: 5}}> <div
<MangaConnectorIcon mangaConnectorName={id.mangaConnectorName} /> style={{
<Typography>{id.mangaConnectorName}</Typography> display: 'flex',
</div> alignItems: 'center',
} gap: 5,
}}
>
<MangaConnectorIcon
mangaConnectorName={
id.mangaConnectorName
}
/>
<Typography>
{id.mangaConnectorName}
</Typography>
</div>
}
/> />
</ListItem> </ListItem>
))} ))}
</List> </List>
</Box> </Box>
<TButton completionAction={setDownload}>Download All</TButton> <TButton completionAction={setDownload}>
Download All
</TButton>
</Stack> </Stack>
</Card> </Card>
</Drawer> </Drawer>
@@ -84,8 +132,8 @@ export default function MangaDownloadDrawer (props: MangaDownloadDrawerProps) :
} }
export interface MangaDownloadDrawerProps { export interface MangaDownloadDrawerProps {
manga?: Manga; manga?: Manga
mangaKey?: string; mangaKey?: string
open: boolean; open: boolean
setOpen: Dispatch<boolean>; setOpen: Dispatch<boolean>
} }

View File

@@ -1,4 +1,4 @@
import {Dispatch, ReactNode, useContext, useEffect, useState} from 'react' import { Dispatch, ReactNode, useContext, useEffect, useState } from 'react'
import { import {
Button, Button,
List, List,
@@ -17,9 +17,9 @@ import MangaConnectorIcon from './Components/Mangas/MangaConnectorIcon.tsx'
import TInput from './Components/Inputs/TInput.tsx' import TInput from './Components/Inputs/TInput.tsx'
import { ApiContext } from './contexts/ApiContext.tsx' 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"; 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)
@@ -27,10 +27,10 @@ export function Search(props: SearchModalProps): ReactNode {
useEffect(() => { useEffect(() => {
if (props.open) { if (props.open) {
setSelectedConnector(undefined); setSelectedConnector(undefined)
setSearchResults([]); setSearchResults([])
} }
}, [open]); }, [props])
const [selectedConnector, setSelectedConnector] = useState<MangaConnector>() const [selectedConnector, setSelectedConnector] = useState<MangaConnector>()
const [searchResults, setSearchResults] = useState<MinimalManga[]>([]) const [searchResults, setSearchResults] = useState<MinimalManga[]>([])
@@ -42,46 +42,52 @@ export function Search(props: SearchModalProps): ReactNode {
setSearchResults([]) setSearchResults([])
if (isUrl(value)) { if (isUrl(value)) {
try { try {
let result = await Api.searchUrlCreate(value); const result = await Api.searchUrlCreate(value)
if (result.ok) { if (result.ok) {
setSearchResults([result.data]) setSearchResults([result.data])
return Promise.resolve() return Promise.resolve()
} else return Promise.reject() } else return Promise.reject()
} catch (reason) { } catch (reason) {
return await Promise.reject(reason); return await Promise.reject(reason)
} }
} else { } else {
if (!selectedConnector) return Promise.reject() if (!selectedConnector) return Promise.reject()
try { try {
let result2 = await Api.searchDetail(selectedConnector?.key, value); const result2 = await Api.searchDetail(
selectedConnector?.key,
value
)
if (result2.ok) { if (result2.ok) {
setSearchResults(result2.data) setSearchResults(result2.data)
return Promise.resolve() return Promise.resolve()
} else return Promise.reject() } else return Promise.reject()
} catch (reason1) { } catch (reason1) {
return await Promise.reject(reason1); return await Promise.reject(reason1)
} }
} }
} }
const [selectedManga, setSelectedManga] = useState<MinimalManga | undefined>(undefined); const [selectedManga, setSelectedManga] = useState<
const [mangaDetailOpen, setMangaDetailOpen] = useState(false); MinimalManga | undefined
const [mangaDownloadDrawerOpen, setMangaDownloadDrawerOpen] = useState(false); >(undefined)
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() { function openMangaDownloadDrawer() {
setMangaDetailOpen(false); setMangaDetailOpen(false)
setMangaDownloadDrawerOpen(true); setMangaDownloadDrawerOpen(true)
} }
return ( return (
<Modal open={props.open} onClose={() => props.setOpen(false)}> <Modal open={props.open} onClose={() => props.setOpen(false)}>
<ModalDialog sx={{width: '90vw'}}> <ModalDialog sx={{ width: '90vw' }}>
<ModalClose/> <ModalClose />
<Stepper> <Stepper>
<Step <Step
orientation={'vertical'} orientation={'vertical'}
@@ -104,7 +110,7 @@ export function Search(props: SearchModalProps): ReactNode {
<Typography <Typography
sx={ sx={
c.key == selectedConnector?.key c.key == selectedConnector?.key
? {fontWeight: 'bold'} ? { fontWeight: 'bold' }
: {} : {}
} }
> >
@@ -127,19 +133,33 @@ export function Search(props: SearchModalProps): ReactNode {
/> />
</Step> </Step>
</Stepper> </Stepper>
<MangaCardList manga={searchResults} mangaOnClick={openMangaDetail}/> <MangaCardList
<MangaDetail mangaKey={selectedManga?.key} open={mangaDetailOpen} setOpen={setMangaDetailOpen} actions={[ manga={searchResults}
<Button onClick={openMangaDownloadDrawer}>Download</Button> mangaOnClick={openMangaDetail}
]} /> />
<MangaDownloadDrawer open={mangaDownloadDrawerOpen} setOpen={setMangaDownloadDrawerOpen} mangaKey={selectedManga?.key} /> <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>
) )
} }
export interface SearchModalProps { export interface SearchModalProps {
open: boolean; open: boolean
setOpen: Dispatch<boolean>; setOpen: Dispatch<boolean>
} }
function isUrl(str: string): boolean { function isUrl(str: string): boolean {

View File

@@ -5,12 +5,16 @@ import {
useEffect, useEffect,
useState, useState,
} from 'react' } from 'react'
import {FileLibrary} from '../api/data-contracts.ts' import { FileLibrary } from '../api/data-contracts.ts'
import { ApiContext } from './ApiContext.tsx' import { ApiContext } from './ApiContext.tsx'
export const FileLibraryContext = createContext<FileLibrary[]>([]); export const FileLibraryContext = createContext<FileLibrary[]>([])
export default function LibraryProvider({ children }: { children: ReactNode }) : ReactNode { export default function LibraryProvider({
children,
}: {
children: ReactNode
}): ReactNode {
const Api = useContext(ApiContext) const Api = useContext(ApiContext)
const [state, setState] = useState<FileLibrary[]>([]) const [state, setState] = useState<FileLibrary[]>([])
@@ -23,7 +27,5 @@ export default function LibraryProvider({ children }: { children: ReactNode }) :
}) })
}, [Api]) }, [Api])
return ( return <FileLibraryContext value={state}>{children}</FileLibraryContext>
<FileLibraryContext value={state}>{children}</FileLibraryContext>
);
} }