mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Add RouterHistory to replace syncing for compatibility
This commit is contained in:
parent
3235e2e594
commit
1adaf00cb3
7 changed files with 84 additions and 83 deletions
|
@ -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 (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ApiProvider>
|
||||
<WebConfigProvider>
|
||||
<RootAppRouter history={history} />
|
||||
<RootAppRouter />
|
||||
</WebConfigProvider>
|
||||
</ApiProvider>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
|
|
|
@ -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 <RouterProvider router={router} />;
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
73
src/components/router/routerHistory.ts
Normal file
73
src/components/router/routerHistory.ts
Normal file
|
@ -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 */
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue