fix(ui): always order album tracks by disc and track number (fixes #3720) (#3975)

* fix(ui): ensure album tracks are always ordered by disc and track number (fixes #3720)

* refactor(ui): remove obsolete release date grouping logic from SongDatagrid and AlbumSongs

* fix(ui): ensure correct album track ordering in context menu and play button

* fix: Update album sort to use album_id instead of release_date

* refactor: Adjust filters in PlayButton and AlbumContextMenu

* fix: correct typo in comment regarding participants in GetMissingAndMatching function

* fix: prevent visual separation of tracks on same disc

Removes the leftover `releaseDate` check from the `firstTracksOfDiscs` calculation in `SongDatagridBody`. This check caused unnecessary `DiscSubtitleRow` insertions when tracks on the same disc had different release dates, leading to an incorrect visual grouping that resembled a multi-disc layout.

This change ensures disc subtitles are only shown when the actual `discNumber` changes, correcting the UI presentation issue reported in issue #3720 after PR #3975.

* fix: remove remaining releaseDate references in SongDatagrid

Cleaned up leftover `releaseDate` references in `SongDatagrid.jsx`:

- Removed `releaseDate` parameter and usage from `handlePlaySubset` in `DiscSubtitleRow`.

- Removed `releaseDate` prop passed to `AlbumContextMenu` in `DiscSubtitleRow`.

- Removed `releaseDate` from the drag item data in `SongDatagridRow`.

- Removed `releaseDate` parameter and the corresponding `else` block from the `playSubset` function in `SongDatagridBody`.

This ensures the component consistently uses `discNumber` for grouping and playback actions initiated from the disc subtitle, fully resolving the inconsistencies related to issue #3720.
This commit is contained in:
Deluan Quintão
2025-04-17 19:23:53 -04:00
committed by GitHub
parent 74803bb43e
commit ed7ee3d9f8
5 changed files with 8 additions and 107 deletions
+2 -2
View File
@@ -77,7 +77,7 @@ func NewMediaFileRepository(ctx context.Context, db dbx.Builder) model.MediaFile
"title": "order_title", "title": "order_title",
"artist": "order_artist_name, order_album_name, release_date, disc_number, track_number", "artist": "order_artist_name, order_album_name, release_date, disc_number, track_number",
"album_artist": "order_album_artist_name, order_album_name, release_date, disc_number, track_number", "album_artist": "order_album_artist_name, order_album_name, release_date, disc_number, track_number",
"album": "order_album_name, release_date, disc_number, track_number, order_artist_name, title", "album": "order_album_name, album_id, disc_number, track_number, order_artist_name, title",
"random": "random", "random": "random",
"created_at": "media_file.created_at", "created_at": "media_file.created_at",
"starred_at": "starred, starred_at", "starred_at": "starred, starred_at",
@@ -242,7 +242,7 @@ func (r *mediaFileRepository) MarkMissingByFolder(missing bool, folderIDs ...str
// GetMissingAndMatching returns all mediafiles that are missing and their potential matches (comparing PIDs) // GetMissingAndMatching returns all mediafiles that are missing and their potential matches (comparing PIDs)
// that were added/updated after the last scan started. The result is ordered by PID. // that were added/updated after the last scan started. The result is ordered by PID.
// It does not need to load bookmarks, annotations and participnts, as they are not used by the scanner. // It does not need to load bookmarks, annotations and participants, as they are not used by the scanner.
func (r *mediaFileRepository) GetMissingAndMatching(libId int) (model.MediaFileCursor, error) { func (r *mediaFileRepository) GetMissingAndMatching(libId int) (model.MediaFileCursor, error) {
subQ := r.newSelect().Columns("pid"). subQ := r.newSelect().Columns("pid").
Where(And{ Where(And{
-1
View File
@@ -185,7 +185,6 @@ const AlbumSongs = (props) => {
{...props} {...props}
hasBulkActions={true} hasBulkActions={true}
showDiscSubtitles={true} showDiscSubtitles={true}
showReleaseDivider={true}
contextAlwaysVisible={!isDesktop} contextAlwaysVisible={!isDesktop}
classes={{ row: classes.row }} classes={{ row: classes.row }}
> >
-1
View File
@@ -231,7 +231,6 @@ export const AlbumContextMenu = (props) =>
sort: { field: 'album', order: 'ASC' }, sort: { field: 'album', order: 'ASC' },
filter: { filter: {
album_id: props.record.id, album_id: props.record.id,
release_date: props.releaseDate,
disc_number: props.discNumber, disc_number: props.discNumber,
missing: false, missing: false,
}, },
-1
View File
@@ -24,7 +24,6 @@ export const PlayButton = ({ record, size, className }) => {
sort: { field: 'album', order: 'ASC' }, sort: { field: 'album', order: 'ASC' },
filter: { filter: {
album_id: record.id, album_id: record.id,
release_date: record.releaseDate,
disc_number: record.discNumber, disc_number: record.discNumber,
}, },
}) })
+6 -102
View File
@@ -59,59 +59,12 @@ const useStyles = makeStyles({
}, },
}) })
const ReleaseRow = forwardRef(
({ record, onClick, colSpan, contextAlwaysVisible }, ref) => {
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
const classes = useStyles({ isDesktop })
const translate = useTranslate()
const handlePlaySubset = (releaseDate) => () => {
onClick(releaseDate)
}
let releaseTitle = []
if (record.releaseDate) {
releaseTitle.push(translate('resources.album.fields.released'))
releaseTitle.push(formatFullDate(record.releaseDate))
if (record.catalogNum && isDesktop) {
releaseTitle.push('· Cat #')
releaseTitle.push(record.catalogNum)
}
}
return (
<TableRow
hover
ref={ref}
onClick={handlePlaySubset(record.releaseDate)}
className={classes.row}
>
<TableCell colSpan={colSpan}>
<Typography variant="h6" className={classes.subtitle}>
{releaseTitle.join(' ')}
</Typography>
</TableCell>
<TableCell>
<AlbumContextMenu
record={{ id: record.albumId }}
releaseDate={record.releaseDate}
showLove={false}
className={classes.contextMenu}
visible={contextAlwaysVisible}
/>
</TableCell>
</TableRow>
)
},
)
ReleaseRow.displayName = 'ReleaseRow'
const DiscSubtitleRow = forwardRef( const DiscSubtitleRow = forwardRef(
({ record, onClick, colSpan, contextAlwaysVisible }, ref) => { ({ record, onClick, colSpan, contextAlwaysVisible }, ref) => {
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')) const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
const classes = useStyles({ isDesktop }) const classes = useStyles({ isDesktop })
const handlePlaySubset = (releaseDate, discNumber) => () => { const handlePlaySubset = (discNumber) => () => {
onClick(releaseDate, discNumber) onClick(discNumber)
} }
let subtitle = [] let subtitle = []
@@ -126,7 +79,7 @@ const DiscSubtitleRow = forwardRef(
<TableRow <TableRow
hover hover
ref={ref} ref={ref}
onClick={handlePlaySubset(record.releaseDate, record.discNumber)} onClick={handlePlaySubset(record.discNumber)}
className={classes.row} className={classes.row}
> >
<TableCell colSpan={colSpan}> <TableCell colSpan={colSpan}>
@@ -139,7 +92,6 @@ const DiscSubtitleRow = forwardRef(
<AlbumContextMenu <AlbumContextMenu
record={{ id: record.albumId }} record={{ id: record.albumId }}
discNumber={record.discNumber} discNumber={record.discNumber}
releaseDate={record.releaseDate}
showLove={false} showLove={false}
className={classes.contextMenu} className={classes.contextMenu}
hideShare={true} hideShare={true}
@@ -158,7 +110,6 @@ export const SongDatagridRow = ({
record, record,
children, children,
firstTracksOfDiscs, firstTracksOfDiscs,
firstTracksOfReleases,
contextAlwaysVisible, contextAlwaysVisible,
onClickSubset, onClickSubset,
className, className,
@@ -176,7 +127,6 @@ export const SongDatagridRow = ({
discs: [ discs: [
{ {
albumId: record?.albumId, albumId: record?.albumId,
releaseDate: record?.releaseDate,
discNumber: record?.discNumber, discNumber: record?.discNumber,
}, },
], ],
@@ -209,15 +159,6 @@ export const SongDatagridRow = ({
const childCount = fields.length const childCount = fields.length
return ( return (
<> <>
{firstTracksOfReleases.has(record.id) && (
<ReleaseRow
ref={dragDiscRef}
record={record}
onClick={onClickSubset}
contextAlwaysVisible={contextAlwaysVisible}
colSpan={childCount + (rest.expand ? 1 : 0)}
/>
)}
{firstTracksOfDiscs.has(record.id) && ( {firstTracksOfDiscs.has(record.id) && (
<DiscSubtitleRow <DiscSubtitleRow
ref={dragDiscRef} ref={dragDiscRef}
@@ -244,7 +185,6 @@ SongDatagridRow.propTypes = {
record: PropTypes.object, record: PropTypes.object,
children: PropTypes.node, children: PropTypes.node,
firstTracksOfDiscs: PropTypes.instanceOf(Set), firstTracksOfDiscs: PropTypes.instanceOf(Set),
firstTracksOfReleases: PropTypes.instanceOf(Set),
contextAlwaysVisible: PropTypes.bool, contextAlwaysVisible: PropTypes.bool,
onClickSubset: PropTypes.func, onClickSubset: PropTypes.func,
} }
@@ -256,23 +196,16 @@ SongDatagridRow.defaultProps = {
const SongDatagridBody = ({ const SongDatagridBody = ({
contextAlwaysVisible, contextAlwaysVisible,
showDiscSubtitles, showDiscSubtitles,
showReleaseDivider,
...rest ...rest
}) => { }) => {
const dispatch = useDispatch() const dispatch = useDispatch()
const { ids, data } = rest const { ids, data } = rest
const playSubset = useCallback( const playSubset = useCallback(
(releaseDate, discNumber) => { (discNumber) => {
let idsToPlay = [] let idsToPlay = []
if (discNumber !== undefined) { if (discNumber !== undefined) {
idsToPlay = ids.filter( idsToPlay = ids.filter((id) => data[id].discNumber === discNumber)
(id) =>
data[id].releaseDate === releaseDate &&
data[id].discNumber === discNumber,
)
} else {
idsToPlay = ids.filter((id) => data[id].releaseDate === releaseDate)
} }
dispatch( dispatch(
playTracks( playTracks(
@@ -297,8 +230,7 @@ const SongDatagridBody = ({
foundSubtitle = foundSubtitle || data[id].discSubtitle foundSubtitle = foundSubtitle || data[id].discSubtitle
if ( if (
acc.length === 0 || acc.length === 0 ||
(last && data[id].discNumber !== data[last].discNumber) || (last && data[id].discNumber !== data[last].discNumber)
(last && data[id].releaseDate !== data[last].releaseDate)
) { ) {
acc.push(id) acc.push(id)
} }
@@ -311,37 +243,12 @@ const SongDatagridBody = ({
return set return set
}, [ids, data, showDiscSubtitles]) }, [ids, data, showDiscSubtitles])
const firstTracksOfReleases = useMemo(() => {
if (!ids) {
return new Set()
}
const set = new Set(
ids
.filter((i) => data[i])
.reduce((acc, id) => {
const last = acc && acc[acc.length - 1]
if (
acc.length === 0 ||
(last && data[id].releaseDate !== data[last].releaseDate)
) {
acc.push(id)
}
return acc
}, []),
)
if (!showReleaseDivider || set.size < 2) {
set.clear()
}
return set
}, [ids, data, showReleaseDivider])
return ( return (
<PureDatagridBody <PureDatagridBody
{...rest} {...rest}
row={ row={
<SongDatagridRow <SongDatagridRow
firstTracksOfDiscs={firstTracksOfDiscs} firstTracksOfDiscs={firstTracksOfDiscs}
firstTracksOfReleases={firstTracksOfReleases}
contextAlwaysVisible={contextAlwaysVisible} contextAlwaysVisible={contextAlwaysVisible}
onClickSubset={playSubset} onClickSubset={playSubset}
/> />
@@ -353,7 +260,6 @@ const SongDatagridBody = ({
export const SongDatagrid = ({ export const SongDatagrid = ({
contextAlwaysVisible, contextAlwaysVisible,
showDiscSubtitles, showDiscSubtitles,
showReleaseDivider,
...rest ...rest
}) => { }) => {
const classes = useStyles() const classes = useStyles()
@@ -366,7 +272,6 @@ export const SongDatagrid = ({
<SongDatagridBody <SongDatagridBody
contextAlwaysVisible={contextAlwaysVisible} contextAlwaysVisible={contextAlwaysVisible}
showDiscSubtitles={showDiscSubtitles} showDiscSubtitles={showDiscSubtitles}
showReleaseDivider={showReleaseDivider}
/> />
} }
/> />
@@ -376,6 +281,5 @@ export const SongDatagrid = ({
SongDatagrid.propTypes = { SongDatagrid.propTypes = {
contextAlwaysVisible: PropTypes.bool, contextAlwaysVisible: PropTypes.bool,
showDiscSubtitles: PropTypes.bool, showDiscSubtitles: PropTypes.bool,
showReleaseDivider: PropTypes.bool,
classes: PropTypes.object, classes: PropTypes.object,
} }