diff --git a/src/RootApp.tsx b/src/RootApp.tsx index bf5f4f76a5..cf2a97ca26 100644 --- a/src/RootApp.tsx +++ b/src/RootApp.tsx @@ -1,4 +1,3 @@ -import { History } from '@remix-run/router'; import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import React from 'react'; @@ -9,12 +8,12 @@ import { queryClient } from 'utils/query/queryClient'; import RootAppRouter from './RootAppRouter'; -const RootApp = ({ history }: Readonly<{ history: History }>) => { +const RootApp = () => { return ( - + diff --git a/src/RootAppRouter.tsx b/src/RootAppRouter.tsx index ae683fe946..bdd7333f16 100644 --- a/src/RootAppRouter.tsx +++ b/src/RootAppRouter.tsx @@ -1,5 +1,4 @@ -import { History } from '@remix-run/router'; import React from 'react'; import { RouterProvider, @@ -13,7 +12,7 @@ import { EXPERIMENTAL_APP_ROUTES } from 'apps/experimental/routes/routes'; import { STABLE_APP_ROUTES } from 'apps/stable/routes/routes'; import AppHeader from 'components/AppHeader'; import Backdrop from 'components/Backdrop'; -import { useLegacyRouterSync } from 'hooks/useLegacyRouterSync'; +import { createRouterHistory } from 'components/router/routerHistory'; import UserThemeProvider from 'themes/UserThemeProvider'; const layoutMode = localStorage.getItem('layout'); @@ -29,9 +28,9 @@ const router = createHashRouter([ } ]); -export default function RootAppRouter({ history }: Readonly<{ history: History}>) { - useLegacyRouterSync({ router, history }); +export const history = createRouterHistory(router); +export default function RootAppRouter() { return ; } diff --git a/src/components/dialogHelper/dialogHelper.js b/src/components/dialogHelper/dialogHelper.js index b03ff9052c..8942431a66 100644 --- a/src/components/dialogHelper/dialogHelper.js +++ b/src/components/dialogHelper/dialogHelper.js @@ -1,4 +1,3 @@ -import { history } from '../router/appRouter'; import focusManager from '../focusManager'; import browser from '../../scripts/browser'; import layoutManager from '../layoutManager'; @@ -6,6 +5,8 @@ import inputManager from '../../scripts/inputManager'; import { toBoolean } from '../../utils/string.ts'; import dom from '../../scripts/dom'; +import { history } from 'RootAppRouter'; + import './dialoghelper.scss'; import '../../styles/scrollstyles.scss'; diff --git a/src/components/router/appRouter.js b/src/components/router/appRouter.js index 3b07a5571c..50ab0a6121 100644 --- a/src/components/router/appRouter.js +++ b/src/components/router/appRouter.js @@ -1,5 +1,5 @@ import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; -import { Action, createHashHistory } from 'history'; +import { Action } from 'history'; import { appHost } from '../apphost'; import { clearBackdrop, setBackdropTransparency } from '../backdrop/backdrop'; @@ -15,8 +15,7 @@ import { queryClient } from 'utils/query/queryClient'; import { getItemQuery } from 'hooks/useItem'; import { toApi } from 'utils/jellyfin-apiclient/compat'; import { ConnectionState } from 'utils/jellyfin-apiclient/ConnectionState.ts'; - -export const history = createHashHistory(); +import { history } from 'RootAppRouter'; /** * Page types of "no return" (when "Go back" should behave differently, probably quitting the application). diff --git a/src/components/router/routerHistory.ts b/src/components/router/routerHistory.ts new file mode 100644 index 0000000000..698af920e4 --- /dev/null +++ b/src/components/router/routerHistory.ts @@ -0,0 +1,73 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { Router, RouterState } from '@remix-run/router'; +import type { History, Listener, To } from 'history'; + +import Events, { type Event } from 'utils/events'; + +const HISTORY_UPDATE_EVENT = 'HISTORY_UPDATE'; + +export class RouterHistory implements History { + _router: Router; + createHref: (arg: any) => string; + + constructor(router: Router) { + this._router = router; + + this._router.subscribe(state => { + console.debug('[RouterHistory] history update', state); + Events.trigger(document, HISTORY_UPDATE_EVENT, [ state ]); + }); + + this.createHref = router.createHref; + } + + get action() { + return this._router.state.historyAction; + } + + get location() { + return this._router.state.location; + } + + back() { + void this._router.navigate(-1); + } + + forward() { + void this._router.navigate(1); + } + + go(delta: number) { + void this._router.navigate(delta); + } + + push(to: To, state?: any) { + void this._router.navigate(to, { state }); + } + + replace(to: To, state?: any): void { + void this._router.navigate(to, { state, replace: true }); + } + + block() { + // NOTE: We don't seem to use this functionality, so leaving it unimplemented. + throw new Error('`history.block()` is not implemented'); + return () => undefined; + } + + listen(listener: Listener) { + const compatListener = (_e: Event, state: RouterState) => { + return listener({ action: state.historyAction, location: state.location }); + }; + + Events.on(document, HISTORY_UPDATE_EVENT, compatListener); + + return () => Events.off(document, HISTORY_UPDATE_EVENT, compatListener); + } +} + +export const createRouterHistory = (router: Router): History => { + return new RouterHistory(router); +}; + +/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/src/hooks/useLegacyRouterSync.ts b/src/hooks/useLegacyRouterSync.ts deleted file mode 100644 index c1af8fa310..0000000000 --- a/src/hooks/useLegacyRouterSync.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Update } from 'history'; -import { useLayoutEffect, useState } from 'react'; -import type { History, Router } from '@remix-run/router'; - -interface UseLegacyRouterSyncProps { - router: Router; - history: History; -} -export function useLegacyRouterSync({ router, history }: UseLegacyRouterSyncProps) { - const [routerLocation, setRouterLocation] = useState(router.state.location); - - useLayoutEffect(() => { - const onHistoryChange = async (update: Update) => { - const isSynced = router.createHref(router.state.location) === router.createHref(update.location); - - /** - * Some legacy codepaths may still use the `#!` routing scheme which is unsupported with the React routing - * implementation, so we need to remove the leading `!` from the pathname. React Router already removes the - * hash for us. - */ - if (update.location.pathname.startsWith('/!/')) { - history.replace( - { ...update.location, pathname: update.location.pathname.replace(/^\/!/, '') }, - update.location.state); - } else if (update.location.pathname.startsWith('/!')) { - history.replace( - { ...update.location, pathname: update.location.pathname.replace(/^\/!/, '/') }, - update.location.state); - } else if (update.location.pathname.startsWith('!')) { - history.replace( - { ...update.location, pathname: update.location.pathname.replace(/^!/, '') }, - update.location.state); - } else if (!isSynced) { - await router.navigate(update.location, { replace: true }); - } - }; - - const unlisten = history.listen(onHistoryChange); - - return () => { - unlisten(); - }; - }, [history, router]); - - /** - * Because the router subscription needs to be in a zero-dependencies effect, syncing changes to the router back to - * the legacy history API needs to be in a separate effect. This should run any time the router location changes. - */ - useLayoutEffect(() => { - const isSynced = router.createHref(routerLocation) === router.createHref(history.location); - if (!isSynced) { - history.replace(routerLocation); - } - }, [history, router, routerLocation]); - - /** - * We want to use an effect with no dependencies here when we set up the router subscription to ensure that we only - * subscribe to the router state once. The router doesn't provide a way to remove subscribers, so we need to be - * careful to not create multiple subscribers. - */ - useLayoutEffect(() => { - router.subscribe((newState) => { - setRouterLocation((prevLocation) => { - if (newState.location !== prevLocation) { - return newState.location; - } - return prevLocation; - }); - }); - }); -} diff --git a/src/index.jsx b/src/index.jsx index f0d72d43ca..693e4793a6 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -22,7 +22,7 @@ import { getPlugins } from './scripts/settings/webSettings'; import { pluginManager } from './components/pluginManager'; import packageManager from './components/packageManager'; import './components/playback/displayMirrorManager.ts'; -import { appRouter, history } from './components/router/appRouter'; +import { appRouter } from './components/router/appRouter'; import './elements/emby-button/emby-button'; import './scripts/autoThemes'; import './components/themeMediaPlayer'; @@ -39,6 +39,7 @@ import './legacy/vendorStyles'; import { currentSettings } from './scripts/settings/userSettings'; import taskButton from './scripts/taskbutton'; import RootApp from './RootApp.tsx'; +import { history } from 'RootAppRouter'; import './styles/livetv.scss'; import './styles/dashboard.scss';