diff --git a/src/renderer/components/item-card/item-card-controls.tsx b/src/renderer/components/item-card/item-card-controls.tsx index fff58569e..11800ee60 100644 --- a/src/renderer/components/item-card/item-card-controls.tsx +++ b/src/renderer/components/item-card/item-card-controls.tsx @@ -19,6 +19,7 @@ import { Album, AlbumArtist, Artist, + Genre, LibraryItem, Playlist, ServerType, @@ -30,7 +31,7 @@ interface ItemCardControlsProps { controls?: ItemControls; enableExpansion?: boolean; internalState?: ItemListStateActions; - item: Album | AlbumArtist | Artist | Playlist | Song | undefined; + item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined; itemType: LibraryItem; showRating: boolean; type?: 'compact' | 'default' | 'poster'; @@ -60,7 +61,7 @@ const containerProps = { const createPlayHandler = ( controls: ItemControls | undefined, - item: Album | AlbumArtist | Artist | Playlist | Song | undefined, + item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined, internalState: ItemListStateActions | undefined, itemType: LibraryItem, playType: Play, @@ -108,7 +109,7 @@ const createPlayHandler = const createFavoriteHandler = ( controls: ItemControls | undefined, - item: Album | AlbumArtist | Artist | Playlist | Song | undefined, + item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined, internalState: ItemListStateActions | undefined, itemType: LibraryItem, ) => @@ -133,7 +134,7 @@ const createFavoriteHandler = const createRatingChangeHandler = ( controls: ItemControls | undefined, - item: Album | AlbumArtist | Artist | Playlist | Song | undefined, + item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined, internalState: ItemListStateActions | undefined, itemType: LibraryItem, ) => @@ -165,7 +166,7 @@ const moreDoubleClickHandler = (e: MouseEvent) => { const createMoreHandler = ( controls: ItemControls | undefined, - item: Album | AlbumArtist | Artist | Playlist | Song | undefined, + item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined, internalState: ItemListStateActions | undefined, itemType: LibraryItem, ) => @@ -183,7 +184,7 @@ const createMoreHandler = const createExpandHandler = ( controls: ItemControls | undefined, - item: Album | AlbumArtist | Artist | Playlist | Song | undefined, + item: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined, internalState: ItemListStateActions | undefined, itemType: LibraryItem, ) => diff --git a/src/renderer/components/item-card/item-card.module.css b/src/renderer/components/item-card/item-card.module.css index c3fbe9ebd..6e521a0aa 100644 --- a/src/renderer/components/item-card/item-card.module.css +++ b/src/renderer/components/item-card/item-card.module.css @@ -113,6 +113,18 @@ border-radius: 50%; } +.genre-placeholder { + box-sizing: border-box; +} + +.genre-placeholder-text { + overflow: hidden; + text-overflow: ellipsis; + font-weight: 600; + line-height: 1.2; + white-space: nowrap; +} + .detail-container { display: flex; flex-direction: column; diff --git a/src/renderer/components/item-card/item-card.tsx b/src/renderer/components/item-card/item-card.tsx index 992f5c127..86c506cb3 100644 --- a/src/renderer/components/item-card/item-card.tsx +++ b/src/renderer/components/item-card/item-card.tsx @@ -38,22 +38,26 @@ import { Album, AlbumArtist, Artist, + Genre, LibraryItem, Playlist, Song, } from '/@/shared/types/domain-types'; import { DragOperation, DragTarget } from '/@/shared/types/drag-and-drop'; +import { stringToColor } from '/@/shared/utils/string-to-color'; export type DataRow = { align?: 'center' | 'end' | 'start'; - format: (data: Album | AlbumArtist | Artist | Playlist | Song) => null | ReactNode | string; + format: ( + data: Album | AlbumArtist | Artist | Genre | Playlist | Song, + ) => null | ReactNode | string; id: string; isMuted?: boolean; }; export interface ItemCardProps { controls?: ItemControls; - data: Album | AlbumArtist | Artist | Playlist | Song | undefined; + data: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined; enableDrag?: boolean; enableExpansion?: boolean; enableMultiSelect?: boolean; @@ -341,16 +345,28 @@ const CompactItemCard = ({ const imageContainerContent = ( <> - + {itemType === LibraryItem.GENRE && + data && + 'name' in data && + typeof (data as Genre).name === 'string' ? ( + + ) : ( + + )} {isFavorite &&
} {hasRating &&
{userRating}
} @@ -565,14 +581,26 @@ const DefaultItemCard = ({ const imageContainerContent = ( <> - + {itemType === LibraryItem.GENRE && + data && + 'name' in data && + typeof (data as Genre).name === 'string' ? ( + + ) : ( + + )} {isFavorite &&
} {hasRating &&
{userRating}
} @@ -850,14 +878,26 @@ const PosterItemCard = ({ const imageContainerContent = ( <> - + {itemType === LibraryItem.GENRE && + data && + 'name' in data && + typeof (data as Genre).name === 'string' ? ( + + ) : ( + + )} {isFavorite &&
} {hasRating &&
{userRating}
} @@ -999,6 +1039,17 @@ export const getDataRows = (type?: 'compact' | 'default' | 'poster'): DataRow[] {data.name} ); + case LibraryItem.GENRE: + return ( + + {data.name} + + ); case LibraryItem.PLAYLIST: return ( { return getDataRows().length; }; -const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | undefined) => { +const getImageUrl = (data: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined) => { if (data && 'imageUrl' in data) { return data.imageUrl || undefined; } @@ -1235,8 +1286,30 @@ const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | unde return undefined; }; +const GenreImagePlaceholder = ({ className, name }: { className?: string; name: string }) => { + const { color, isLight } = useMemo(() => stringToColor(name), [name]); + return ( +
+ {name} +
+ ); +}; + const getItemNavigationPath = ( - data: Album | AlbumArtist | Artist | Playlist | Song | undefined, + data: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined, itemType: LibraryItem, ): null | string => { if (!data || !('id' in data) || !data.id) { @@ -1255,7 +1328,7 @@ const ItemCardRow = memo( row, type, }: { - data: Album | AlbumArtist | Artist | Playlist | Song | undefined; + data: Album | AlbumArtist | Artist | Genre | Playlist | Song | undefined; index: number; row: DataRow; type?: 'compact' | 'default' | 'poster'; diff --git a/src/renderer/components/item-list/helpers/get-dragged-items.ts b/src/renderer/components/item-list/helpers/get-dragged-items.ts index ee752a3cb..5654ba3fc 100644 --- a/src/renderer/components/item-list/helpers/get-dragged-items.ts +++ b/src/renderer/components/item-list/helpers/get-dragged-items.ts @@ -2,7 +2,15 @@ import { ItemListStateActions, ItemListStateItemWithRequiredProperties, } from '/@/renderer/components/item-list/helpers/item-list-state'; -import { Album, AlbumArtist, Artist, Folder, Playlist, Song } from '/@/shared/types/domain-types'; +import { + Album, + AlbumArtist, + Artist, + Folder, + Genre, + Playlist, + Song, +} from '/@/shared/types/domain-types'; /** * Type guard to assert that an item has the required properties for dragging @@ -34,7 +42,7 @@ const hasRequiredDragProperties = ( * @returns Array of items that should be dragged (with original values, asserting id, itemType, and _serverId) */ export const getDraggedItems = ( - data: Album | AlbumArtist | Artist | Folder | Playlist | Song | undefined, + data: Album | AlbumArtist | Artist | Folder | Genre | Playlist | Song | undefined, internalState?: ItemListStateActions, updateSelection: boolean = true, ): ItemListStateItemWithRequiredProperties[] => { diff --git a/src/renderer/components/item-list/helpers/use-grid-rows.ts b/src/renderer/components/item-list/helpers/use-grid-rows.ts index ae462732f..4a9c05188 100644 --- a/src/renderer/components/item-list/helpers/use-grid-rows.ts +++ b/src/renderer/components/item-list/helpers/use-grid-rows.ts @@ -26,6 +26,10 @@ const getDefaultRowsForItemType = ( return [rowMap.get('name')].filter( (row): row is NonNullable => row !== undefined, ); + case LibraryItem.GENRE: + return [rowMap.get('name')].filter( + (row): row is NonNullable => row !== undefined, + ); case LibraryItem.PLAYLIST: return [rowMap.get('name')].filter( (row): row is NonNullable => row !== undefined, @@ -53,6 +57,7 @@ const getRowIdFromTableColumn = (tableColumn: TableColumn): null | string => { [TableColumn.CHANNELS]: null, [TableColumn.CODEC]: null, [TableColumn.COMMENT]: null, + [TableColumn.COMPOSER]: null, [TableColumn.DATE_ADDED]: 'createdAt', [TableColumn.DISC_NUMBER]: null, [TableColumn.DURATION]: 'duration', diff --git a/src/renderer/components/item-list/types.ts b/src/renderer/components/item-list/types.ts index 227148aec..cb7c8565f 100644 --- a/src/renderer/components/item-list/types.ts +++ b/src/renderer/components/item-list/types.ts @@ -4,6 +4,7 @@ import { AlbumArtist, Artist, Folder, + Genre, LibraryItem, Playlist, Song, @@ -78,7 +79,15 @@ export interface ItemListHandle { scrollToOffset: (offset: number, options?: { behavior?: 'auto' | 'smooth' }) => void; } -export type ItemListItem = Album | AlbumArtist | Artist | Folder | Playlist | Song | undefined; +export type ItemListItem = + | Album + | AlbumArtist + | Artist + | Folder + | Genre + | Playlist + | Song + | undefined; export interface ItemListTableComponentProps extends ItemListComponentProps { autoFitColumns?: boolean;