mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
refactor(playlist): G.65 — extract Tracklist + FilterToolbar + displayedSongs (cluster) (#632)
Three-cut cluster on PlaylistDetail.tsx. 1065 → 742 LOC (−323). PlaylistTracklist — the big one. Owns the bulk-action bar, column visibility picker, sortable header (3-click cycle: asc → desc → natural, plus arrow indicator + drag-resize handles between columns), empty state with "add first song" button, and the song rows themselves (drag-over indicators, current-track highlight, bulk-selection check, ctrl/meta/shift selection vs. play vs. orbit-queue-hint, inline play-next + preview buttons in the title cell, artist/album links, star/rating cells, format/duration/delete columns). Subscribes directly to playerStore (currentTrack / isPlaying / playTrack / openContextMenu / starredOverrides / userRatingOverrides), previewStore (previewingId / audioStarted), themeStore (showBitrate), useDragDrop (isDragging), useOrbitSongRowBehavior — so the parent doesn't have to thread any of that through props. PL_CENTERED moves to the component because the only remaining tracklist header lives there now. PlaylistFilterToolbar — small filter input with clear-X. playlistDisplayedSongs — pure `getDisplayedSongs(songs, opts)` that returns the filtered + sorted song list. Exports PlaylistSortKey / PlaylistSortDir types so PlaylistDetail's sortKey/sortDir state and PlaylistTracklist's props share the same union types. PlaylistDetail's import list loses the icons/components that only the tracklist used (AudioLines, ChevronDown, Check, Heart, RotateCcw, StarRating, AddToPlaylistSubmenu) — they followed the component to the new file. Pure code move otherwise.
This commit is contained in:
committed by
GitHub
parent
2e57f54bb2
commit
0b84a199e4
@@ -0,0 +1,43 @@
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
|
||||
export type PlaylistSortKey = 'natural' | 'title' | 'artist' | 'album' | 'favorite' | 'rating' | 'duration';
|
||||
export type PlaylistSortDir = 'asc' | 'desc';
|
||||
|
||||
export interface DisplayedSongsOptions {
|
||||
filterText: string;
|
||||
sortKey: PlaylistSortKey;
|
||||
sortDir: PlaylistSortDir;
|
||||
ratings: Record<string, number>;
|
||||
userRatingOverrides: Record<string, number>;
|
||||
starredOverrides: Record<string, boolean>;
|
||||
starredSongs: Set<string>;
|
||||
}
|
||||
|
||||
export function getDisplayedSongs(songs: SubsonicSong[], opts: DisplayedSongsOptions): SubsonicSong[] {
|
||||
const q = opts.filterText.trim().toLowerCase();
|
||||
if (!q && opts.sortKey === 'natural') return songs;
|
||||
let result = [...songs];
|
||||
if (q) result = result.filter(s => s.title.toLowerCase().includes(q) || (s.artist ?? '').toLowerCase().includes(q));
|
||||
if (opts.sortKey !== 'natural') {
|
||||
result.sort((a, b) => {
|
||||
let av: string | number;
|
||||
let bv: string | number;
|
||||
const effectiveRating = (s: SubsonicSong) => opts.ratings[s.id] ?? opts.userRatingOverrides[s.id] ?? s.userRating ?? 0;
|
||||
const effectiveStarred = (s: SubsonicSong) => (s.id in opts.starredOverrides ? opts.starredOverrides[s.id] : opts.starredSongs.has(s.id)) ? 1 : 0;
|
||||
switch (opts.sortKey) {
|
||||
case 'title': av = a.title; bv = b.title; break;
|
||||
case 'artist': av = a.artist ?? ''; bv = b.artist ?? ''; break;
|
||||
case 'album': av = a.album ?? ''; bv = b.album ?? ''; break;
|
||||
case 'favorite': av = effectiveStarred(a); bv = effectiveStarred(b); break;
|
||||
case 'rating': av = effectiveRating(a); bv = effectiveRating(b); break;
|
||||
case 'duration': av = a.duration ?? 0; bv = b.duration ?? 0; break;
|
||||
default: av = a.title; bv = b.title;
|
||||
}
|
||||
if (typeof av === 'number' && typeof bv === 'number') {
|
||||
return opts.sortDir === 'asc' ? av - bv : bv - av;
|
||||
}
|
||||
return opts.sortDir === 'asc' ? (av as string).localeCompare(bv as string) : (bv as string).localeCompare(av as string);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user