mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
refactor(favorites): G.83 — extract SongsSectionHeader + SongsTracklist + selection hook (cluster) (#650)
Three-cut cluster pulling the dominant songs section out of Favorites.tsx. 645 → 217 LOC (−428). FavoritesSongsSectionHeader — the section above the tracklist: title with showing-N-of-M indicator, Play-All / Enqueue-All buttons, filter toggle, clear-all button (resets artist + genre + year + sort), filters panel with GenreFilterBar + dual-range year sliders, and the "clear artist filter" button when an artist filter is active. Takes the minYear / currentYear constants explicitly so the page still owns them. FavoritesSongsTracklist — the tracklist below: bulk-action bar (N selected + Add-to-playlist submenu + clear), column-visibility picker, sortable column header, song rows (selection check + bulk toggle, currentTrack highlight, inline play + preview buttons in the title cell, artist/album link cells, genre/format/duration/ rating cells, remove button), and the no-filter-results empty state. Subscribes to playerStore / previewStore / selectionStore / useDragDrop / useOrbitSongRowBehavior directly. useFavoritesSelection — owns lastSelectedIdxRef and the two useEffects (clear-on-songs-change + clear-on-click-outside) plus the toggleSelect callback with shift-range support. Favorites drops the inline definitions and removes the now-unused direct useRef / useCallback declarations. Pure code move otherwise.
This commit is contained in:
committed by
GitHub
parent
a4b1b29dd6
commit
c207f748da
@@ -0,0 +1,55 @@
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import type { SubsonicSong } from '../api/subsonicTypes';
|
||||
import { useSelectionStore } from '../store/selectionStore';
|
||||
|
||||
export interface FavoritesSelectionResult {
|
||||
toggleSelect: (id: string, idx: number, shift: boolean) => void;
|
||||
}
|
||||
|
||||
export function useFavoritesSelection(
|
||||
songs: SubsonicSong[],
|
||||
inSelectMode: boolean,
|
||||
tracklistRef: React.RefObject<HTMLDivElement | null>,
|
||||
): FavoritesSelectionResult {
|
||||
const lastSelectedIdxRef = useRef<number | null>(null);
|
||||
|
||||
// Clear selection when song list changes
|
||||
useEffect(() => {
|
||||
useSelectionStore.getState().clearAll();
|
||||
lastSelectedIdxRef.current = null;
|
||||
}, [songs]);
|
||||
|
||||
// Clear selection on click outside tracklist
|
||||
useEffect(() => {
|
||||
if (!inSelectMode) return;
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (tracklistRef.current && !tracklistRef.current.contains(e.target as Node)) {
|
||||
useSelectionStore.getState().clearAll();
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handler);
|
||||
return () => document.removeEventListener('mousedown', handler);
|
||||
}, [inSelectMode, tracklistRef]);
|
||||
|
||||
const toggleSelect = useCallback((id: string, idx: number, shift: boolean) => {
|
||||
useSelectionStore.getState().setSelectedIds(prev => {
|
||||
const next = new Set(prev);
|
||||
if (shift && lastSelectedIdxRef.current !== null) {
|
||||
const from = Math.min(lastSelectedIdxRef.current, idx);
|
||||
const to = Math.max(lastSelectedIdxRef.current, idx);
|
||||
// we need visibleSongs here — read from latest closure via ref trick
|
||||
// Instead, just toggle range based on idx into songs array
|
||||
for (let j = from; j <= to; j++) {
|
||||
const sid = songs[j]?.id;
|
||||
if (sid) next.add(sid);
|
||||
}
|
||||
} else {
|
||||
if (next.has(id)) { next.delete(id); }
|
||||
else { next.add(id); lastSelectedIdxRef.current = idx; }
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, [songs]);
|
||||
|
||||
return { toggleSelect };
|
||||
}
|
||||
Reference in New Issue
Block a user