Fix navbar back button behavior (#195)

* Enable passing backLink through location state. Explicitly handle cases for BACK

* Allow pages to override default backLink. Fix manga page back link

* Cleanup
This commit is contained in:
Valter Martinek
2022-11-21 01:33:45 +01:00
committed by GitHub
parent 68f67273aa
commit 3972d7757f
11 changed files with 94 additions and 31 deletions

View File

@@ -15,6 +15,7 @@ import useLocalStorage from 'util/useLocalStorage';
import SpinnerImage from 'components/util/SpinnerImage'; import SpinnerImage from 'components/util/SpinnerImage';
import { Box, styled } from '@mui/system'; import { Box, styled } from '@mui/system';
import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext'; import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext';
import { BACK } from 'util/useBackTo';
const BottomGradient = styled('div')({ const BottomGradient = styled('div')({
position: 'absolute', position: 'absolute',
@@ -88,12 +89,14 @@ const MangaCard = React.forwardRef<HTMLDivElement, IProps>((props: IProps, ref)
const [useCache] = useLocalStorage<boolean>('useCache', true); const [useCache] = useLocalStorage<boolean>('useCache', true);
const [ItemWidth] = useLocalStorage<number>('ItemWidth', 300); const [ItemWidth] = useLocalStorage<number>('ItemWidth', 300);
const mangaLinkTo = { pathname: `/manga/${id}/`, state: { backLink: BACK } };
if (gridLayout !== 2) { if (gridLayout !== 2) {
const colomns = Math.round(dimensions / ItemWidth); const colomns = Math.round(dimensions / ItemWidth);
return ( return (
// @ts-ignore gridsize type isnt allowed to be a decimal but it works fine // @ts-ignore gridsize type isnt allowed to be a decimal but it works fine
<Grid item xs={12 / colomns} sm={12 / colomns} md={12 / colomns} lg={12 / colomns}> <Grid item xs={12 / colomns} sm={12 / colomns} md={12 / colomns} lg={12 / colomns}>
<Link to={`/manga/${id}/`} style={(gridLayout === 1) ? { textDecoration: 'none' } : {}}> <Link to={mangaLinkTo} style={(gridLayout === 1) ? { textDecoration: 'none' } : {}}>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@@ -190,7 +193,7 @@ const MangaCard = React.forwardRef<HTMLDivElement, IProps>((props: IProps, ref)
} }
return ( return (
<Grid item xs={12} sm={12} md={12} lg={12}> <Grid item xs={12} sm={12} md={12} lg={12}>
<Link to={`/manga/${id}/`} style={{ textDecoration: 'none', color: 'unset' }}> <Link to={mangaLinkTo} style={{ textDecoration: 'none', color: 'unset' }}>
<CardContent sx={{ <CardContent sx={{
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',

View File

@@ -5,9 +5,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import React from 'react'; import React, { useContext, useEffect } from 'react';
type ContextType = { type ContextType = {
// Default back button url
defaultBackTo: string | undefined
setDefaultBackTo: React.Dispatch<React.SetStateAction<string | undefined>>
// AppBar title // AppBar title
title: string title: string
setTitle: React.Dispatch<React.SetStateAction<string>> setTitle: React.Dispatch<React.SetStateAction<string>>
@@ -22,6 +26,8 @@ type ContextType = {
}; };
const NavBarContext = React.createContext<ContextType>({ const NavBarContext = React.createContext<ContextType>({
defaultBackTo: undefined,
setDefaultBackTo: ():void => {},
title: 'Tachidesk', title: 'Tachidesk',
setTitle: ():void => {}, setTitle: ():void => {},
action: <div />, action: <div />,
@@ -31,3 +37,14 @@ const NavBarContext = React.createContext<ContextType>({
}); });
export default NavBarContext; export default NavBarContext;
export const useNavBarContext = () => useContext(NavBarContext);
export const useSetDefaultBackTo = (value: string) => {
const { setDefaultBackTo } = useNavBarContext();
useEffect(() => {
setDefaultBackTo(value);
return () => setDefaultBackTo(undefined);
}, [value]);
};

View File

@@ -29,6 +29,7 @@ import Typography from '@mui/material/Typography';
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import client from 'util/client'; import client from 'util/client';
import { BACK } from 'util/useBackTo';
interface IProps{ interface IProps{
chapter: IChapter chapter: IChapter
@@ -118,7 +119,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
}} }}
> >
<Link <Link
to={`/manga/${chapter.mangaId}/chapter/${chapter.index}`} to={{ pathname: `/manga/${chapter.mangaId}/chapter/${chapter.index}`, state: { backLink: BACK } }}
style={{ style={{
textDecoration: 'none', textDecoration: 'none',
color: theme.palette.text[chapter.read ? 'disabled' : 'primary'], color: theme.palette.text[chapter.read ? 'disabled' : 'primary'],

View File

@@ -9,6 +9,7 @@ import React from 'react';
import { Fab } from '@mui/material'; import { Fab } from '@mui/material';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { PlayArrow } from '@mui/icons-material'; import { PlayArrow } from '@mui/icons-material';
import { BACK } from 'util/useBackTo';
interface ResumeFABProps{ interface ResumeFABProps{
chapter: IChapter chapter: IChapter
@@ -23,7 +24,7 @@ export default function ResumeFab(props: ResumeFABProps) {
component={Link} component={Link}
variant="extended" variant="extended"
color="primary" color="primary"
to={`/manga/${mangaId}/chapter/${index}/page/${lastPageRead}`} to={{ pathname: `/manga/${mangaId}/chapter/${index}/page/${lastPageRead}`, state: { backLink: BACK } }}
> >
<PlayArrow /> <PlayArrow />
{index === 1 ? 'Start' : 'Resume' } {index === 1 ? 'Start' : 'Resume' }

View File

@@ -23,12 +23,13 @@ import GetAppIcon from '@mui/icons-material/GetApp';
import GetAppOutlinedIcon from '@mui/icons-material/GetAppOutlined'; import GetAppOutlinedIcon from '@mui/icons-material/GetAppOutlined';
import SettingsIcon from '@mui/icons-material/Settings'; import SettingsIcon from '@mui/icons-material/Settings';
import ArrowBack from '@mui/icons-material/ArrowBack'; import ArrowBack from '@mui/icons-material/ArrowBack';
import { useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import NavBarContext from 'components/context/NavbarContext'; import NavBarContext from 'components/context/NavbarContext';
import DarkTheme from 'components/context/DarkTheme'; import DarkTheme from 'components/context/DarkTheme';
import ExtensionOutlinedIcon from 'components/util/CustomExtensionOutlinedIcon'; import ExtensionOutlinedIcon from 'components/util/CustomExtensionOutlinedIcon';
import { Box } from '@mui/system'; import { Box } from '@mui/system';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import useBackTo from 'util/useBackTo';
import DesktopSideBar from './navigation/DesktopSideBar'; import DesktopSideBar from './navigation/DesktopSideBar';
import MobileBottomBar from './navigation/MobileBottomBar'; import MobileBottomBar from './navigation/MobileBottomBar';
@@ -80,6 +81,7 @@ const navbarItems: Array<NavbarItem> = [
export default function DefaultNavBar() { export default function DefaultNavBar() {
const { title, action, override } = useContext(NavBarContext); const { title, action, override } = useContext(NavBarContext);
const backTo = useBackTo();
const { darkTheme } = useContext(DarkTheme); const { darkTheme } = useContext(DarkTheme);
const theme = useTheme(); const theme = useTheme();
@@ -100,28 +102,30 @@ export default function DefaultNavBar() {
navbar = <DesktopSideBar navBarItems={navbarItems.filter((it) => it.show !== 'mobile')} />; navbar = <DesktopSideBar navBarItems={navbarItems.filter((it) => it.show !== 'mobile')} />;
} }
const handleBack = () => {
if (backTo.url != null) return;
history.goBack();
};
return ( return (
<Box sx={{ flexGrow: 1 }}> <Box sx={{ flexGrow: 1 }}>
<AppBar position="fixed" color={darkTheme ? 'default' : 'primary'}> <AppBar position="fixed" color={darkTheme ? 'default' : 'primary'}>
<Toolbar> <Toolbar>
{ {!isMainRoute && (
!navbarItems.some(({ path }) => path === history.location.pathname) <IconButton
&& ( component={backTo.url ? Link : 'button'}
<IconButton to={backTo.url}
edge="start" edge="start"
sx={{ marginRight: theme.spacing(2) }} sx={{ marginRight: theme.spacing(2) }}
color="inherit" color="inherit"
aria-label="menu" aria-label="menu"
disableRipple disableRipple
// when page is opened in new tab backbutton will size="large"
// take you to the library onClick={handleBack}
onClick={() => (history.length === 1 ? history.push('/library') : history.goBack())} >
size="large" <ArrowBack />
> </IconButton>
<ArrowBack /> )}
</IconButton>
)
}
<Typography variant={isMobileWidth ? 'h6' : 'h5'} sx={{ flexGrow: 1 }} noWrap textOverflow="ellipsis"> <Typography variant={isMobileWidth ? 'h6' : 'h5'} sx={{ flexGrow: 1 }} noWrap textOverflow="ellipsis">
{title} {title}
</Typography> </Typography>

View File

@@ -13,6 +13,7 @@ interface IProps{
} }
export default function NavBarProvider({ children }:IProps) { export default function NavBarProvider({ children }:IProps) {
const [defaultBackTo, setDefaultBackTo] = useState<string | undefined>();
const [title, setTitle] = useState<string>('Tachidesk'); const [title, setTitle] = useState<string>('Tachidesk');
const [action, setAction] = useState<any>(<div />); const [action, setAction] = useState<any>(<div />);
const [override, setOverride] = useState<INavbarOverride>({ const [override, setOverride] = useState<INavbarOverride>({
@@ -21,6 +22,8 @@ export default function NavBarProvider({ children }:IProps) {
}); });
const value = { const value = {
defaultBackTo,
setDefaultBackTo,
title, title,
setTitle, setTitle,
action, action,

View File

@@ -27,6 +27,7 @@ import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
import Collapse from '@mui/material/Collapse'; import Collapse from '@mui/material/Collapse';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import { styled } from '@mui/system'; import { styled } from '@mui/system';
import useBackTo from 'util/useBackTo';
const Root = styled('div')(({ theme }) => ({ const Root = styled('div')(({ theme }) => ({
top: 0, top: 0,
@@ -129,6 +130,7 @@ interface IProps {
export default function ReaderNavBar(props: IProps) { export default function ReaderNavBar(props: IProps) {
const history = useHistory(); const history = useHistory();
const backTo = useBackTo();
const { const {
settings, setSettings, manga, chapter, curPage, settings, setSettings, manga, chapter, curPage,
@@ -167,6 +169,12 @@ export default function ReaderNavBar(props: IProps) {
}; };
}, [handleScroll]);// handleScroll changes on every render }, [handleScroll]);// handleScroll changes on every render
const handleClose = () => {
if (backTo.back) history.goBack();
else if (backTo.url) history.push(backTo.url);
else history.push(`/manga/${manga.id}`);
};
return ( return (
<> <>
<Slide <Slide
@@ -203,7 +211,7 @@ export default function ReaderNavBar(props: IProps) {
color="inherit" color="inherit"
aria-label="menu" aria-label="menu"
disableRipple disableRipple
onClick={() => history.push('..')} onClick={handleClose}
size="large" size="large"
sx={{ mr: -1 }} sx={{ mr: -1 }}
> >
@@ -315,7 +323,7 @@ export default function ReaderNavBar(props: IProps) {
&& ( && (
<Link <Link
replace replace
to={`/manga/${manga.id}/chapter/${chapter.index - 1}`} to={{ pathname: `/manga/${manga.id}/chapter/${chapter.index - 1}`, state: history.location.state }}
> >
<Button <Button
variant="outlined" variant="outlined"
@@ -331,7 +339,7 @@ export default function ReaderNavBar(props: IProps) {
<Link <Link
replace replace
style={{ gridArea: 'next' }} style={{ gridArea: 'next' }}
to={`/manga/${manga.id}/chapter/${chapter.index + 1}`} to={{ pathname: `/manga/${manga.id}/chapter/${chapter.index + 1}`, state: history.location.state }}
> >
<Button <Button
variant="outlined" variant="outlined"

View File

@@ -10,7 +10,7 @@ import {
CircularProgress, IconButton, Stack, Tooltip, CircularProgress, IconButton, Stack, Tooltip,
} from '@mui/material'; } from '@mui/material';
import { Box } from '@mui/system'; import { Box } from '@mui/system';
import NavbarContext from 'components/context/NavbarContext'; import NavbarContext, { useSetDefaultBackTo } from 'components/context/NavbarContext';
import ChapterList from 'components/manga/ChapterList'; import ChapterList from 'components/manga/ChapterList';
import { useRefreshManga } from 'components/manga/hooks'; import { useRefreshManga } from 'components/manga/hooks';
import MangaDetails from 'components/manga/MangaDetails'; import MangaDetails from 'components/manga/MangaDetails';
@@ -37,6 +37,8 @@ const Manga: React.FC = () => {
const [refresh, { loading: refreshing }] = useRefreshManga(id); const [refresh, { loading: refreshing }] = useRefreshManga(id);
useSetDefaultBackTo(manga?.inLibrary === false && manga.sourceId != null ? `/sources/${manga.sourceId}/popular` : '/library');
useEffect(() => { useEffect(() => {
// Automatically fetch manga from source if data is older then 24 hours // Automatically fetch manga from source if data is older then 24 hours
// Automatic fetch is done only once, to prevent issues when server does // Automatic fetch is done only once, to prevent issues when server does

View File

@@ -157,13 +157,13 @@ export default function Reader() {
formData.append('read', 'true'); formData.append('read', 'true');
client.patch(`/api/v1/manga/${manga.id}/chapter/${chapter.index}`, formData); client.patch(`/api/v1/manga/${manga.id}/chapter/${chapter.index}`, formData);
history.replace(`/manga/${manga.id}/chapter/${chapter.index + 1}`); history.replace({ pathname: `/manga/${manga.id}/chapter/${chapter.index + 1}`, state: history.location.state });
} }
}; };
const prevChapter = () => { const prevChapter = () => {
if (chapter.index > 1) { if (chapter.index > 1) {
history.replace(`/manga/${manga.id}/chapter/${chapter.index - 1}`); history.replace({ pathname: `/manga/${manga.id}/chapter/${chapter.index - 1}`, state: history.location.state });
} }
}; };

View File

@@ -181,7 +181,7 @@ export default function Updates() {
transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
}, },
}} }}
onClick={() => history.push(`/manga/${chapter.mangaId}/chapter/${chapter.index}`)} onClick={() => history.push({ pathname: `/manga/${chapter.mangaId}/chapter/${chapter.index}`, state: history.location.state })}
> >
<CardContent sx={{ <CardContent sx={{
display: 'flex', display: 'flex',

24
src/util/useBackTo.ts Normal file
View File

@@ -0,0 +1,24 @@
/*
* 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 { useNavBarContext } from 'components/context/NavbarContext';
import { useLocation } from 'react-router-dom';
export const BACK = '__BACK__';
const useBackTo = (): { url?: string, back: boolean } => {
const location = useLocation<{ backLink?: string }>();
const { defaultBackTo } = useNavBarContext();
const url = location.state?.backLink ?? defaultBackTo;
return {
url: url === BACK ? undefined : url,
back: url === BACK,
};
};
export default useBackTo;