diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 000000000..50661303d --- /dev/null +++ b/src/App.tsx @@ -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(toApi(connections.currentApiClient())); + const [ user, setUser ] = useState(); + + 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 ( + + + + + + + + ); +}; + +export default App; diff --git a/src/apiclient.d.ts b/src/apiclient.d.ts index 9ad794dd6..33f3752ca 100644 --- a/src/apiclient.d.ts +++ b/src/apiclient.d.ts @@ -117,6 +117,7 @@ declare module 'jellyfin-apiclient' { getCountries(): Promise; getCriticReviews(itemId: string, options?: any): Promise; getCultures(): Promise; + getCurrentUser(cache?: boolean): Promise; getCurrentUserId(): string; getDateParamValue(date: Date): string; getDefaultImageQuality(imageType: ImageType): number; diff --git a/src/components/search/SearchSuggestions.tsx b/src/components/search/SearchSuggestions.tsx index 244286189..887fa2481 100644 --- a/src/components/search/SearchSuggestions.tsx +++ b/src/components/search/SearchSuggestions.tsx @@ -1,10 +1,14 @@ 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 React, { FunctionComponent, useEffect, useState } from 'react'; import { appRouter } from '../appRouter'; +import { useApi } from '../../hooks/useApi'; +import { useUser } from '../../hooks/useUser'; import globalize from '../../scripts/globalize'; -import ServerConnections from '../ServerConnections'; import '../../elements/emby-button/emby-button'; @@ -21,27 +25,31 @@ const createSuggestionLink = ({ name, href }: { name: string, href: string }) => }); type SearchSuggestionsProps = { - serverId?: string; parentId?: string | null; } -const SearchSuggestions: FunctionComponent = ({ serverId = window.ApiClient.serverId(), parentId }: SearchSuggestionsProps) => { +const SearchSuggestions: FunctionComponent = ({ parentId }: SearchSuggestionsProps) => { const [ suggestions, setSuggestions ] = useState([]); + const api = useApi(); + const user = useUser(); useEffect(() => { - const apiClient = ServerConnections.getApiClient(serverId); - - apiClient.getItems(apiClient.getCurrentUserId(), { - SortBy: 'IsFavoriteOrLiked,Random', - IncludeItemTypes: 'Movie,Series,MusicArtist', - Limit: 20, - Recursive: true, - ImageTypeLimit: 0, - EnableImages: false, - ParentId: parentId, - EnableTotalRecordCount: false - }).then(result => setSuggestions(result.Items || [])); - }, [parentId, serverId]); + if (api && user?.Id) { + getItemsApi(api) + .getItemsByUserId({ + userId: user.Id, + sortBy: [ItemSortBy.IsFavoriteOrLiked, ItemSortBy.Random], + includeItemTypes: [BaseItemKind.Movie, BaseItemKind.Series, BaseItemKind.MusicArtist], + limit: 20, + recursive: true, + imageTypeLimit: 0, + enableImages: false, + parentId: parentId || undefined, + enableTotalRecordCount: false + }) + .then(result => setSuggestions(result.data.Items || [])); + } + }, [api, parentId, user?.Id]); return (
(undefined); +export const useApi = () => useContext(ApiContext); diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts new file mode 100644 index 000000000..ad681f71d --- /dev/null +++ b/src/hooks/useUser.ts @@ -0,0 +1,5 @@ +import { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto'; +import { createContext, useContext } from 'react'; + +export const UserContext = createContext(undefined); +export const useUser = () => useContext(UserContext); diff --git a/src/index.jsx b/src/index.jsx index 2c786b736..15d6b48d1 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -37,8 +37,7 @@ import './legacy/htmlMediaElement'; import './legacy/vendorStyles'; import { currentSettings } from './scripts/settings/userSettings'; import taskButton from './scripts/taskbutton'; -import { HistoryRouter } from './components/HistoryRouter.tsx'; -import AppRoutes from './routes/index.tsx'; +import App from './App.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', '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( - - - + , document.getElementById('reactRoot') ); diff --git a/src/routes/search.tsx b/src/routes/search.tsx index 059a81783..d154018b4 100644 --- a/src/routes/search.tsx +++ b/src/routes/search.tsx @@ -21,7 +21,6 @@ const Search: FunctionComponent = () => { {!query && } diff --git a/src/utils/sdk.ts b/src/utils/sdk.ts new file mode 100644 index 000000000..444dbee45 --- /dev/null +++ b/src/utils/sdk.ts @@ -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() + ); +};