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

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';
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';
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';
import { useTranslation } from 'react-i18next';
import { ChapterOffset, IChapter, IManga, IMangaCard, IReaderSettings } from '@/typings';
import ReaderSettingsOptions from '@/components/reader/ReaderSettingsOptions';
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,
display: 'flex',
alignItems: 'center',
minHeight: '64px',
paddingLeft: '24px',
paddingRight: '24px',
transition: 'left 2s ease',
'& button': {
flexGrow: 0,
flexShrink: 0,
2021-03-23 03:50:55 +04:30
},
'& button:nth-child(1)': {
marginRight: '16px',
},
2021-03-23 03:50:55 +04:30
'& h1': {
fontSize: '1.25rem',
flexGrow: 1,
2021-03-23 03:50:55 +04:30
},
},
}));
const Navigation = styled('div')({
margin: '0 16px',
'& > span:nth-child(1)': {
textAlign: 'center',
display: 'block',
marginTop: '16px',
},
});
const PageNavigation = styled('div')({
display: 'flex',
flexWrap: 'wrap',
alignContent: 'center',
alignItems: 'center',
justifyContent: 'center',
});
const ChapterNavigation = styled('div')({
display: 'grid',
gridTemplateRows: 'auto auto auto',
gridTemplateColumns: '0fr 1fr 0fr',
gridTemplateAreas: '"pre current next"',
gridColumnGap: '5px',
margin: '10px 0',
'& a': {
textDecoration: 'none',
color: 'inherit',
display: 'flex',
flexWrap: 'wrap',
alignContent: 'center',
},
});
const MenuProps = { PaperProps: { style: { maxHeight: 150 } } };
const OpenDrawerButton = styled(IconButton)(({ theme }) => ({
position: 'fixed',
top: 0 + 20,
left: 10 + 20,
height: '40px',
width: '40px',
borderRadius: 5,
backgroundColor: theme.palette.custom.main,
'&:hover': {
backgroundColor: theme.palette.custom.light,
},
}));
2021-03-18 21:46:24 +03:30
interface IProps {
settings: IReaderSettings;
setSettingValue: (key: keyof IReaderSettings, value: string | boolean) => void;
manga: IManga | IMangaCard;
chapter: IChapter;
curPage: number;
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) {
const { t } = useTranslation();
const navigate = useNavigate();
Save reader settings per manga in Meta (#216) * [#213] Add missing "meta" property - IManga - IMangaCard (optional) - IChapter - ICategory * [#213] Add util functions for handling metadata - global server - manga - chapter - category * [#213] Use "ReaderSettings" from the manga metadata - get ReaderSettings from manga metadata - remove unnecessary check "make sure settings has all the keys" in case the stored settings in the metadata are outdated they will be filled with default values * [#213] Reload manga only in case "mangaId" changed In case the chapter is still from the same manga, a reload is unnecessary * [#213] Only update the changed reader setting Otherwise the app has to send patch requests to the server for every setting even if it didn't change * [#213] Hide "openButton" on first render in case "navBar" is shown The "openButton" was always set to be visible event in case the navBar was shown on the first render * [#213] Open the "ReaderNavBar" if needed after receiving new settings Otherwise, the drawer won't open in case the "navBar" was hidden on the first render since the default state in that case was set to false * [#213] Update "ReaderSettings" after receiving the manga response Otherwise, the default settings aren't getting removed * [#213] Hide/Show "OpenButton" when opening/closing the drawer Otherwise, the button stays hidden until it gets updated due to scrolling * [#213] Keep "ReaderNavBar" state when opening prev/next chapter In case the navBar wasn't sticky but opened, it got closed when the prev/next chapter got opened * [#213] Prevent "ReaderNavBar" from closing when changing "staticNav" setting
2023-01-06 17:15:29 +01:00
const location = useLocation<{
prevDrawerOpen?: boolean;
prevSettingsCollapseOpen?: boolean;
Save reader settings per manga in Meta (#216) * [#213] Add missing "meta" property - IManga - IMangaCard (optional) - IChapter - ICategory * [#213] Add util functions for handling metadata - global server - manga - chapter - category * [#213] Use "ReaderSettings" from the manga metadata - get ReaderSettings from manga metadata - remove unnecessary check "make sure settings has all the keys" in case the stored settings in the metadata are outdated they will be filled with default values * [#213] Reload manga only in case "mangaId" changed In case the chapter is still from the same manga, a reload is unnecessary * [#213] Only update the changed reader setting Otherwise the app has to send patch requests to the server for every setting even if it didn't change * [#213] Hide "openButton" on first render in case "navBar" is shown The "openButton" was always set to be visible event in case the navBar was shown on the first render * [#213] Open the "ReaderNavBar" if needed after receiving new settings Otherwise, the drawer won't open in case the "navBar" was hidden on the first render since the default state in that case was set to false * [#213] Update "ReaderSettings" after receiving the manga response Otherwise, the default settings aren't getting removed * [#213] Hide/Show "OpenButton" when opening/closing the drawer Otherwise, the button stays hidden until it gets updated due to scrolling * [#213] Keep "ReaderNavBar" state when opening prev/next chapter In case the navBar wasn't sticky but opened, it got closed when the prev/next chapter got opened * [#213] Prevent "ReaderNavBar" from closing when changing "staticNav" setting
2023-01-06 17:15:29 +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
Save reader settings per manga in Meta (#216) * [#213] Add missing "meta" property - IManga - IMangaCard (optional) - IChapter - ICategory * [#213] Add util functions for handling metadata - global server - manga - chapter - category * [#213] Use "ReaderSettings" from the manga metadata - get ReaderSettings from manga metadata - remove unnecessary check "make sure settings has all the keys" in case the stored settings in the metadata are outdated they will be filled with default values * [#213] Reload manga only in case "mangaId" changed In case the chapter is still from the same manga, a reload is unnecessary * [#213] Only update the changed reader setting Otherwise the app has to send patch requests to the server for every setting even if it didn't change * [#213] Hide "openButton" on first render in case "navBar" is shown The "openButton" was always set to be visible event in case the navBar was shown on the first render * [#213] Open the "ReaderNavBar" if needed after receiving new settings Otherwise, the drawer won't open in case the "navBar" was hidden on the first render since the default state in that case was set to false * [#213] Update "ReaderSettings" after receiving the manga response Otherwise, the default settings aren't getting removed * [#213] Hide/Show "OpenButton" when opening/closing the drawer Otherwise, the button stays hidden until it gets updated due to scrolling * [#213] Keep "ReaderNavBar" state when opening prev/next chapter In case the navBar wasn't sticky but opened, it got closed when the prev/next chapter got opened * [#213] Prevent "ReaderNavBar" from closing when changing "staticNav" setting
2023-01-06 17:15:29 +01:00
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);
const [settingsCollapseOpen, setSettingsCollapseOpen] = useState(prevSettingsCollapseOpen ?? true);
2021-03-18 21:46:24 +03:30
const disableChapterNavButtons = retrievingNextChapter;
Save reader settings per manga in Meta (#216) * [#213] Add missing "meta" property - IManga - IMangaCard (optional) - IChapter - ICategory * [#213] Add util functions for handling metadata - global server - manga - chapter - category * [#213] Use "ReaderSettings" from the manga metadata - get ReaderSettings from manga metadata - remove unnecessary check "make sure settings has all the keys" in case the stored settings in the metadata are outdated they will be filled with default values * [#213] Reload manga only in case "mangaId" changed In case the chapter is still from the same manga, a reload is unnecessary * [#213] Only update the changed reader setting Otherwise the app has to send patch requests to the server for every setting even if it didn't change * [#213] Hide "openButton" on first render in case "navBar" is shown The "openButton" was always set to be visible event in case the navBar was shown on the first render * [#213] Open the "ReaderNavBar" if needed after receiving new settings Otherwise, the drawer won't open in case the "navBar" was hidden on the first render since the default state in that case was set to false * [#213] Update "ReaderSettings" after receiving the manga response Otherwise, the default settings aren't getting removed * [#213] Hide/Show "OpenButton" when opening/closing the drawer Otherwise, the button stays hidden until it gets updated due to scrolling * [#213] Keep "ReaderNavBar" state when opening prev/next chapter In case the navBar wasn't sticky but opened, it got closed when the prev/next chapter got opened * [#213] Prevent "ReaderNavBar" from closing when changing "staticNav" setting
2023-01-06 17:15:29 +01:00
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);
}
};
Save reader settings per manga in Meta (#216) * [#213] Add missing "meta" property - IManga - IMangaCard (optional) - IChapter - ICategory * [#213] Add util functions for handling metadata - global server - manga - chapter - category * [#213] Use "ReaderSettings" from the manga metadata - get ReaderSettings from manga metadata - remove unnecessary check "make sure settings has all the keys" in case the stored settings in the metadata are outdated they will be filled with default values * [#213] Reload manga only in case "mangaId" changed In case the chapter is still from the same manga, a reload is unnecessary * [#213] Only update the changed reader setting Otherwise the app has to send patch requests to the server for every setting even if it didn't change * [#213] Hide "openButton" on first render in case "navBar" is shown The "openButton" was always set to be visible event in case the navBar was shown on the first render * [#213] Open the "ReaderNavBar" if needed after receiving new settings Otherwise, the drawer won't open in case the "navBar" was hidden on the first render since the default state in that case was set to false * [#213] Update "ReaderSettings" after receiving the manga response Otherwise, the default settings aren't getting removed * [#213] Hide/Show "OpenButton" when opening/closing the drawer Otherwise, the button stays hidden until it gets updated due to scrolling * [#213] Keep "ReaderNavBar" state when opening prev/next chapter In case the navBar wasn't sticky but opened, it got closed when the prev/next chapter got opened * [#213] Prevent "ReaderNavBar" from closing when changing "staticNav" setting
2023-01-06 17:15:29 +01:00
useEffect(() => {
if (updateDrawerOnRender) {
updateDrawer(settings.staticNav);
}
}, [settings.staticNav]);
2021-03-18 21:46:24 +03:30
useEffect(() => {
window.addEventListener('scroll', handleScroll);
const rootEl: HTMLDivElement = document.querySelector('#root')!;
const mainContainer: HTMLDivElement = document.querySelector('#appMainContainer')!;
2021-03-18 21:46:24 +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 () => {
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
};
}, [handleScroll]); // handleScroll changes on every render
2021-03-18 21:46:24 +03:30
const handleClose = () => {
const isLastPageInHistory = location.key === 'default';
if (isLastPageInHistory) {
navigate(`/manga/${manga.id}`);
return;
}
// 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);
};
2021-03-18 21:46:24 +03:30
return (
<>
<Slide direction="right" in={drawerOpen} timeout={200} appear={false} mountOnEnter unmountOnExit>
<Root
sx={{
position: 'fixed',
}}
>
2021-05-15 23:22:37 +04:30
<header>
{!settings.staticNav && (
<IconButton
edge="start"
color="inherit"
aria-label="menu"
disableRipple
Save reader settings per manga in Meta (#216) * [#213] Add missing "meta" property - IManga - IMangaCard (optional) - IChapter - ICategory * [#213] Add util functions for handling metadata - global server - manga - chapter - category * [#213] Use "ReaderSettings" from the manga metadata - get ReaderSettings from manga metadata - remove unnecessary check "make sure settings has all the keys" in case the stored settings in the metadata are outdated they will be filled with default values * [#213] Reload manga only in case "mangaId" changed In case the chapter is still from the same manga, a reload is unnecessary * [#213] Only update the changed reader setting Otherwise the app has to send patch requests to the server for every setting even if it didn't change * [#213] Hide "openButton" on first render in case "navBar" is shown The "openButton" was always set to be visible event in case the navBar was shown on the first render * [#213] Open the "ReaderNavBar" if needed after receiving new settings Otherwise, the drawer won't open in case the "navBar" was hidden on the first render since the default state in that case was set to false * [#213] Update "ReaderSettings" after receiving the manga response Otherwise, the default settings aren't getting removed * [#213] Hide/Show "OpenButton" when opening/closing the drawer Otherwise, the button stays hidden until it gets updated due to scrolling * [#213] Keep "ReaderNavBar" state when opening prev/next chapter In case the navBar wasn't sticky but opened, it got closed when the prev/next chapter got opened * [#213] Prevent "ReaderNavBar" from closing when changing "staticNav" setting
2023-01-06 17:15:29 +01:00
onClick={() => updateDrawer(false)}
size="large"
>
<KeyboardArrowLeftIcon />
</IconButton>
)}
<Typography variant="h1" textOverflow="ellipsis" overflow="hidden" sx={{ py: 1 }}>
{chapter.name}
</Typography>
<IconButton
edge="start"
color="inherit"
aria-label="menu"
disableRipple
onClick={handleClose}
2021-09-09 17:51:22 +04:30
size="large"
sx={{ mr: -1 }}
>
<CloseIcon />
</IconButton>
2021-05-15 23:22:37 +04:30
</header>
<ListItem
ContainerComponent="div"
sx={{
'& span': {
fontWeight: 'bold',
},
}}
>
<ListItemText primary={t('reader.settings.title.reader_settings')} />
2021-05-15 23:22:37 +04:30
<ListItemSecondaryAction>
2021-03-23 03:50:55 +04:30
<IconButton
edge="start"
color="inherit"
aria-label="menu"
disableRipple
2021-05-15 23:22:37 +04:30
disableFocusRipple
onClick={() => setSettingsCollapseOpen(!settingsCollapseOpen)}
2021-09-09 17:51:22 +04:30
size="large"
2021-03-23 03:50:55 +04:30
>
2021-05-15 23:22:37 +04:30
{settingsCollapseOpen && <KeyboardArrowUpIcon />}
{!settingsCollapseOpen && <KeyboardArrowDownIcon />}
2021-03-23 03:50:55 +04:30
</IconButton>
2021-05-15 23:22:37 +04:30
</ListItemSecondaryAction>
</ListItem>
<Collapse in={settingsCollapseOpen} timeout="auto" unmountOnExit>
<ReaderSettingsOptions
setSettingValue={updateSettingValue}
staticNav={settings.staticNav}
showPageNumber={settings.showPageNumber}
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 }} />
<Navigation>
<PageNavigation>
<span>{t('reader.page_info.label.currently_on_page')}</span>
<FormControl
size="small"
sx={{ margin: '0 5px' }}
disabled={disableChapterNavButtons || chapter.pageCount === -1}
>
<Select
MenuProps={MenuProps}
value={chapter.pageCount > -1 ? curPage : ''}
displayEmpty
onChange={({ target: { value: selectedPage } }) => {
scrollToPage(Number(selectedPage));
}}
>
{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>
<span>{t('reader.page_info.label.of_max_pages', { maxPages: chapter.pageCount })}</span>
</PageNavigation>
<ChapterNavigation>
<IconButton
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,
},
});
Save reader settings per manga in Meta (#216) * [#213] Add missing "meta" property - IManga - IMangaCard (optional) - IChapter - ICategory * [#213] Add util functions for handling metadata - global server - manga - chapter - category * [#213] Use "ReaderSettings" from the manga metadata - get ReaderSettings from manga metadata - remove unnecessary check "make sure settings has all the keys" in case the stored settings in the metadata are outdated they will be filled with default values * [#213] Reload manga only in case "mangaId" changed In case the chapter is still from the same manga, a reload is unnecessary * [#213] Only update the changed reader setting Otherwise the app has to send patch requests to the server for every setting even if it didn't change * [#213] Hide "openButton" on first render in case "navBar" is shown The "openButton" was always set to be visible event in case the navBar was shown on the first render * [#213] Open the "ReaderNavBar" if needed after receiving new settings Otherwise, the drawer won't open in case the "navBar" was hidden on the first render since the default state in that case was set to false * [#213] Update "ReaderSettings" after receiving the manga response Otherwise, the default settings aren't getting removed * [#213] Hide/Show "OpenButton" when opening/closing the drawer Otherwise, the button stays hidden until it gets updated due to scrolling * [#213] Keep "ReaderNavBar" state when opening prev/next chapter In case the navBar wasn't sticky but opened, it got closed when the prev/next chapter got opened * [#213] Prevent "ReaderNavBar" from closing when changing "staticNav" setting
2023-01-06 17:15:29 +01:00
}}
2021-03-23 03:50:55 +04:30
>
{Array(Math.max(0, chapter.chapterCount))
.fill(1)
.map((ignoreValue, index) => (
<MenuItem key={`Chapter#${index + 1}`} value={index + 1}>{`${t(
'chapter.title',
)} ${index + 1}`}</MenuItem>
))}
</Select>
</FormControl>
<IconButton
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>
</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}>
<OpenDrawerButton
2021-03-18 21:46:24 +03:30
edge="start"
aria-label="menu"
disableRipple
disableFocusRipple
Save reader settings per manga in Meta (#216) * [#213] Add missing "meta" property - IManga - IMangaCard (optional) - IChapter - ICategory * [#213] Add util functions for handling metadata - global server - manga - chapter - category * [#213] Use "ReaderSettings" from the manga metadata - get ReaderSettings from manga metadata - remove unnecessary check "make sure settings has all the keys" in case the stored settings in the metadata are outdated they will be filled with default values * [#213] Reload manga only in case "mangaId" changed In case the chapter is still from the same manga, a reload is unnecessary * [#213] Only update the changed reader setting Otherwise the app has to send patch requests to the server for every setting even if it didn't change * [#213] Hide "openButton" on first render in case "navBar" is shown The "openButton" was always set to be visible event in case the navBar was shown on the first render * [#213] Open the "ReaderNavBar" if needed after receiving new settings Otherwise, the drawer won't open in case the "navBar" was hidden on the first render since the default state in that case was set to false * [#213] Update "ReaderSettings" after receiving the manga response Otherwise, the default settings aren't getting removed * [#213] Hide/Show "OpenButton" when opening/closing the drawer Otherwise, the button stays hidden until it gets updated due to scrolling * [#213] Keep "ReaderNavBar" state when opening prev/next chapter In case the navBar wasn't sticky but opened, it got closed when the prev/next chapter got opened * [#213] Prevent "ReaderNavBar" from closing when changing "staticNav" setting
2023-01-06 17:15:29 +01:00
onClick={() => updateDrawer(true)}
2021-09-09 17:51:22 +04:30
size="large"
2021-03-18 21:46:24 +03:30
>
<KeyboardArrowRightIcon />
</OpenDrawerButton>
2021-03-18 21:46:24 +03:30
</Fade>
</Zoom>
</>
);
}