* Remove "useBackTo" Not needed * Use browser back navigation to close the reader This will cause the previous page (manga or updates) to be opened instead of always opening the manga page
274 lines
11 KiB
TypeScript
274 lines
11 KiB
TypeScript
/*
|
|
* Copyright (C) Contributors to the Suwayomi project
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* 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/.
|
|
*/
|
|
|
|
import React from 'react';
|
|
import Card from '@mui/material/Card';
|
|
import CardActionArea from '@mui/material/CardActionArea';
|
|
import Typography from '@mui/material/Typography';
|
|
import { Link } from 'react-router-dom';
|
|
import { Avatar, Box, CardContent, Grid, styled } from '@mui/material';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { IMangaCard } from '@/typings';
|
|
import requestManager from '@/lib/RequestManager';
|
|
import useLocalStorage from '@/util/useLocalStorage';
|
|
import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
|
import SpinnerImage from '@/components/util/SpinnerImage';
|
|
|
|
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%)',
|
|
});
|
|
|
|
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',
|
|
borderRadius: '5px',
|
|
overflow: 'hidden',
|
|
'& p': {
|
|
color: 'white',
|
|
padding: '0.1em',
|
|
paddingInline: '0.2em',
|
|
fontSize: '1.05rem',
|
|
},
|
|
});
|
|
|
|
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;
|
|
};
|
|
|
|
interface IProps {
|
|
manga: IMangaCard;
|
|
gridLayout?: GridLayout;
|
|
dimensions: number;
|
|
inLibraryIndicator?: boolean;
|
|
}
|
|
|
|
const MangaCard = React.forwardRef<HTMLDivElement, IProps>((props: IProps, ref) => {
|
|
const { t } = useTranslation();
|
|
|
|
const {
|
|
manga: { id, title, thumbnailUrl, downloadCount, unreadCount: unread, inLibrary },
|
|
gridLayout,
|
|
dimensions,
|
|
inLibraryIndicator,
|
|
} = props;
|
|
const {
|
|
options: { showUnreadBadge, showDownloadBadge },
|
|
} = useLibraryOptionsContext();
|
|
|
|
const [ItemWidth] = useLocalStorage<number>('ItemWidth', 300);
|
|
|
|
const mangaLinkTo = `/manga/${id}/`;
|
|
|
|
if (gridLayout !== GridLayout.List) {
|
|
const columns = Math.ceil(dimensions / ItemWidth);
|
|
const columnsPerItem = 12 / columns;
|
|
return (
|
|
<Grid item xs={columnsPerItem}>
|
|
<Link to={mangaLinkTo} style={gridLayout === GridLayout.Comfortable ? { textDecoration: 'none' } : {}}>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
}}
|
|
>
|
|
<Card
|
|
sx={{
|
|
// force standard aspect ratio of manga covers
|
|
aspectRatio: '225/350',
|
|
display: 'flex',
|
|
}}
|
|
ref={ref}
|
|
>
|
|
<CardActionArea
|
|
sx={{
|
|
position: 'relative',
|
|
height: '100%',
|
|
}}
|
|
>
|
|
<BadgeContainer
|
|
sx={{
|
|
position: 'absolute',
|
|
top: 5,
|
|
left: 5,
|
|
}}
|
|
>
|
|
{inLibraryIndicator && inLibrary && (
|
|
<Typography sx={{ backgroundColor: 'primary.dark', zIndex: '1' }}>
|
|
{t('manga.button.in_library')}
|
|
</Typography>
|
|
)}
|
|
{showUnreadBadge && unread! > 0 && (
|
|
<Typography sx={{ backgroundColor: 'primary.dark' }}>{unread}</Typography>
|
|
)}
|
|
{showDownloadBadge && downloadCount! > 0 && (
|
|
<Typography
|
|
sx={{
|
|
backgroundColor: 'success.dark',
|
|
}}
|
|
>
|
|
{downloadCount}
|
|
</Typography>
|
|
)}
|
|
</BadgeContainer>
|
|
<SpinnerImage
|
|
alt={title}
|
|
src={requestManager.getValidImgUrlFor(thumbnailUrl)}
|
|
imgStyle={
|
|
inLibraryIndicator && inLibrary
|
|
? {
|
|
height: '100%',
|
|
width: '100%',
|
|
objectFit: 'cover',
|
|
filter: 'brightness(0.4)',
|
|
}
|
|
: {
|
|
height: '100%',
|
|
width: '100%',
|
|
objectFit: 'cover',
|
|
}
|
|
}
|
|
spinnerStyle={{
|
|
display: 'grid',
|
|
placeItems: 'center',
|
|
}}
|
|
/>
|
|
{gridLayout !== GridLayout.Comfortable && (
|
|
<>
|
|
<BottomGradient />
|
|
<BottomGradientDoubledDown />
|
|
<MangaTitle
|
|
sx={{
|
|
color: 'white',
|
|
textShadow: '0px 0px 3px #000000',
|
|
}}
|
|
title={title}
|
|
>
|
|
{truncateText(title, 61)}
|
|
</MangaTitle>
|
|
</>
|
|
)}
|
|
</CardActionArea>
|
|
</Card>
|
|
{gridLayout === GridLayout.Comfortable && (
|
|
<MangaTitle
|
|
sx={{
|
|
position: 'relative',
|
|
color: 'text.primary',
|
|
}}
|
|
title={title}
|
|
>
|
|
{truncateText(title, 61)}
|
|
</MangaTitle>
|
|
)}
|
|
</Box>
|
|
</Link>
|
|
</Grid>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Grid item xs={12}>
|
|
<Card ref={ref}>
|
|
<CardActionArea component={Link} to={mangaLinkTo}>
|
|
<CardContent
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
padding: 2,
|
|
position: 'relative',
|
|
}}
|
|
>
|
|
<Avatar
|
|
variant="rounded"
|
|
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={requestManager.getValidImgUrlFor(thumbnailUrl)}
|
|
/>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
flexGrow: 1,
|
|
width: 'min-content',
|
|
}}
|
|
>
|
|
<Typography variant="h5" component="h2" title={title}>
|
|
{truncateText(title, 61)}
|
|
</Typography>
|
|
</Box>
|
|
<BadgeContainer>
|
|
{inLibraryIndicator && inLibrary && (
|
|
<Typography sx={{ backgroundColor: 'primary.dark' }}>
|
|
{t('manga.button.in_library')}
|
|
</Typography>
|
|
)}
|
|
{showUnreadBadge && unread! > 0 && (
|
|
<Typography sx={{ backgroundColor: 'primary.dark' }}>{unread}</Typography>
|
|
)}
|
|
{showDownloadBadge && downloadCount! > 0 && (
|
|
<Typography
|
|
sx={{
|
|
backgroundColor: 'success.dark',
|
|
}}
|
|
>
|
|
{downloadCount}
|
|
</Typography>
|
|
)}
|
|
</BadgeContainer>
|
|
</CardContent>
|
|
</CardActionArea>
|
|
</Card>
|
|
</Grid>
|
|
);
|
|
});
|
|
|
|
export default MangaCard;
|