Update manga page styling
This commit is contained in:
@@ -12,7 +12,6 @@ import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
|||||||
import { loadErrorMessages, loadDevMessages } from '@apollo/client/dev';
|
import { loadErrorMessages, loadDevMessages } from '@apollo/client/dev';
|
||||||
import { loadable } from 'react-lazily/loadable';
|
import { loadable } from 'react-lazily/loadable';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
|
||||||
import { AppContext } from '@/components/context/AppContext';
|
import { AppContext } from '@/components/context/AppContext';
|
||||||
import '@/i18n';
|
import '@/i18n';
|
||||||
import { DefaultNavBar } from '@/components/navbar/DefaultNavBar';
|
import { DefaultNavBar } from '@/components/navbar/DefaultNavBar';
|
||||||
@@ -79,23 +78,22 @@ const BackgroundSubscriptions = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const MainApp = () => {
|
const MainApp = () => {
|
||||||
const { navBarWidth, bottomBarHeight } = useNavBarContext();
|
const { navBarWidth, appBarHeight, bottomBarHeight } = useNavBarContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
id="appMainContainer"
|
id="appMainContainer"
|
||||||
component="main"
|
component="main"
|
||||||
sx={{
|
sx={{
|
||||||
minHeight: `calc(100vh - ${bottomBarHeight}px)`,
|
minHeight: `calc(100vh - ${appBarHeight + bottomBarHeight}px)`,
|
||||||
width: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`,
|
width: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`,
|
||||||
minWidth: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`,
|
minWidth: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`,
|
||||||
maxWidth: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`,
|
maxWidth: `calc(100vw - (100vw - 100%) - ${navBarWidth}px)`,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
mt: `${appBarHeight}px`,
|
||||||
mb: `${bottomBarHeight}px`,
|
mb: `${bottomBarHeight}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Toolbar />
|
|
||||||
|
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* General Routes */}
|
{/* General Routes */}
|
||||||
|
|||||||
@@ -8,15 +8,17 @@
|
|||||||
|
|
||||||
import Button, { ButtonProps } from '@mui/material/Button';
|
import Button, { ButtonProps } from '@mui/material/Button';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import { TypographyProps } from '@mui/material/Typography';
|
import { ForwardedRef, forwardRef } from 'react';
|
||||||
|
|
||||||
export const CustomIconButton = <C extends React.ElementType>({
|
export const CustomIconButton = forwardRef(
|
||||||
children,
|
<C extends React.ElementType>(
|
||||||
...props
|
{ children, ...props }: ButtonProps<C, { component?: C }>,
|
||||||
}: ButtonProps & TypographyProps<C, { component?: C }>) => (
|
ref: ForwardedRef<HTMLButtonElement | null>,
|
||||||
<Button {...props}>
|
) => (
|
||||||
<Stack direction="row" alignItems="center" justifyContent="center" gap="10px" flexWrap="wrap">
|
<Button ref={ref} {...props}>
|
||||||
{children}
|
<Stack direction="row" alignItems="center" justifyContent="center" gap={1} flexWrap="wrap">
|
||||||
</Stack>
|
{children}
|
||||||
</Button>
|
</Stack>
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import BookmarkRemove from '@mui/icons-material/BookmarkRemove';
|
|||||||
import BookmarkAdd from '@mui/icons-material/BookmarkAdd';
|
import BookmarkAdd from '@mui/icons-material/BookmarkAdd';
|
||||||
import DoneAll from '@mui/icons-material/DoneAll';
|
import DoneAll from '@mui/icons-material/DoneAll';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import PublicIcon from '@mui/icons-material/Public';
|
import LaunchIcon from '@mui/icons-material/Launch';
|
||||||
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
||||||
import {
|
import {
|
||||||
actionToTranslationKey,
|
actionToTranslationKey,
|
||||||
@@ -159,7 +159,7 @@ export const ChapterActionMenuItems = ({
|
|||||||
)}
|
)}
|
||||||
{isSingleMode && (
|
{isSingleMode && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Icon={PublicIcon}
|
Icon={LaunchIcon}
|
||||||
disabled={!chapter!.realUrl}
|
disabled={!chapter!.realUrl}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(chapter!.realUrl!, '_blank', 'noopener,noreferrer');
|
window.open(chapter!.realUrl!, '_blank', 'noopener,noreferrer');
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ const StyledVirtuoso = styled(Virtuoso)(({ theme }) => ({
|
|||||||
padding: 0,
|
padding: 0,
|
||||||
minHeight: '200px',
|
minHeight: '200px',
|
||||||
[theme.breakpoints.up('md')]: {
|
[theme.breakpoints.up('md')]: {
|
||||||
width: '50vw',
|
|
||||||
// 64px for the Appbar, 48px for the ChapterCount Header
|
// 64px for the Appbar, 48px for the ChapterCount Header
|
||||||
height: 'calc(100vh - 64px - 48px)',
|
height: 'calc(100vh - 64px - 48px)',
|
||||||
margin: 0,
|
margin: 0,
|
||||||
@@ -165,7 +164,7 @@ export const ChapterList = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack direction="column" sx={{ position: 'relative' }}>
|
<Stack direction="column" sx={{ position: 'relative', flexBasis: '60%' }}>
|
||||||
<ChapterListHeader direction="row" alignItems="center" justifyContent="space-between">
|
<ChapterListHeader direction="row" alignItems="center" justifyContent="space-between">
|
||||||
<Typography variant="h5">
|
<Typography variant="h5">
|
||||||
{`${visibleChapters.length} ${t('chapter.title_one', {
|
{`${visibleChapters.length} ${t('chapter.title_one', {
|
||||||
|
|||||||
@@ -8,14 +8,17 @@
|
|||||||
|
|
||||||
import FavoriteIcon from '@mui/icons-material/Favorite';
|
import FavoriteIcon from '@mui/icons-material/Favorite';
|
||||||
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
|
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
|
||||||
import PublicIcon from '@mui/icons-material/Public';
|
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import { ComponentProps, useEffect, useMemo } from 'react';
|
import { ComponentProps, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { t as translate } from 'i18next';
|
import { t as translate } from 'i18next';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { useLongPress } from 'use-long-press';
|
import { useLongPress } from 'use-long-press';
|
||||||
|
import Chip from '@mui/material/Chip';
|
||||||
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
|
import LaunchIcon from '@mui/icons-material/Launch';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
import { makeToast } from '@/components/util/Toast';
|
import { makeToast } from '@/components/util/Toast';
|
||||||
import { Mangas, MangaThumbnailInfo, MangaTrackRecordInfo } from '@/lib/data/Mangas.ts';
|
import { Mangas, MangaThumbnailInfo, MangaTrackRecordInfo } from '@/lib/data/Mangas.ts';
|
||||||
import { SpinnerImage } from '@/components/util/SpinnerImage.tsx';
|
import { SpinnerImage } from '@/components/util/SpinnerImage.tsx';
|
||||||
@@ -27,132 +30,66 @@ import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts
|
|||||||
import { MangaType, SourceType } from '@/lib/graphql/generated/graphql.ts';
|
import { MangaType, SourceType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
const DetailsWrapper = styled('div')(({ theme }) => ({
|
const DetailsWrapper = styled('div')(({ theme }) => ({
|
||||||
width: '100%',
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
padding: theme.spacing(1),
|
||||||
[theme.breakpoints.up('md')]: {
|
[theme.breakpoints.up('md')]: {
|
||||||
position: 'sticky',
|
flexBasis: '40%',
|
||||||
top: '64px',
|
|
||||||
left: '0px',
|
|
||||||
width: '50vw',
|
|
||||||
height: 'calc(100vh - 64px)',
|
height: 'calc(100vh - 64px)',
|
||||||
alignSelf: 'flex-start',
|
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const TopContentWrapper = styled('div')(() => ({
|
const TopContentWrapper = styled('div')(() => ({}));
|
||||||
padding: '10px',
|
|
||||||
// [theme.breakpoints.up('md')]: {
|
|
||||||
// minWidth: '50%',
|
|
||||||
// },
|
|
||||||
}));
|
|
||||||
|
|
||||||
const ThumbnailMetadataWrapper = styled('div')(() => ({
|
const ThumbnailMetadataWrapper = styled('div')(() => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const Thumbnail = styled('div')(() => ({
|
const Thumbnail = styled('div')(({ theme }) => ({
|
||||||
'& img': {
|
'& img': {
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
maxWidth: '100%',
|
width: '150px',
|
||||||
minWidth: '100%',
|
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
},
|
[theme.breakpoints.up('lg')]: {
|
||||||
maxWidth: '50%',
|
width: '200px',
|
||||||
// [theme.breakpoints.up('md')]: {
|
},
|
||||||
// minWidth: '100px',
|
[theme.breakpoints.up('xl')]: {
|
||||||
// },
|
width: '300px',
|
||||||
}));
|
|
||||||
|
|
||||||
const MetadataContainer = styled('div')(({ theme }) => ({
|
|
||||||
marginLeft: 15,
|
|
||||||
maxWidth: '100%',
|
|
||||||
'& span': {
|
|
||||||
fontWeight: '400',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.up('lg')]: {
|
|
||||||
fontSize: '1.3em',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const Metadata = (props: ComponentProps<typeof BaseMetadata>) => (
|
|
||||||
<BaseMetadata {...props} titleProps={{ variant: 'h6' }} valueProps={{ variant: 'h6' }} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const MangaButtonsContainer = styled('div')(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-around',
|
|
||||||
'& button, a': {
|
|
||||||
borderRadius: '25px',
|
|
||||||
textTransform: 'none',
|
|
||||||
paddingLeft: '20px',
|
|
||||||
paddingRight: '20px',
|
|
||||||
fontSize: 'x-large',
|
|
||||||
[theme.breakpoints.down('sm')]: {
|
|
||||||
fontSize: 'larger',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
const BottomContentWrapper = styled('div')(({ theme }) => ({
|
|
||||||
paddingLeft: '10px',
|
const MetadataContainer = styled('div')(({ theme }) => ({
|
||||||
paddingRight: '10px',
|
marginLeft: theme.spacing(1),
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
fontSize: '1.2em',
|
|
||||||
// maxWidth: '50%',
|
|
||||||
},
|
|
||||||
[theme.breakpoints.up('lg')]: {
|
|
||||||
fontSize: '1.3em',
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
const Description = styled('div')(() => ({
|
|
||||||
'& h4': {
|
const Metadata = (props: ComponentProps<typeof BaseMetadata>) => <BaseMetadata {...props} />;
|
||||||
marginTop: '1em',
|
|
||||||
marginBottom: 0,
|
const MangaButtonsContainer = styled('div')(({ theme }) => ({
|
||||||
},
|
|
||||||
'& p': {
|
|
||||||
textAlign: 'justify',
|
|
||||||
textJustify: 'inter-word',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
const Genres = styled('div')(() => ({
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexWrap: 'wrap',
|
gap: theme.spacing(1),
|
||||||
'& h5': {
|
|
||||||
border: '2px solid #2196f3',
|
|
||||||
borderRadius: '1.13em',
|
|
||||||
marginRight: '1em',
|
|
||||||
marginTop: 0,
|
|
||||||
marginBottom: '10px',
|
|
||||||
padding: '0.3em',
|
|
||||||
color: '#2196f3',
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const OpenSourceButton = ({ url }: { url?: string | null }) => {
|
const OpenSourceButton = ({ url }: { url?: string | null }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const button = useMemo(
|
return (
|
||||||
() => (
|
<Tooltip title={t('global.button.open_site')}>
|
||||||
<CustomIconButton
|
<CustomIconButton
|
||||||
size="large"
|
size="medium"
|
||||||
disabled={!url}
|
disabled={!url}
|
||||||
sx={{ color: 'inherit' }}
|
|
||||||
component={Link}
|
component={Link}
|
||||||
href={url ?? undefined}
|
href={url ?? undefined}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
|
variant="outlined"
|
||||||
>
|
>
|
||||||
<PublicIcon />
|
<LaunchIcon />
|
||||||
{t('global.button.open_site')}
|
|
||||||
</CustomIconButton>
|
</CustomIconButton>
|
||||||
),
|
</Tooltip>
|
||||||
[url],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
return button;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getSourceName(source?: Pick<SourceType, 'id' | 'displayName'> | null): string {
|
function getSourceName(source?: Pick<SourceType, 'id' | 'displayName'> | null): string {
|
||||||
@@ -167,6 +104,30 @@ function getValueOrUnknown(val?: string | null) {
|
|||||||
return val ?? translate('global.label.unknown');
|
return val ?? translate('global.label.unknown');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DescriptionGenre = ({
|
||||||
|
manga: { description, genre: genres },
|
||||||
|
}: {
|
||||||
|
manga: Pick<MangaType, 'description' | 'genre'>;
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<Typography style={{ whiteSpace: 'pre-line', textAlign: 'justify', textJustify: 'inter-word' }}>
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
<Stack
|
||||||
|
sx={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: isCollapsed ? 'no-wrap' : 'wrap',
|
||||||
|
gap: 1,
|
||||||
|
overflowX: isCollapsed ? 'auto' : null,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{genres.map((genre) => (
|
||||||
|
<Chip label={genre} variant="outlined" />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
export const MangaDetails = ({
|
export const MangaDetails = ({
|
||||||
manga,
|
manga,
|
||||||
}: {
|
}: {
|
||||||
@@ -208,8 +169,8 @@ export const MangaDetails = ({
|
|||||||
</Thumbnail>
|
</Thumbnail>
|
||||||
<MetadataContainer>
|
<MetadataContainer>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h4"
|
variant="h5"
|
||||||
component="h1"
|
component="h2"
|
||||||
sx={{ mb: 1, '@media not (pointer: fine)': { userSelect: 'none' } }}
|
sx={{ mb: 1, '@media not (pointer: fine)': { userSelect: 'none' } }}
|
||||||
{...copyTitleLongPressBind()}
|
{...copyTitleLongPressBind()}
|
||||||
>
|
>
|
||||||
@@ -223,9 +184,9 @@ export const MangaDetails = ({
|
|||||||
</ThumbnailMetadataWrapper>
|
</ThumbnailMetadataWrapper>
|
||||||
<MangaButtonsContainer>
|
<MangaButtonsContainer>
|
||||||
<CustomIconButton
|
<CustomIconButton
|
||||||
|
size="medium"
|
||||||
onClick={updateLibraryState}
|
onClick={updateLibraryState}
|
||||||
size="large"
|
variant={manga.inLibrary ? 'contained' : 'outlined'}
|
||||||
sx={{ color: manga.inLibrary ? '#2196f3' : 'inherit' }}
|
|
||||||
>
|
>
|
||||||
{manga.inLibrary ? <FavoriteIcon /> : <FavoriteBorderIcon />}
|
{manga.inLibrary ? <FavoriteIcon /> : <FavoriteBorderIcon />}
|
||||||
{manga.inLibrary ? t('manga.button.in_library') : t('manga.button.add_to_library')}
|
{manga.inLibrary ? t('manga.button.in_library') : t('manga.button.add_to_library')}
|
||||||
@@ -234,17 +195,7 @@ export const MangaDetails = ({
|
|||||||
<OpenSourceButton url={manga.realUrl} />
|
<OpenSourceButton url={manga.realUrl} />
|
||||||
</MangaButtonsContainer>
|
</MangaButtonsContainer>
|
||||||
</TopContentWrapper>
|
</TopContentWrapper>
|
||||||
<BottomContentWrapper>
|
<DescriptionGenre manga={manga} />
|
||||||
<Description>
|
|
||||||
<h4>{t('settings.about.title')}</h4>
|
|
||||||
<p style={{ whiteSpace: 'pre-line' }}>{manga.description}</p>
|
|
||||||
</Description>
|
|
||||||
<Genres>
|
|
||||||
{manga.genre.map((g) => (
|
|
||||||
<h5 key={g}>{g}</h5>
|
|
||||||
))}
|
|
||||||
</Genres>
|
|
||||||
</BottomContentWrapper>
|
|
||||||
</DetailsWrapper>
|
</DetailsWrapper>
|
||||||
{CategorySelectComponent}
|
{CategorySelectComponent}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ export const TrackMangaButton = ({ manga }: { manga: MangaTrackRecordInfo & Pick
|
|||||||
<>
|
<>
|
||||||
<CustomIconButton
|
<CustomIconButton
|
||||||
{...bindTrigger(popupState)}
|
{...bindTrigger(popupState)}
|
||||||
|
size="medium"
|
||||||
disabled={trackerList.loading || !!trackerList.error}
|
disabled={trackerList.loading || !!trackerList.error}
|
||||||
onClick={() => handleClick(popupState.open)}
|
onClick={() => handleClick(popupState.open)}
|
||||||
onTouchStart={() => handleClick(popupState.open)}
|
onTouchStart={() => handleClick(popupState.open)}
|
||||||
size="large"
|
variant={trackersInUse.length ? 'contained' : 'outlined'}
|
||||||
sx={{ color: trackersInUse.length ? '#2196f3' : 'inherit' }}
|
|
||||||
>
|
>
|
||||||
{trackersInUse.length ? <CheckIcon /> : <SyncIcon />}
|
{trackersInUse.length ? <CheckIcon /> : <SyncIcon />}
|
||||||
{trackersInUse.length
|
{trackersInUse.length
|
||||||
|
|||||||
@@ -164,12 +164,7 @@ export function DefaultNavBar() {
|
|||||||
{getOptionForDirection(<ArrowBack />, <ArrowForwardIcon />)}
|
{getOptionForDirection(<ArrowBack />, <ArrowForwardIcon />)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
<Typography
|
<Typography variant="h5" component="h1" sx={{ flexGrow: 1 }} noWrap textOverflow="ellipsis">
|
||||||
variant={isMobileWidth ? 'h6' : 'h5'}
|
|
||||||
sx={{ flexGrow: 1 }}
|
|
||||||
noWrap
|
|
||||||
textOverflow="ellipsis"
|
|
||||||
>
|
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
{action}
|
{action}
|
||||||
|
|||||||
Reference in New Issue
Block a user