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

391 lines
16 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-03-18 21:46:24 +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-03-18 21:46:24 +03:30
2021-09-09 17:51:22 +04:30
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft';
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
2021-12-01 03:03:52 +03:30
import React, { useEffect, useState } from 'react';
2021-09-09 17:51:22 +04:30
import Typography from '@mui/material/Typography';
import { useLocation, useNavigate } from 'react-router-dom';
2021-09-09 17:51:22 +04:30
import Slide from '@mui/material/Slide';
import Fade from '@mui/material/Fade';
import Zoom from '@mui/material/Zoom';
2023-06-04 21:08:39 +02:00
import { Divider, FormControl, MenuItem, Select, styled } from '@mui/material';
2021-09-09 17:51:22 +04:30
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
import Collapse from '@mui/material/Collapse';
2023-03-23 13:59:32 +01:00
import { useTranslation } from 'react-i18next';
2023-06-05 01:42:39 +02:00
import { ChapterOffset, IChapter, IManga, IMangaCard, IReaderSettings } from '@/typings';
import ReaderSettingsOptions from '@/components/reader/ReaderSettingsOptions';
2021-03-18 21:46:24 +03:30
2021-12-01 03:03:52 +03:30
const Root = styled('div')(({ theme }) => ({
top: 0,
left: 0,
width: '300px',
minWidth: '300px',
height: '100vh',
overflowY: 'auto',
backgroundColor: theme.palette.background.default,
'& header': {
backgroundColor: theme.palette.action.hover,
2021-03-18 21:46:24 +03:30
display: 'flex',
2021-12-01 03:03:52 +03:30
alignItems: 'center',
minHeight: '64px',
paddingLeft: '24px',
paddingRight: '24px',
2021-03-18 21:46:24 +03:30
2021-12-01 03:03:52 +03:30
transition: 'left 2s ease',
2021-03-18 21:46:24 +03:30
2021-12-01 03:03:52 +03:30
'& button': {
flexGrow: 0,
flexShrink: 0,
2021-03-18 21:46:24 +03:30
},
2021-12-01 03:03:52 +03:30
'& button:nth-child(1)': {
marginRight: '16px',
},
'& h1': {
fontSize: '1.25rem',
flexGrow: 1,
2021-03-23 03:50:55 +04:30
},
},
2021-12-01 03:03:52 +03:30
}));
2021-03-23 03:50:55 +04:30
2021-12-01 03:03:52 +03:30
const Navigation = styled('div')({
margin: '0 16px',
'& > span:nth-child(1)': {
textAlign: 'center',
display: 'block',
marginTop: '16px',
},
});
2023-02-08 18:26:09 +01:00
const PageNavigation = styled('div')({
display: 'flex',
flexWrap: 'wrap',
alignContent: 'center',
alignItems: 'center',
justifyContent: 'center',
});
2021-12-01 03:03:52 +03:30
const ChapterNavigation = styled('div')({
display: 'grid',
gridTemplateRows: 'auto auto auto',
gridTemplateColumns: '0fr 1fr 0fr',
gridTemplateAreas: '"pre current next"',
2021-12-01 03:03:52 +03:30
gridColumnGap: '5px',
margin: '10px 0',
'& a': {
textDecoration: 'none',
color: 'inherit',
display: 'flex',
flexWrap: 'wrap',
alignContent: 'center',
2021-03-18 21:46:24 +03:30
},
2021-12-01 03:03:52 +03:30
});
2021-03-18 21:46:24 +03:30
2023-02-08 18:26:09 +01:00
const MenuProps = { PaperProps: { style: { maxHeight: 150 } } };
2021-12-01 03:03:52 +03:30
const OpenDrawerButton = styled(IconButton)(({ theme }) => ({
position: 'fixed',
top: 0 + 20,
left: 10 + 20,
height: '40px',
width: '40px',
borderRadius: 5,
backgroundColor: theme.palette.custom.main,
2021-12-01 03:03:52 +03:30
'&:hover': {
backgroundColor: theme.palette.custom.light,
2021-03-18 21:46:24 +03:30
},
}));
2021-03-18 21:46:24 +03:30
interface IProps {
2023-02-06 10:06:33 +01:00
settings: IReaderSettings;
setSettingValue: (key: keyof IReaderSettings, value: string | boolean) => void;
manga: IManga | IMangaCard;
chapter: IChapter;
curPage: number;
2023-02-12 16:12:17 +01:00
scrollToPage: (page: number) => void;
openNextChapter: (offset: ChapterOffset, setHistory: (nextChapterIndex: number) => void) => Promise<void>;
retrievingNextChapter: boolean;
2021-03-18 21:46:24 +03:30
}
export default function ReaderNavBar(props: IProps) {
2023-03-23 13:59:32 +01:00
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation<{
2023-02-06 10:06:33 +01:00
prevDrawerOpen?: boolean;
prevSettingsCollapseOpen?: boolean;
}>();
2023-02-06 10:06:33 +01:00
const { prevDrawerOpen, prevSettingsCollapseOpen } = location.state ?? {};
2021-03-18 21:46:24 +03:30
const { settings, setSettingValue, manga, chapter, curPage, scrollToPage, openNextChapter, retrievingNextChapter } =
props;
2021-03-19 14:52:20 +03:30
const [drawerOpen, setDrawerOpen] = useState(settings.staticNav || prevDrawerOpen);
const [updateDrawerOnRender, setUpdateDrawerOnRender] = useState(true);
const [hideOpenButton, setHideOpenButton] = useState(settings.staticNav || prevDrawerOpen);
2021-03-18 21:46:24 +03:30
const [prevScrollPos, setPrevScrollPos] = useState(0);
2023-02-08 18:19:26 +01:00
const [settingsCollapseOpen, setSettingsCollapseOpen] = useState(prevSettingsCollapseOpen ?? true);
2021-03-18 21:46:24 +03:30
const disableChapterNavButtons = retrievingNextChapter;
const updateSettingValue = (key: keyof IReaderSettings, value: string | boolean) => {
// prevent closing the navBar when updating the "staticNav" setting
setUpdateDrawerOnRender(key !== 'staticNav');
setSettingValue(key, value);
};
const updateDrawer = (open: boolean) => {
setDrawerOpen(open);
setHideOpenButton(open);
};
2021-03-18 21:46:24 +03:30
const handleScroll = () => {
const currentScrollPos = window.pageYOffset;
if (Math.abs(currentScrollPos - prevScrollPos) > 20) {
setHideOpenButton(currentScrollPos > prevScrollPos);
setPrevScrollPos(currentScrollPos);
}
};
useEffect(() => {
if (updateDrawerOnRender) {
updateDrawer(settings.staticNav);
}
}, [settings.staticNav]);
2021-03-18 21:46:24 +03:30
useEffect(() => {
window.addEventListener('scroll', handleScroll);
2023-02-06 10:06:33 +01:00
const rootEl: HTMLDivElement = document.querySelector('#root')!;
const mainContainer: HTMLDivElement = document.querySelector('#appMainContainer')!;
2021-03-18 21:46:24 +03:30
2021-12-01 03:03:52 +03:30
// main container and root div need to change styles...
rootEl.style.display = 'flex';
mainContainer.style.display = 'none';
2021-03-18 21:46:24 +03:30
return () => {
2021-12-01 03:03:52 +03:30
rootEl.style.display = 'block';
mainContainer.style.display = 'block';
2021-03-23 04:28:23 +04:30
window.removeEventListener('scroll', handleScroll);
2021-03-18 21:46:24 +03:30
};
2023-02-06 10:06:33 +01:00
}, [handleScroll]); // handleScroll changes on every render
2021-03-18 21:46:24 +03:30
2022-11-21 01:33:45 +01:00
const handleClose = () => {
const isLastPageInHistory = location.key === 'default';
if (isLastPageInHistory) {
navigate(`/manga/${manga.id}`);
return;
}
2023-06-08 14:25:24 +02:00
// this works because opening previous/next chapter will replace the current history element.
// in case this gets changed this has to be updated
navigate(-1);
2022-11-21 01:33:45 +01:00
};
2021-03-18 21:46:24 +03:30
return (
<>
2023-02-08 18:19:26 +01:00
<Slide direction="right" in={drawerOpen} timeout={200} appear={false} mountOnEnter unmountOnExit>
2023-02-06 10:06:33 +01:00
<Root
sx={{
2023-02-12 16:12:17 +01:00
position: 'fixed',
2023-02-06 10:06:33 +01:00
}}
2021-12-01 03:03:52 +03:30
>
2021-05-15 23:22:37 +04:30
<header>
2023-02-06 10:06:33 +01:00
{!settings.staticNav && (
2021-12-01 03:03:52 +03:30
<IconButton
edge="start"
color="inherit"
aria-label="menu"
disableRipple
onClick={() => updateDrawer(false)}
2021-12-01 03:03:52 +03:30
size="large"
>
<KeyboardArrowLeftIcon />
</IconButton>
)}
2023-02-08 18:19:26 +01:00
<Typography variant="h1" textOverflow="ellipsis" overflow="hidden" sx={{ py: 1 }}>
{chapter.name}
</Typography>
<IconButton
edge="start"
color="inherit"
aria-label="menu"
disableRipple
2022-11-21 01:33:45 +01:00
onClick={handleClose}
2021-09-09 17:51:22 +04:30
size="large"
2021-12-01 03:03:52 +03:30
sx={{ mr: -1 }}
>
<CloseIcon />
</IconButton>
2021-05-15 23:22:37 +04:30
</header>
2021-12-01 03:03:52 +03:30
<ListItem
ContainerComponent="div"
sx={{
'& span': {
fontWeight: 'bold',
},
}}
>
2023-03-23 13:59:32 +01:00
<ListItemText primary={t('reader.settings.title.reader_settings')} />
2021-05-15 23:22:37 +04:30
<ListItemSecondaryAction>
<IconButton
edge="start"
color="inherit"
aria-label="menu"
disableRipple
disableFocusRipple
onClick={() => setSettingsCollapseOpen(!settingsCollapseOpen)}
2021-09-09 17:51:22 +04:30
size="large"
2021-05-15 23:22:37 +04:30
>
{settingsCollapseOpen && <KeyboardArrowUpIcon />}
{!settingsCollapseOpen && <KeyboardArrowDownIcon />}
</IconButton>
</ListItemSecondaryAction>
</ListItem>
<Collapse in={settingsCollapseOpen} timeout="auto" unmountOnExit>
<ReaderSettingsOptions
setSettingValue={updateSettingValue}
staticNav={settings.staticNav}
showPageNumber={settings.showPageNumber}
2023-01-06 18:19:45 +01:00
loadNextOnEnding={settings.loadNextOnEnding}
skipDupChapters={settings.skipDupChapters}
fitPageToWindow={settings.fitPageToWindow}
readerType={settings.readerType}
/>
2021-05-15 23:22:37 +04:30
</Collapse>
<Divider sx={{ my: 1, mx: 2 }} />
2021-12-01 03:03:52 +03:30
<Navigation>
2023-02-08 18:26:09 +01:00
<PageNavigation>
2023-03-23 13:59:32 +01:00
<span>{t('reader.page_info.label.currently_on_page')}</span>
<FormControl
size="small"
sx={{ margin: '0 5px' }}
disabled={disableChapterNavButtons || chapter.pageCount === -1}
>
2023-02-08 18:26:09 +01:00
<Select
MenuProps={MenuProps}
value={chapter.pageCount > -1 ? curPage : ''}
displayEmpty
onChange={({ target: { value: selectedPage } }) => {
2023-02-12 16:12:17 +01:00
scrollToPage(Number(selectedPage));
2023-02-08 18:26:09 +01:00
}}
>
{Array(Math.max(0, chapter.pageCount))
.fill(1)
.map((ignoreValue, index) => (
// eslint-disable-next-line react/no-array-index-key
<MenuItem key={`Page#${index}`} value={index}>
{index + 1}
</MenuItem>
))}
</Select>
</FormControl>
2023-03-23 13:59:32 +01:00
<span>{t('reader.page_info.label.of_max_pages', { maxPages: chapter.pageCount })}</span>
2023-02-08 18:26:09 +01:00
</PageNavigation>
2021-12-01 03:03:52 +03:30
<ChapterNavigation>
<IconButton
2023-03-23 13:59:32 +01:00
title={t('reader.button.previous_chapter')}
sx={{ gridArea: 'pre' }}
disabled={disableChapterNavButtons || chapter.index <= 1}
onClick={() =>
openNextChapter(ChapterOffset.PREV, (prevChapterIndex) => {
navigate(`/manga/${manga.id}/chapter/${prevChapterIndex}`, {
replace: true,
state: {
prevDrawerOpen: drawerOpen,
prevSettingsCollapseOpen: settingsCollapseOpen,
},
});
})
}
>
<KeyboardArrowLeftIcon />
</IconButton>
<FormControl
sx={{ gridArea: 'current' }}
size="small"
disabled={disableChapterNavButtons || chapter.index < 1}
>
<Select
MenuProps={MenuProps}
value={chapter.index >= 1 ? chapter.index : ''}
displayEmpty
onChange={({ target: { value: selectedChapter } }) => {
navigate(`/manga/${manga.id}/chapter/${selectedChapter}`, {
replace: true,
state: {
prevDrawerOpen: drawerOpen,
prevSettingsCollapseOpen: settingsCollapseOpen,
},
});
}}
2021-03-23 03:50:55 +04:30
>
{Array(Math.max(0, chapter.chapterCount))
.fill(1)
.map((ignoreValue, index) => (
2023-03-23 13:59:32 +01:00
<MenuItem key={`Chapter#${index + 1}`} value={index + 1}>{`${t(
'chapter.title',
)} ${index + 1}`}</MenuItem>
))}
</Select>
</FormControl>
<IconButton
2023-03-23 13:59:32 +01:00
title={t('reader.button.next_chapter')}
sx={{ gridArea: 'next' }}
disabled={
disableChapterNavButtons ||
chapter.index < 1 ||
chapter.index >= chapter.chapterCount
}
onClick={() => {
openNextChapter(ChapterOffset.NEXT, (nextChapterIndex) =>
navigate(`/manga/${manga.id}/chapter/${nextChapterIndex}`, {
replace: true,
state: {
prevDrawerOpen: drawerOpen,
prevSettingsCollapseOpen: settingsCollapseOpen,
},
}),
);
}}
>
<KeyboardArrowRightIcon />
</IconButton>
2021-12-01 03:03:52 +03:30
</ChapterNavigation>
</Navigation>
</Root>
2021-05-15 23:22:37 +04:30
</Slide>
2021-03-18 21:46:24 +03:30
<Zoom in={!drawerOpen}>
<Fade in={!hideOpenButton}>
2021-12-01 03:03:52 +03:30
<OpenDrawerButton
2021-03-18 21:46:24 +03:30
edge="start"
aria-label="menu"
disableRipple
disableFocusRipple
onClick={() => updateDrawer(true)}
2021-09-09 17:51:22 +04:30
size="large"
2021-03-18 21:46:24 +03:30
>
<KeyboardArrowRightIcon />
2021-12-01 03:03:52 +03:30
</OpenDrawerButton>
2021-03-18 21:46:24 +03:30
</Fade>
</Zoom>
</>
);
}