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

Add api and user contexts

This commit is contained in:
Bill Thornton 2022-10-28 01:09:59 -04:00
parent f20ceb4274
commit d297f23932
8 changed files with 124 additions and 22 deletions

64
src/App.tsx Normal file
View file

@ -0,0 +1,64 @@
import { Api } from '@jellyfin/sdk';
import { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
import { History } from '@remix-run/router';
import { ApiClient, ConnectionManager } from 'jellyfin-apiclient';
import React, { useEffect, useState } from 'react';
import { HistoryRouter } from './components/HistoryRouter';
import ServerConnections from './components/ServerConnections';
import { ApiContext } from './hooks/useApi';
import { UserContext } from './hooks/useUser';
import AppRoutes from './routes/index';
import events from './utils/events';
import { toApi } from './utils/sdk';
interface ServerConnections extends ConnectionManager {
currentApiClient: () => ApiClient
}
const App = ({ history, connections }: { history: History, connections: ServerConnections }) => {
const [ api, setApi ] = useState<Api | undefined>(toApi(connections.currentApiClient()));
const [ user, setUser ] = useState<UserDto | undefined>();
useEffect(() => {
connections.currentApiClient()
.getCurrentUser()
.then(newUser => setUser(newUser))
.catch(err => {
console.warn('[App] Could not get current user', err);
});
const udpateApiUser = (_e: any, newUser: UserDto) => {
setUser(newUser);
if (newUser.ServerId) {
setApi(toApi(connections.getApiClient(newUser.ServerId)));
}
};
const resetApiUser = () => {
setApi(undefined);
setUser(undefined);
};
events.on(connections, 'localusersignedin', udpateApiUser);
events.on(connections, 'localusersignedout', resetApiUser);
return () => {
events.off(connections, 'localusersignedin', udpateApiUser);
events.off(connections, 'localusersignedout', resetApiUser);
};
}, [ connections ]);
return (
<ApiContext.Provider value={api}>
<UserContext.Provider value={user}>
<HistoryRouter history={history}>
<AppRoutes />
</HistoryRouter>
</UserContext.Provider>
</ApiContext.Provider>
);
};
export default App;

1
src/apiclient.d.ts vendored
View file

@ -117,6 +117,7 @@ declare module 'jellyfin-apiclient' {
getCountries(): Promise<CountryInfo[]>; getCountries(): Promise<CountryInfo[]>;
getCriticReviews(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>; getCriticReviews(itemId: string, options?: any): Promise<BaseItemDtoQueryResult>;
getCultures(): Promise<CultureDto[]>; getCultures(): Promise<CultureDto[]>;
getCurrentUser(cache?: boolean): Promise<UserDto>;
getCurrentUserId(): string; getCurrentUserId(): string;
getDateParamValue(date: Date): string; getDateParamValue(date: Date): string;
getDefaultImageQuality(imageType: ImageType): number; getDefaultImageQuality(imageType: ImageType): number;

View file

@ -1,10 +1,14 @@
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
import escapeHtml from 'escape-html'; import escapeHtml from 'escape-html';
import React, { FunctionComponent, useEffect, useState } from 'react'; import React, { FunctionComponent, useEffect, useState } from 'react';
import { appRouter } from '../appRouter'; import { appRouter } from '../appRouter';
import { useApi } from '../../hooks/useApi';
import { useUser } from '../../hooks/useUser';
import globalize from '../../scripts/globalize'; import globalize from '../../scripts/globalize';
import ServerConnections from '../ServerConnections';
import '../../elements/emby-button/emby-button'; import '../../elements/emby-button/emby-button';
@ -21,27 +25,31 @@ const createSuggestionLink = ({ name, href }: { name: string, href: string }) =>
}); });
type SearchSuggestionsProps = { type SearchSuggestionsProps = {
serverId?: string;
parentId?: string | null; parentId?: string | null;
} }
const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ serverId = window.ApiClient.serverId(), parentId }: SearchSuggestionsProps) => { const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ parentId }: SearchSuggestionsProps) => {
const [ suggestions, setSuggestions ] = useState<BaseItemDto[]>([]); const [ suggestions, setSuggestions ] = useState<BaseItemDto[]>([]);
const api = useApi();
const user = useUser();
useEffect(() => { useEffect(() => {
const apiClient = ServerConnections.getApiClient(serverId); if (api && user?.Id) {
getItemsApi(api)
apiClient.getItems(apiClient.getCurrentUserId(), { .getItemsByUserId({
SortBy: 'IsFavoriteOrLiked,Random', userId: user.Id,
IncludeItemTypes: 'Movie,Series,MusicArtist', sortBy: [ItemSortBy.IsFavoriteOrLiked, ItemSortBy.Random],
Limit: 20, includeItemTypes: [BaseItemKind.Movie, BaseItemKind.Series, BaseItemKind.MusicArtist],
Recursive: true, limit: 20,
ImageTypeLimit: 0, recursive: true,
EnableImages: false, imageTypeLimit: 0,
ParentId: parentId, enableImages: false,
EnableTotalRecordCount: false parentId: parentId || undefined,
}).then(result => setSuggestions(result.Items || [])); enableTotalRecordCount: false
}, [parentId, serverId]); })
.then(result => setSuggestions(result.data.Items || []));
}
}, [api, parentId, user?.Id]);
return ( return (
<div <div

5
src/hooks/useApi.ts Normal file
View file

@ -0,0 +1,5 @@
import { Api } from '@jellyfin/sdk';
import { createContext, useContext } from 'react';
export const ApiContext = createContext<Api | undefined>(undefined);
export const useApi = () => useContext(ApiContext);

5
src/hooks/useUser.ts Normal file
View file

@ -0,0 +1,5 @@
import { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
import { createContext, useContext } from 'react';
export const UserContext = createContext<UserDto | undefined>(undefined);
export const useUser = () => useContext(UserContext);

View file

@ -37,8 +37,7 @@ import './legacy/htmlMediaElement';
import './legacy/vendorStyles'; import './legacy/vendorStyles';
import { currentSettings } from './scripts/settings/userSettings'; import { currentSettings } from './scripts/settings/userSettings';
import taskButton from './scripts/taskbutton'; import taskButton from './scripts/taskbutton';
import { HistoryRouter } from './components/HistoryRouter.tsx'; import App from './App.tsx';
import AppRoutes from './routes/index.tsx';
function loadCoreDictionary() { 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', 'eu', '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']; 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', 'eu', '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'];
@ -146,9 +145,7 @@ async function onAppReady() {
ReactDOM.render( ReactDOM.render(
<StrictMode> <StrictMode>
<HistoryRouter history={history}> <App connections={ServerConnections} history={history} />
<AppRoutes />
</HistoryRouter>
</StrictMode>, </StrictMode>,
document.getElementById('reactRoot') document.getElementById('reactRoot')
); );

View file

@ -21,7 +21,6 @@ const Search: FunctionComponent = () => {
<SearchFields onSearch={setQuery} /> <SearchFields onSearch={setQuery} />
{!query && {!query &&
<SearchSuggestions <SearchSuggestions
serverId={searchParams.get('serverId') || window.ApiClient.serverId()}
parentId={searchParams.get('parentId')} parentId={searchParams.get('parentId')}
/> />
} }

23
src/utils/sdk.ts Normal file
View file

@ -0,0 +1,23 @@
import { Api, Jellyfin } from '@jellyfin/sdk';
import { ApiClient } from 'jellyfin-apiclient';
/**
* Returns an SDK Api instance using the same parameters as the provided ApiClient.
* @param {ApiClient} apiClient The (legacy) ApiClient.
* @returns {Api} An equivalent SDK Api instance.
*/
export const toApi = (apiClient: ApiClient): Api => {
return (new Jellyfin({
clientInfo: {
name: apiClient.appName(),
version: apiClient.appVersion()
},
deviceInfo: {
name: apiClient.deviceName(),
id: apiClient.deviceId()
}
})).createApi(
apiClient.serverAddress(),
apiClient.accessToken()
);
};