diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fd9304f..b548e818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * When browsing albums sorted by artist, each artist's albums appeared in an arbitrary order. They are now ordered A–Z by album title within each artist. +### "Add to playlist" from the player bar added the whole album + +**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1117](https://github.com/Psychotoxical/psysonic/pull/1117)** + +* Right-clicking the current track in the player bar opened an album menu, so "Add to playlist" added the entire album instead of the playing song. The player bar menu now acts on the current song. + ## [1.48.1] - 2026-06-15 diff --git a/src/components/PlayerBar.test.tsx b/src/components/PlayerBar.test.tsx index e9d98bc1..4e57f3cf 100644 --- a/src/components/PlayerBar.test.tsx +++ b/src/components/PlayerBar.test.tsx @@ -144,6 +144,25 @@ describe('PlayerBar — control wiring', () => { }); }); +describe('PlayerBar — current track context menu', () => { + it('right-clicking the track name opens a song-scoped menu for the current track (#1116)', () => { + const track = makeTrack({ id: 'cur', albumId: 'alb-1' }); + usePlayerStore.setState({ currentTrack: track, isPlaying: true }); + const spy = vi.spyOn(usePlayerStore.getState(), 'openContextMenu'); + + const { container } = renderWithProviders(); + const trackName = container.querySelector('.player-track-name'); + expect(trackName).not.toBeNull(); + fireEvent.contextMenu(trackName!); + + expect(spy).toHaveBeenCalledTimes(1); + const [, , item, type, , , , , pin] = spy.mock.calls[0]; + expect(item).toBe(track); + expect(type).toBe('song'); + expect(pin).toBe(true); + }); +}); + describe('PlayerBar — empty state (no current track)', () => { it('still renders the region landmark when no track is loaded', () => { usePlayerStore.setState({ currentTrack: null, isPlaying: false }); diff --git a/src/components/playerBar/PlayerTrackInfo.tsx b/src/components/playerBar/PlayerTrackInfo.tsx index dd028495..fa7725d2 100644 --- a/src/components/playerBar/PlayerTrackInfo.tsx +++ b/src/components/playerBar/PlayerTrackInfo.tsx @@ -1,7 +1,7 @@ import { Cast, Heart, Maximize2, Music } from 'lucide-react'; import type { TFunction } from 'i18next'; import { queueSongRating } from '../../store/pendingStarSync'; -import type { InternetRadioStation, SubsonicAlbum, SubsonicOpenArtistRef } from '../../api/subsonicTypes'; +import type { InternetRadioStation, SubsonicOpenArtistRef } from '../../api/subsonicTypes'; import type { PlayerState, Track } from '../../store/playerStoreTypes'; import type { RadioMetadata } from '../../hooks/useRadioMetadata'; import type { PreviewingTrack } from '../../store/previewStore'; @@ -137,19 +137,14 @@ export function PlayerTrackInfo({ className="player-track-name" style={{ cursor: !isRadio && !showPreviewMeta && currentTrack?.albumId ? 'pointer' : 'default' }} onClick={() => !isRadio && !showPreviewMeta && currentTrack?.albumId && navigate(`/album/${currentTrack.albumId}`)} - onContextMenu={!isRadio && !showPreviewMeta && currentTrack?.albumId + onContextMenu={!isRadio && !showPreviewMeta && currentTrack ? (e) => { e.preventDefault(); - const album: SubsonicAlbum = { - id: currentTrack.albumId!, - name: currentTrack.album, - artist: currentTrack.artist, - artistId: currentTrack.artistId ?? '', - coverArt: currentTrack.coverArt, - songCount: 0, - duration: 0, - }; - openContextMenu(e.clientX, e.clientY, album, 'album', undefined, undefined, undefined, undefined, true); + // The player bar represents the current song, so its menu is + // song-scoped (e.g. "Add to playlist" adds this track, not the + // whole album). pinToPlaybackServer: the track plays from the + // playback server, which may differ from the active one. + openContextMenu(e.clientX, e.clientY, currentTrack, 'song', undefined, undefined, undefined, undefined, true); } : undefined} />