From 4182b67732abba4790577c21e67c1bc06950ce7b Mon Sep 17 00:00:00 2001 From: Psychotoxical Date: Tue, 7 Apr 2026 22:20:35 +0200 Subject: [PATCH] feat(most-played): add toggle to filter compilation artists Toggle button in the Top Artists section header hides known compilation artist names (Various Artists, VA, Soundtracks, OST, etc.) from the ranking. Default off. Active state shown in accent color. Co-Authored-By: Claude Sonnet 4.6 --- src/locales/de.ts | 3 +++ src/locales/en.ts | 3 +++ src/pages/MostPlayed.tsx | 38 +++++++++++++++++++++++++++++++++----- src/styles/components.css | 25 +++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/locales/de.ts b/src/locales/de.ts index 8bd291cd..9a74d3b3 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -791,6 +791,9 @@ export const deTranslation = { sortLeast: 'Wenigste Plays zuerst', loadMore: 'Mehr Alben laden', noData: 'Noch keine Wiedergabedaten. Fang an zu hören!', + noArtists: 'Alle Künstler herausgefiltert.', + filterCompilations: 'Sampler-Künstler ausblenden (Various Artists, Soundtracks usw.)', + filterCompilationsShort: 'Sampler ausblenden', }, radio: { title: 'Internetradio', diff --git a/src/locales/en.ts b/src/locales/en.ts index 0a8fdc4d..5b1fa647 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -792,6 +792,9 @@ export const enTranslation = { sortLeast: 'Fewest plays first', loadMore: 'Load more albums', noData: 'No play data yet. Start listening!', + noArtists: 'All artists filtered out.', + filterCompilations: 'Hide compilation artists (Various Artists, Soundtracks, etc.)', + filterCompilationsShort: 'Hide compilations', }, radio: { title: 'Internet Radio', diff --git a/src/pages/MostPlayed.tsx b/src/pages/MostPlayed.tsx index a3331637..2a93d877 100644 --- a/src/pages/MostPlayed.tsx +++ b/src/pages/MostPlayed.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import { ArrowUpDown, ArrowDown, ArrowUp, TrendingUp } from 'lucide-react'; +import { ArrowUpDown, ArrowDown, ArrowUp, TrendingUp, UsersRound } from 'lucide-react'; import { getAlbumList, SubsonicAlbum, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic'; import { useAuthStore } from '../store/authStore'; import CachedImage from '../components/CachedImage'; @@ -16,11 +16,24 @@ interface ArtistEntry { totalPlays: number; } -function deriveTopArtists(albums: SubsonicAlbum[]): ArtistEntry[] { +const COMPILATION_NAMES = new Set([ + 'various artists', 'various', 'va', 'v.a.', 'v.a', + 'diverse artister', 'diversos artistas', 'artistes variés', + 'vários artistas', 'verschiedene künstler', 'verscheidene artiesten', + 'compilations', 'soundtrack', 'original soundtrack', 'ost', + 'original motion picture soundtrack', 'original score', +]); + +function isCompilation(name: string): boolean { + return COMPILATION_NAMES.has(name.toLowerCase().trim()); +} + +function deriveTopArtists(albums: SubsonicAlbum[], filterCompilations: boolean): ArtistEntry[] { const map = new Map(); for (const a of albums) { const plays = a.playCount ?? 0; if (plays === 0) continue; + if (filterCompilations && isCompilation(a.artist ?? '')) continue; const entry = map.get(a.artistId); if (entry) { entry.totalPlays += plays; @@ -46,8 +59,9 @@ export default function MostPlayed() { const [loadingMore, setLoadingMore] = useState(false); const [hasMore, setHasMore] = useState(true); const [sortAsc, setSortAsc] = useState(false); // false = most plays first + const [filterCompilations, setFilterCompilations] = useState(false); - const topArtists = deriveTopArtists(albums).slice(0, 10); + const topArtists = deriveTopArtists(albums, filterCompilations).slice(0, 10); const load = useCallback(async () => { setLoading(true); @@ -96,9 +110,23 @@ export default function MostPlayed() { {/* ── Top Artists ── */} - {!loading && topArtists.length > 0 && ( + {!loading && (
-

{t('mostPlayed.topArtists')}

+
+

{t('mostPlayed.topArtists')}

+ +
+ {topArtists.length === 0 && ( +
{t('mostPlayed.noArtists')}
+ )}
{topArtists.map((artist, i) => (