From 19e45424ae848370694100538a48889d24fb783c Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sat, 20 Sep 2025 19:44:27 +0200 Subject: [PATCH] Make react router routing possible outside components --- src/App.tsx | 14 ++++++++- .../reader/services/ReaderControls.ts | 6 ++-- src/features/reader/services/ReaderService.ts | 15 ++++------ src/lib/react-router/ReactRouter.ts | 29 +++++++++++++++++++ 4 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 src/lib/react-router/ReactRouter.ts diff --git a/src/App.tsx b/src/App.tsx index eec219e5..8ef58e81 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,7 @@ import CssBaseline from '@mui/material/CssBaseline'; import React, { useEffect, useLayoutEffect } from 'react'; -import { Navigate, Outlet, Route, Routes, useLocation } from 'react-router-dom'; +import { Navigate, Outlet, Route, Routes, useLocation, useNavigate } from 'react-router-dom'; import { loadErrorMessages, loadDevMessages } from '@apollo/client/dev'; import { loadable } from 'react-lazily/loadable'; import Box from '@mui/material/Box'; @@ -32,6 +32,7 @@ import { LoginPage } from '@/features/authentication/screens/LoginPage.tsx'; import { AuthGuard } from '@/features/authentication/components/AuthGuard.tsx'; import { SearchParam } from '@/base/Base.types.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; +import { ReactRouter } from '@/lib/react-router/ReactRouter.ts'; const { Browse } = loadable(() => import('@/features/browse/screens/Browse.tsx'), lazyLoadFallback); const { DownloadQueue } = loadable(() => import('@/features/downloads/screens/DownloadQueue.tsx'), lazyLoadFallback); @@ -143,6 +144,16 @@ const BackgroundSubscriptions = () => { return null; }; +const ReactRouterSetter = () => { + const navigate = useNavigate(); + + useEffect(() => { + ReactRouter.setNavigateFn(navigate); + }, []); + + return null; +}; + const PrivateRoutes = () => { const { isAuthRequired, accessToken, refreshToken } = useSessionContext(); @@ -291,6 +302,7 @@ export const App: React.FC = () => ( + diff --git a/src/features/reader/services/ReaderControls.ts b/src/features/reader/services/ReaderControls.ts index 3587cb37..ecb09fd6 100644 --- a/src/features/reader/services/ReaderControls.ts +++ b/src/features/reader/services/ReaderControls.ts @@ -166,8 +166,6 @@ export class ReaderControls { ) => void { const { t } = useTranslation(); - const openChapter = ReaderService.useNavigateToChapter(); - return useCallback( (offset, doTransitionCheck = true, scrollIntoView = true) => { const { @@ -250,7 +248,7 @@ export class ReaderControls { ); } - openChapter(chapterToOpen, { + ReaderService.navigateToChapter(chapterToOpen, { resumeMode: getReaderOpenChapterResumeMode( isSpecificChapterMode || !keepRenderedChapters, isPreviousChapter, @@ -264,7 +262,7 @@ export class ReaderControls { doOpenChapter().catch(defaultPromiseErrorHandler('ReaderControls#useOpenChapter')); }, - [t, openChapter], + [t], ); } diff --git a/src/features/reader/services/ReaderService.ts b/src/features/reader/services/ReaderService.ts index ac33fcc1..fa3c8ffb 100644 --- a/src/features/reader/services/ReaderService.ts +++ b/src/features/reader/services/ReaderService.ts @@ -47,6 +47,7 @@ import { FALLBACK_MANGA } from '@/features/manga/Manga.constants.ts'; import { getMetadataKey } from '@/features/metadata/Metadata.utils.ts'; import { DirectionOffset } from '@/base/Base.types.ts'; import { getReaderStore, useReaderStore } from '@/features/reader/stores/ReaderStore.ts'; +import { ReactRouter } from '@/lib/react-router/ReactRouter.ts'; const DIRECTION_TO_INVERTED: Record = { ltr: 'rtl', @@ -71,15 +72,11 @@ export class ReaderService { return ReaderService.chapterUpdateQueues.get(id)!; } - static useNavigateToChapter(): (chapter: TChapterReader, state?: ReaderOpenChapterLocationState) => void { - const navigate = useNavigate(); - - return useCallback((chapter, state) => { - navigate(Chapters.getReaderUrl(chapter), { - replace: true, - state, - }); - }, []); + static navigateToChapter(chapter: TChapterReader, state?: ReaderOpenChapterLocationState): void { + ReactRouter.navigate(Chapters.getReaderUrl(chapter), { + replace: true, + state, + }); } static downloadAhead( diff --git a/src/lib/react-router/ReactRouter.ts b/src/lib/react-router/ReactRouter.ts new file mode 100644 index 00000000..c8f8f3f6 --- /dev/null +++ b/src/lib/react-router/ReactRouter.ts @@ -0,0 +1,29 @@ +/* + * 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 { NavigateFunction, NavigateOptions, To } from 'react-router-dom'; +import { noOp } from '@/lib/HelperFunctions.ts'; + +export class ReactRouter { + private static navigateFn: NavigateFunction = noOp; + + static setNavigateFn(navigate: NavigateFunction) { + this.navigateFn = navigate; + } + + static navigate(delta: number): void; + static navigate(to: To, options?: NavigateOptions): void; + static navigate(toOrDelta: number | To, options?: NavigateOptions): void { + if (typeof toOrDelta === 'number') { + this.navigateFn(toOrDelta); + return; + } + + this.navigateFn(toOrDelta, options); + } +}