From 2beb30f871fb2558724479864506f542a36a86e9 Mon Sep 17 00:00:00 2001 From: Frank Stellmacher <171614930+Psychotoxical@users.noreply.github.com> Date: Sun, 17 May 2026 12:55:31 +0200 Subject: [PATCH] fix(favorites): artist link no-play + inline bulk action chips (#746) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(favorites): artist link in songs table no longer triggers playback The artist cell's click handler navigated without `stopPropagation`, so the row's onClick still fired and started the track. Matches the album cell's behaviour and the other tracklists (SongRow, PlaylistTracklist, PlaylistSuggestions, SongCard, AlbumCard) — all of which already guard the navigation click. * fix(favorites): inline bulk action chips in section header The full-width bulk-action bar above the column header pushed every row down by ~36 px when an item was selected, making it harder to pick adjacent items. Move the "X selected / Add to playlist / Clear" cluster into the existing action-buttons row (right-aligned via margin-left: auto), matching the album toolbar pattern. Selection mode no longer shifts the rows. Clear-selection button uses `btn-surface` to match the other secondary actions on the page (post-#745 convention). * docs(changelog): note Favorites artist-link + bulk-bar fixes (#746) --- CHANGELOG.md | 7 +++ .../favorites/FavoritesSongsSectionHeader.tsx | 40 +++++++++++++++ .../favorites/FavoritesSongsTracklist.tsx | 50 +++++-------------- src/pages/Favorites.tsx | 6 ++- 4 files changed, 63 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 099614bb..9253d5ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -638,6 +638,13 @@ Foundational work: faster reviews, narrower diffs, and a safety net under the pa * Hero pills stay visible against light-toned cover art (opaque fill), and the pagination dots are readable on every backdrop (brighter inactive dot with a dark outline, accent-coloured active dot). * Composers grid no longer reserves ~200 px per virtual row for ~78 px text-only tiles. The Tracks "browse all" header now lives inside the scroll container so columns line up with the rows under wider fonts like **OpenDyslexic**, and the header stays pinned while scrolling. +### Favorites — artist link no longer triggers playback, bulk selection no longer shifts the rows + +**By [@Psychotoxical](https://github.com/Psychotoxical), thanks to zunoz for the report on the Psysonic Discord, PR [#746](https://github.com/Psychotoxical/psysonic/pull/746)** + +* Clicking the **artist** in the Favorites songs table opened the artist page _and_ started the song — the cell was missing the click guard the album cell already had. Now matches every other tracklist in the app. +* Selecting a song no longer pushes the column header and every row down by one line. The "X selected / Add to playlist / Clear" cluster moved out of the full-width bar into the existing action-buttons row (right-aligned), matching the album toolbar, so the next item stays under the same cursor position. + ## [1.45.0] - 2026-05-04 ## Added diff --git a/src/components/favorites/FavoritesSongsSectionHeader.tsx b/src/components/favorites/FavoritesSongsSectionHeader.tsx index ee67d39e..5b91ec71 100644 --- a/src/components/favorites/FavoritesSongsSectionHeader.tsx +++ b/src/components/favorites/FavoritesSongsSectionHeader.tsx @@ -3,7 +3,9 @@ import { useTranslation } from 'react-i18next'; import { ListPlus, Play, SlidersHorizontal, X } from 'lucide-react'; import type { SubsonicSong } from '../../api/subsonicTypes'; import { usePlayerStore } from '../../store/playerStore'; +import { useSelectionStore } from '../../store/selectionStore'; import { songToTrack } from '../../utils/playback/songToTrack'; +import { AddToPlaylistSubmenu } from '../ContextMenu'; import GenreFilterBar from '../GenreFilterBar'; interface Props { @@ -25,6 +27,10 @@ interface Props { starredOverrides: Record; minYear: number; currentYear: number; + inSelectMode: boolean; + selectedCount: number; + showPlPicker: boolean; + setShowPlPicker: React.Dispatch>; } export default function FavoritesSongsSectionHeader({ @@ -32,6 +38,7 @@ export default function FavoritesSongsSectionHeader({ selectedGenres, setSelectedGenres, yearRange, setYearRange, showFilters, setShowFilters, setSortKey, setSortClickCount, playTrack, enqueue, starredOverrides, minYear, currentYear, + inSelectMode, selectedCount, showPlPicker, setShowPlPicker, }: Props) { const { t } = useTranslation(); @@ -100,6 +107,39 @@ export default function FavoritesSongsSectionHeader({ {t('common.clearAll')} )} + + {/* Bulk action chips — inline at row end so a selection does not + push the column header / rows downward (matches Album toolbar). */} + {inSelectMode && ( +
+ + {t('common.bulkSelected', { count: selectedCount })} + +
+ + {showPlPicker && ( + { setShowPlPicker(false); useSelectionStore.getState().clearAll(); }} + dropDown + /> + )} +
+ +
+ )} {/* Filters Panel */} diff --git a/src/components/favorites/FavoritesSongsTracklist.tsx b/src/components/favorites/FavoritesSongsTracklist.tsx index 682c36ce..4ec1d2e7 100644 --- a/src/components/favorites/FavoritesSongsTracklist.tsx +++ b/src/components/favorites/FavoritesSongsTracklist.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { - AudioLines, Check, ChevronDown, ChevronRight, ListPlus, Play, RotateCcw, + AudioLines, Check, ChevronDown, ChevronRight, Play, RotateCcw, Square, X, } from 'lucide-react'; import type { ColDef } from '../../utils/useTracklistColumns'; @@ -16,7 +16,6 @@ import { songToTrack } from '../../utils/playback/songToTrack'; import { formatTrackTime } from '../../utils/format/formatDuration'; import { formatLastSeen } from '../../utils/componentHelpers/userMgmtHelpers'; import i18n from '../../i18n'; -import { AddToPlaylistSubmenu } from '../ContextMenu'; import StarRating from '../StarRating'; const SORTABLE_COLUMNS = new Set(['title', 'artist', 'album', 'rating', 'duration', 'playCount', 'lastPlayed', 'bpm']); @@ -27,8 +26,6 @@ interface Props { selectedCount: number; inSelectMode: boolean; toggleSelect: (id: string, idx: number, shift: boolean) => void; - showPlPicker: boolean; - setShowPlPicker: React.Dispatch>; allColumns: readonly ColDef[]; visibleCols: ColDef[]; gridStyle: React.CSSProperties; @@ -50,7 +47,6 @@ interface Props { export default function FavoritesSongsTracklist({ visibleSongs, selectedIds, selectedCount, inSelectMode, toggleSelect, - showPlPicker, setShowPlPicker, allColumns, visibleCols, gridStyle, colVisible, toggleColumn, resetColumns, pickerOpen, setPickerOpen, pickerRef, tracklistRef, startResize, handleSortClick, getSortIndicator, @@ -73,38 +69,6 @@ export default function FavoritesSongsTracklist({ if (inSelectMode && e.target === e.currentTarget) useSelectionStore.getState().clearAll(); }}> - {/* ── Bulk action bar ── */} - {inSelectMode && ( -
- - {t('common.bulkSelected', { count: selectedCount })} - -
- - {showPlPicker && ( - { setShowPlPicker(false); useSelectionStore.getState().clearAll(); }} - dropDown - /> - )} -
- -
- )} - {/* Column visibility picker */}
@@ -306,7 +270,17 @@ export default function FavoritesSongsTracklist({ ); case 'artist': return (
- song.artistId && navigate(`/artist/${song.artistId}`)}>{song.artist} + { + if (!song.artistId) return; + e.stopPropagation(); + navigate(`/artist/${song.artistId}`); + }} + > + {song.artist} +
); case 'album': return ( diff --git a/src/pages/Favorites.tsx b/src/pages/Favorites.tsx index bb11414a..224bc931 100644 --- a/src/pages/Favorites.tsx +++ b/src/pages/Favorites.tsx @@ -194,6 +194,10 @@ export default function Favorites() { starredOverrides={starredOverrides} minYear={MIN_YEAR} currentYear={CURRENT_YEAR} + inSelectMode={inSelectMode} + selectedCount={selectedCount} + showPlPicker={showPlPicker} + setShowPlPicker={setShowPlPicker} />