diff --git a/src/App.tsx b/src/App.tsx index 9b98120d..36b1a176 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -49,7 +49,7 @@ const App: React.FC = () => ( > {/* General Routes */} - } /> + } /> } /> } /> diff --git a/src/components/navbar/DefaultNavBar.tsx b/src/components/navbar/DefaultNavBar.tsx index 11f9cf4b..d04acab3 100644 --- a/src/components/navbar/DefaultNavBar.tsx +++ b/src/components/navbar/DefaultNavBar.tsx @@ -24,13 +24,14 @@ import GetAppIcon from '@mui/icons-material/GetApp'; import GetAppOutlinedIcon from '@mui/icons-material/GetAppOutlined'; import SettingsIcon from '@mui/icons-material/Settings'; import ArrowBack from '@mui/icons-material/ArrowBack'; -import { Link, useLocation, useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { createPortal } from 'react-dom'; import { NavbarItem } from '@/typings'; import NavBarContext from '@/components/context/NavbarContext'; import ExtensionOutlinedIcon from '@/components/util/CustomExtensionOutlinedIcon'; import DesktopSideBar from '@/components/navbar/navigation/DesktopSideBar'; import MobileBottomBar from '@/components/navbar/navigation/MobileBottomBar'; +import { useHistory } from '@/util/useHistory'; const navbarItems: Array = [ { @@ -89,7 +90,8 @@ export default function DefaultNavBar() { const theme = useTheme(); const navigate = useNavigate(); - const { pathname } = useLocation(); + const { pathname, ...location } = useLocation(); + const history = useHistory(); const isMobileWidth = useMediaQuery(theme.breakpoints.down('sm')); const isMainRoute = navbarItems.some(({ path }) => path === pathname); @@ -107,7 +109,14 @@ export default function DefaultNavBar() { } const handleBack = () => { - if (backToUrl != null) return; + const isLastPageInHistory = location.key === 'default'; + const wasPreviousPageReader = history[history.length - 2]?.match(/\/manga\/[0-9]+\/chapter\/[0-9]+.*/g); + + if (isLastPageInHistory || wasPreviousPageReader) { + navigate(backToUrl ?? ''); + return; + } + navigate(-1); }; @@ -117,8 +126,7 @@ export default function DefaultNavBar() { {!isMainRoute && ( { + 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); diff --git a/src/screens/Manga.tsx b/src/screens/Manga.tsx index 9339d68d..3660c23c 100644 --- a/src/screens/Manga.tsx +++ b/src/screens/Manga.tsx @@ -12,7 +12,7 @@ import React, { useContext, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import requestManager from '@/lib/RequestManager'; -import NavbarContext from '@/components/context/NavbarContext'; +import NavbarContext, { useSetDefaultBackTo } from '@/components/context/NavbarContext'; import ChapterList from '@/components/manga/ChapterList'; import { useRefreshManga } from '@/components/manga/hooks'; import MangaDetails from '@/components/manga/MangaDetails'; @@ -32,6 +32,7 @@ const Manga: React.FC = () => { const { data: manga, error, isLoading, isValidating, mutate } = requestManager.useGetManga(id); const [refresh, { loading: refreshing }] = useRefreshManga(id); + useSetDefaultBackTo('library'); useEffect(() => { // Automatically fetch manga from source if data is older then 24 hours diff --git a/src/screens/settings/About.tsx b/src/screens/settings/About.tsx index 81c0ec7c..c927e787 100644 --- a/src/screens/settings/About.tsx +++ b/src/screens/settings/About.tsx @@ -13,7 +13,7 @@ import ListItemText from '@mui/material/ListItemText'; import { useTranslation } from 'react-i18next'; import requestManager from '@/lib/RequestManager'; import ListItemLink from '@/components/util/ListItemLink'; -import NavbarContext from '@/components/context/NavbarContext'; +import NavbarContext, { useSetDefaultBackTo } from '@/components/context/NavbarContext'; import LoadingPlaceholder from '@/components/util/LoadingPlaceholder'; export default function About() { @@ -27,6 +27,8 @@ export default function About() { const { data: about } = requestManager.useGetAbout(); + useSetDefaultBackTo('settings'); + if (about === undefined) { return ; } diff --git a/src/screens/settings/Backup.tsx b/src/screens/settings/Backup.tsx index fc839eb4..0c4bb205 100644 --- a/src/screens/settings/Backup.tsx +++ b/src/screens/settings/Backup.tsx @@ -15,7 +15,7 @@ import { ListItemButton } from '@mui/material'; import requestManager from '@/lib/RequestManager'; import makeToast from '@/components/util/Toast'; import ListItemLink from '@/components/util/ListItemLink'; -import NavbarContext from '@/components/context/NavbarContext'; +import NavbarContext, { useSetDefaultBackTo } from '@/components/context/NavbarContext'; export default function Backup() { const { t } = useTranslation(); @@ -25,6 +25,8 @@ export default function Backup() { setAction(null); }, [t]); + useSetDefaultBackTo('settings'); + const submitBackup = (file: File) => { if (file.name.toLowerCase().endsWith('proto.gz')) { makeToast(t('settings.backup.label.restoring_backup'), 'info'); diff --git a/src/screens/settings/Categories.tsx b/src/screens/settings/Categories.tsx index ef8e1c65..edf3c81d 100644 --- a/src/screens/settings/Categories.tsx +++ b/src/screens/settings/Categories.tsx @@ -28,7 +28,7 @@ import { ICategory } from '@/typings'; import requestManager from '@/lib/RequestManager'; import StrictModeDroppable from '@/lib/StrictModeDroppable'; import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab'; -import NavbarContext from '@/components/context/NavbarContext'; +import NavbarContext, { useSetDefaultBackTo } from '@/components/context/NavbarContext'; const getItemStyle = ( isDragging: boolean, @@ -67,6 +67,8 @@ export default function Categories() { const [dialogDefault, setDialogDefault] = useState(false); const theme = useTheme(); + useSetDefaultBackTo('settings'); + const categoryReorder = (list: ICategory[], from: number, to: number) => { const newData = [...list]; const [removed] = newData.splice(from, 1); diff --git a/src/screens/settings/DefaultReaderSettings.tsx b/src/screens/settings/DefaultReaderSettings.tsx index 08271991..e54238d5 100644 --- a/src/screens/settings/DefaultReaderSettings.tsx +++ b/src/screens/settings/DefaultReaderSettings.tsx @@ -19,7 +19,7 @@ import { } from '@/util/readerSettings'; import ReaderSettingsOptions from '@/components/reader/ReaderSettingsOptions'; import makeToast from '@/components/util/Toast'; -import NavbarContext from '@/components/context/NavbarContext'; +import NavbarContext, { useSetDefaultBackTo } from '@/components/context/NavbarContext'; export default function DefaultReaderSettings() { const { t } = useTranslation(); @@ -31,6 +31,8 @@ export default function DefaultReaderSettings() { const { metadata, settings, loading } = useDefaultReaderSettings(); + useSetDefaultBackTo('settings'); + const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => { requestUpdateServerMetadata(metadata ?? {}, [[key, value]]).catch(() => makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'), diff --git a/src/screens/settings/LibrarySettings.tsx b/src/screens/settings/LibrarySettings.tsx index 38186e02..de5c3fa0 100644 --- a/src/screens/settings/LibrarySettings.tsx +++ b/src/screens/settings/LibrarySettings.tsx @@ -24,7 +24,7 @@ import { ICategory, IncludeInGlobalUpdate } from '@/typings'; import requestManager from '@/lib/RequestManager'; import makeToast from '@/components/util/Toast'; import ThreeStateCheckboxInput from '@/components/atoms/ThreeStateCheckboxInput'; -import NavbarContext from '@/components/context/NavbarContext'; +import NavbarContext, { useSetDefaultBackTo } from '@/components/context/NavbarContext'; import SearchSettings from '@/screens/settings/SearchSettings'; const CategoriesDiv = styled('div')({ @@ -78,6 +78,8 @@ export default function LibrarySettings() { setAction(null); }, [t]); + useSetDefaultBackTo('settings'); + const { data: categories = [], error: requestError, mutate } = requestManager.useGetCategories(); const [dialogCategories, setDialogCategories] = useState(categories); const [isDialogOpen, setIsDialogOpen] = useState(false); diff --git a/src/screens/settings/SearchSettings.tsx b/src/screens/settings/SearchSettings.tsx index 487726d7..c6bad8b9 100644 --- a/src/screens/settings/SearchSettings.tsx +++ b/src/screens/settings/SearchSettings.tsx @@ -16,11 +16,14 @@ import { SearchMetadataKeys } from '@/typings'; import { requestUpdateServerMetadata } from '@/util/metadata'; import { useSearchSettings } from '@/util/searchSettings'; import makeToast from '@/components/util/Toast'; +import { useSetDefaultBackTo } from '@/components/context/NavbarContext'; export default function SearchSettings() { const { t } = useTranslation(); const { metadata, settings } = useSearchSettings(); + useSetDefaultBackTo('settings'); + const setSettingValue = (key: SearchMetadataKeys, value: boolean) => { requestUpdateServerMetadata(metadata ?? {}, [[key, value]]).catch(() => makeToast(t('search.error.label.failed_to_save_settings'), 'warning'), diff --git a/src/util/useHistory.ts b/src/util/useHistory.ts new file mode 100644 index 00000000..55307465 --- /dev/null +++ b/src/util/useHistory.ts @@ -0,0 +1,42 @@ +/* + * 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 { useEffect, useState } from 'react'; +import { NavigationType, useLocation, useNavigationType } from 'react-router-dom'; + +// eslint-disable-next-line import/prefer-default-export +export const useHistory = () => { + const location = useLocation(); + const navigationType = useNavigationType(); + + const [history, setHistory] = useState([location.pathname]); + + useEffect(() => { + const isLastPageInHistory = location.key === 'default'; + const ignoreInitialPop = isLastPageInHistory && history.length === 1; + if (ignoreInitialPop) { + return; + } + + switch (navigationType) { + case NavigationType.Pop: + setHistory([...history.slice(0, -1)]); + break; + case NavigationType.Push: + setHistory([...history, location.pathname]); + break; + case NavigationType.Replace: + setHistory([...history.slice(0, -1), location.pathname]); + break; + default: + throw new Error(`Unexpected NavigationType "${navigationType}"`); + } + }, [location]); + + return history; +};