/* * 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 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'; import Typography from '@mui/material/Typography'; import { useLocation, useNavigate } from 'react-router-dom'; 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'; 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, }, '& button:nth-child(1)': { marginRight: '16px', }, '& h1': { fontSize: '1.25rem', flexGrow: 1, }, }, })); 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, }, })); 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; retrievingNextChapter: boolean; } export default function ReaderNavBar(props: IProps) { const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation<{ prevDrawerOpen?: boolean; prevSettingsCollapseOpen?: boolean; }>(); const { prevDrawerOpen, prevSettingsCollapseOpen } = location.state ?? {}; const { settings, setSettingValue, manga, chapter, curPage, scrollToPage, openNextChapter, retrievingNextChapter } = props; const [drawerOpen, setDrawerOpen] = useState(settings.staticNav || prevDrawerOpen); const [updateDrawerOnRender, setUpdateDrawerOnRender] = useState(true); const [hideOpenButton, setHideOpenButton] = useState(settings.staticNav || prevDrawerOpen); const [prevScrollPos, setPrevScrollPos] = useState(0); const [settingsCollapseOpen, setSettingsCollapseOpen] = useState(prevSettingsCollapseOpen ?? true); 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); }; 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]); useEffect(() => { window.addEventListener('scroll', handleScroll); const rootEl: HTMLDivElement = document.querySelector('#root')!; const mainContainer: HTMLDivElement = document.querySelector('#appMainContainer')!; // main container and root div need to change styles... rootEl.style.display = 'flex'; mainContainer.style.display = 'none'; return () => { rootEl.style.display = 'block'; mainContainer.style.display = 'block'; window.removeEventListener('scroll', handleScroll); }; }, [handleScroll]); // handleScroll changes on every render 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); }; return ( <>
{!settings.staticNav && ( updateDrawer(false)} size="large" > )} {chapter.name}
setSettingsCollapseOpen(!settingsCollapseOpen)} size="large" > {settingsCollapseOpen && } {!settingsCollapseOpen && } {t('reader.page_info.label.currently_on_page')} {t('reader.page_info.label.of_max_pages', { maxPages: chapter.pageCount })} openNextChapter(ChapterOffset.PREV, (prevChapterIndex) => { navigate(`/manga/${manga.id}/chapter/${prevChapterIndex}`, { replace: true, state: { prevDrawerOpen: drawerOpen, prevSettingsCollapseOpen: settingsCollapseOpen, }, }); }) } > = chapter.chapterCount } onClick={() => { openNextChapter(ChapterOffset.NEXT, (nextChapterIndex) => navigate(`/manga/${manga.id}/chapter/${nextChapterIndex}`, { replace: true, state: { prevDrawerOpen: drawerOpen, prevSettingsCollapseOpen: settingsCollapseOpen, }, }), ); }} >
updateDrawer(true)} size="large" > ); }