feat(random-mix): context-aware remix button + genre reset chip

Remix button now targets the active context: relabels to "{{genre}} neu
mixen" when a genre is selected and re-fetches that genre instead of the
global random pool. "Alle Songs" chip added as first genre option to
reset back to the full library mix without leaving the page.

Also fixes album-grid cards stretching to full page height on macOS
WebKit (align-items: start on .album-grid-wrap).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-10 22:19:22 +02:00
parent dcc3e52ad1
commit 549677ffd4
9 changed files with 41 additions and 2 deletions
+3
View File
@@ -217,6 +217,8 @@ export const deTranslation = {
title: 'Zufallsmix', title: 'Zufallsmix',
remix: 'Neu mixen', remix: 'Neu mixen',
remixTooltip: 'Neue Songs laden', remixTooltip: 'Neue Songs laden',
remixGenre: '{{genre}} neu mixen',
remixTooltipGenre: 'Neue {{genre}}-Songs laden',
playAll: 'Alle abspielen', playAll: 'Alle abspielen',
trackTitle: 'Titel', trackTitle: 'Titel',
trackArtist: 'Künstler', trackArtist: 'Künstler',
@@ -238,6 +240,7 @@ export const deTranslation = {
blacklistToggle: 'Keyword-Filter', blacklistToggle: 'Keyword-Filter',
genreMixTitle: 'Genre-Mix', genreMixTitle: 'Genre-Mix',
genreMixDesc: 'Top 20 Genres nach Songanzahl — klicken für einen Zufallsmix', genreMixDesc: 'Top 20 Genres nach Songanzahl — klicken für einen Zufallsmix',
genreMixAll: 'Alle Songs',
genreMixLoadMore: '10 weitere laden', genreMixLoadMore: '10 weitere laden',
genreMixNoGenres: 'Keine Genres auf dem Server gefunden.', genreMixNoGenres: 'Keine Genres auf dem Server gefunden.',
shuffleGenres: 'Andere Genres anzeigen', shuffleGenres: 'Andere Genres anzeigen',
+3
View File
@@ -218,6 +218,8 @@ export const enTranslation = {
title: 'Random Mix', title: 'Random Mix',
remix: 'Remix', remix: 'Remix',
remixTooltip: 'Load new random songs', remixTooltip: 'Load new random songs',
remixGenre: 'Remix {{genre}}',
remixTooltipGenre: 'Load new {{genre}} songs',
playAll: 'Play All', playAll: 'Play All',
trackTitle: 'Title', trackTitle: 'Title',
trackArtist: 'Artist', trackArtist: 'Artist',
@@ -239,6 +241,7 @@ export const enTranslation = {
blacklistToggle: 'Keyword Filter', blacklistToggle: 'Keyword Filter',
genreMixTitle: 'Genre Mix', genreMixTitle: 'Genre Mix',
genreMixDesc: 'Top 20 genres by song count — click to load a random mix', genreMixDesc: 'Top 20 genres by song count — click to load a random mix',
genreMixAll: 'All Songs',
genreMixLoadMore: 'Load 10 more', genreMixLoadMore: 'Load 10 more',
genreMixNoGenres: 'No genres found on server.', genreMixNoGenres: 'No genres found on server.',
shuffleGenres: 'Show different genres', shuffleGenres: 'Show different genres',
+3
View File
@@ -217,6 +217,8 @@ export const frTranslation = {
title: 'Mix aléatoire', title: 'Mix aléatoire',
remix: 'Remixer', remix: 'Remixer',
remixTooltip: 'Charger de nouveaux morceaux aléatoires', remixTooltip: 'Charger de nouveaux morceaux aléatoires',
remixGenre: 'Remixer {{genre}}',
remixTooltipGenre: 'Charger de nouveaux morceaux {{genre}}',
playAll: 'Tout lire', playAll: 'Tout lire',
trackTitle: 'Titre', trackTitle: 'Titre',
trackArtist: 'Artiste', trackArtist: 'Artiste',
@@ -238,6 +240,7 @@ export const frTranslation = {
blacklistToggle: 'Filtre par mot-clé', blacklistToggle: 'Filtre par mot-clé',
genreMixTitle: 'Mix par genre', genreMixTitle: 'Mix par genre',
genreMixDesc: 'Top 20 genres par nombre de morceaux — cliquer pour un mix aléatoire', genreMixDesc: 'Top 20 genres par nombre de morceaux — cliquer pour un mix aléatoire',
genreMixAll: 'Tous les morceaux',
genreMixLoadMore: 'Charger 10 de plus', genreMixLoadMore: 'Charger 10 de plus',
genreMixNoGenres: 'Aucun genre trouvé sur le serveur.', genreMixNoGenres: 'Aucun genre trouvé sur le serveur.',
shuffleGenres: 'Afficher d\'autres genres', shuffleGenres: 'Afficher d\'autres genres',
+3
View File
@@ -217,6 +217,8 @@ export const nbTranslation = {
title: 'Tilfeldig miks', title: 'Tilfeldig miks',
remix: 'Remiks', remix: 'Remiks',
remixTooltip: 'Last inn nye tilfeldige sanger', remixTooltip: 'Last inn nye tilfeldige sanger',
remixGenre: 'Remiks {{genre}}',
remixTooltipGenre: 'Last inn nye {{genre}}-sanger',
playAll: 'Spill alt', playAll: 'Spill alt',
trackTitle: 'Tittel', trackTitle: 'Tittel',
trackArtist: 'Artist', trackArtist: 'Artist',
@@ -238,6 +240,7 @@ export const nbTranslation = {
blacklistToggle: 'Nøkkelordfilter', blacklistToggle: 'Nøkkelordfilter',
genreMixTitle: 'Sjangermiks', genreMixTitle: 'Sjangermiks',
genreMixDesc: 'Topp 20 sjangre etter antall sanger - klikk for å laste en tilfeldig miks', genreMixDesc: 'Topp 20 sjangre etter antall sanger - klikk for å laste en tilfeldig miks',
genreMixAll: 'Alle sanger',
genreMixLoadMore: 'Last 10 til', genreMixLoadMore: 'Last 10 til',
genreMixNoGenres: 'Ingen sjangre funnet på tjeneren.', genreMixNoGenres: 'Ingen sjangre funnet på tjeneren.',
shuffleGenres: 'Vis andre sjangre', shuffleGenres: 'Vis andre sjangre',
+3
View File
@@ -217,6 +217,8 @@ export const nlTranslation = {
title: 'Willekeurige mix', title: 'Willekeurige mix',
remix: 'Opnieuw mixen', remix: 'Opnieuw mixen',
remixTooltip: 'Nieuwe willekeurige nummers laden', remixTooltip: 'Nieuwe willekeurige nummers laden',
remixGenre: '{{genre}} mixen',
remixTooltipGenre: 'Nieuwe {{genre}}-nummers laden',
playAll: 'Alles afspelen', playAll: 'Alles afspelen',
trackTitle: 'Titel', trackTitle: 'Titel',
trackArtist: 'Artiest', trackArtist: 'Artiest',
@@ -238,6 +240,7 @@ export const nlTranslation = {
blacklistToggle: 'Trefwoordfilter', blacklistToggle: 'Trefwoordfilter',
genreMixTitle: 'Genremix', genreMixTitle: 'Genremix',
genreMixDesc: 'Top 20 genres op aantal nummers — klik voor een willekeurige mix', genreMixDesc: 'Top 20 genres op aantal nummers — klik voor een willekeurige mix',
genreMixAll: 'Alle nummers',
genreMixLoadMore: '10 meer laden', genreMixLoadMore: '10 meer laden',
genreMixNoGenres: 'Geen genres gevonden op server.', genreMixNoGenres: 'Geen genres gevonden op server.',
shuffleGenres: 'Andere genres tonen', shuffleGenres: 'Andere genres tonen',
+3
View File
@@ -223,6 +223,8 @@ export const ruTranslation = {
title: 'Случайный микс', title: 'Случайный микс',
remix: 'Новый микс', remix: 'Новый микс',
remixTooltip: 'Подобрать другие случайные треки', remixTooltip: 'Подобрать другие случайные треки',
remixGenre: 'Новый микс: {{genre}}',
remixTooltipGenre: 'Подобрать другие треки жанра {{genre}}',
playAll: 'Воспроизвести всё', playAll: 'Воспроизвести всё',
trackTitle: 'Название', trackTitle: 'Название',
trackArtist: 'Исполнитель', trackArtist: 'Исполнитель',
@@ -245,6 +247,7 @@ export const ruTranslation = {
blacklistToggle: 'Фильтр по словам', blacklistToggle: 'Фильтр по словам',
genreMixTitle: 'Микс по жанру', genreMixTitle: 'Микс по жанру',
genreMixDesc: 'Топ-20 жанров по числу треков — нажмите, чтобы собрать микс', genreMixDesc: 'Топ-20 жанров по числу треков — нажмите, чтобы собрать микс',
genreMixAll: 'Все треки',
genreMixLoadMore: 'Ещё 10 жанров', genreMixLoadMore: 'Ещё 10 жанров',
genreMixNoGenres: 'На сервере нет данных о жанрах.', genreMixNoGenres: 'На сервере нет данных о жанрах.',
shuffleGenres: 'Другие жанры', shuffleGenres: 'Другие жанры',
+3
View File
@@ -217,6 +217,8 @@ export const zhTranslation = {
title: '随机混音', title: '随机混音',
remix: '重新混音', remix: '重新混音',
remixTooltip: '加载新的随机歌曲', remixTooltip: '加载新的随机歌曲',
remixGenre: '重混 {{genre}}',
remixTooltipGenre: '加载新的{{genre}}歌曲',
playAll: '全部播放', playAll: '全部播放',
trackTitle: '标题', trackTitle: '标题',
trackArtist: '艺术家', trackArtist: '艺术家',
@@ -238,6 +240,7 @@ export const zhTranslation = {
blacklistToggle: '关键词过滤', blacklistToggle: '关键词过滤',
genreMixTitle: '流派混音', genreMixTitle: '流派混音',
genreMixDesc: '按歌曲数量排列的前 20 个流派 — 点击获取随机混音', genreMixDesc: '按歌曲数量排列的前 20 个流派 — 点击获取随机混音',
genreMixAll: '所有歌曲',
genreMixLoadMore: '加载 10 首更多', genreMixLoadMore: '加载 10 首更多',
genreMixNoGenres: '服务器上未找到流派。', genreMixNoGenres: '服务器上未找到流派。',
shuffleGenres: '显示其他流派', shuffleGenres: '显示其他流派',
+19 -2
View File
@@ -182,8 +182,17 @@ export default function RandomMix() {
<h1 className="page-title">{t('randomMix.title')}</h1> <h1 className="page-title">{t('randomMix.title')}</h1>
<div style={{ display: 'flex', gap: '0.5rem' }}> <div style={{ display: 'flex', gap: '0.5rem' }}>
<button className="btn btn-surface" onClick={fetchSongs} disabled={loading} data-tooltip={t('randomMix.remixTooltip')}> <button
<RefreshCw size={18} className={loading ? 'spin' : ''} /> {t('randomMix.remix')} className="btn btn-surface"
onClick={selectedGenre ? () => loadGenreMix(selectedGenre) : fetchSongs}
disabled={selectedGenre ? genreMixLoading : loading}
data-tooltip={selectedGenre
? t('randomMix.remixTooltipGenre', { genre: selectedGenre })
: t('randomMix.remixTooltip')
}
>
<RefreshCw size={18} className={(selectedGenre ? genreMixLoading : loading) ? 'spin' : ''} />
{selectedGenre ? t('randomMix.remixGenre', { genre: selectedGenre }) : t('randomMix.remix')}
</button> </button>
{(() => { {(() => {
const isGenreLoading = selectedGenre && !genreMixComplete; const isGenreLoading = selectedGenre && !genreMixComplete;
@@ -313,6 +322,14 @@ export default function RandomMix() {
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{t('randomMix.genreMixNoGenres')}</span> <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{t('randomMix.genreMixNoGenres')}</span>
) : ( ) : (
<> <>
<button
className={`btn ${selectedGenre === null ? 'btn-primary' : 'btn-surface'}`}
style={{ fontSize: 12, padding: '4px 12px' }}
onClick={() => { setSelectedGenre(null); setGenreMixSongs([]); setGenreMixComplete(false); fetchSongs(); }}
disabled={genreMixLoading}
>
{t('randomMix.genreMixAll')}
</button>
{displayedGenres.map(genre => ( {displayedGenres.map(genre => (
<button <button
key={genre} key={genre}
+1
View File
@@ -392,6 +392,7 @@
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: var(--space-4); gap: var(--space-4);
align-items: start;
} }
.album-grid-wrap .album-card { .album-grid-wrap .album-card {