diff --git a/src/renderer/components/item-list/item-detail-list/columns/actions-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/actions-column.tsx index 2e90299ce..54cfdbf47 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/actions-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/actions-column.tsx @@ -1,3 +1,38 @@ import { ItemDetailListCellProps } from './types'; -export const ActionsColumn = (_props: ItemDetailListCellProps) => null; +import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; +import { LibraryItem } from '/@/shared/types/domain-types'; + +export const ActionsColumn = ({ controls, internalState, song }: ItemDetailListCellProps) => { + const handleClick = (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + const index = internalState?.findItemIndex(song.id) ?? -1; + controls?.onMore?.({ + event, + index, + internalState: internalState ?? undefined, + item: song, + itemType: LibraryItem.SONG, + }); + }; + + const handleDoubleClick = (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + }; + + return ( + + ); +}; diff --git a/src/renderer/components/item-list/item-detail-list/columns/album-artist-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/album-artist-column.tsx index 350414769..c9d4f8498 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/album-artist-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/album-artist-column.tsx @@ -1,4 +1,4 @@ import { ItemDetailListCellProps } from './types'; export const AlbumArtistColumn = ({ song }: ItemDetailListCellProps) => - song.albumArtistName ?? '—'; + song.albumArtistName ?? <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/album-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/album-column.tsx index 902edecd5..d42517ce1 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/album-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/album-column.tsx @@ -1,3 +1,3 @@ import { ItemDetailListCellProps } from './types'; -export const AlbumColumn = ({ song }: ItemDetailListCellProps) => song.album ?? '—'; +export const AlbumColumn = ({ song }: ItemDetailListCellProps) => song.album ?? <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/artist-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/artist-column.tsx index ffc8d1a02..3a4084a6f 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/artist-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/artist-column.tsx @@ -1,3 +1,3 @@ import { ItemDetailListCellProps } from './types'; -export const ArtistColumn = ({ song }: ItemDetailListCellProps) => song.artistName ?? '—'; +export const ArtistColumn = ({ song }: ItemDetailListCellProps) => song.artistName ?? <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/bit-depth-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/bit-depth-column.tsx index b268652dd..512a80687 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/bit-depth-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/bit-depth-column.tsx @@ -1,4 +1,3 @@ import { ItemDetailListCellProps } from './types'; -export const BitDepthColumn = ({ song }: ItemDetailListCellProps) => - song.bitDepth != null ? String(song.bitDepth) : '—'; +export const BitDepthColumn = ({ song }: ItemDetailListCellProps) => song.bitDepth; diff --git a/src/renderer/components/item-list/item-detail-list/columns/bit-rate-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/bit-rate-column.tsx index a4584bac4..3da37a1d8 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/bit-rate-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/bit-rate-column.tsx @@ -1,4 +1,4 @@ import { ItemDetailListCellProps } from './types'; export const BitRateColumn = ({ song }: ItemDetailListCellProps) => - song.bitRate != null ? `${song.bitRate} kbps` : '—'; + song.bitRate != null ? `${song.bitRate} kbps` : <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/bpm-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/bpm-column.tsx index b8cfffc4f..6bb88b4f6 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/bpm-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/bpm-column.tsx @@ -1,4 +1,3 @@ import { ItemDetailListCellProps } from './types'; -export const BpmColumn = ({ song }: ItemDetailListCellProps) => - song.bpm != null ? String(song.bpm) : '—'; +export const BpmColumn = ({ song }: ItemDetailListCellProps) => song.bpm ?? <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/channels-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/channels-column.tsx index d15eac622..ac6b78e0d 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/channels-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/channels-column.tsx @@ -1,4 +1,4 @@ import { ItemDetailListCellProps } from './types'; export const ChannelsColumn = ({ song }: ItemDetailListCellProps) => - song.channels != null ? String(song.channels) : '—'; + song.channels != null ? String(song.channels) : <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/codec-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/codec-column.tsx index 8878a4a04..0f7f0d2d5 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/codec-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/codec-column.tsx @@ -1,3 +1,3 @@ import { ItemDetailListCellProps } from './types'; -export const CodecColumn = ({ song }: ItemDetailListCellProps) => song.container ?? '—'; +export const CodecColumn = ({ song }: ItemDetailListCellProps) => song.container ?? <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/comment-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/comment-column.tsx index 890aedf4d..1d36577b7 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/comment-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/comment-column.tsx @@ -1,3 +1,3 @@ import { ItemDetailListCellProps } from './types'; -export const CommentColumn = ({ song }: ItemDetailListCellProps) => song.comment ?? '—'; +export const CommentColumn = ({ song }: ItemDetailListCellProps) => song.comment ?? <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/composer-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/composer-column.tsx index 57bd2e7f1..877e63882 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/composer-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/composer-column.tsx @@ -2,6 +2,6 @@ import { ItemDetailListCellProps } from './types'; export const ComposerColumn = ({ song }: ItemDetailListCellProps) => { const composers = song.participants?.composer; - if (!composers?.length) return '—'; + if (!composers?.length) return <> ; return composers.map((a) => a.name).join(', '); }; diff --git a/src/renderer/components/item-list/item-detail-list/columns/date-added-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/date-added-column.tsx index 4252bb667..874b443d6 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/date-added-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/date-added-column.tsx @@ -1,5 +1,6 @@ import { ItemDetailListCellProps } from './types'; + import { formatDateAbsolute } from '/@/renderer/utils/format'; export const DateAddedColumn = ({ song }: ItemDetailListCellProps) => - song.createdAt ? formatDateAbsolute(song.createdAt) : '—'; + song.createdAt ? formatDateAbsolute(song.createdAt) : <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/default-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/default-column.tsx index 88a980746..94d221579 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/default-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/default-column.tsx @@ -1,5 +1,4 @@ import { ItemDetailListCellProps } from './types'; -import { TableColumn } from '/@/shared/types/types'; interface DefaultColumnProps extends ItemDetailListCellProps { columnId: string; @@ -7,6 +6,6 @@ interface DefaultColumnProps extends ItemDetailListCellProps { export const DefaultColumn = ({ columnId, song }: DefaultColumnProps) => { const raw = (song as Record)[columnId]; - if (raw === undefined || raw === null || typeof raw === 'object') return '—'; + if (raw === undefined || raw === null || typeof raw === 'object') return <> ; return String(raw); }; diff --git a/src/renderer/components/item-list/item-detail-list/columns/disc-number-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/disc-number-column.tsx index 9edbd78b1..267312581 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/disc-number-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/disc-number-column.tsx @@ -1,4 +1,3 @@ import { ItemDetailListCellProps } from './types'; -export const DiscNumberColumn = ({ song }: ItemDetailListCellProps) => - String(song.discNumber ?? 1); +export const DiscNumberColumn = ({ song }: ItemDetailListCellProps) => String(song.discNumber ?? 1); diff --git a/src/renderer/components/item-list/item-detail-list/columns/favorite-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/favorite-column.tsx index 75bfa2eff..99291e607 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/favorite-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/favorite-column.tsx @@ -1,24 +1,54 @@ import { ItemDetailListCellProps } from './types'; -import { Icon } from '/@/shared/components/icon/icon'; + +import { useIsMutatingCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation'; +import { useIsMutatingDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation'; +import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; +import { LibraryItem } from '/@/shared/types/domain-types'; export const FavoriteColumn = ({ + controls, + internalState, isMutatingFavorite, onFavoriteClick, song, -}: ItemDetailListCellProps) => ( -
{ - event.stopPropagation(); - event.preventDefault(); - onFavoriteClick?.(song); - }} - onDoubleClick={(event) => { - event.stopPropagation(); - event.preventDefault(); - }} - role="button" - > - -
-); +}: ItemDetailListCellProps) => { + const isMutatingCreateFavorite = useIsMutatingCreateFavorite(); + const isMutatingDeleteFavorite = useIsMutatingDeleteFavorite(); + const isMutating = isMutatingFavorite ?? (isMutatingCreateFavorite || isMutatingDeleteFavorite); + const isFavorite = song.userFavorite ?? false; + + return ( + { + event.stopPropagation(); + event.preventDefault(); + const index = internalState?.findItemIndex(song.id) ?? -1; + if (controls?.onFavorite) { + controls.onFavorite({ + event, + favorite: !isFavorite, + index, + internalState: internalState ?? undefined, + item: song, + itemType: LibraryItem.SONG, + }); + } else { + onFavoriteClick?.(song); + } + }} + onDoubleClick={(event) => { + event.stopPropagation(); + event.preventDefault(); + }} + size="xs" + variant="subtle" + /> + ); +}; diff --git a/src/renderer/components/item-list/item-detail-list/columns/genre-badge-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/genre-badge-column.tsx index d88a93000..d196d77ef 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/genre-badge-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/genre-badge-column.tsx @@ -1,4 +1,4 @@ import { ItemDetailListCellProps } from './types'; export const GenreBadgeColumn = ({ song }: ItemDetailListCellProps) => - song.genres?.length ? song.genres.map((g) => g.name).join(', ') : '—'; + song.genres?.length ? song.genres.map((g) => g.name).join(', ') : <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/genre-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/genre-column.tsx index ad73b97d5..e337fe123 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/genre-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/genre-column.tsx @@ -1,4 +1,4 @@ import { ItemDetailListCellProps } from './types'; export const GenreColumn = ({ song }: ItemDetailListCellProps) => - song.genres?.length ? song.genres.map((g) => g.name).join(', ') : '—'; + song.genres?.length ? song.genres.map((g) => g.name).join(', ') : <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/image-column.module.css b/src/renderer/components/item-list/item-detail-list/columns/image-column.module.css new file mode 100644 index 000000000..e9fe65ef1 --- /dev/null +++ b/src/renderer/components/item-list/item-detail-list/columns/image-column.module.css @@ -0,0 +1,22 @@ +.compact-container { + flex: 1 1 0; + width: 100%; + min-width: 0; + height: 100%; + min-height: 0; + max-height: 100%; + aspect-ratio: unset; + padding-top: var(--theme-spacing-xs); + padding-bottom: var(--theme-spacing-xs); + overflow: hidden; + border-radius: var(--theme-radius-md); +} + +.compact-image { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + border-radius: var(--theme-radius-md); +} diff --git a/src/renderer/components/item-list/item-detail-list/columns/image-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/image-column.tsx index 8e3a11483..fbbf2202a 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/image-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/image-column.tsx @@ -1,10 +1,15 @@ +import styles from './image-column.module.css'; import { ItemDetailListCellProps } from './types'; + import { ItemImage } from '/@/renderer/components/item-image/item-image'; import { LibraryItem } from '/@/shared/types/domain-types'; -export const ImageColumn = ({ song }: ItemDetailListCellProps) => - song.imageId ? ( - - ) : ( - '—' - ); +export const ImageColumn = ({ song }: ItemDetailListCellProps) => ( + +); diff --git a/src/renderer/components/item-list/item-detail-list/columns/last-played-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/last-played-column.tsx index 8c6c3aa63..c538b0378 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/last-played-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/last-played-column.tsx @@ -1,5 +1,6 @@ import { ItemDetailListCellProps } from './types'; + import { formatDateRelative } from '/@/renderer/utils/format'; export const LastPlayedColumn = ({ song }: ItemDetailListCellProps) => - song.lastPlayedAt ? formatDateRelative(song.lastPlayedAt) : '—'; + song.lastPlayedAt ? formatDateRelative(song.lastPlayedAt) : <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/path-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/path-column.tsx index 54c605203..df244597d 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/path-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/path-column.tsx @@ -1,3 +1,3 @@ import { ItemDetailListCellProps } from './types'; -export const PathColumn = ({ song }: ItemDetailListCellProps) => song.path ?? '—'; +export const PathColumn = ({ song }: ItemDetailListCellProps) => song.path ?? <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/play-count-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/play-count-column.tsx index 28a307d1a..e7c507ce4 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/play-count-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/play-count-column.tsx @@ -1,4 +1,4 @@ import { ItemDetailListCellProps } from './types'; export const PlayCountColumn = ({ song }: ItemDetailListCellProps) => - String(song.playCount ?? 0); + song.playCount ? String(song.playCount) : <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/rating-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/rating-column.tsx index 300a6e63c..db871fb9a 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/rating-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/rating-column.tsx @@ -1,6 +1,29 @@ import { ItemDetailListCellProps } from './types'; -import { ReadOnlyRating } from '/@/shared/components/read-only-rating/read-only-rating'; -export const RatingColumn = ({ song }: ItemDetailListCellProps) => ( - -); +import { useIsMutatingRating } from '/@/renderer/features/shared/mutations/set-rating-mutation'; +import { Rating } from '/@/shared/components/rating/rating'; +import { LibraryItem } from '/@/shared/types/domain-types'; + +export const RatingColumn = ({ controls, internalState, song }: ItemDetailListCellProps) => { + const isMutatingRating = useIsMutatingRating(); + const value = song.userRating ?? 0; + + return ( + { + const index = internalState?.findItemIndex(song.id) ?? -1; + controls?.onRating?.({ + event: null, + index, + internalState: internalState ?? undefined, + item: song, + itemType: LibraryItem.SONG, + rating, + }); + }} + readOnly={isMutatingRating} + size="xs" + value={value} + /> + ); +}; diff --git a/src/renderer/components/item-list/item-detail-list/columns/release-date-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/release-date-column.tsx index f2ef18179..a893fe32b 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/release-date-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/release-date-column.tsx @@ -1,5 +1,6 @@ import { ItemDetailListCellProps } from './types'; + import { formatDateAbsoluteUTC } from '/@/renderer/utils/format'; export const ReleaseDateColumn = ({ song }: ItemDetailListCellProps) => - song.releaseDate ? formatDateAbsoluteUTC(song.releaseDate) : '—'; + song.releaseDate ? formatDateAbsoluteUTC(song.releaseDate) : <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/sample-rate-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/sample-rate-column.tsx index 58f9e125b..317c85603 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/sample-rate-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/sample-rate-column.tsx @@ -1,4 +1,4 @@ import { ItemDetailListCellProps } from './types'; export const SampleRateColumn = ({ song }: ItemDetailListCellProps) => - song.sampleRate != null ? `${song.sampleRate} Hz` : '—'; + song.sampleRate ? `${song.sampleRate} Hz` : <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/size-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/size-column.tsx index 9b8b45287..c15b3d556 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/size-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/size-column.tsx @@ -1,5 +1,6 @@ import { ItemDetailListCellProps } from './types'; + import { formatSizeString } from '/@/renderer/utils/format'; export const SizeColumn = ({ song }: ItemDetailListCellProps) => - song.size != null ? formatSizeString(song.size) : '—'; + song.size ? formatSizeString(song.size) : <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/title-artist-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/title-artist-column.tsx index 8f7c46dfa..adbecec8f 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/title-artist-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/title-artist-column.tsx @@ -1,4 +1,4 @@ import { ItemDetailListCellProps } from './types'; export const TitleArtistColumn = ({ song }: ItemDetailListCellProps) => - [song.name, song.artistName].filter(Boolean).join(' — ') || '—'; + [song.name, song.artistName].filter(Boolean).join(' — ') ?? <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/title-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/title-column.tsx index d56ff562c..8fff612fc 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/title-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/title-column.tsx @@ -1,3 +1,3 @@ import { ItemDetailListCellProps } from './types'; -export const TitleColumn = ({ song }: ItemDetailListCellProps) => song.name ?? '—'; +export const TitleColumn = ({ song }: ItemDetailListCellProps) => song.name ?? <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/title-combined-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/title-combined-column.tsx index 42afb081e..0e06836e3 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/title-combined-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/title-combined-column.tsx @@ -1,4 +1,4 @@ import { ItemDetailListCellProps } from './types'; export const TitleCombinedColumn = ({ song }: ItemDetailListCellProps) => - [song.name, song.artistName].filter(Boolean).join(' — ') || '—'; + [song.name, song.artistName].filter(Boolean).join(' — ') ?? <> ; diff --git a/src/renderer/components/item-list/item-detail-list/columns/track-number-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/track-number-column.tsx index c234225c0..b2ee3f7f4 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/track-number-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/track-number-column.tsx @@ -3,5 +3,5 @@ import { ItemDetailListCellProps } from './types'; export const TrackNumberColumn = ({ song }: ItemDetailListCellProps) => { const disc = song.discNumber ?? 1; const track = song.trackNumber.toString().padStart(2, '0'); - return `${disc} - ${track}`; + return `${disc}-${track}`; }; diff --git a/src/renderer/components/item-list/item-detail-list/columns/types.ts b/src/renderer/components/item-list/item-detail-list/columns/types.ts index b61dbe849..dd462344f 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/types.ts +++ b/src/renderer/components/item-list/item-detail-list/columns/types.ts @@ -1,8 +1,13 @@ +import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state'; +import { ItemControls } from '/@/renderer/components/item-list/types'; import { Song } from '/@/shared/types/domain-types'; export interface ItemDetailListCellProps { + controls?: ItemControls; + internalState?: ItemListStateActions; isMutatingFavorite?: boolean; onFavoriteClick?: (song: Song) => void; rowIndex?: number; + size?: 'compact' | 'default' | 'large'; song: Song; } diff --git a/src/renderer/components/item-list/item-detail-list/columns/year-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/year-column.tsx index a736d7834..9cb90c173 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/year-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/year-column.tsx @@ -1,4 +1,4 @@ import { ItemDetailListCellProps } from './types'; export const YearColumn = ({ song }: ItemDetailListCellProps) => - song.releaseYear != null ? String(song.releaseYear) : '—'; + song.releaseYear ? String(song.releaseYear) : <> ; diff --git a/src/renderer/components/item-list/item-detail-list/item-detail.module.css b/src/renderer/components/item-list/item-detail-list/item-detail.module.css index 13c085b60..d08ec688f 100644 --- a/src/renderer/components/item-list/item-detail-list/item-detail.module.css +++ b/src/renderer/components/item-list/item-detail-list/item-detail.module.css @@ -67,6 +67,7 @@ flex-direction: column; gap: var(--theme-spacing-xs); align-items: center; + padding: var(--theme-spacing-sm); overflow: hidden; text-overflow: ellipsis; font-size: var(--theme-font-size-md); @@ -88,9 +89,18 @@ } .row .tracks-table { + display: flex; + flex-direction: column; width: 100%; font-size: var(--theme-font-size-sm); - table-layout: fixed; +} + +.row .track-row { + display: flex; + flex-wrap: nowrap; + align-items: center; + min-width: 0; + overflow: hidden; } .row .track-header-cell { @@ -100,6 +110,7 @@ } .row .track-cell { + min-width: 0; padding-right: var(--theme-spacing-sm); padding-left: var(--theme-spacing-sm); overflow: hidden; @@ -107,25 +118,38 @@ white-space: nowrap; } -.row .track-row-size-compact .track-cell { - padding-top: var(--theme-spacing-xs); - padding-bottom: var(--theme-spacing-xs); +.row .track-row-size-compact { + height: 32px; + min-height: 32px; + max-height: 32px; } -.row .track-row-size-default .track-cell { - padding-top: var(--theme-spacing-sm); - padding-bottom: var(--theme-spacing-sm); +.row .track-row-size-default { + height: 40px; + min-height: 40px; + max-height: 40px; } -.row .track-row-size-large .track-cell { - padding-top: var(--theme-spacing-md); - padding-bottom: var(--theme-spacing-md); +.row .track-row-size-large { + height: 48px; + min-height: 48px; + max-height: 48px; } .row .track-cell-muted { color: var(--theme-colors-foreground-muted); } +.row .track-cell-image { + display: flex; + align-self: stretch; + min-height: 0; + max-height: 100%; + padding-right: var(--theme-spacing-sm); + padding-left: var(--theme-spacing-sm); + overflow: hidden; +} + .track-row-selected { @mixin dark { background-color: lighten(var(--theme-colors-surface), 5%); @@ -172,18 +196,21 @@ } .skeleton-tracks-size-compact .skeleton-track-row { - padding-top: var(--theme-spacing-xs); - padding-bottom: var(--theme-spacing-xs); + height: 32px; + padding-top: 0; + padding-bottom: 0; } .skeleton-tracks-size-default .skeleton-track-row { - padding-top: var(--theme-spacing-sm); - padding-bottom: var(--theme-spacing-sm); + height: 40px; + padding-top: 0; + padding-bottom: 0; } .skeleton-tracks-size-large .skeleton-track-row { - padding-top: var(--theme-spacing-md); - padding-bottom: var(--theme-spacing-md); + height: 48px; + padding-top: 0; + padding-bottom: 0; } .skeleton-track-cell { diff --git a/src/renderer/components/item-list/item-detail-list/item-detail.tsx b/src/renderer/components/item-list/item-detail-list/item-detail.tsx index 48ab4a9c2..f717ea5cb 100644 --- a/src/renderer/components/item-list/item-detail-list/item-detail.tsx +++ b/src/renderer/components/item-list/item-detail-list/item-detail.tsx @@ -20,6 +20,10 @@ import { } from '/@/renderer/components/item-list/helpers/item-list-state'; import { parseTableColumns } from '/@/renderer/components/item-list/helpers/parse-table-columns'; import { getDetailListCellComponent } from '/@/renderer/components/item-list/item-detail-list/columns'; +import { + getTrackColumnFixed, + shouldShowHoverOnlyColumnContent, +} from '/@/renderer/components/item-list/item-detail-list/utils'; import { pickTableColumns, SONG_TABLE_COLUMNS, @@ -62,6 +66,7 @@ interface RowData { interface TrackRowProps { columns: ItemTableListColumnConfig[]; columnWidthPercents: number[]; + controls?: ItemControls; internalState: ItemListStateActions; isMutatingFavorite: boolean; onFavoriteClick: (song: Song) => void; @@ -77,6 +82,7 @@ const TrackRow = memo( ({ columns, columnWidthPercents, + controls, internalState, isMutatingFavorite, onFavoriteClick, @@ -85,7 +91,7 @@ const TrackRow = memo( song, }: TrackRowProps) => { const playerContext = usePlayer(); - const { dragRef, isDragging } = useItemDragDropState({ + const { dragRef, isDragging } = useItemDragDropState({ enableDrag: true, internalState, isDataRow: true, @@ -93,6 +99,7 @@ const TrackRow = memo( itemType: LibraryItem.SONG, playerContext, }); + const [isRowHovered, setIsRowHovered] = useState(false); const isSelected = useItemSelectionState(internalState, song.id); const handleRowClick = useCallback( @@ -176,8 +183,8 @@ const TrackRow = memo( ); return ( - setIsRowHovered(true)} + onMouseLeave={() => setIsRowHovered(false)} ref={dragRef ?? undefined} + role="row" > {columns.map((col, colIndex) => { const percent = columnWidthPercents[colIndex] ?? 0; + const { fixedWidth, isFixedColumn } = getTrackColumnFixed(col.id); const style: React.CSSProperties = { + flex: isFixedColumn ? `0 0 ${fixedWidth}px` : `${percent} 1 0`, fontFamily: col.id === TableColumn.DURATION || col.id === TableColumn.TRACK_NUMBER ? 'monospace' : undefined, - minWidth: 0, + minWidth: isFixedColumn ? fixedWidth : 0, textAlign: textAlignFromAlign(col.align), - width: `${percent}%`, }; const CellComponent = getDetailListCellComponent(col.id); - const content = ( + const isTitleColumn = col.id === TableColumn.TITLE; + const isImageColumn = col.id === TableColumn.IMAGE; + const showHoverContent = shouldShowHoverOnlyColumnContent( + col.id, + isRowHovered, + song, + ); + + const content = showHoverContent ? ( + ) : ( + '\u00A0' ); - const isTitleColumn = col.id === TableColumn.TITLE; return ( - {content} - + ); })} - + ); }, ); @@ -357,23 +381,22 @@ const RowContent = memo(
- - - {songs.map((song, rowIndex) => ( - - ))} - -
+
+ {songs.map((song, rowIndex) => ( + + ))} +
); diff --git a/src/renderer/components/item-list/item-detail-list/utils.ts b/src/renderer/components/item-list/item-detail-list/utils.ts new file mode 100644 index 000000000..ca30d7768 --- /dev/null +++ b/src/renderer/components/item-list/item-detail-list/utils.ts @@ -0,0 +1,44 @@ +import { TableColumn } from '/@/shared/types/types'; + +const FIXED_TRACK_COLUMN_WIDTHS: Partial> = { + [TableColumn.ACTIONS]: 48, + [TableColumn.TRACK_NUMBER]: 56, + [TableColumn.USER_FAVORITE]: 48, + [TableColumn.USER_RATING]: 76, +}; + +const HOVER_ONLY_COLUMNS: TableColumn[] = [ + TableColumn.ACTIONS, + TableColumn.USER_FAVORITE, + TableColumn.USER_RATING, +]; + +export function getTrackColumnFixed(columnId: TableColumn): { + fixedWidth: number; + isFixedColumn: boolean; +} { + const width = FIXED_TRACK_COLUMN_WIDTHS[columnId]; + return width !== undefined + ? { fixedWidth: width, isFixedColumn: true } + : { fixedWidth: 0, isFixedColumn: false }; +} + +export function isTrackColumnHoverOnly(columnId: TableColumn): boolean { + return HOVER_ONLY_COLUMNS.includes(columnId); +} + +export function shouldShowHoverOnlyColumnContent( + columnId: TableColumn, + isRowHovered: boolean, + song: { userFavorite?: boolean | null; userRating?: null | number }, +): boolean { + if (!HOVER_ONLY_COLUMNS.includes(columnId)) { + return true; + } + + return ( + isRowHovered || + (columnId === TableColumn.USER_FAVORITE && song.userFavorite !== false) || + (columnId === TableColumn.USER_RATING && song.userRating != null) + ); +}