mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge pull request #3433 from dmitrylyzo/types-react
[TypeScript] Disable implicit any
This commit is contained in:
commit
bb46b32402
33 changed files with 883 additions and 254 deletions
|
@ -1,4 +1,6 @@
|
|||
import { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import classNames from 'classnames';
|
||||
import { ApiClient } from 'jellyfin-apiclient';
|
||||
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||
|
||||
import globalize from '../../scripts/globalize';
|
||||
|
@ -27,14 +29,14 @@ type LiveTVSearchResultsProps = {
|
|||
/*
|
||||
* React component to display search result rows for live tv library search
|
||||
*/
|
||||
const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serverId, parentId, collectionType, query }: LiveTVSearchResultsProps) => {
|
||||
const [ movies, setMovies ] = useState([]);
|
||||
const [ episodes, setEpisodes ] = useState([]);
|
||||
const [ sports, setSports ] = useState([]);
|
||||
const [ kids, setKids ] = useState([]);
|
||||
const [ news, setNews ] = useState([]);
|
||||
const [ programs, setPrograms ] = useState([]);
|
||||
const [ channels, setChannels ] = useState([]);
|
||||
const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serverId = window.ApiClient.serverId(), parentId, collectionType, query }: LiveTVSearchResultsProps) => {
|
||||
const [ movies, setMovies ] = useState<BaseItemDto[]>([]);
|
||||
const [ episodes, setEpisodes ] = useState<BaseItemDto[]>([]);
|
||||
const [ sports, setSports ] = useState<BaseItemDto[]>([]);
|
||||
const [ kids, setKids ] = useState<BaseItemDto[]>([]);
|
||||
const [ news, setNews ] = useState<BaseItemDto[]>([]);
|
||||
const [ programs, setPrograms ] = useState<BaseItemDto[]>([]);
|
||||
const [ channels, setChannels ] = useState<BaseItemDto[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const getDefaultParameters = () => ({
|
||||
|
@ -53,7 +55,7 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
|
|||
});
|
||||
|
||||
// FIXME: This query does not support Live TV filters
|
||||
const fetchItems = (apiClient, params = {}) => apiClient?.getItems(
|
||||
const fetchItems = (apiClient: ApiClient, params = {}) => apiClient?.getItems(
|
||||
apiClient?.getCurrentUserId(),
|
||||
{
|
||||
...getDefaultParameters(),
|
||||
|
@ -72,8 +74,7 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
|
|||
setChannels([]);
|
||||
|
||||
if (query && collectionType === 'livetv') {
|
||||
// TODO: Remove type casting once we're using a properly typed API client
|
||||
const apiClient = (ServerConnections as any).getApiClient(serverId);
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
|
||||
// Movies row
|
||||
fetchItems(apiClient, {
|
||||
|
@ -83,7 +84,7 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
|
|||
IsSports: false,
|
||||
IsKids: false,
|
||||
IsNews: false
|
||||
}).then(result => setMovies(result.Items));
|
||||
}).then(result => setMovies(result.Items || []));
|
||||
// Episodes row
|
||||
fetchItems(apiClient, {
|
||||
IncludeItemTypes: 'LiveTvProgram',
|
||||
|
@ -92,7 +93,7 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
|
|||
IsSports: false,
|
||||
IsKids: false,
|
||||
IsNews: false
|
||||
}).then(result => setEpisodes(result.Items));
|
||||
}).then(result => setEpisodes(result.Items || []));
|
||||
// Sports row
|
||||
fetchItems(apiClient, {
|
||||
IncludeItemTypes: 'LiveTvProgram',
|
||||
|
@ -101,7 +102,7 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
|
|||
IsSports: true,
|
||||
IsKids: false,
|
||||
IsNews: false
|
||||
}).then(result => setSports(result.Items));
|
||||
}).then(result => setSports(result.Items || []));
|
||||
// Kids row
|
||||
fetchItems(apiClient, {
|
||||
IncludeItemTypes: 'LiveTvProgram',
|
||||
|
@ -110,7 +111,7 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
|
|||
IsSports: false,
|
||||
IsKids: true,
|
||||
IsNews: false
|
||||
}).then(result => setKids(result.Items));
|
||||
}).then(result => setKids(result.Items || []));
|
||||
// News row
|
||||
fetchItems(apiClient, {
|
||||
IncludeItemTypes: 'LiveTvProgram',
|
||||
|
@ -119,7 +120,7 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
|
|||
IsSports: false,
|
||||
IsKids: false,
|
||||
IsNews: true
|
||||
}).then(result => setNews(result.Items));
|
||||
}).then(result => setNews(result.Items || []));
|
||||
// Programs row
|
||||
fetchItems(apiClient, {
|
||||
IncludeItemTypes: 'LiveTvProgram',
|
||||
|
@ -128,10 +129,10 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
|
|||
IsSports: false,
|
||||
IsKids: false,
|
||||
IsNews: false
|
||||
}).then(result => setPrograms(result.Items));
|
||||
}).then(result => setPrograms(result.Items || []));
|
||||
// Channels row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'TvChannel' })
|
||||
.then(result => setChannels(result.Items));
|
||||
.then(result => setChannels(result.Items || []));
|
||||
}
|
||||
}, [collectionType, parentId, query, serverId]);
|
||||
|
||||
|
|
|
@ -31,20 +31,20 @@ const createInputElement = () => ({
|
|||
const normalizeInput = (value = '') => value.trim();
|
||||
|
||||
type SearchFieldsProps = {
|
||||
onSearch?: () => void
|
||||
onSearch?: (query: string) => void
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const SearchFields: FunctionComponent<SearchFieldsProps> = ({ onSearch = () => {} }: SearchFieldsProps) => {
|
||||
const element = useRef(null);
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const getSearchInput = () => element?.current?.querySelector('.searchfields-txtSearch');
|
||||
const getSearchInput = () => element?.current?.querySelector<HTMLInputElement>('.searchfields-txtSearch');
|
||||
|
||||
const debouncedOnSearch = useMemo(() => debounce(onSearch, 400), [onSearch]);
|
||||
|
||||
useEffect(() => {
|
||||
getSearchInput()?.addEventListener('input', e => {
|
||||
debouncedOnSearch(normalizeInput(e.target?.value));
|
||||
debouncedOnSearch(normalizeInput((e.target as HTMLInputElement).value));
|
||||
});
|
||||
getSearchInput()?.focus();
|
||||
|
||||
|
@ -53,10 +53,15 @@ const SearchFields: FunctionComponent<SearchFieldsProps> = ({ onSearch = () => {
|
|||
};
|
||||
}, [debouncedOnSearch]);
|
||||
|
||||
const onAlphaPicked = e => {
|
||||
const value = e.detail.value;
|
||||
const onAlphaPicked = (e: Event) => {
|
||||
const value = (e as CustomEvent).detail.value;
|
||||
const searchInput = getSearchInput();
|
||||
|
||||
if (!searchInput) {
|
||||
console.error('Unexpected null reference');
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === 'backspace') {
|
||||
const currentValue = searchInput.value;
|
||||
searchInput.value = currentValue.length ? currentValue.substring(0, currentValue.length - 1) : '';
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import classNames from 'classnames';
|
||||
import { ApiClient } from 'jellyfin-apiclient';
|
||||
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||
|
||||
import globalize from '../../scripts/globalize';
|
||||
|
@ -15,22 +17,22 @@ type SearchResultsProps = {
|
|||
/*
|
||||
* React component to display search result rows for global search and non-live tv library search
|
||||
*/
|
||||
const SearchResults: FunctionComponent<SearchResultsProps> = ({ serverId, parentId, collectionType, query }: SearchResultsProps) => {
|
||||
const [ movies, setMovies ] = useState([]);
|
||||
const [ shows, setShows ] = useState([]);
|
||||
const [ episodes, setEpisodes ] = useState([]);
|
||||
const [ videos, setVideos ] = useState([]);
|
||||
const [ programs, setPrograms ] = useState([]);
|
||||
const [ channels, setChannels ] = useState([]);
|
||||
const [ playlists, setPlaylists ] = useState([]);
|
||||
const [ artists, setArtists ] = useState([]);
|
||||
const [ albums, setAlbums ] = useState([]);
|
||||
const [ songs, setSongs ] = useState([]);
|
||||
const [ photoAlbums, setPhotoAlbums ] = useState([]);
|
||||
const [ photos, setPhotos ] = useState([]);
|
||||
const [ audioBooks, setAudioBooks ] = useState([]);
|
||||
const [ books, setBooks ] = useState([]);
|
||||
const [ people, setPeople ] = useState([]);
|
||||
const SearchResults: FunctionComponent<SearchResultsProps> = ({ serverId = window.ApiClient.serverId(), parentId, collectionType, query }: SearchResultsProps) => {
|
||||
const [ movies, setMovies ] = useState<BaseItemDto[]>([]);
|
||||
const [ shows, setShows ] = useState<BaseItemDto[]>([]);
|
||||
const [ episodes, setEpisodes ] = useState<BaseItemDto[]>([]);
|
||||
const [ videos, setVideos ] = useState<BaseItemDto[]>([]);
|
||||
const [ programs, setPrograms ] = useState<BaseItemDto[]>([]);
|
||||
const [ channels, setChannels ] = useState<BaseItemDto[]>([]);
|
||||
const [ playlists, setPlaylists ] = useState<BaseItemDto[]>([]);
|
||||
const [ artists, setArtists ] = useState<BaseItemDto[]>([]);
|
||||
const [ albums, setAlbums ] = useState<BaseItemDto[]>([]);
|
||||
const [ songs, setSongs ] = useState<BaseItemDto[]>([]);
|
||||
const [ photoAlbums, setPhotoAlbums ] = useState<BaseItemDto[]>([]);
|
||||
const [ photos, setPhotos ] = useState<BaseItemDto[]>([]);
|
||||
const [ audioBooks, setAudioBooks ] = useState<BaseItemDto[]>([]);
|
||||
const [ books, setBooks ] = useState<BaseItemDto[]>([]);
|
||||
const [ people, setPeople ] = useState<BaseItemDto[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const getDefaultParameters = () => ({
|
||||
|
@ -48,7 +50,7 @@ const SearchResults: FunctionComponent<SearchResultsProps> = ({ serverId, parent
|
|||
IncludeArtists: false
|
||||
});
|
||||
|
||||
const fetchArtists = (apiClient, params = {}) => apiClient?.getArtists(
|
||||
const fetchArtists = (apiClient: ApiClient, params = {}) => apiClient?.getArtists(
|
||||
apiClient?.getCurrentUserId(),
|
||||
{
|
||||
...getDefaultParameters(),
|
||||
|
@ -57,7 +59,7 @@ const SearchResults: FunctionComponent<SearchResultsProps> = ({ serverId, parent
|
|||
}
|
||||
);
|
||||
|
||||
const fetchItems = (apiClient, params = {}) => apiClient?.getItems(
|
||||
const fetchItems = (apiClient: ApiClient, params = {}) => apiClient?.getItems(
|
||||
apiClient?.getCurrentUserId(),
|
||||
{
|
||||
...getDefaultParameters(),
|
||||
|
@ -66,7 +68,7 @@ const SearchResults: FunctionComponent<SearchResultsProps> = ({ serverId, parent
|
|||
}
|
||||
);
|
||||
|
||||
const fetchPeople = (apiClient, params = {}) => apiClient?.getPeople(
|
||||
const fetchPeople = (apiClient: ApiClient, params = {}) => apiClient?.getPeople(
|
||||
apiClient?.getCurrentUserId(),
|
||||
{
|
||||
...getDefaultParameters(),
|
||||
|
@ -99,45 +101,44 @@ const SearchResults: FunctionComponent<SearchResultsProps> = ({ serverId, parent
|
|||
setPeople([]);
|
||||
|
||||
if (query) {
|
||||
// TODO: Remove type casting once we're using a properly typed API client
|
||||
const apiClient = (ServerConnections as any).getApiClient(serverId);
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
|
||||
// Movie libraries
|
||||
if (!collectionType || isMovies()) {
|
||||
// Movies row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'Movie' })
|
||||
.then(result => setMovies(result.Items));
|
||||
.then(result => setMovies(result.Items || []));
|
||||
}
|
||||
|
||||
// TV Show libraries
|
||||
if (!collectionType || isTVShows()) {
|
||||
// Shows row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'Series' })
|
||||
.then(result => setShows(result.Items));
|
||||
.then(result => setShows(result.Items || []));
|
||||
// Episodes row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'Episode' })
|
||||
.then(result => setEpisodes(result.Items));
|
||||
.then(result => setEpisodes(result.Items || []));
|
||||
}
|
||||
|
||||
// People are included for Movies and TV Shows
|
||||
if (!collectionType || isMovies() || isTVShows()) {
|
||||
// People row
|
||||
fetchPeople(apiClient).then(result => setPeople(result.Items));
|
||||
fetchPeople(apiClient).then(result => setPeople(result.Items || []));
|
||||
}
|
||||
|
||||
// Music libraries
|
||||
if (!collectionType || isMusic()) {
|
||||
// Playlists row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'Playlist' })
|
||||
.then(results => setPlaylists(results.Items));
|
||||
.then(results => setPlaylists(results.Items || []));
|
||||
// Artists row
|
||||
fetchArtists(apiClient).then(result => setArtists(result.Items));
|
||||
fetchArtists(apiClient).then(result => setArtists(result.Items || []));
|
||||
// Albums row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'MusicAlbum' })
|
||||
.then(result => setAlbums(result.Items));
|
||||
.then(result => setAlbums(result.Items || []));
|
||||
// Songs row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'Audio' })
|
||||
.then(result => setSongs(result.Items));
|
||||
.then(result => setSongs(result.Items || []));
|
||||
}
|
||||
|
||||
// Other libraries do not support in-library search currently
|
||||
|
@ -146,25 +147,25 @@ const SearchResults: FunctionComponent<SearchResultsProps> = ({ serverId, parent
|
|||
fetchItems(apiClient, {
|
||||
MediaTypes: 'Video',
|
||||
ExcludeItemTypes: 'Movie,Episode,TvChannel'
|
||||
}).then(result => setVideos(result.Items));
|
||||
}).then(result => setVideos(result.Items || []));
|
||||
// Programs row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'LiveTvProgram' })
|
||||
.then(result => setPrograms(result.Items));
|
||||
.then(result => setPrograms(result.Items || []));
|
||||
// Channels row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'TvChannel' })
|
||||
.then(result => setChannels(result.Items));
|
||||
.then(result => setChannels(result.Items || []));
|
||||
// Photo Albums row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'PhotoAlbum' })
|
||||
.then(results => setPhotoAlbums(results.Items));
|
||||
.then(results => setPhotoAlbums(results.Items || []));
|
||||
// Photos row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'Photo' })
|
||||
.then(results => setPhotos(results.Items));
|
||||
.then(results => setPhotos(results.Items || []));
|
||||
// Audio Books row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'AudioBook' })
|
||||
.then(results => setAudioBooks(results.Items));
|
||||
.then(results => setAudioBooks(results.Items || []));
|
||||
// Books row
|
||||
fetchItems(apiClient, { IncludeItemTypes: 'Book' })
|
||||
.then(results => setBooks(results.Items));
|
||||
.then(results => setBooks(results.Items || []));
|
||||
}
|
||||
}
|
||||
}, [collectionType, parentId, query, serverId]);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import React, { FunctionComponent, useEffect, useRef } from 'react';
|
||||
|
||||
import cardBuilder from '../cardbuilder/cardBuilder';
|
||||
|
@ -17,12 +18,12 @@ const createScroller = ({ title = '' }) => ({
|
|||
|
||||
type SearchResultsRowProps = {
|
||||
title?: string;
|
||||
items?: Array<any>; // TODO: Should be Array<BaseItemDto> once we have a typed API client
|
||||
items?: BaseItemDto[];
|
||||
cardOptions?: Record<string, any>;
|
||||
}
|
||||
|
||||
const SearchResultsRow: FunctionComponent<SearchResultsRowProps> = ({ title, items = [], cardOptions = {} }: SearchResultsRowProps) => {
|
||||
const element = useRef(null);
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
cardBuilder.buildCards(items, {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
||||
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||
|
||||
import { appRouter } from '../appRouter';
|
||||
|
@ -9,7 +10,7 @@ import '../../elements/emby-button/emby-button';
|
|||
// There seems to be some compatibility issues here between
|
||||
// React and our legacy web components, so we need to inject
|
||||
// them as an html string for now =/
|
||||
const createSuggestionLink = ({name, href}) => ({
|
||||
const createSuggestionLink = ({ name, href }: { name: string, href: string }) => ({
|
||||
__html: `<a
|
||||
is='emby-linkbutton'
|
||||
class='button-link'
|
||||
|
@ -23,12 +24,11 @@ type SearchSuggestionsProps = {
|
|||
parentId?: string;
|
||||
}
|
||||
|
||||
const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ serverId, parentId }: SearchSuggestionsProps) => {
|
||||
const [ suggestions, setSuggestions ] = useState([]);
|
||||
const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ serverId = window.ApiClient.serverId(), parentId }: SearchSuggestionsProps) => {
|
||||
const [ suggestions, setSuggestions ] = useState<BaseItemDto[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: Remove type casting once we're using a properly typed API client
|
||||
const apiClient = (ServerConnections as any).getApiClient(serverId);
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
|
||||
apiClient.getItems(apiClient.getCurrentUserId(), {
|
||||
SortBy: 'IsFavoriteOrLiked,Random',
|
||||
|
@ -39,7 +39,7 @@ const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ serverId
|
|||
EnableImages: false,
|
||||
ParentId: parentId,
|
||||
EnableTotalRecordCount: false
|
||||
}).then(result => setSuggestions(result.Items));
|
||||
}).then(result => setSuggestions(result.Items || []));
|
||||
}, [parentId, serverId]);
|
||||
|
||||
return (
|
||||
|
@ -58,7 +58,7 @@ const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ serverId
|
|||
<div
|
||||
key={`suggestion-${item.Id}`}
|
||||
dangerouslySetInnerHTML={createSuggestionLink({
|
||||
name: item.Name,
|
||||
name: item.Name || '',
|
||||
href: appRouter.getRouteUrl(item)
|
||||
})}
|
||||
/>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue