1
0
Fork 0
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:
Bill Thornton 2024-07-24 15:12:10 -04:00
parent 3235e2e594
commit 1adaf00cb3
7 changed files with 84 additions and 83 deletions

View file

@ -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} />

View file

@ -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} />;
}

View file

@ -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';

View file

@ -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).

View 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 */

View file

@ -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;
});
});
});
}

View file

@ -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';