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:
parent
f20ceb4274
commit
d297f23932
8 changed files with 124 additions and 22 deletions
64
src/App.tsx
Normal file
64
src/App.tsx
Normal 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
1
src/apiclient.d.ts
vendored
|
@ -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;
|
||||||
|
|
|
@ -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
5
src/hooks/useApi.ts
Normal 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
5
src/hooks/useUser.ts
Normal 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);
|
|
@ -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')
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
23
src/utils/sdk.ts
Normal 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()
|
||||||
|
);
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue