mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
fix(favorites): artist link no-play + inline bulk action chips (#746)
* 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)
This commit is contained in:
committed by
GitHub
parent
3b94368ffa
commit
2beb30f871
@@ -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<string, boolean>;
|
||||
minYear: number;
|
||||
currentYear: number;
|
||||
inSelectMode: boolean;
|
||||
selectedCount: number;
|
||||
showPlPicker: boolean;
|
||||
setShowPlPicker: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
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')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Bulk action chips — inline at row end so a selection does not
|
||||
push the column header / rows downward (matches Album toolbar). */}
|
||||
{inSelectMode && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginLeft: 'auto' }}>
|
||||
<span className="bulk-action-count">
|
||||
{t('common.bulkSelected', { count: selectedCount })}
|
||||
</span>
|
||||
<div className="bulk-pl-picker-wrap">
|
||||
<button
|
||||
className="btn btn-surface btn-sm"
|
||||
onClick={() => setShowPlPicker(v => !v)}
|
||||
>
|
||||
<ListPlus size={14} />
|
||||
{t('common.bulkAddToPlaylist')}
|
||||
</button>
|
||||
{showPlPicker && (
|
||||
<AddToPlaylistSubmenu
|
||||
songIds={[...useSelectionStore.getState().selectedIds]}
|
||||
onDone={() => { setShowPlPicker(false); useSelectionStore.getState().clearAll(); }}
|
||||
dropDown
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-surface btn-sm"
|
||||
onClick={() => useSelectionStore.getState().clearAll()}
|
||||
>
|
||||
<X size={13} />
|
||||
{t('common.bulkClear')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Filters Panel */}
|
||||
|
||||
@@ -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<React.SetStateAction<boolean>>;
|
||||
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 && (
|
||||
<div className="bulk-action-bar">
|
||||
<span className="bulk-action-count">
|
||||
{t('common.bulkSelected', { count: selectedCount })}
|
||||
</span>
|
||||
<div className="bulk-pl-picker-wrap">
|
||||
<button
|
||||
className="btn btn-surface btn-sm"
|
||||
onClick={() => setShowPlPicker(v => !v)}
|
||||
>
|
||||
<ListPlus size={14} />
|
||||
{t('common.bulkAddToPlaylist')}
|
||||
</button>
|
||||
{showPlPicker && (
|
||||
<AddToPlaylistSubmenu
|
||||
songIds={[...useSelectionStore.getState().selectedIds]}
|
||||
onDone={() => { setShowPlPicker(false); useSelectionStore.getState().clearAll(); }}
|
||||
dropDown
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={() => useSelectionStore.getState().clearAll()}
|
||||
>
|
||||
<X size={13} />
|
||||
{t('common.bulkClear')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Column visibility picker */}
|
||||
<div className="tracklist-col-picker-wrapper" ref={pickerRef}>
|
||||
<div className="tracklist-col-picker">
|
||||
@@ -306,7 +270,17 @@ export default function FavoritesSongsTracklist({
|
||||
);
|
||||
case 'artist': return (
|
||||
<div key="artist" className="track-artist-cell">
|
||||
<span className={`track-artist${song.artistId ? ' track-artist-link' : ''}`} style={{ cursor: song.artistId ? 'pointer' : 'default' }} onClick={() => song.artistId && navigate(`/artist/${song.artistId}`)}>{song.artist}</span>
|
||||
<span
|
||||
className={`track-artist${song.artistId ? ' track-artist-link' : ''}`}
|
||||
style={{ cursor: song.artistId ? 'pointer' : 'default' }}
|
||||
onClick={(e) => {
|
||||
if (!song.artistId) return;
|
||||
e.stopPropagation();
|
||||
navigate(`/artist/${song.artistId}`);
|
||||
}}
|
||||
>
|
||||
{song.artist}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 'album': return (
|
||||
|
||||
@@ -194,6 +194,10 @@ export default function Favorites() {
|
||||
starredOverrides={starredOverrides}
|
||||
minYear={MIN_YEAR}
|
||||
currentYear={CURRENT_YEAR}
|
||||
inSelectMode={inSelectMode}
|
||||
selectedCount={selectedCount}
|
||||
showPlPicker={showPlPicker}
|
||||
setShowPlPicker={setShowPlPicker}
|
||||
/>
|
||||
<FavoritesSongsTracklist
|
||||
visibleSongs={visibleSongs}
|
||||
@@ -201,8 +205,6 @@ export default function Favorites() {
|
||||
selectedCount={selectedCount}
|
||||
inSelectMode={inSelectMode}
|
||||
toggleSelect={toggleSelect}
|
||||
showPlPicker={showPlPicker}
|
||||
setShowPlPicker={setShowPlPicker}
|
||||
allColumns={FAV_COLUMNS}
|
||||
visibleCols={visibleCols}
|
||||
gridStyle={gridStyle}
|
||||
|
||||
Reference in New Issue
Block a user