mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
c207f748da
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.
56 lines
1.9 KiB
TypeScript
56 lines
1.9 KiB
TypeScript
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 };
|
|
}
|