1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Add react-router

This commit is contained in:
Bill Thornton 2022-04-14 01:19:27 -04:00
parent 6534c0a596
commit b2372a96e2
16 changed files with 176 additions and 59 deletions

17
package-lock.json generated
View file

@ -10760,6 +10760,23 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
},
"react-router": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
"integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
"requires": {
"history": "^5.2.0"
}
},
"react-router-dom": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
"integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
"requires": {
"history": "^5.2.0",
"react-router": "6.3.0"
}
},
"read-file-stdin": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/read-file-stdin/-/read-file-stdin-0.2.1.tgz",

View file

@ -96,6 +96,7 @@
"pdfjs-dist": "2.12.313",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-router-dom": "6.3.0",
"resize-observer-polyfill": "1.5.1",
"screenfull": "6.0.0",
"sortablejs": "1.14.0",

View file

@ -0,0 +1,22 @@
import React, { useLayoutEffect } from 'react';
import { HistoryRouterProps, Router } from 'react-router-dom';
export function HistoryRouter({ basename, children, history }: HistoryRouterProps) {
const [state, setState] = React.useState({
action: history.action,
location: history.location
});
useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
basename={basename}
// eslint-disable-next-line react/no-children-prop
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}

50
src/components/Page.tsx Normal file
View file

@ -0,0 +1,50 @@
import React, { FunctionComponent, useEffect, useRef } from 'react';
import viewManager from './viewManager/viewManager';
type PageProps = {
title?: string
};
/**
* Page component that handles hiding active non-react views, triggering the required events for
* navigation and appRouter state updates, and setting the correct classes and data attributes.
*/
const Page: FunctionComponent<PageProps> = ({ children, title }) => {
const element = useRef<HTMLDivElement>(null);
useEffect(() => {
// hide active non-react views
viewManager.hideView();
}, []);
useEffect(() => {
const event = {
bubbles: true,
cancelable: false,
detail: {
isRestored: false
}
};
// pagebeforeshow - hides tabs on tabless pages in libraryMenu
element.current?.dispatchEvent(new CustomEvent('pagebeforeshow', event));
// viewshow - updates state of appRouter
element.current?.dispatchEvent(new CustomEvent('viewshow', event));
// pageshow - updates header/navigation in libraryMenu
element.current?.dispatchEvent(new CustomEvent('pageshow', event));
}, [ element ]);
return (
<div
ref={element}
data-role='page'
className='mainAnimatedPage page libraryPage allLibraryPage noSecondaryNavPage'
data-title={title}
data-backbutton='true'
>
{children}
</div>
);
};
export default Page;

View file

@ -122,7 +122,7 @@ class AppRouter {
isBack: action === Action.Pop
});
} else {
console.warn('[appRouter] "%s" route not found', normalizedPath, location);
console.info('[appRouter] "%s" route not found', normalizedPath, location);
}
}
@ -139,7 +139,7 @@ class AppRouter {
Events.on(apiClient, 'requestfail', this.onRequestFail);
});
ServerConnections.connect().then(result => {
return ServerConnections.connect().then(result => {
this.firstConnectionResult = result;
// Handle the initial route

View file

@ -1,42 +0,0 @@
import React, { FunctionComponent, useState } from 'react';
import SearchFields from '../search/SearchFields';
import SearchResults from '../search/SearchResults';
import SearchSuggestions from '../search/SearchSuggestions';
import LiveTVSearchResults from '../search/LiveTVSearchResults';
type SearchProps = {
serverId?: string,
parentId?: string,
collectionType?: string
};
const SearchPage: FunctionComponent<SearchProps> = ({ serverId, parentId, collectionType }: SearchProps) => {
const [ query, setQuery ] = useState<string>();
return (
<>
<SearchFields onSearch={setQuery} />
{!query &&
<SearchSuggestions
serverId={serverId || window.ApiClient.serverId()}
parentId={parentId}
/>
}
<SearchResults
serverId={serverId || window.ApiClient.serverId()}
parentId={parentId}
collectionType={collectionType}
query={query}
/>
<LiveTVSearchResults
serverId={serverId || window.ApiClient.serverId()}
parentId={parentId}
collectionType={collectionType}
query={query}
/>
</>
);
};
export default SearchPage;

View file

@ -21,8 +21,8 @@ const CARD_OPTIONS = {
type LiveTVSearchResultsProps = {
serverId?: string;
parentId?: string;
collectionType?: string;
parentId?: string | null;
collectionType?: string | null;
query?: string;
}

View file

@ -9,8 +9,8 @@ import SearchResultsRow from './SearchResultsRow';
type SearchResultsProps = {
serverId?: string;
parentId?: string;
collectionType?: string;
parentId?: string | null;
collectionType?: string | null;
query?: string;
}

View file

@ -22,7 +22,7 @@ const createSuggestionLink = ({ name, href }: { name: string, href: string }) =>
type SearchSuggestionsProps = {
serverId?: string;
parentId?: string;
parentId?: string | null;
}
const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ serverId = window.ApiClient.serverId(), parentId }: SearchSuggestionsProps) => {

View file

@ -147,6 +147,15 @@ class ViewManager {
});
}
hideView() {
if (currentView) {
dispatchViewEvent(currentView, null, 'viewbeforehide');
dispatchViewEvent(currentView, null, 'viewhide');
currentView.classList.add('hide');
currentView = null;
}
}
tryRestoreView(options, onViewChanging) {
if (options.cancel) {
return Promise.reject({ cancelled: true });

View file

@ -1,2 +0,0 @@
<div id="searchPage" data-role="page" class="page libraryPage allLibraryPage noSecondaryNavPage" data-title="${Search}" data-backbutton="true">
</div>

View file

@ -158,6 +158,7 @@
<div class="mainAnimatedPages skinBody">
<div class="splashLogo"></div>
</div>
<div id="reactRoot"></div>
<div class="mainDrawerHandle"></div>
</body>
</html>

16
src/routes/index.tsx Normal file
View file

@ -0,0 +1,16 @@
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import SearchPage from './search';
const AppRoutes = () => (
<Routes>
<Route path='/'>
<Route path='search.html' element={<SearchPage />} />
{/* Suppress warnings for unhandled routes */}
<Route path='*' element={null} />
</Route>
</Routes>
);
export default AppRoutes;

40
src/routes/search.tsx Normal file
View file

@ -0,0 +1,40 @@
import React, { FunctionComponent, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import Page from '../components/Page';
import SearchFields from '../components/search/SearchFields';
import SearchResults from '../components/search/SearchResults';
import SearchSuggestions from '../components/search/SearchSuggestions';
import LiveTVSearchResults from '../components/search/LiveTVSearchResults';
import globalize from '../scripts/globalize';
const SearchPage: FunctionComponent = () => {
const [ query, setQuery ] = useState<string>();
const [ searchParams ] = useSearchParams();
return (
<Page title={globalize.translate('Search')}>
<SearchFields onSearch={setQuery} />
{!query &&
<SearchSuggestions
serverId={searchParams.get('serverId') || window.ApiClient.serverId()}
parentId={searchParams.get('parentId')}
/>
}
<SearchResults
serverId={searchParams.get('serverId') || window.ApiClient.serverId()}
parentId={searchParams.get('parentId')}
collectionType={searchParams.get('collectionType')}
query={query}
/>
<LiveTVSearchResults
serverId={searchParams.get('serverId') || window.ApiClient.serverId()}
parentId={searchParams.get('parentId')}
collectionType={searchParams.get('collectionType')}
query={query}
/>
</Page>
);
};
export default SearchPage;

View file

@ -308,12 +308,6 @@ import { appRouter } from '../components/appRouter';
type: 'home'
});
defineRoute({
alias: '/search.html',
path: 'search.html',
pageComponent: 'SearchPage'
});
defineRoute({
alias: '/list.html',
path: 'list.html',

View file

@ -7,6 +7,8 @@ import 'classlist.js';
import 'whatwg-fetch';
import 'resize-observer-polyfill';
import '../assets/css/site.scss';
import React from 'react';
import * as ReactDOM from 'react-dom';
import { Events } from 'jellyfin-apiclient';
import ServerConnections from '../components/ServerConnections';
import globalize from './globalize';
@ -18,7 +20,7 @@ import { appHost } from '../components/apphost';
import { getPlugins } from './settings/webSettings';
import { pluginManager } from '../components/pluginManager';
import packageManager from '../components/packageManager';
import { appRouter } from '../components/appRouter';
import { appRouter, history } from '../components/appRouter';
import '../elements/emby-button/emby-button';
import './autoThemes';
import './libraryMenu';
@ -40,6 +42,8 @@ import SyncPlayHtmlVideoPlayer from '../components/syncPlay/ui/players/HtmlVideo
import SyncPlayHtmlAudioPlayer from '../components/syncPlay/ui/players/HtmlAudioPlayer';
import { currentSettings } from './settings/userSettings';
import taskButton from './taskbutton';
import { HistoryRouter } from '../components/HistoryRouter.tsx';
import AppRoutes from '../routes/index.tsx';
function loadCoreDictionary() {
const languages = ['af', 'ar', 'be-by', 'bg-bg', 'bn_bd', 'ca', 'cs', 'cy', 'da', 'de', 'el', 'en-gb', 'en-us', 'eo', 'es', 'es-419', 'es-ar', 'es_do', 'es-mx', 'et', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'gl', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'lv', 'mr', 'ms', 'nb', 'nl', 'nn', 'pl', 'pr', 'pt', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sq', 'sv', 'ta', 'th', 'tr', 'uk', 'ur_pk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw'];
@ -167,7 +171,14 @@ async function onAppReady() {
ServerConnections.currentApiClient()?.ensureWebSocket();
});
appRouter.start();
await appRouter.start();
ReactDOM.render(
<HistoryRouter history={history}>
<AppRoutes />
</HistoryRouter>,
document.getElementById('reactRoot')
);
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
import('../components/nowPlayingBar/nowPlayingBar');