mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
47e16ebfef
* fix: RC3 queue link underline, genre album artist split, Artists "Other" bucket Three zunoz reports, one change: - Queue now-playing card: the artist and album links now underline on hover (not just recolour), matching clickable names everywhere else. The album link sits on .queue-current-sub itself; artist links are nested .is-link spans — both selectors covered. - Genre album cards split multi-artist credits into individual links like the rest of the app. Root cause was the local-index genre query hardcoding NULL for the album raw_json column, so OpenSubsonic artists[] never reached the card and it fell back to the flat "A • B" single link. Select a.raw_json instead (parity with the All Albums / advanced-search album queries). - Artists page alphabet index: # is now digits-only, and a new "Other" bucket collects accented Latin (Æ/Ø/Å…) and non-Latin scripts (CJK, Cyrillic, …) that previously fell into the # catch-all. Adds artistBucketKey/compareBuckets helpers (unit-tested), an OTHER bucket sorted last, and the artists.other label across 9 locales. * docs(changelog): queue link hover, genre card artist split, Artists Other bucket (#977)
92 lines
3.2 KiB
TypeScript
92 lines
3.2 KiB
TypeScript
import { useMemo } from 'react';
|
|
import type { SubsonicArtist } from '../api/subsonicTypes';
|
|
import { usePlayerStore } from '../store/playerStore';
|
|
import { ALL_SENTINEL, artistBucketKey, compareBuckets, type ArtistListFlatRow } from '../utils/componentHelpers/artistsHelpers';
|
|
|
|
interface UseArtistsFilteringArgs {
|
|
artists: SubsonicArtist[];
|
|
filter: string;
|
|
letterFilter: string;
|
|
starredOnly: boolean;
|
|
visibleCount: number;
|
|
viewMode: 'grid' | 'list';
|
|
}
|
|
|
|
interface UseArtistsFilteringResult {
|
|
filtered: SubsonicArtist[];
|
|
visible: SubsonicArtist[];
|
|
hasMore: boolean;
|
|
groups: Record<string, SubsonicArtist[]>;
|
|
letters: string[];
|
|
artistListFlatRows: ArtistListFlatRow[];
|
|
}
|
|
|
|
/**
|
|
* Memoised filter + group pipeline for the artists page. Reading
|
|
* `starredOverrides` here keeps the star-toggle reactive without
|
|
* dragging the full player store through Artists.tsx props.
|
|
*
|
|
* Walking 5000+ artists per render was measurable — every cheap state
|
|
* update (selection mode, view mode, page size) used to re-filter the
|
|
* whole list. With this hook the three artist arrays
|
|
* (filtered → visible → flat-rows) only recompute when their explicit
|
|
* deps change.
|
|
*
|
|
* Group-by-letter and flat-row construction short-circuit when the user
|
|
* is on the grid view, since neither output is needed there.
|
|
*/
|
|
export function useArtistsFiltering({
|
|
artists,
|
|
filter,
|
|
letterFilter,
|
|
starredOnly,
|
|
visibleCount,
|
|
viewMode,
|
|
}: UseArtistsFilteringArgs): UseArtistsFilteringResult {
|
|
const starredOverrides = usePlayerStore(s => s.starredOverrides);
|
|
|
|
const filtered = useMemo(() => {
|
|
let out = artists;
|
|
if (letterFilter !== ALL_SENTINEL) {
|
|
out = out.filter(a => artistBucketKey(a.name) === letterFilter);
|
|
}
|
|
if (filter) {
|
|
const needle = filter.toLowerCase();
|
|
out = out.filter(a => a.name.toLowerCase().includes(needle));
|
|
}
|
|
if (starredOnly) {
|
|
out = out.filter(a => a.id in starredOverrides ? starredOverrides[a.id] : !!a.starred);
|
|
}
|
|
return out;
|
|
}, [artists, letterFilter, filter, starredOnly, starredOverrides]);
|
|
|
|
const visible = useMemo(() => filtered.slice(0, visibleCount), [filtered, visibleCount]);
|
|
const hasMore = visibleCount < filtered.length;
|
|
|
|
const { groups, letters } = useMemo(() => {
|
|
if (viewMode !== 'list') return { groups: {} as Record<string, SubsonicArtist[]>, letters: [] as string[] };
|
|
const g: Record<string, SubsonicArtist[]> = {};
|
|
for (const a of visible) {
|
|
const key = artistBucketKey(a.name);
|
|
if (!g[key]) g[key] = [];
|
|
g[key].push(a);
|
|
}
|
|
return { groups: g, letters: Object.keys(g).sort(compareBuckets) };
|
|
}, [visible, viewMode]);
|
|
|
|
const artistListFlatRows = useMemo((): ArtistListFlatRow[] => {
|
|
if (viewMode !== 'list') return [];
|
|
const out: ArtistListFlatRow[] = [];
|
|
for (const letter of letters) {
|
|
out.push({ kind: 'letter', letter });
|
|
const group = groups[letter];
|
|
for (let i = 0; i < group.length; i++) {
|
|
out.push({ kind: 'artist', artist: group[i], isLastInLetter: i === group.length - 1 });
|
|
}
|
|
}
|
|
return out;
|
|
}, [viewMode, letters, groups]);
|
|
|
|
return { filtered, visible, hasMore, groups, letters, artistListFlatRows };
|
|
}
|