mirror of
https://github.com/C9Glax/tranga-website.git
synced 2025-05-20 05:03:02 +02:00
MangaPopup Drawer
This commit is contained in:
parent
79c13ceb1d
commit
11549a07c3
@ -38,7 +38,7 @@ export default function App () {
|
||||
<Badge invisible sx={{margin: "8px !important"}}>
|
||||
<Card onClick={() => setShowSearch(true)} sx={{height:"fit-content",width:"fit-content"}}>
|
||||
<CardCover sx={{margin:"var(--Card-padding)"}}>
|
||||
<img src={"/blahaj.png"} style={{height: CardHeight + "px", width: CardWidth + "px"}} />
|
||||
<img src={"/blahaj.png"} style={{height: CardHeight + "px", width: CardWidth + 10 + "px"}} />
|
||||
</CardCover>
|
||||
<CardCover sx={{
|
||||
background: 'rgba(234, 119, 246, 0.14)',
|
||||
@ -46,7 +46,7 @@ export default function App () {
|
||||
webkitBackdropFilter: 'blur(6.9px)',
|
||||
}}/>
|
||||
<CardContent>
|
||||
<Box style={{height: CardHeight + "px", width: CardWidth + "px"}} >
|
||||
<Box style={{height: CardHeight + "px", width: CardWidth + 10 + "px"}} >
|
||||
<Typography level={"h1"}>Search</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
|
@ -2,25 +2,16 @@ import {
|
||||
Badge,
|
||||
Box,
|
||||
Card,
|
||||
CardActions,
|
||||
CardContent, CardCover,
|
||||
Chip, CircularProgress,
|
||||
Input,
|
||||
Link,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Typography
|
||||
} from "@mui/joy";
|
||||
import IManga, {DefaultManga} from "../api/types/IManga.ts";
|
||||
import {CSSProperties, ReactElement, useCallback, useContext, useEffect, useRef, useState} from "react";
|
||||
import {GetLatestChapterAvailable, GetMangaById, GetMangaCoverImageUrl, SetIgnoreThreshold} from "../api/Manga.tsx";
|
||||
import {GetMangaById, GetMangaCoverImageUrl} from "../api/Manga.tsx";
|
||||
import {ApiUriContext, getData} from "../api/fetchApi.tsx";
|
||||
import AuthorTag from "./AuthorTag.tsx";
|
||||
import LinkTag from "./LinkTag.tsx";
|
||||
import {ReleaseStatusToPalette} from "../api/types/EnumMangaReleaseStatus.ts";
|
||||
import IChapter from "../api/types/IChapter.ts";
|
||||
import MarkdownPreview from "@uiw/react-markdown-preview";
|
||||
import {SxProps} from "@mui/joy/styles/types";
|
||||
import MangaPopup from "./MangaPopup.tsx";
|
||||
|
||||
export function MangaFromId({mangaId, children} : { mangaId: string, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined }){
|
||||
const [manga, setManga] = useState(DefaultManga);
|
||||
@ -37,45 +28,31 @@ export function MangaFromId({mangaId, children} : { mangaId: string, children?:
|
||||
loadManga();
|
||||
}, []);
|
||||
|
||||
return <Manga manga={manga} loading={loading} children={children} />
|
||||
return (
|
||||
<>
|
||||
{loading ? <></> : <Manga manga={manga} children={children} /> }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const CardWidth = 190;
|
||||
export const CardHeight = 300;
|
||||
|
||||
export function Manga({manga, children, loading} : { manga: IManga | undefined, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined, loading?: boolean}) {
|
||||
const useManga = manga ?? DefaultManga;
|
||||
loading = loading ?? false;
|
||||
export function Manga({manga: manga, children} : { manga: IManga, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined}) {
|
||||
const CoverRef = useRef<HTMLImageElement>(null);
|
||||
|
||||
const apiUri = useContext(ApiUriContext);
|
||||
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const [mangaMaxChapter, setMangaMaxChapter] = useState<IChapter>();
|
||||
const [maxChapterLoading, setMaxChapterLoading] = useState<boolean>(true);
|
||||
const LoadMaxChapter = useCallback(() => {
|
||||
setMaxChapterLoading(true);
|
||||
GetLatestChapterAvailable(apiUri, useManga.mangaId)
|
||||
.then(setMangaMaxChapter)
|
||||
.finally(() => setMaxChapterLoading(false));
|
||||
}, [useManga, apiUri]);
|
||||
|
||||
const [updatingThreshold, setUpdatingThreshold] = useState<boolean>(false);
|
||||
const updateIgnoreThreshhold = useCallback((value: number) => {
|
||||
setUpdatingThreshold(true);
|
||||
SetIgnoreThreshold(apiUri, useManga.mangaId, value).finally(() => setUpdatingThreshold(false));
|
||||
},[useManga, apiUri])
|
||||
|
||||
useEffect(() => {
|
||||
LoadMaxChapter();
|
||||
LoadMangaCover();
|
||||
}, [useManga]);
|
||||
}, [manga]);
|
||||
|
||||
const LoadMangaCover = useCallback(() => {
|
||||
if(CoverRef.current == null)
|
||||
return;
|
||||
const coverUrl = GetMangaCoverImageUrl(apiUri, useManga.mangaId, CoverRef.current);
|
||||
const coverUrl = GetMangaCoverImageUrl(apiUri, manga.mangaId, CoverRef.current);
|
||||
if(CoverRef.current.src == coverUrl)
|
||||
return;
|
||||
|
||||
@ -83,7 +60,7 @@ export function Manga({manga, children, loading} : { manga: IManga | undefined,
|
||||
getData(coverUrl).then(() => {
|
||||
if(CoverRef.current) CoverRef.current.src = coverUrl;
|
||||
});
|
||||
}, [useManga, apiUri])
|
||||
}, [manga, apiUri])
|
||||
|
||||
const coverSx : SxProps = {
|
||||
height: CardHeight + "px",
|
||||
@ -91,12 +68,6 @@ export function Manga({manga, children, loading} : { manga: IManga | undefined,
|
||||
position: "relative",
|
||||
}
|
||||
|
||||
const descriptionSx : SxProps = {
|
||||
height: CardHeight + "px",
|
||||
width: CardWidth * 2 + "px",
|
||||
position: "relative"
|
||||
}
|
||||
|
||||
const coverCss : CSSProperties = {
|
||||
maxHeight: "calc("+CardHeight+"px + 2rem)",
|
||||
maxWidth: "calc("+CardWidth+"px + 2rem)",
|
||||
@ -104,17 +75,17 @@ export function Manga({manga, children, loading} : { manga: IManga | undefined,
|
||||
|
||||
const interactiveElements = ["button", "input", "textarea", "a", "select", "option", "li"];
|
||||
|
||||
const mangaName = useManga.name.length > 30 ? useManga.name.substring(0, 27) + "..." : useManga.name;
|
||||
const mangaName = manga.name.length > 30 ? manga.name.substring(0, 27) + "..." : manga.name;
|
||||
|
||||
return (
|
||||
<Badge sx={{margin:"8px !important"}} badgeContent={useManga.mangaConnectorName} color={ReleaseStatusToPalette(useManga.releaseStatus)} size={"lg"}>
|
||||
<Badge sx={{margin:"8px !important"}} badgeContent={manga.mangaConnectorName} color={ReleaseStatusToPalette(manga.releaseStatus)} size={"lg"}>
|
||||
<Card sx={{height:"fit-content",width:"fit-content"}} onClick={(e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if(interactiveElements.find(x => x == target.localName) == undefined)
|
||||
setExpanded(!expanded)}
|
||||
}>
|
||||
<CardCover>
|
||||
<img style={coverCss} src="/blahaj.png" alt="Manga Cover"
|
||||
<img style={coverCss} src={GetMangaCoverImageUrl(apiUri, manga.mangaId, CoverRef.current)} alt="Manga Cover"
|
||||
ref={CoverRef}
|
||||
onLoad={LoadMangaCover}/>
|
||||
</CardCover>
|
||||
@ -124,61 +95,12 @@ export function Manga({manga, children, loading} : { manga: IManga | undefined,
|
||||
}}/>
|
||||
<CardContent sx={{display: "flex", alignItems: "center", flexFlow: "row nowrap"}}>
|
||||
<Box sx={coverSx}>
|
||||
<Skeleton loading={loading}>
|
||||
<Link href={useManga.websiteUrl} level={"h3"} sx={{height:"min-content",width:"fit-content",color:"white",margin:"0 0 0 10px"}}>
|
||||
{mangaName}
|
||||
</Link>
|
||||
</Skeleton>
|
||||
<Link href={manga.websiteUrl} level={"h3"} sx={{height:"min-content",width:"fit-content",color:"white",margin:"0 0 0 10px"}}>
|
||||
{mangaName}
|
||||
</Link>
|
||||
</Box>
|
||||
{
|
||||
expanded ?
|
||||
<Box sx={descriptionSx}>
|
||||
<Skeleton loading={loading} variant={"text"} level={"title-lg"}>
|
||||
<Stack direction={"row"} flexWrap={"wrap"} spacing={0.5} sx={{maxHeight:CardHeight*0.3+"px", overflowY:"auto", scrollbarWidth: "thin"}}>
|
||||
{useManga.authors?.map(author => <AuthorTag key={author.authorId} author={author} color={"success"} />)}
|
||||
{useManga.mangaTags?.map(tag => <Chip key={tag.tag} variant={"soft"} size={"md"} color={"primary"}>{tag.tag}</Chip>)}
|
||||
{useManga.links?.map(link => <LinkTag key={link.linkId} link={link} color={"warning"} />)}
|
||||
</Stack>
|
||||
</Skeleton>
|
||||
<Skeleton loading={loading} sx={{maxHeight:"300px"}}>
|
||||
<MarkdownPreview source={useManga.description} style={{backgroundColor: "transparent", color: "black", maxHeight:CardHeight*0.7+"px", overflowY:"auto", marginTop:"10px", scrollbarWidth: "thin"}} />
|
||||
</Skeleton>
|
||||
</Box>
|
||||
: null
|
||||
}
|
||||
</CardContent>
|
||||
{
|
||||
expanded ?
|
||||
<CardActions sx={{justifyContent:"space-between"}}>
|
||||
<Skeleton loading={loading} sx={{maxHeight: "30px", maxWidth:"calc(100% - 40px)"}}>
|
||||
<Input
|
||||
type={"number"}
|
||||
placeholder={"0.0"}
|
||||
startDecorator={
|
||||
<>
|
||||
{
|
||||
updatingThreshold ?
|
||||
<CircularProgress color={"primary"} size={"sm"} />
|
||||
: <Typography>Ch.</Typography>
|
||||
}
|
||||
</>
|
||||
}
|
||||
endDecorator={
|
||||
<Typography>
|
||||
<Skeleton loading={maxChapterLoading}>
|
||||
/{mangaMaxChapter?.chapterNumber??"Load Failed"}
|
||||
</Skeleton>
|
||||
</Typography>
|
||||
}
|
||||
sx={{width:"min-content"}}
|
||||
size={"md"}
|
||||
onChange={(e) => updateIgnoreThreshhold(e.currentTarget.valueAsNumber)}
|
||||
/>
|
||||
{children}
|
||||
</Skeleton>
|
||||
</CardActions>
|
||||
: null
|
||||
}
|
||||
<MangaPopup manga={manga} open={expanded}>{children}</MangaPopup>
|
||||
</Card>
|
||||
</Badge>
|
||||
);
|
||||
|
110
tranga-website/src/Components/MangaPopup.tsx
Normal file
110
tranga-website/src/Components/MangaPopup.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import IManga from "../api/types/IManga.ts";
|
||||
import {Badge, Box, Chip, CircularProgress, Drawer, Input, Skeleton, Stack, Typography} from "@mui/joy";
|
||||
import {ReactElement, useCallback, useContext, useEffect, useRef, useState} from "react";
|
||||
import {GetLatestChapterAvailable, GetMangaCoverImageUrl, SetIgnoreThreshold} from "../api/Manga.tsx";
|
||||
import {ApiUriContext, getData} from "../api/fetchApi.tsx";
|
||||
import AuthorTag from "./AuthorTag.tsx";
|
||||
import LinkTag from "./LinkTag.tsx";
|
||||
import MarkdownPreview from "@uiw/react-markdown-preview";
|
||||
import {CardHeight} from "./Manga.tsx";
|
||||
import IChapter from "../api/types/IChapter.ts";
|
||||
import {MangaReleaseStatus, ReleaseStatusToPalette} from "../api/types/EnumMangaReleaseStatus.ts";
|
||||
|
||||
|
||||
export default function MangaPopup({manga, open, children} : {manga: IManga | null, open: boolean, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined}) {
|
||||
|
||||
const apiUri = useContext(ApiUriContext);
|
||||
|
||||
const CoverRef = useRef<HTMLImageElement>(null);
|
||||
|
||||
const LoadMangaCover = useCallback(() => {
|
||||
if(CoverRef.current == null || manga == null)
|
||||
return;
|
||||
const coverUrl = GetMangaCoverImageUrl(apiUri, manga.mangaId, CoverRef.current);
|
||||
if(CoverRef.current.src == coverUrl)
|
||||
return;
|
||||
|
||||
//Check if we can fetch the image exists (by fetching it), only then update
|
||||
getData(coverUrl).then(() => {
|
||||
if(CoverRef.current) CoverRef.current.src = coverUrl;
|
||||
});
|
||||
}, [manga, apiUri])
|
||||
|
||||
useEffect(() => {
|
||||
if(!open)
|
||||
return;
|
||||
LoadMaxChapter();
|
||||
LoadMangaCover();
|
||||
}, [open]);
|
||||
|
||||
const [mangaMaxChapter, setMangaMaxChapter] = useState<IChapter>();
|
||||
const [maxChapterLoading, setMaxChapterLoading] = useState<boolean>(true);
|
||||
const LoadMaxChapter = useCallback(() => {
|
||||
if(manga == null)
|
||||
return;
|
||||
setMaxChapterLoading(true);
|
||||
GetLatestChapterAvailable(apiUri, manga.mangaId)
|
||||
.then(setMangaMaxChapter)
|
||||
.finally(() => setMaxChapterLoading(false));
|
||||
}, [manga, apiUri]);
|
||||
|
||||
const [updatingThreshold, setUpdatingThreshold] = useState<boolean>(false);
|
||||
const updateIgnoreThreshhold = useCallback((value: number) => {
|
||||
if(manga == null)
|
||||
return;
|
||||
setUpdatingThreshold(true);
|
||||
SetIgnoreThreshold(apiUri, manga.mangaId, value).finally(() => setUpdatingThreshold(false));
|
||||
},[manga, apiUri])
|
||||
|
||||
return (
|
||||
<Drawer anchor="bottom" size="lg" open={open}>
|
||||
<Stack direction="column" spacing={2} margin={"10px"}>
|
||||
{ /* Cover and Description */ }
|
||||
<Stack direction="row" spacing={2} margin={"10px"}>
|
||||
<Badge sx={{margin:"8px !important"}} badgeContent={manga?.mangaConnectorName} color={ReleaseStatusToPalette(manga?.releaseStatus??MangaReleaseStatus.Unreleased)} size={"lg"}>
|
||||
<img src="/blahaj.png" alt="Manga Cover"
|
||||
ref={CoverRef}
|
||||
onLoad={LoadMangaCover}/>
|
||||
</Badge>
|
||||
<Box>
|
||||
<Typography level={"h2"} marginTop={"20px"}>{manga?.name}</Typography>
|
||||
<Stack direction={"row"} flexWrap={"wrap"} spacing={0.5} sx={{maxHeight:CardHeight*0.3+"px", overflowY:"auto", scrollbarWidth: "thin"}}>
|
||||
{manga?.authors?.map(author => <AuthorTag key={author.authorId} author={author} color={"success"} />)}
|
||||
{manga?.mangaTags?.map(tag => <Chip key={tag.tag} variant={"soft"} size={"md"} color={"primary"}>{tag.tag}</Chip>)}
|
||||
{manga?.links?.map(link => <LinkTag key={link.linkId} link={link} color={"warning"} />)}
|
||||
</Stack>
|
||||
<MarkdownPreview source={manga?.description} style={{backgroundColor: "transparent", color: "var(--joy-palette-neutral-50)", maxHeight:CardHeight*0.7+"px", overflowY:"auto", marginTop:"10px", scrollbarWidth: "thin"}} />
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{ /* Actions */ }
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Input
|
||||
type={"number"}
|
||||
placeholder={"0.0"}
|
||||
startDecorator={
|
||||
<>
|
||||
{
|
||||
updatingThreshold ?
|
||||
<CircularProgress color={"primary"} size={"sm"} />
|
||||
: <Typography>Ch.</Typography>
|
||||
}
|
||||
</>
|
||||
}
|
||||
endDecorator={
|
||||
<Typography>
|
||||
<Skeleton loading={maxChapterLoading}>
|
||||
/{mangaMaxChapter?.chapterNumber??"-"}
|
||||
</Skeleton>
|
||||
</Typography>
|
||||
}
|
||||
sx={{width:"min-content"}}
|
||||
size={"md"}
|
||||
onChange={(e) => updateIgnoreThreshhold(e.currentTarget.valueAsNumber)}
|
||||
/>
|
||||
{children}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
@ -24,7 +24,7 @@ export const DeleteManga = async (apiUri: string, mangaId: string) : Promise<voi
|
||||
return await deleteData(`${apiUri}/v2/Manga/${mangaId}`);
|
||||
}
|
||||
|
||||
export const GetMangaCoverImageUrl = (apiUri: string, mangaId: string, ref: HTMLImageElement | undefined) : string => {
|
||||
export const GetMangaCoverImageUrl = (apiUri: string, mangaId: string, ref: HTMLImageElement | undefined | null) : string => {
|
||||
if(ref == null || ref == undefined)
|
||||
return `${apiUri}/v2/Manga/${mangaId}/Cover?width=64&height=64`;
|
||||
return `${apiUri}/v2/Manga/${mangaId}/Cover?width=${ref.clientWidth}&height=${ref.clientHeight}`;
|
||||
|
Loading…
x
Reference in New Issue
Block a user