From 3fa4270a8d1863b47e3f87fb64c890a4a7bf8419 Mon Sep 17 00:00:00 2001 From: Rishi Rallapalli Date: Wed, 13 Nov 2024 09:45:44 -0500 Subject: [PATCH 1/4] albums with multiple CDs now have track number as a decimal --- packages/core/src/plugins/meta/spotify.test.ts | 1 + packages/core/src/plugins/meta/spotify.ts | 1 + packages/core/src/rest/Spotify.ts | 1 + packages/core/src/structs/Track.ts | 2 ++ packages/ui/lib/components/GridTrackTable/index.tsx | 2 +- packages/ui/lib/types/index.ts | 1 + 6 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/src/plugins/meta/spotify.test.ts b/packages/core/src/plugins/meta/spotify.test.ts index 21ebe84e04..b9c90c6c52 100644 --- a/packages/core/src/plugins/meta/spotify.test.ts +++ b/packages/core/src/plugins/meta/spotify.test.ts @@ -355,6 +355,7 @@ describe('SpotifyMetaProvider', () => { name: 'test track', artists: [{ name: 'test artist' }], duration_ms: 1000, + disc_number: 0, type: 'track' }] }, diff --git a/packages/core/src/plugins/meta/spotify.ts b/packages/core/src/plugins/meta/spotify.ts index 478485f5db..c86b4dca19 100644 --- a/packages/core/src/plugins/meta/spotify.ts +++ b/packages/core/src/plugins/meta/spotify.ts @@ -169,6 +169,7 @@ export class SpotifyMetaProvider extends MetaProvider { title: track.name, artist: result.artists[0].name, duration: track.duration_ms/1000, + discNumber: track.disc_number, position: track.track_number, thumbnail: thumb })) diff --git a/packages/core/src/rest/Spotify.ts b/packages/core/src/rest/Spotify.ts index 6d3c1a448f..230bc0d5cb 100644 --- a/packages/core/src/rest/Spotify.ts +++ b/packages/core/src/rest/Spotify.ts @@ -65,6 +65,7 @@ export type SpotifyTrack = { popularity: number; track_number: number; duration_ms: number; + disc_number: number; type: 'track'; } diff --git a/packages/core/src/structs/Track.ts b/packages/core/src/structs/Track.ts index c8d14adce5..9bcfc196b4 100644 --- a/packages/core/src/structs/Track.ts +++ b/packages/core/src/structs/Track.ts @@ -15,6 +15,7 @@ export default class Track { title: string; name?: string; duration: string | number; + discNumber?: number; position?: string | number; playcount?: string | number; @@ -35,6 +36,7 @@ export default class Track { this.duration = data.duration; this.position = data.position; this.thumbnail = data.thumbnail; + this.discNumber = data.discNumber; } addSearchResultData(data: SearchResultsTrack): void { diff --git a/packages/ui/lib/components/GridTrackTable/index.tsx b/packages/ui/lib/components/GridTrackTable/index.tsx index eafc22967e..bfb52fbe74 100644 --- a/packages/ui/lib/components/GridTrackTable/index.tsx +++ b/packages/ui/lib/components/GridTrackTable/index.tsx @@ -88,7 +88,7 @@ export const GridTrackTable = ({ header={positionHeader} isCentered />, - accessor: (track: T) => track.position, + accessor: (track: T) => `${track.discNumber || 1}.${track.position}`, Cell: PositionCell, enableSorting: true, columnWidth: '4em' diff --git a/packages/ui/lib/types/index.ts b/packages/ui/lib/types/index.ts index 49f753f909..2c9027a6ea 100644 --- a/packages/ui/lib/types/index.ts +++ b/packages/ui/lib/types/index.ts @@ -12,6 +12,7 @@ export type Track = { album?: string; duration?: number | string; position?: number | string; + discNumber?: number; playcount?: number | string; thumbnail?: string; image?: { '#text'?: string }[]; From 27b0324ef2aa3b9dd6c58e3d23f3d3b8efef50c0 Mon Sep 17 00:00:00 2001 From: kedij777 Date: Sun, 17 Nov 2024 19:10:19 -0500 Subject: [PATCH 2/4] updated ui on album with multiple disc, on spotify search result only --- packages/core/src/plugins/meta/spotify.ts | 6 +- packages/core/src/plugins/plugins.types.ts | 1 + packages/core/src/structs/Track.ts | 5 +- .../lib/components/GridTrackTable/index.tsx | 446 +++++++++--------- packages/ui/lib/types/index.ts | 8 +- 5 files changed, 244 insertions(+), 222 deletions(-) diff --git a/packages/core/src/plugins/meta/spotify.ts b/packages/core/src/plugins/meta/spotify.ts index c86b4dca19..1ba2c67146 100644 --- a/packages/core/src/plugins/meta/spotify.ts +++ b/packages/core/src/plugins/meta/spotify.ts @@ -1,3 +1,4 @@ +import spotify from '../../helpers/playlist/spotify'; import { SpotifyArtist, SpotifyClientProvider, SpotifyImage, SpotifySimplifiedAlbum, SpotifyTrack, getImageSet } from '../../rest/Spotify'; import Track from '../../structs/Track'; import MetaProvider from '../metaProvider'; @@ -13,7 +14,7 @@ export class SpotifyMetaProvider extends MetaProvider { this.image = null; this.isDefault = true; } - + async searchForArtists(query: string, limit=10): Promise { const client = await SpotifyClientProvider.get(); const results = await client.searchArtists(query, limit); @@ -67,6 +68,7 @@ export class SpotifyMetaProvider extends MetaProvider { id: spotifyTrack.id, title: spotifyTrack.name, artist: spotifyTrack.artists[0].name, + discNumber: spotifyTrack.disc_number, source: SearchResultsSource.Spotify, thumb }; @@ -182,6 +184,6 @@ export class SpotifyMetaProvider extends MetaProvider { return this.fetchAlbumDetails(result.id, albumType); } - + } diff --git a/packages/core/src/plugins/plugins.types.ts b/packages/core/src/plugins/plugins.types.ts index 955528f8ac..323595d2a5 100644 --- a/packages/core/src/plugins/plugins.types.ts +++ b/packages/core/src/plugins/plugins.types.ts @@ -62,6 +62,7 @@ export type SearchResultsTrack = { artist: string; source: SearchResultsSource; thumb?: string; + discNumber?: number | string; } export type ArtistTopTrack = { diff --git a/packages/core/src/structs/Track.ts b/packages/core/src/structs/Track.ts index 9bcfc196b4..7544682e56 100644 --- a/packages/core/src/structs/Track.ts +++ b/packages/core/src/structs/Track.ts @@ -15,9 +15,9 @@ export default class Track { title: string; name?: string; duration: string | number; - discNumber?: number; - + position?: string | number; + discNumber?: string | number; playcount?: string | number; thumbnail?: string; extraArtists?: string[]; @@ -44,6 +44,7 @@ export default class Track { this.artist = data.artist; this.title = data.title; this.name = data.title; + this.discNumber = data.discNumber; } static fromSearchResultData(data: SearchResultsTrack): Track { diff --git a/packages/ui/lib/components/GridTrackTable/index.tsx b/packages/ui/lib/components/GridTrackTable/index.tsx index bfb52fbe74..3a003db69d 100644 --- a/packages/ui/lib/components/GridTrackTable/index.tsx +++ b/packages/ui/lib/components/GridTrackTable/index.tsx @@ -3,18 +3,18 @@ import React, { useMemo, useState } from 'react'; import { DragDropContext, DragDropContextProps, Droppable } from 'react-beautiful-dnd'; import { FixedSizeList } from 'react-window'; import AutoSizer from 'react-virtualized-auto-sizer'; - + import { TrackTableColumn, TrackTableExtraProps, TrackTableHeaders, TrackTableSettings, TrackTableStrings } from '../TrackTable/types'; import { TextHeader } from './Headers/TextHeader'; import { TextCell } from './Cells/TextCell'; import { Track } from '../../types'; import { getTrackThumbnail } from '../TrackRow'; - + import styles from './styles.scss'; import artPlaceholder from '../../../resources/media/art_placeholder.png'; import { ThumbnailCell } from './Cells/ThumbnailCell'; import { GridTrackTableRow } from './GridTrackTableRow'; -import { isNumber, isString } from 'lodash'; +import { groupBy, isNumber, isString } from 'lodash'; import { SelectionCell } from './Cells/SelectionCell'; import { SelectionHeader } from './Headers/SelectionHeader'; import { formatDuration } from '../../utils'; @@ -25,7 +25,7 @@ import { FavoriteCell } from './Cells/FavoriteCell'; import { TitleCell } from './Cells/TitleCell'; import { Input } from 'semantic-ui-react'; import Button from '../Button'; - + export type GridTrackTableProps = { className?: string; tracks: T[]; @@ -36,28 +36,28 @@ export type GridTrackTableProps = { } & TrackTableHeaders & TrackTableSettings & TrackTableExtraProps; - + type ColumnWithWidth = Column & { columnWidth: string; }; type TrackTableColumnInstance = ColumnInstance & UseSortByColumnProps; type TrackTableHeaderGroup = HeaderGroup & UseSortByColumnProps; type TrackTableInstance = TableInstance & UseGlobalFiltersInstanceProps & UseSortByInstanceProps & UseRowSelectInstanceProps; type TrackTableState = TableState & UseSortByState; export type TrackTableRow = Row & UseRowSelectRowProps; - + export const GridTrackTable = ({ className, tracks, customColumns=[], isTrackFavorite, onDragEnd, - + positionHeader, thumbnailHeader, artistHeader, titleHeader, albumHeader, durationHeader, - + displayHeaders=true, displayDeleteButton=true, displayPosition=true, @@ -69,217 +69,235 @@ export const GridTrackTable = ({ displayCustom=true, selectable=true, searchable=false, - + ...extraProps }: GridTrackTableProps) => { const shouldDisplayDuration = displayDuration && tracks.every(track => Boolean(track.duration)); - const columns = useMemo(() => [ - selectable && { - id: TrackTableColumn.Selection, - Header: SelectionHeader, - Cell: SelectionCell, - columnWidth: '7.5em' - }, - displayPosition && { - id: TrackTableColumn.Position, - Header: ({ column }) => } - header={positionHeader} - isCentered - />, - accessor: (track: T) => `${track.discNumber || 1}.${track.position}`, - Cell: PositionCell, - enableSorting: true, - columnWidth: '4em' - } as Column, - displayThumbnail && { - id: TrackTableColumn.Thumbnail, - Header: ({ column }) => , - accessor: (track: T) => getTrackThumbnail(track) || artPlaceholder, - Cell: ThumbnailCell, - columnWidth: '3em' - }, - displayFavorite && { - id: TrackTableColumn.Favorite, - accessor: isTrackFavorite, - Cell: FavoriteCell, - columnWidth: '3em' - }, - { - id: TrackTableColumn.Title, - Header: ({ column }) => , - accessor: (track: T) => track.title ?? track.name, - Cell: TitleCell, - enableSorting: true, - columnWidth: 'minmax(8em, 1fr)' - }, - displayArtist && { - id: TrackTableColumn.Artist, - Header: ({ column }) => , - accessor: (track: T) => isString(track.artist) - ? track.artist - : track.artist.name, - Cell: TextCell, - enableSorting: true, - columnWidth: '6em' - }, - displayAlbum && { - id: TrackTableColumn.Album, - Header: ({ column }: { column: TrackTableColumnInstance }) => , - accessor: (track: T) => track.album, - enableSorting: true, - Cell: TextCell, - columnWidth: '6em' - } as Column, - shouldDisplayDuration && { - id: TrackTableColumn.Duration, - Header: ({ column }) => , - accessor: (track: T) => { - if (isString(track.duration)) { - return track.duration; - } else if (isNumber(track.duration)) { - return formatDuration(track.duration); - } else { - return null; - } - }, - Cell: TextCell, - columnWidth: '6em' - }, - ...customColumns, - displayDeleteButton && { - id: TrackTableColumn.Delete, - Cell: DeleteCell, - columnWidth: '3em' - } as Column - ].filter(Boolean), [displayDeleteButton, displayPosition, displayThumbnail, displayFavorite, isTrackFavorite, titleHeader, displayArtist, artistHeader, displayAlbum, albumHeader, shouldDisplayDuration, durationHeader, selectable, positionHeader, thumbnailHeader]); - - const data = useMemo(() => tracks, [tracks]); - - const initialState: Partial & UseSortByState> = { - sortBy: [{ id: TrackTableColumn.Position, desc: false }] - }; - const table = useTable({ columns, data, initialState }, useGlobalFilter, useSortBy, useRowSelect); + const groupedDisc = useMemo(() => groupBy(tracks, (track) => track.discNumber || 1), [tracks]); + const [globalFilter, setGlobalFilterState] = useState(''); // Required, because useGlobalFilter does not provide a way to get the current filter value - - const { - getTableProps, - getTableBodyProps, - headerGroups, - rows, - prepareRow, - setGlobalFilter, - state: tableState, - selectedFlatRows - } = table as TrackTableInstance; - - const onFilterClick = () => { - setGlobalFilter(''); - setGlobalFilterState(''); - }; - - const gridTemplateColumns = columns.map((column: ColumnWithWidth) => column.columnWidth ?? '1fr').join(' '); - - // Disabled when there are selected rows, or when sorted by anything other than position - const isDragDisabled = !onDragEnd || selectedFlatRows.length > 0 || (tableState as TrackTableState).sortBy[0]?.id !== TrackTableColumn.Position; - - return
- { - searchable && -
- { - setGlobalFilter(e.target.value); - setGlobalFilterState(e.target.value); - }} - value={globalFilter} - /> -
- } -
-
- {headerGroups.map(headerGroup => ( -
- { - headerGroup.headers.map((column: TrackTableHeaderGroup) => ( -
- {column.render('Header', extraProps)} -
)) - } -
- ))} -
- - - {(droppableProvided, droppableSnapshot) => ( -
- - {({ height, width }) => - [], - prepareRow: prepareRow as (row: Row) => void, - gridTemplateColumns, - isDragDisabled, - extraProps - }} - outerRef={droppableProvided.innerRef} + + return ( +
+ {Object.entries(groupedDisc).map(([discNumber, discTracks]) => { + const data = useMemo(() => discTracks, [discTracks]); + const initialState: Partial & UseSortByState> = { + sortBy: [{ id: TrackTableColumn.Position, desc: false }] + }; + const columns = useMemo(() => [ + selectable && { + id: TrackTableColumn.Selection, + Header: SelectionHeader, + Cell: SelectionCell, + columnWidth: '7.5em' + }, + displayPosition && { + id: TrackTableColumn.Position, + Header: ({ column }) => } + header={positionHeader} + isCentered + />, + accessor: (track: T) => track.position, + Cell: PositionCell, + enableSorting: true, + columnWidth: '4em' + } as Column, + displayThumbnail && { + id: TrackTableColumn.Thumbnail, + Header: ({ column }) => , + accessor: (track: T) => getTrackThumbnail(track) || artPlaceholder, + Cell: ThumbnailCell, + columnWidth: '3em' + }, + displayFavorite && { + id: TrackTableColumn.Favorite, + accessor: isTrackFavorite, + Cell: FavoriteCell, + columnWidth: '3em' + }, + { + id: TrackTableColumn.Title, + Header: ({ column }) => , + accessor: (track: T) => track.title ?? track.name, + Cell: TitleCell, + enableSorting: true, + columnWidth: 'minmax(8em, 1fr)' + }, + displayArtist && { + id: TrackTableColumn.Artist, + Header: ({ column }) => , + accessor: (track: T) => isString(track.artist) + ? track.artist + : track.artist.name, + Cell: TextCell, + enableSorting: true, + columnWidth: '6em' + }, + displayAlbum && { + id: TrackTableColumn.Album, + Header: ({ column }: { column: TrackTableColumnInstance }) => , + accessor: (track: T) => track.album, + enableSorting: true, + Cell: TextCell, + columnWidth: '6em' + } as Column, + shouldDisplayDuration && { + id: TrackTableColumn.Duration, + Header: ({ column }) => , + accessor: (track: T) => { + if (isString(track.duration)) { + return track.duration; + } else if (isNumber(track.duration)) { + return formatDuration(track.duration); + } else { + return null; + } + }, + Cell: TextCell, + columnWidth: '6em' + }, + ...customColumns, + displayDeleteButton && { + id: TrackTableColumn.Delete, + Cell: DeleteCell, + columnWidth: '3em' + } as Column + ].filter(Boolean), [displayDeleteButton, displayPosition, displayThumbnail, displayFavorite, isTrackFavorite, titleHeader, displayArtist, artistHeader, displayAlbum, albumHeader, shouldDisplayDuration, durationHeader, selectable, positionHeader, thumbnailHeader]); + + const table = useTable( + { columns, data, initialState }, + useGlobalFilter, + useSortBy, + useRowSelect + ); + + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + setGlobalFilter, + state: tableState, + selectedFlatRows + } = table as TrackTableInstance; + + const onFilterClick = () => { + setGlobalFilter(''); + setGlobalFilterState(''); + }; + + const gridTemplateColumns = columns.map((column: ColumnWithWidth) => column.columnWidth ?? '1fr').join(' '); + + // Disabled when there are selected rows, or when sorted by anything other than position + const isDragDisabled = !onDragEnd || selectedFlatRows.length > 0 || (tableState as TrackTableState).sortBy[0]?.id !== TrackTableColumn.Position; + return ( +
+ {searchable && ( +
+ { + setGlobalFilter(e.target.value); + setGlobalFilterState(e.target.value); + }} + value={globalFilter} + /> +
+ )} +
+ {Object.keys(groupedDisc).length !== 1 && ( +

Disc {discNumber}

+ )} +
+ {headerGroups.map((headerGroup) => ( +
- {GridTrackTableRow} - - } - + {headerGroup.headers.map( + (column: TrackTableHeaderGroup) => ( +
+ {column.render('Header', extraProps)} +
+ ) + )} +
+ ))} +
+ + + + {(droppableProvided) => ( +
+ + {({ height, width }) => + [], + prepareRow: prepareRow as (row: Row) => void, + gridTemplateColumns, + isDragDisabled, + extraProps + }} + outerRef={droppableProvided.innerRef} + > + {GridTrackTableRow} + + } + +
+ )} +
+
- )} - - +
+ ); + } + )}
-
; + ); }; - + diff --git a/packages/ui/lib/types/index.ts b/packages/ui/lib/types/index.ts index 2c9027a6ea..c135f412bc 100644 --- a/packages/ui/lib/types/index.ts +++ b/packages/ui/lib/types/index.ts @@ -1,9 +1,9 @@ export type Track = { uuid?: string; loading?: boolean; - error?: boolean | { - message: string; - details: string + error?: boolean | { + message: string; + details: string }; local?: boolean; artist: { name: string } | string; @@ -12,7 +12,7 @@ export type Track = { album?: string; duration?: number | string; position?: number | string; - discNumber?: number; + discNumber?: number | string; playcount?: number | string; thumbnail?: string; image?: { '#text'?: string }[]; From 5b2662c7537147049bd5266811f94a6ccf02d3b1 Mon Sep 17 00:00:00 2001 From: Rishi Rallapalli Date: Sat, 23 Nov 2024 11:34:59 -0500 Subject: [PATCH 3/4] made unit tests and changed index.tsx to account for albums with no tracks --- .../core/src/plugins/meta/spotify.test.ts | 1 + .../lib/components/GridTrackTable/index.tsx | 374 +++++------ .../gridTrackTable.test.tsx.snap | 605 ++++++++++++++++++ packages/ui/test/gridTrackTable.test.tsx | 28 + 4 files changed, 790 insertions(+), 218 deletions(-) diff --git a/packages/core/src/plugins/meta/spotify.test.ts b/packages/core/src/plugins/meta/spotify.test.ts index b9c90c6c52..d4c788e0f6 100644 --- a/packages/core/src/plugins/meta/spotify.test.ts +++ b/packages/core/src/plugins/meta/spotify.test.ts @@ -380,6 +380,7 @@ describe('SpotifyMetaProvider', () => { title: 'test track', artist: 'test artist', duration: 1, + discNumber: 0, thumbnail: 'thumbnail.jpg' }] }); diff --git a/packages/ui/lib/components/GridTrackTable/index.tsx b/packages/ui/lib/components/GridTrackTable/index.tsx index 3a003db69d..c5104e9029 100644 --- a/packages/ui/lib/components/GridTrackTable/index.tsx +++ b/packages/ui/lib/components/GridTrackTable/index.tsx @@ -77,227 +77,165 @@ export const GridTrackTable = ({ const [globalFilter, setGlobalFilterState] = useState(''); // Required, because useGlobalFilter does not provide a way to get the current filter value - return ( -
- {Object.entries(groupedDisc).map(([discNumber, discTracks]) => { - const data = useMemo(() => discTracks, [discTracks]); - const initialState: Partial & UseSortByState> = { - sortBy: [{ id: TrackTableColumn.Position, desc: false }] - }; - const columns = useMemo(() => [ - selectable && { - id: TrackTableColumn.Selection, - Header: SelectionHeader, - Cell: SelectionCell, - columnWidth: '7.5em' - }, - displayPosition && { - id: TrackTableColumn.Position, - Header: ({ column }) => } - header={positionHeader} - isCentered - />, - accessor: (track: T) => track.position, - Cell: PositionCell, - enableSorting: true, - columnWidth: '4em' - } as Column, - displayThumbnail && { - id: TrackTableColumn.Thumbnail, - Header: ({ column }) => , - accessor: (track: T) => getTrackThumbnail(track) || artPlaceholder, - Cell: ThumbnailCell, - columnWidth: '3em' - }, - displayFavorite && { - id: TrackTableColumn.Favorite, - accessor: isTrackFavorite, - Cell: FavoriteCell, - columnWidth: '3em' - }, - { - id: TrackTableColumn.Title, - Header: ({ column }) => , - accessor: (track: T) => track.title ?? track.name, - Cell: TitleCell, - enableSorting: true, - columnWidth: 'minmax(8em, 1fr)' - }, - displayArtist && { - id: TrackTableColumn.Artist, - Header: ({ column }) => , - accessor: (track: T) => isString(track.artist) - ? track.artist - : track.artist.name, - Cell: TextCell, - enableSorting: true, - columnWidth: '6em' - }, - displayAlbum && { - id: TrackTableColumn.Album, - Header: ({ column }: { column: TrackTableColumnInstance }) => , - accessor: (track: T) => track.album, - enableSorting: true, - Cell: TextCell, - columnWidth: '6em' - } as Column, - shouldDisplayDuration && { - id: TrackTableColumn.Duration, - Header: ({ column }) => , - accessor: (track: T) => { - if (isString(track.duration)) { - return track.duration; - } else if (isNumber(track.duration)) { - return formatDuration(track.duration); - } else { - return null; - } - }, - Cell: TextCell, - columnWidth: '6em' - }, - ...customColumns, - displayDeleteButton && { - id: TrackTableColumn.Delete, - Cell: DeleteCell, - columnWidth: '3em' - } as Column - ].filter(Boolean), [displayDeleteButton, displayPosition, displayThumbnail, displayFavorite, isTrackFavorite, titleHeader, displayArtist, artistHeader, displayAlbum, albumHeader, shouldDisplayDuration, durationHeader, selectable, positionHeader, thumbnailHeader]); - - const table = useTable( - { columns, data, initialState }, - useGlobalFilter, - useSortBy, - useRowSelect - ); - - const { - getTableProps, - getTableBodyProps, - headerGroups, - rows, - prepareRow, - setGlobalFilter, - state: tableState, - selectedFlatRows - } = table as TrackTableInstance; - - const onFilterClick = () => { - setGlobalFilter(''); - setGlobalFilterState(''); - }; - - const gridTemplateColumns = columns.map((column: ColumnWithWidth) => column.columnWidth ?? '1fr').join(' '); - - // Disabled when there are selected rows, or when sorted by anything other than position - const isDragDisabled = !onDragEnd || selectedFlatRows.length > 0 || (tableState as TrackTableState).sortBy[0]?.id !== TrackTableColumn.Position; - return ( -
- {searchable && ( -
- { - setGlobalFilter(e.target.value); - setGlobalFilterState(e.target.value); - }} - value={globalFilter} - /> -
- )} -
- {Object.keys(groupedDisc).length !== 1 && ( -

Disc {discNumber}

- )} -
- {headerGroups.map((headerGroup) => ( -
- {headerGroup.headers.map( - (column: TrackTableHeaderGroup) => ( -
- {column.render('Header', extraProps)} -
- ) - )} + const generateColumns = () => [ + selectable && { + id: TrackTableColumn.Selection, + Header: SelectionHeader, + Cell: SelectionCell, + columnWidth: '7.5em' + }, + displayPosition && { + id: TrackTableColumn.Position, + Header: ({ column }) => , + accessor: (track: T) => track.position, + Cell: PositionCell, + enableSorting: true, + columnWidth: '4em' + }, + displayThumbnail && { + id: TrackTableColumn.Thumbnail, + Header: ({ column }) => , + accessor: (track: T) => getTrackThumbnail(track) || artPlaceholder, + Cell: ThumbnailCell, + columnWidth: '3em' + }, + displayFavorite && { + id: TrackTableColumn.Favorite, + accessor: isTrackFavorite, + Cell: FavoriteCell, + columnWidth: '3em' + }, + { + id: TrackTableColumn.Title, + Header: ({ column }) => , + accessor: (track: T) => track.title ?? track.name, + Cell: TitleCell, + enableSorting: true, + columnWidth: 'minmax(8em, 1fr)' + }, + displayArtist && { + id: TrackTableColumn.Artist, + Header: ({ column }) => , + accessor: (track: T) => isString(track.artist) ? track.artist : track.artist.name, + Cell: TextCell, + enableSorting: true, + columnWidth: '6em' + }, + displayAlbum && { + id: TrackTableColumn.Album, + Header: ({ column }) => , + accessor: (track: T) => track.album, + enableSorting: true, + Cell: TextCell, + columnWidth: '6em' + }, + shouldDisplayDuration && { + id: TrackTableColumn.Duration, + Header: ({ column }) => , + accessor: (track: T) => isString(track.duration) ? track.duration : formatDuration(track.duration), + Cell: TextCell, + columnWidth: '6em' + }, + ...customColumns, + displayDeleteButton && { + id: TrackTableColumn.Delete, + Cell: DeleteCell, + columnWidth: '3em' + } + ].filter(Boolean); + + const renderTable = (data: T[], discNumber?: string) => { + const columns = useMemo(generateColumns, [displayDeleteButton, displayPosition, displayThumbnail, displayFavorite, isTrackFavorite, titleHeader, displayArtist, artistHeader, displayAlbum, albumHeader, shouldDisplayDuration, durationHeader, selectable, positionHeader, thumbnailHeader]); + + const table = useTable( + { columns, data, initialState: { sortBy: [{ id: TrackTableColumn.Position, desc: false }] } }, + useGlobalFilter, useSortBy, useRowSelect + ); + + const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, setGlobalFilter, state: tableState, selectedFlatRows } = table as TrackTableInstance; + const onFilterClick = () => setGlobalFilter(''); + const gridTemplateColumns = columns.map((col: ColumnWithWidth) => col.columnWidth ?? '1fr').join(' '); + + const isDragDisabled = !onDragEnd || selectedFlatRows.length > 0 || (tableState as TrackTableState).sortBy[0]?.id !== TrackTableColumn.Position; + + return ( +
+ {searchable && ( +
+ setGlobalFilter(e.target.value)} + value={globalFilter} + /> +
+ )} +
+ {discNumber &&

Disc {discNumber}

} +
+ {headerGroups.map((headerGroup) => ( +
+ {headerGroup.headers.map((column) => ( +
+ {column.render('Header', extraProps)}
))}
- - - - {(droppableProvided) => ( -
- - {({ height, width }) => - [], - prepareRow: prepareRow as (row: Row) => void, - gridTemplateColumns, - isDragDisabled, - extraProps - }} - outerRef={droppableProvided.innerRef} - > - {GridTrackTableRow} - - } - -
- )} -
-
-
+ ))}
- ); - } - )} -
- ); + + + {(droppableProvided) => ( +
+ + {({ height, width }) => ( + [], + prepareRow: prepareRow as (row: Row) => void, + gridTemplateColumns, + isDragDisabled, + extraProps + }} + outerRef={droppableProvided.innerRef} + > + {GridTrackTableRow} + + )} + +
+ )} +
+
+
+
+ ); + }; + + if (Object.keys(groupedDisc).length < 2) { + return renderTable(tracks); + } else { + return ( +
+ {Object.entries(groupedDisc).map(([discNumber, discTracks]) => renderTable(discTracks, discNumber))} +
+ ); + } + }; diff --git a/packages/ui/test/__snapshots__/gridTrackTable.test.tsx.snap b/packages/ui/test/__snapshots__/gridTrackTable.test.tsx.snap index d759a8cf26..4b4c0c5cf8 100644 --- a/packages/ui/test/__snapshots__/gridTrackTable.test.tsx.snap +++ b/packages/ui/test/__snapshots__/gridTrackTable.test.tsx.snap @@ -736,3 +736,608 @@ exports[`(Snapshot) Grid track table - example data with all rows should render
`; + +exports[`(Snapshot) Grid track table - example data with multiple discs should render correctly 1`] = ` + +
+
+
+

+ Disc 0 +

+
+
+
+
+
+ +
+
+
+
+
+ Position +