refactor MangaCard

This commit is contained in:
Aria Moradi
2021-10-31 21:02:45 +03:30
parent e89497df33
commit c15ee7ab66
2 changed files with 88 additions and 84 deletions

View File

@@ -6,7 +6,6 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import React from 'react'; import React from 'react';
import makeStyles from '@mui/styles/makeStyles';
import Card from '@mui/material/Card'; import Card from '@mui/material/Card';
import CardActionArea from '@mui/material/CardActionArea'; import CardActionArea from '@mui/material/CardActionArea';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
@@ -14,63 +13,43 @@ import { Link } from 'react-router-dom';
import { Grid } from '@mui/material'; import { Grid } from '@mui/material';
import useLocalStorage from 'util/useLocalStorage'; import useLocalStorage from 'util/useLocalStorage';
import SpinnerImage from 'components/util/SpinnerImage'; import SpinnerImage from 'components/util/SpinnerImage';
import { styled } from '@mui/system';
const useStyles = makeStyles((theme) => ({ const BottomGradient = styled('div')({
root: {
display: 'flex',
// Preserve the shape of the manga cards, without this the manga card would chage it's
// if all the images in a row failed to load then the whole row would change into a
// square shape
aspectRatio: '225/350',
},
wrapper: {
position: 'relative',
height: '100%',
},
gradient: {
position: 'absolute', position: 'absolute',
bottom: 0, bottom: 0,
width: '100%', width: '100%',
height: '30%', height: '30%',
background: 'linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%)', background: 'linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%)',
opacity: 1, });
},
gradient2: { const BottomGradientDoubledDown = styled('div')({
position: 'absolute', position: 'absolute',
bottom: 0, bottom: 0,
width: '100%', width: '100%',
height: '20%', height: '20%',
background: 'linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%)', background: 'linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%)',
}, });
title: {
const MangaTitle = styled(Typography)({
position: 'absolute', position: 'absolute',
bottom: 0, bottom: 0,
padding: '0.5em', padding: '0.5em',
color: 'white', color: 'white',
fontSize: '1.05rem', fontSize: '1.05rem',
textShadow: '0px 0px 3px #000000', textShadow: '0px 0px 3px #000000',
}, });
badge: {
const UnreadBadge = styled(Typography)(({ theme }) => ({
position: 'absolute', position: 'absolute',
top: 2, top: 2,
left: 2, left: 2,
backgroundColor: theme.palette.primary.dark, backgroundColor: theme.palette.primary.dark,
borderRadius: 5, borderRadius: '5px',
color: 'white', color: 'white',
padding: '0.1em', padding: '0.1em',
paddingInline: '0.3em', paddingInline: '0.3em',
fontSize: '1.05rem', fontSize: '1.05rem',
},
image: {
height: '100%',
width: '100%',
},
spinner: {
minHeight: '400px',
display: 'grid',
placeItems: 'center',
},
})); }));
const truncateText = (str: string, maxLength: number) => { const truncateText = (str: string, maxLength: number) => {
@@ -93,35 +72,52 @@ const MangaCard = React.forwardRef<HTMLDivElement, IProps>((props: IProps, ref)
id, title, thumbnailUrl, unreadCount: unread, id, title, thumbnailUrl, unreadCount: unread,
}, },
} = props; } = props;
const classes = useStyles();
const [serverAddress] = useLocalStorage<String>('serverBaseURL', ''); const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
const [useCache] = useLocalStorage<boolean>('useCache', true); const [useCache] = useLocalStorage<boolean>('useCache', true);
return ( return (
<Grid item xs={6} sm={4} md={3} lg={2}> <Grid item xs={6} sm={4} md={3} lg={2}>
<Link to={`/manga/${id}/`}> <Link to={`/manga/${id}/`}>
<Card className={classes.root} ref={ref}> <Card
<CardActionArea> sx={{
<div className={classes.wrapper}> // force standard aspect ratio of manga covers
{unread aspectRatio: '225/350',
? ( display: 'flex',
<Typography className={classes.badge} component="span"> }}
ref={ref}
>
<CardActionArea
sx={{
position: 'relative',
height: '100%',
}}
>
{unread! > 0
&& (
<UnreadBadge>
{unread} {unread}
</Typography> </UnreadBadge>
) ) }
: null}
<SpinnerImage <SpinnerImage
alt={title} alt={title}
src={`${serverAddress}${thumbnailUrl}?useCache=${useCache}`} src={`${serverAddress}${thumbnailUrl}?useCache=${useCache}`}
spinnerClassName={classes.spinner} imgStyle={{
imgClassName={classes.image} height: '100%',
width: '100%',
}}
spinnerStyle={{
minHeight: '400px',
display: 'grid',
placeItems: 'center',
}}
/> />
<div className={classes.gradient} /> <BottomGradient />
<div className={classes.gradient2} /> <BottomGradientDoubledDown />
<Typography className={classes.title}>
<MangaTitle>
{truncateText(title, 61)} {truncateText(title, 61)}
</Typography> </MangaTitle>
</div>
</CardActionArea> </CardActionArea>
</Card> </Card>
</Link> </Link>

View File

@@ -5,8 +5,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState, CSSProperties } from 'react';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/system/Box';
import { Theme } from '@mui/system/createTheme';
import { SxProps } from '@mui/system/styleFunctionSx';
interface IProps { interface IProps {
src: string src: string
@@ -15,14 +18,16 @@ interface IProps {
imgRef?: React.RefObject<HTMLImageElement> imgRef?: React.RefObject<HTMLImageElement>
spinnerClassName?: string spinnerClassName?: string
spinnerStyle?: SxProps<Theme>
imgClassName?: string imgClassName?: string
imgStyle?: CSSProperties
onImageLoad?: () => void onImageLoad?: () => void
} }
export default function SpinnerImage(props: IProps) { export default function SpinnerImage(props: IProps) {
const { const {
src, alt, onImageLoad, imgRef, spinnerClassName, imgClassName, src, alt, onImageLoad, imgRef, spinnerClassName, imgClassName, spinnerStyle, imgStyle,
} = props; } = props;
const [imageSrc, setImagsrc] = useState<string>(''); const [imageSrc, setImagsrc] = useState<string>('');
@@ -48,19 +53,20 @@ export default function SpinnerImage(props: IProps) {
if (imageSrc.length === 0) { if (imageSrc.length === 0) {
return ( return (
<div className={spinnerClassName}> <Box className={spinnerClassName} sx={spinnerStyle}>
<CircularProgress thickness={5} /> <CircularProgress thickness={5} />
</div> </Box>
); );
} }
if (imageSrc === 'Not Found') { if (imageSrc === 'Not Found') {
return <div className={spinnerClassName} />; return <Box className={spinnerClassName} sx={spinnerStyle} />;
} }
return ( return (
<img <img
className={imgClassName} className={imgClassName}
style={imgStyle}
ref={imgRef} ref={imgRef}
src={imageSrc} src={imageSrc}
alt={alt} alt={alt}
@@ -71,6 +77,8 @@ export default function SpinnerImage(props: IProps) {
SpinnerImage.defaultProps = { SpinnerImage.defaultProps = {
spinnerClassName: '', spinnerClassName: '',
imgClassName: '', imgClassName: '',
spinnerStyle: {},
imgStyle: {},
onImageLoad: () => {}, onImageLoad: () => {},
imgRef: undefined, imgRef: undefined,
}; };