mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
510 lines
16 KiB
TypeScript
510 lines
16 KiB
TypeScript
![]() |
import type { AxiosRequestConfig } from 'axios';
|
||
|
import type { Api } from '@jellyfin/sdk';
|
||
|
import type {
|
||
|
ArtistsApiGetArtistsRequest,
|
||
|
BaseItemDto,
|
||
|
ItemsApiGetItemsRequest,
|
||
|
PersonsApiGetPersonsRequest
|
||
|
} from '@jellyfin/sdk/lib/generated-client';
|
||
|
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||
|
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||
|
import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields';
|
||
|
import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
|
||
|
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
|
||
|
import { getPersonsApi } from '@jellyfin/sdk/lib/utils/api/persons-api';
|
||
|
import { getArtistsApi } from '@jellyfin/sdk/lib/utils/api/artists-api';
|
||
|
import { useQuery } from '@tanstack/react-query';
|
||
|
import { useApi } from '../useApi';
|
||
|
import type { CardOptions } from 'types/cardOptions';
|
||
|
import { CardShape } from 'utils/card';
|
||
|
|
||
|
const Parameters_OPTIONS = {
|
||
|
limit: 100,
|
||
|
fields: [
|
||
|
ItemFields.PrimaryImageAspectRatio,
|
||
|
ItemFields.CanDelete,
|
||
|
ItemFields.MediaSourceCount
|
||
|
],
|
||
|
enableTotalRecordCount: false,
|
||
|
imageTypeLimit: 1
|
||
|
};
|
||
|
|
||
|
const fetchItemsByType = async (
|
||
|
api: Api,
|
||
|
userId?: string,
|
||
|
params?: ItemsApiGetItemsRequest,
|
||
|
options?: AxiosRequestConfig
|
||
|
) => {
|
||
|
const response = await getItemsApi(api).getItems(
|
||
|
{
|
||
|
...Parameters_OPTIONS,
|
||
|
userId: userId,
|
||
|
recursive: true,
|
||
|
...params
|
||
|
},
|
||
|
options
|
||
|
);
|
||
|
return response.data;
|
||
|
};
|
||
|
|
||
|
const fetchPeople = async (
|
||
|
api: Api,
|
||
|
userId: string,
|
||
|
params?: PersonsApiGetPersonsRequest,
|
||
|
options?: AxiosRequestConfig
|
||
|
) => {
|
||
|
const response = await getPersonsApi(api).getPersons(
|
||
|
{
|
||
|
...Parameters_OPTIONS,
|
||
|
userId: userId,
|
||
|
...params
|
||
|
},
|
||
|
options
|
||
|
);
|
||
|
return response.data;
|
||
|
};
|
||
|
|
||
|
const fetchArtists = async (
|
||
|
api: Api,
|
||
|
userId: string,
|
||
|
params?: ArtistsApiGetArtistsRequest,
|
||
|
options?: AxiosRequestConfig
|
||
|
) => {
|
||
|
const response = await getArtistsApi(api).getArtists(
|
||
|
{
|
||
|
...Parameters_OPTIONS,
|
||
|
userId: userId,
|
||
|
...params
|
||
|
},
|
||
|
options
|
||
|
);
|
||
|
return response.data;
|
||
|
};
|
||
|
|
||
|
const isMovies = (collectionType: string) =>
|
||
|
collectionType === CollectionType.Movies;
|
||
|
|
||
|
const isMusic = (collectionType: string) =>
|
||
|
collectionType === CollectionType.Music;
|
||
|
|
||
|
const isTVShows = (collectionType: string) =>
|
||
|
collectionType === CollectionType.Tvshows;
|
||
|
|
||
|
const isLivetv = (collectionType: string) =>
|
||
|
collectionType === CollectionType.Livetv;
|
||
|
|
||
|
const Livetv_CARD_OPTIONS = {
|
||
|
preferThumb: true,
|
||
|
inheritThumb: false,
|
||
|
showParentTitleOrTitle: true,
|
||
|
showTitle: false,
|
||
|
coverImage: true,
|
||
|
overlayMoreButton: true,
|
||
|
showAirTime: true,
|
||
|
showAirDateTime: true,
|
||
|
showChannelName: true
|
||
|
};
|
||
|
|
||
|
export interface Section {
|
||
|
title: string
|
||
|
items: BaseItemDto[];
|
||
|
cardOptions?: CardOptions;
|
||
|
}
|
||
|
|
||
|
export const useSearchItems = (
|
||
|
parentId?: string,
|
||
|
collectionType?: string,
|
||
|
searchTerm?: string
|
||
|
) => {
|
||
|
const { api, user } = useApi();
|
||
|
const userId = user?.Id;
|
||
|
|
||
|
return useQuery({
|
||
|
queryKey: ['SearchItems', { parentId, collectionType, searchTerm }],
|
||
|
queryFn: async ({ signal }) => {
|
||
|
if (!api) throw new Error('No API instance available');
|
||
|
if (!userId) throw new Error('No User ID provided');
|
||
|
|
||
|
const sections: Section[] = [];
|
||
|
|
||
|
const addSection = (
|
||
|
title: string,
|
||
|
items: BaseItemDto[] | null | undefined,
|
||
|
cardOptions?: CardOptions
|
||
|
) => {
|
||
|
if (items && items?.length > 0) {
|
||
|
sections.push({ title, items, cardOptions });
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Livetv libraries
|
||
|
if (collectionType && isLivetv(collectionType)) {
|
||
|
// Movies row
|
||
|
const moviesData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.LiveTvProgram],
|
||
|
isMovie: true,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Movies', moviesData.Items, {
|
||
|
...Livetv_CARD_OPTIONS,
|
||
|
shape: CardShape.PortraitOverflow
|
||
|
});
|
||
|
|
||
|
// Episodes row
|
||
|
const episodesData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.LiveTvProgram],
|
||
|
isMovie: false,
|
||
|
isSeries: true,
|
||
|
isSports: false,
|
||
|
isKids: false,
|
||
|
isNews: false,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Episodes', episodesData.Items, {
|
||
|
...Livetv_CARD_OPTIONS
|
||
|
});
|
||
|
|
||
|
// Sports row
|
||
|
const sportsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.LiveTvProgram],
|
||
|
isSports: true,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Sports', sportsData.Items, {
|
||
|
...Livetv_CARD_OPTIONS
|
||
|
});
|
||
|
|
||
|
// Kids row
|
||
|
const kidsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.LiveTvProgram],
|
||
|
isKids: true,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Kids', kidsData.Items, {
|
||
|
...Livetv_CARD_OPTIONS
|
||
|
});
|
||
|
|
||
|
// News row
|
||
|
const newsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.LiveTvProgram],
|
||
|
isNews: true,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('News', newsData.Items, {
|
||
|
...Livetv_CARD_OPTIONS
|
||
|
});
|
||
|
|
||
|
// Programs row
|
||
|
const programsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.LiveTvProgram],
|
||
|
isMovie: false,
|
||
|
isSeries: false,
|
||
|
isSports: false,
|
||
|
isKids: false,
|
||
|
isNews: false,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Programs', programsData.Items, {
|
||
|
...Livetv_CARD_OPTIONS
|
||
|
});
|
||
|
|
||
|
// Channels row
|
||
|
const channelsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.TvChannel],
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Channels', channelsData.Items);
|
||
|
}
|
||
|
|
||
|
// Movie libraries
|
||
|
if (!collectionType || isMovies(collectionType)) {
|
||
|
// Movies row
|
||
|
const moviesData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.Movie],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Movies', moviesData.Items, {
|
||
|
showYear: true
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// TV Show libraries
|
||
|
if (!collectionType || isTVShows(collectionType)) {
|
||
|
// Shows row
|
||
|
const showsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.Series],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Shows', showsData.Items, {
|
||
|
showYear: true
|
||
|
});
|
||
|
|
||
|
// Episodes row
|
||
|
const episodesData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.Episode],
|
||
|
parentId: parentId,
|
||
|
isMissing: user?.Configuration?.DisplayMissingEpisodes,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Episodes', episodesData.Items, {
|
||
|
coverImage: true,
|
||
|
showParentTitle: true
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// People are included for Movies and TV Shows
|
||
|
if (
|
||
|
!collectionType
|
||
|
|| isMovies(collectionType)
|
||
|
|| isTVShows(collectionType)
|
||
|
) {
|
||
|
// People row
|
||
|
const peopleData = await fetchPeople(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('People', peopleData.Items, {
|
||
|
coverImage: true
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Music libraries
|
||
|
if (!collectionType || isMusic(collectionType)) {
|
||
|
// Playlists row
|
||
|
const playlistsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.Playlist],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Playlists', playlistsData.Items);
|
||
|
|
||
|
// Artists row
|
||
|
const artistsData = await fetchArtists(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Artists', artistsData.Items, {
|
||
|
coverImage: true
|
||
|
});
|
||
|
|
||
|
// Albums row
|
||
|
const albumsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.MusicAlbum],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Albums', albumsData.Items, {
|
||
|
showYear: true
|
||
|
});
|
||
|
|
||
|
// Songs row
|
||
|
const songsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.Audio],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Songs', songsData.Items, {
|
||
|
showParentTitle: true,
|
||
|
shape: CardShape.SquareOverflow
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Other libraries do not support in-library search currently
|
||
|
if (!collectionType) {
|
||
|
// Videos row
|
||
|
const videosData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
mediaTypes: [MediaType.Video],
|
||
|
excludeItemTypes: [
|
||
|
BaseItemKind.Movie,
|
||
|
BaseItemKind.Episode,
|
||
|
BaseItemKind.TvChannel
|
||
|
],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
|
||
|
addSection('Videos', videosData.Items, {
|
||
|
showParentTitle: true
|
||
|
});
|
||
|
|
||
|
// Programs row
|
||
|
const programsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.LiveTvProgram],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Programs', programsData.Items, {
|
||
|
...Livetv_CARD_OPTIONS
|
||
|
});
|
||
|
|
||
|
// Channels row
|
||
|
const channelsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.TvChannel],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Channels', channelsData.Items);
|
||
|
|
||
|
// Photo Albums row
|
||
|
const photoAlbumsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.PhotoAlbum],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('PhotoAlbums', photoAlbumsData.Items);
|
||
|
|
||
|
// Photos row
|
||
|
const photosData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.Photo],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Photos', photosData.Items);
|
||
|
|
||
|
// Audio Books row
|
||
|
const audioBooksData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.AudioBook],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('AudioBooks', audioBooksData.Items);
|
||
|
|
||
|
// Books row
|
||
|
const booksData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.Book],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Books', booksData.Items);
|
||
|
|
||
|
// Collections row
|
||
|
const collectionsData = await fetchItemsByType(
|
||
|
api,
|
||
|
userId,
|
||
|
{
|
||
|
includeItemTypes: [BaseItemKind.BoxSet],
|
||
|
parentId: parentId,
|
||
|
searchTerm: searchTerm
|
||
|
},
|
||
|
{ signal }
|
||
|
);
|
||
|
addSection('Collections', collectionsData.Items);
|
||
|
}
|
||
|
|
||
|
return sections;
|
||
|
},
|
||
|
enabled: !!api && !!userId
|
||
|
});
|
||
|
};
|