diff --git a/src/apps/experimental/components/LibraryIcon.tsx b/src/apps/experimental/components/LibraryIcon.tsx index 66975f0556e..846e3f887c0 100644 --- a/src/apps/experimental/components/LibraryIcon.tsx +++ b/src/apps/experimental/components/LibraryIcon.tsx @@ -1,4 +1,5 @@ import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import Movie from '@mui/icons-material/Movie'; import MusicNote from '@mui/icons-material/MusicNote'; import Photo from '@mui/icons-material/Photo'; @@ -7,11 +8,11 @@ import Tv from '@mui/icons-material/Tv'; import Theaters from '@mui/icons-material/Theaters'; import MusicVideo from '@mui/icons-material/MusicVideo'; import Book from '@mui/icons-material/Book'; -import Collections from '@mui/icons-material/Collections'; import Queue from '@mui/icons-material/Queue'; +import Quiz from '@mui/icons-material/Quiz'; +import VideoLibrary from '@mui/icons-material/VideoLibrary'; import Folder from '@mui/icons-material/Folder'; import React, { FC } from 'react'; -import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; interface LibraryIconProps { item: BaseItemDto @@ -39,9 +40,11 @@ const LibraryIcon: FC = ({ case CollectionType.Books: return ; case CollectionType.Boxsets: - return ; + return ; case CollectionType.Playlists: return ; + case undefined: + return ; default: return ; } diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 94d9f613b2b..b4b8c77ddcf 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -4,6 +4,7 @@ * @module components/cardBuilder/cardBuilder */ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import { PersonKind } from '@jellyfin/sdk/lib/generated-client/models/person-kind'; import escapeHtml from 'escape-html'; @@ -12,7 +13,7 @@ import datetime from 'scripts/datetime'; import dom from 'scripts/dom'; import globalize from 'lib/globalize'; import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card'; -import imageHelper from 'utils/image'; +import { getItemTypeIcon, getLibraryIcon } from 'utils/image'; import focusManager from '../focusManager'; import imageLoader from '../images/imageLoader'; @@ -1053,7 +1054,7 @@ function buildCard(index, item, apiClient, options) { indicatorsHtml += indicators.getPlayedIndicatorHtml(item); } - if (item.Type === 'CollectionFolder' || item.CollectionType) { + if (item.Type === BaseItemKind.CollectionFolder || item.CollectionType) { const refreshClass = item.RefreshProgress ? '' : ' class="hide"'; indicatorsHtml += '
'; importRefreshIndicator(); @@ -1180,41 +1181,18 @@ function getHoverMenuHtml(item, action) { * @returns {string} HTML markup of the card overlay. */ export function getDefaultText(item, options) { - if (item.CollectionType) { - return ''; - } - - switch (item.Type) { - case 'MusicAlbum': - return ''; - case 'MusicArtist': - case 'Person': - return ''; - case 'Audio': - return ''; - case 'Movie': - return ''; - case 'Episode': - case 'Series': - return ''; - case 'Program': - return ''; - case 'Book': - return ''; - case 'Folder': - return ''; - case 'BoxSet': - return ''; - case 'Playlist': - return ''; - case 'Photo': - return ''; - case 'PhotoAlbum': - return ''; - } - - if (options?.defaultCardImageIcon) { - return ''; + let icon; + + if (item.Type === BaseItemKind.CollectionFolder || item.CollectionType) { + icon = getLibraryIcon(item.CollectionType); + } + + if (!icon) { + icon = getItemTypeIcon(item.Type, options?.defaultCardImageIcon); + } + + if (icon) { + return ``; } const defaultName = isUsingLiveTvNaming(item.Type) ? item.Name : itemHelper.getDisplayName(item); diff --git a/src/components/common/DefaultIconText.tsx b/src/components/common/DefaultIconText.tsx index c6ea81b18e8..7624a0fd135 100644 --- a/src/components/common/DefaultIconText.tsx +++ b/src/components/common/DefaultIconText.tsx @@ -1,7 +1,7 @@ import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import React, { type FC } from 'react'; import Icon from '@mui/material/Icon'; -import imageHelper from 'utils/image'; +import { getItemTypeIcon, getLibraryIcon } from 'utils/image'; import DefaultName from './DefaultName'; import type { ItemDto } from 'types/base/models/item-dto'; @@ -14,38 +14,24 @@ const DefaultIconText: FC = ({ item, defaultCardImageIcon }) => { - if (item.CollectionType) { - return ( - - ); + let icon; + + if (item.Type === BaseItemKind.CollectionFolder || item.CollectionType) { + icon = getLibraryIcon(item.CollectionType); } - if (item.Type && !(item.Type === BaseItemKind.TvChannel || item.Type === BaseItemKind.Studio )) { - return ( - - ); + if (!icon) { + icon = getItemTypeIcon(item.Type, defaultCardImageIcon); } - if (defaultCardImageIcon) { + if (icon) { return ( ); } diff --git a/src/utils/image.test.ts b/src/utils/image.test.ts new file mode 100644 index 00000000000..8fc5bb36bb4 --- /dev/null +++ b/src/utils/image.test.ts @@ -0,0 +1,101 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { describe, expect, it } from 'vitest'; + +import { getItemTypeIcon, getLibraryIcon } from './image'; +import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; + +const ITEM_ICON_MAP: Record = { + AggregateFolder: undefined, + Audio: 'audiotrack', + AudioBook: undefined, + BasePluginFolder: undefined, + Book: 'book', + BoxSet: 'video_library', + Channel: undefined, + ChannelFolderItem: undefined, + CollectionFolder: undefined, + Episode: 'tv', + Folder: 'folder', + Genre: undefined, + LiveTvChannel: undefined, + LiveTvProgram: undefined, + ManualPlaylistsFolder: undefined, + Movie: 'movie', + MusicAlbum: 'album', + MusicArtist: 'person', + MusicGenre: undefined, + MusicVideo: undefined, + Person: 'person', + Photo: 'photo', + PhotoAlbum: 'photo_album', + Playlist: 'queue', + PlaylistsFolder: undefined, + Program: 'live_tv', + Recording: undefined, + Season: undefined, + Series: 'tv', + Studio: undefined, + Trailer: undefined, + TvChannel: undefined, + TvProgram: undefined, + UserRootFolder: undefined, + UserView: undefined, + Video: undefined, + Year: undefined +}; + +const LIBRARY_ICON_MAP: Record = { + Books: 'book', + Boxsets: 'video_library', + Folders: 'folder', + Homevideos: 'photo', + Livetv: 'live_tv', + Movies: 'movie', + Music: 'music_note', + Musicvideos: 'music_video', + Photos: 'photo', + Playlists: 'queue', + Trailers: 'theaters', + Tvshows: 'tv', + Unknown: 'folder' +}; + +describe('getItemTypeIcon()', () => { + it('Should return the correct icon for item type', () => { + Object.entries(BaseItemKind).forEach(([key, value]) => { + expect(Object.prototype.hasOwnProperty.call(ITEM_ICON_MAP, key)).toBe(true); + expect(`${key}=${getItemTypeIcon(value)}`).toBe(`${key}=${ITEM_ICON_MAP[key]}`); + }); + }); + + it('Should return the default icon for unknown type if provided', () => { + expect(getItemTypeIcon('foobar', 'default')) + .toBe('default'); + }); + + it('Should return undefined for unknown type', () => { + expect(getItemTypeIcon('foobar')) + .toBeUndefined(); + }); +}); + +describe('getLibraryIcon()', () => { + it('Should return the correct icon for collection type', () => { + Object.entries(CollectionType).forEach(([key, value]) => { + expect(Object.prototype.hasOwnProperty.call(LIBRARY_ICON_MAP, key)).toBe(true); + expect(`${key}=${getLibraryIcon(value)}`).toBe(`${key}=${LIBRARY_ICON_MAP[key]}`); + }); + }); + + it('Should return the correct icon for nonstandard types', () => { + expect(getLibraryIcon(undefined)) + .toBe('quiz'); + expect(getLibraryIcon('channels')) + .toBe('videocam'); + }); + + it('Should return the default icon for unknown types', () => { + expect(getLibraryIcon('foobar')) + .toBe('folder'); + }); +}); diff --git a/src/utils/image.ts b/src/utils/image.ts index dc32e879733..23ef8081b82 100644 --- a/src/utils/image.ts +++ b/src/utils/image.ts @@ -1,3 +1,4 @@ +import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import type { DeviceInfo } from '@jellyfin/sdk/lib/generated-client/models/device-info'; import type { SessionInfo } from '@jellyfin/sdk/lib/generated-client/models/session-info'; @@ -75,36 +76,39 @@ export function getDeviceIcon(info: DeviceInfo | SessionInfo) { } } -export function getLibraryIcon(library: string | null | undefined) { +export function getLibraryIcon(library: CollectionType | string | null | undefined) { switch (library) { - case 'movies': - return 'video_library'; - case 'music': - return 'library_music'; - case 'photos': - return 'photo_library'; - case 'livetv': + case CollectionType.Movies: + return 'movie'; + case CollectionType.Music: + return 'music_note'; + case CollectionType.Homevideos: + case CollectionType.Photos: + return 'photo'; + case CollectionType.Livetv: return 'live_tv'; - case 'tvshows': + case CollectionType.Tvshows: return 'tv'; - case 'trailers': - return 'local_movies'; - case 'homevideos': - return 'photo_library'; - case 'musicvideos': + case CollectionType.Trailers: + return 'theaters'; + case CollectionType.Musicvideos: return 'music_video'; - case 'books': - return 'library_books'; + case CollectionType.Books: + return 'book'; + case CollectionType.Boxsets: + return 'video_library'; + case CollectionType.Playlists: + return 'queue'; case 'channels': return 'videocam'; - case 'playlists': - return 'view_list'; + case undefined: + return 'quiz'; default: return 'folder'; } } -export function getItemTypeIcon(itemType: BaseItemKind | string) { +export function getItemTypeIcon(itemType: BaseItemKind | string | undefined, defaultIcon?: string) { switch (itemType) { case BaseItemKind.MusicAlbum: return 'album'; @@ -125,15 +129,15 @@ export function getItemTypeIcon(itemType: BaseItemKind | string) { case BaseItemKind.Folder: return 'folder'; case BaseItemKind.BoxSet: - return 'collections'; + return 'video_library'; case BaseItemKind.Playlist: - return 'view_list'; + return 'queue'; case BaseItemKind.Photo: return 'photo'; case BaseItemKind.PhotoAlbum: return 'photo_album'; default: - return 'folder'; + return defaultIcon; } }