Files
suwayomi-material-you-webui/src/components/MangaCard.tsx
T

278 lines
11 KiB
TypeScript
Raw Normal View History

/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
2021-01-26 23:32:12 +03:30
* 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/. */
2021-01-29 14:25:18 +03:30
import React from 'react';
2021-09-09 17:51:22 +04:30
import Card from '@mui/material/Card';
import CardActionArea from '@mui/material/CardActionArea';
import Typography from '@mui/material/Typography';
2021-01-19 20:20:28 +03:30
import { Link } from 'react-router-dom';
2022-03-06 21:03:15 +00:00
import { Avatar, CardContent, Grid } from '@mui/material';
2021-05-27 05:13:01 +04:30
import useLocalStorage from 'util/useLocalStorage';
2021-10-30 16:10:34 +03:30
import SpinnerImage from 'components/util/SpinnerImage';
2022-03-06 16:45:25 +00:00
import { Box, styled } from '@mui/system';
import { GridLayout, useLibraryOptionsContext } from 'components/context/LibraryOptionsContext';
2022-11-21 01:33:45 +01:00
import { BACK } from 'util/useBackTo';
import { IMangaCard } from 'typings';
2020-12-25 17:42:22 +01:00
2021-10-31 21:02:45 +03:30
const BottomGradient = styled('div')({
position: 'absolute',
bottom: 0,
width: '100%',
height: '30%',
background: 'linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%)',
});
2021-05-28 19:37:26 +04:30
2021-10-31 21:02:45 +03:30
const BottomGradientDoubledDown = styled('div')({
position: 'absolute',
bottom: 0,
width: '100%',
height: '20%',
background: 'linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%)',
});
const MangaTitle = styled(Typography)({
position: 'absolute',
bottom: 0,
padding: '0.5em',
fontSize: '1.05rem',
});
const BadgeContainer = styled('div')({
display: 'flex',
height: 'fit-content',
2021-10-31 21:02:45 +03:30
borderRadius: '5px',
overflow: 'hidden',
'& p': {
color: 'white',
padding: '0.1em',
paddingInline: '0.2em',
fontSize: '1.05rem',
},
});
2020-12-25 17:42:22 +01:00
2021-08-29 21:46:20 +04:30
const truncateText = (str: string, maxLength: number) => {
const ending = '...';
// trim the string to the maximum length
const trimmedString = str.substr(0, maxLength - ending.length);
if (trimmedString.length < str.length) {
return trimmedString + ending;
}
return str;
};
2020-12-25 17:42:22 +01:00
interface IProps {
2023-02-06 10:06:33 +01:00
manga: IMangaCard;
gridLayout?: GridLayout;
dimensions: number;
inLibraryIndicator?: boolean;
2020-12-25 17:42:22 +01:00
}
2023-02-06 10:06:33 +01:00
const MangaCard = React.forwardRef<HTMLDivElement, IProps>((props: IProps, ref) => {
2020-12-25 17:42:22 +01:00
const {
manga: {
2022-03-11 15:54:45 +00:00
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2023-02-06 10:06:33 +01:00
id,
title,
thumbnailUrl,
downloadCount,
unreadCount: unread,
inLibrary,
2021-01-29 14:25:18 +03:30
},
2022-03-06 16:45:25 +00:00
gridLayout,
2022-04-02 01:34:10 +01:00
dimensions,
2022-11-24 21:04:30 +01:00
inLibraryIndicator,
2020-12-25 17:42:22 +01:00
} = props;
2023-02-06 10:06:33 +01:00
const {
options: { showUnreadBadge, showDownloadBadge },
} = useLibraryOptionsContext();
2021-10-31 21:02:45 +03:30
2021-03-07 22:35:27 +03:30
const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
2021-09-18 20:59:23 +04:30
const [useCache] = useLocalStorage<boolean>('useCache', true);
2022-04-02 01:34:10 +01:00
const [ItemWidth] = useLocalStorage<number>('ItemWidth', 300);
2020-12-25 17:42:22 +01:00
2022-11-21 01:33:45 +01:00
const mangaLinkTo = { pathname: `/manga/${id}/`, state: { backLink: BACK } };
if (gridLayout !== GridLayout.List) {
const cols = Math.ceil(dimensions / ItemWidth);
2022-03-06 21:03:15 +00:00
return (
<Grid item columns={cols} xs={1}>
2023-02-08 18:19:26 +01:00
<Link to={mangaLinkTo} style={gridLayout === GridLayout.Comfortable ? { textDecoration: 'none' } : {}}>
2022-03-06 21:03:15 +00:00
<Box
2021-10-31 21:02:45 +03:30
sx={{
2022-03-06 16:45:25 +00:00
display: 'flex',
2022-03-06 21:03:15 +00:00
flexDirection: 'column',
2021-10-31 21:02:45 +03:30
}}
>
2022-03-06 21:03:15 +00:00
<Card
2022-03-06 16:45:25 +00:00
sx={{
// force standard aspect ratio of manga covers
2022-03-06 21:03:15 +00:00
aspectRatio: '225/350',
display: 'flex',
2021-10-31 21:02:45 +03:30
}}
2022-03-06 21:03:15 +00:00
ref={ref}
2022-03-06 16:45:25 +00:00
>
2022-03-06 21:03:15 +00:00
<CardActionArea
sx={{
position: 'relative',
2022-03-06 16:45:25 +00:00
height: '100%',
}}
2022-03-06 21:03:15 +00:00
>
<BadgeContainer
sx={{
position: 'absolute',
top: 5,
left: 5,
}}
>
2022-11-24 21:04:30 +01:00
{inLibraryIndicator && inLibrary && (
2023-02-08 18:19:26 +01:00
<Typography sx={{ backgroundColor: 'primary.dark', zIndex: '1' }}>
2022-03-11 15:54:45 +00:00
In library
</Typography>
)}
2023-02-06 10:06:33 +01:00
{showUnreadBadge && unread! > 0 && (
2023-02-08 18:19:26 +01:00
<Typography sx={{ backgroundColor: 'primary.dark' }}>{unread}</Typography>
2022-03-06 21:03:15 +00:00
)}
2023-02-06 10:06:33 +01:00
{showDownloadBadge && downloadCount! > 0 && (
<Typography
sx={{
backgroundColor: 'success.dark',
}}
2022-03-06 21:03:15 +00:00
>
{downloadCount}
</Typography>
)}
</BadgeContainer>
<SpinnerImage
alt={title}
src={`${serverAddress}${thumbnailUrl}?useCache=${useCache}`}
2023-02-06 10:06:33 +01:00
imgStyle={
inLibraryIndicator && inLibrary
? {
height: '100%',
width: '100%',
objectFit: 'cover',
filter: 'brightness(0.4)',
}
: {
height: '100%',
width: '100%',
objectFit: 'cover',
}
}
2022-03-06 21:03:15 +00:00
spinnerStyle={{
display: 'grid',
placeItems: 'center',
}}
/>
2023-03-11 18:05:58 +01:00
{gridLayout !== GridLayout.Comfortable && (
2022-03-06 21:03:15 +00:00
<>
<BottomGradient />
<BottomGradientDoubledDown />
2023-03-11 18:05:58 +01:00
<MangaTitle
sx={{
color: 'white',
textShadow: '0px 0px 3px #000000',
}}
title={title}
>
{truncateText(title, 61)}
</MangaTitle>
2022-03-06 21:03:15 +00:00
</>
)}
</CardActionArea>
</Card>
2023-03-11 18:05:58 +01:00
{gridLayout === GridLayout.Comfortable && (
2022-03-06 21:03:15 +00:00
<MangaTitle
sx={{
position: 'relative',
color: 'text.primary',
2022-03-06 16:45:25 +00:00
}}
title={title}
2022-03-06 21:03:15 +00:00
>
{truncateText(title, 61)}
</MangaTitle>
2023-02-06 10:06:33 +01:00
)}
2022-03-06 21:03:15 +00:00
</Box>
</Link>
</Grid>
);
}
2022-03-06 21:03:15 +00:00
return (
<Grid item xs={12}>
<Card>
2023-02-06 10:06:33 +01:00
<CardActionArea component={Link} to={mangaLinkTo}>
<CardContent
2022-03-06 21:03:15 +00:00
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: 2,
position: 'relative',
2022-03-06 21:03:15 +00:00
}}
>
<Avatar
variant="rounded"
2023-02-06 10:06:33 +01:00
sx={
inLibraryIndicator && inLibrary
? {
width: 56,
height: 56,
flex: '0 0 auto',
marginRight: 2,
imageRendering: 'pixelated',
filter: 'brightness(0.4)',
}
: {
width: 56,
height: 56,
flex: '0 0 auto',
marginRight: 2,
imageRendering: 'pixelated',
}
}
src={`${serverAddress}${thumbnailUrl}?useCache=${useCache}`}
/>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
flexGrow: 1,
width: 'min-content',
2022-03-06 21:03:15 +00:00
}}
>
<Typography variant="h5" component="h2" title={title}>
{truncateText(title, 61)}
2022-03-06 21:03:15 +00:00
</Typography>
</Box>
<BadgeContainer>
{inLibraryIndicator && inLibrary && (
2023-02-08 18:19:26 +01:00
<Typography sx={{ backgroundColor: 'primary.dark' }}>In library</Typography>
)}
2023-02-06 10:06:33 +01:00
{showUnreadBadge && unread! > 0 && (
2023-02-08 18:19:26 +01:00
<Typography sx={{ backgroundColor: 'primary.dark' }}>{unread}</Typography>
)}
2023-02-06 10:06:33 +01:00
{showDownloadBadge && downloadCount! > 0 && (
<Typography
sx={{
backgroundColor: 'success.dark',
}}
>
{downloadCount}
</Typography>
)}
</BadgeContainer>
</CardContent>
</CardActionArea>
</Card>
2021-01-23 01:58:18 +03:30
</Grid>
2020-12-25 17:42:22 +01:00
);
2021-01-22 21:11:00 +03:30
});
export default MangaCard;