feat(seekbar+ratings): 5 new seekbar styles and entity/mix rating system (PR #130)

Seekbar styles (5 new, by Psychotoxical):
- Neon Glow: transparent track, played section as multi-layer glowing neon tube
- Pulse Wave: flat line with animated gaussian sine pulse at playhead (rAF)
- Particle Trail: particles spawn at playhead and drift with glow (rAF)
- Liquid Fill: glass tube fills with liquid and animated wave surface (rAF)
- Retro Tape: two spinning reels connected by tape, shrink/grow with progress (rAF)

Ratings system (PR #130 by cucadmuh):
- Shared StarRating component with pulse/clear animations, disabled/locked states
- Entity ratings for albums and artists via OpenSubsonic probe (setEntityRatingSupport)
- Skip→1★: after N consecutive manual skips, set unrated track to 1★ (persisted per server)
- Mix rating filter: exclude low-rated songs/albums/artists from RandomMix, RandomAlbums, Hero, Home
- Batch refill in fetchRandomMixSongsUntilFull to fill list despite strict filters
- Prefetch artist/album ratings via getArtist/getAlbum for incomplete list payloads
- Settings: new Ratings section for skip threshold and per-axis mix filter stars
- i18n: all 7 languages (en, de, fr, nl, zh, nb, ru)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-08 10:29:21 +02:00
30 changed files with 1806 additions and 126 deletions
+24
View File
@@ -145,6 +145,13 @@ export const deTranslation = {
ratingLabel: 'Bewertung',
enlargeCover: 'Vergrößern',
},
entityRating: {
albumShort: 'Albumbewertung',
artistShort: 'Künstlerbewertung',
albumAriaLabel: 'Albumbewertung',
artistAriaLabel: 'Künstlerbewertung',
saveFailed: 'Bewertung konnte nicht gespeichert werden.',
},
artistDetail: {
back: 'Zurück',
albums: 'Alben',
@@ -512,6 +519,18 @@ export const deTranslation = {
shortcutNativeFullscreen: 'Nativer Vollbildmodus',
tabSystem: 'System',
tabGeneral: 'Allgemein',
ratingsSectionTitle: 'Bewertungen',
ratingsSkipStarTitle: 'Überspringen für 1 Stern',
ratingsSkipStarDesc:
'Bei N Überspringen hintereinander: Titel auf 1 Stern setzen. Nur für zuvor unbewertete Titel.',
ratingsSkipStarThresholdLabel: 'Überspringer bis 1★',
ratingsMixFilterTitle: 'Filter nach Bewertung',
ratingsMixFilterDesc:
'Inhalte mit niedriger Bewertung in {{mix}} und {{albums}} filtern. Erneut auf den gewählten Stern klicken, um die Schwelle zu deaktivieren.',
ratingsMixMinSong: 'Titel',
ratingsMixMinAlbum: 'Alben',
ratingsMixMinArtist: 'Interpreten',
ratingsMixMinThresholdAria: 'Mindest-Sterne: {{label}}',
backupTitle: 'Backup & Wiederherstellung',
backupExport: 'Einstellungen exportieren',
backupExportDesc: 'Speichert alle Einstellungen, Serverprofile, Last.fm-Konfiguration, Theme, EQ und Tastenkürzel in eine .psybkp-Datei. Passwörter werden im Klartext gespeichert — Datei sicher aufbewahren.',
@@ -550,6 +569,11 @@ export const deTranslation = {
seekbarBar: 'Balken',
seekbarThick: 'Dicker Balken',
seekbarSegmented: 'Segmentiert',
seekbarNeon: 'Neonröhre',
seekbarPulsewave: 'Pulswelle',
seekbarParticletrail: 'Partikel-Spur',
seekbarLiquidfill: 'Flüssigkeit',
seekbarRetrotape: 'Retro-Band',
},
changelog: {
modalTitle: 'Was ist neu',
+24
View File
@@ -146,6 +146,13 @@ export const enTranslation = {
ratingLabel: 'Rating',
enlargeCover: 'Enlarge',
},
entityRating: {
albumShort: 'Album rating',
artistShort: 'Artist rating',
albumAriaLabel: 'Album rating',
artistAriaLabel: 'Artist rating',
saveFailed: 'Could not save rating.',
},
artistDetail: {
back: 'Back',
albums: 'Albums',
@@ -497,6 +504,18 @@ export const enTranslation = {
tabServer: 'Server',
tabSystem: 'System',
tabGeneral: 'General',
ratingsSectionTitle: 'Ratings',
ratingsSkipStarTitle: 'Skip for 1 star',
ratingsSkipStarDesc:
'After N skips in a row, set the track to 1★. Only for tracks not rated before.',
ratingsSkipStarThresholdLabel: 'Skips before 1★',
ratingsMixFilterTitle: 'Filter by rating',
ratingsMixFilterDesc:
'Filter low-rated items in {{mix}} and {{albums}}. Click the selected star again to turn off the threshold.',
ratingsMixMinSong: 'Songs',
ratingsMixMinAlbum: 'Albums',
ratingsMixMinArtist: 'Artists',
ratingsMixMinThresholdAria: 'Minimum stars: {{label}}',
backupTitle: 'Backup & Restore',
backupExport: 'Export settings',
backupExportDesc: 'Saves all settings, server profiles, Last.fm config, theme, EQ and keybindings to a .psybkp file. Passwords are stored in plaintext — keep the file secure.',
@@ -551,6 +570,11 @@ export const enTranslation = {
seekbarBar: 'Bar',
seekbarThick: 'Thick Bar',
seekbarSegmented: 'Segmented',
seekbarNeon: 'Neon Glow',
seekbarPulsewave: 'Pulse Wave',
seekbarParticletrail: 'Particle Trail',
seekbarLiquidfill: 'Liquid Fill',
seekbarRetrotape: 'Retro Tape',
},
changelog: {
modalTitle: "What's New",
+24
View File
@@ -145,6 +145,13 @@ export const frTranslation = {
ratingLabel: 'Note',
enlargeCover: 'Agrandir',
},
entityRating: {
albumShort: 'Note de lalbum',
artistShort: 'Note de lartiste',
albumAriaLabel: 'Note de lalbum',
artistAriaLabel: 'Note de lartiste',
saveFailed: 'Impossible denregistrer la note.',
},
artistDetail: {
back: 'Retour',
albums: 'Albums',
@@ -510,6 +517,18 @@ export const frTranslation = {
shortcutNativeFullscreen: 'Plein écran natif',
tabSystem: 'Système',
tabGeneral: 'Général',
ratingsSectionTitle: 'Notes',
ratingsSkipStarTitle: 'Passer pour 1 étoile',
ratingsSkipStarDesc:
'Après N sauts daffilée : mettre le morceau à 1 étoile. Uniquement sil n’était pas encore noté.',
ratingsSkipStarThresholdLabel: 'Sauts avant 1★',
ratingsMixFilterTitle: 'Filtrage par note',
ratingsMixFilterDesc:
'Filtrer le contenu peu noté dans {{mix}} et {{albums}}. Cliquer de nouveau sur l’étoile choisie désactive le seuil.',
ratingsMixMinSong: 'Morceaux',
ratingsMixMinAlbum: 'Albums',
ratingsMixMinArtist: 'Artistes',
ratingsMixMinThresholdAria: 'Étoiles minimum : {{label}}',
backupTitle: 'Sauvegarde & Restauration',
backupExport: 'Exporter les paramètres',
backupExportDesc: 'Enregistre tous les paramètres, profils serveur, configuration Last.fm, thème, EQ et raccourcis dans un fichier .psybkp. Les mots de passe sont stockés en clair — conservez le fichier en sécurité.',
@@ -548,6 +567,11 @@ export const frTranslation = {
seekbarBar: 'Barre',
seekbarThick: 'Barre épaisse',
seekbarSegmented: 'Segmentée',
seekbarNeon: 'Néon',
seekbarPulsewave: 'Onde pulsée',
seekbarParticletrail: 'Traînée de particules',
seekbarLiquidfill: 'Tube liquide',
seekbarRetrotape: 'Bande rétro',
},
changelog: {
modalTitle: 'Quoi de neuf',
+24
View File
@@ -145,6 +145,13 @@ export const nbTranslation = {
ratingLabel: 'Vurdering',
enlargeCover: 'Forstørr',
},
entityRating: {
albumShort: 'Albumvurdering',
artistShort: 'Artistvurdering',
albumAriaLabel: 'Albumvurdering',
artistAriaLabel: 'Artistvurdering',
saveFailed: 'Kunne ikke lagre vurderingen.',
},
artistDetail: {
back: 'Tilbake',
albums: 'Album',
@@ -493,6 +500,18 @@ export const nbTranslation = {
tabServer: 'Tjener',
tabSystem: 'System',
tabGeneral: 'Generelt',
ratingsSectionTitle: 'Vurderinger',
ratingsSkipStarTitle: 'Hopp for 1 stjerne',
ratingsSkipStarDesc:
'Etter N hopp på rad: sett sporet til 1 stjerne. Bare for spor som ikke var vurdert før.',
ratingsSkipStarThresholdLabel: 'Hopp før 1★',
ratingsMixFilterTitle: 'Filtrering etter vurdering',
ratingsMixFilterDesc:
'Filtrer innhold med lav vurdering i {{mix}} og {{albums}}. Klikk på den valgte stjerna igjen for å slå av terskelen.',
ratingsMixMinSong: 'Spor',
ratingsMixMinAlbum: 'Album',
ratingsMixMinArtist: 'Artister',
ratingsMixMinThresholdAria: 'Minimum stjerner: {{label}}',
backupTitle: 'Sikkerhetskopiering og gjenoppretting',
backupExport: 'Eksporter innstillinger',
backupExportDesc: 'Lagrer alle innstillinger, tjenerprofiler, Last.fm-konfigurasjon, tema, jevnstiller og tastebindinger til en .psybkp-fil. Passordet lagres i klartekst hold filen sikker.',
@@ -547,6 +566,11 @@ export const nbTranslation = {
seekbarBar: 'Linje',
seekbarThick: 'Tykk linje',
seekbarSegmented: 'Segmentert',
seekbarNeon: 'Neon',
seekbarPulsewave: 'Pulsbølge',
seekbarParticletrail: 'Partikkelspor',
seekbarLiquidfill: 'Væskerør',
seekbarRetrotape: 'Retrotape',
},
changelog: {
modalTitle: "Nyheter",
+24
View File
@@ -145,6 +145,13 @@ export const nlTranslation = {
ratingLabel: 'Beoordeling',
enlargeCover: 'Vergroten',
},
entityRating: {
albumShort: 'Albumbeoordeling',
artistShort: 'Artiestbeoordeling',
albumAriaLabel: 'Albumbeoordeling',
artistAriaLabel: 'Artiestbeoordeling',
saveFailed: 'Beoordeling opslaan mislukt.',
},
artistDetail: {
back: 'Terug',
albums: 'Albums',
@@ -510,6 +517,18 @@ export const nlTranslation = {
shortcutNativeFullscreen: 'Systeemvolledig scherm',
tabSystem: 'Systeem',
tabGeneral: 'Algemeen',
ratingsSectionTitle: 'Beoordelingen',
ratingsSkipStarTitle: 'Overslaan voor 1 ster',
ratingsSkipStarDesc:
'Na N overslagen op rij: nummer op 1 ster zetten. Alleen voor nummers die nog niet beoordeeld waren.',
ratingsSkipStarThresholdLabel: 'Overslagen voor 1★',
ratingsMixFilterTitle: 'Filter op beoordeling',
ratingsMixFilterDesc:
'Content met lage beoordeling filteren in {{mix}} en {{albums}}. Klik opnieuw op de gekozen ster om de drempel uit te zetten.',
ratingsMixMinSong: 'Nummers',
ratingsMixMinAlbum: 'Albums',
ratingsMixMinArtist: 'Artiesten',
ratingsMixMinThresholdAria: 'Minimum sterren: {{label}}',
backupTitle: 'Back-up & Herstel',
backupExport: 'Instellingen exporteren',
backupExportDesc: 'Slaat alle instellingen, serverprofielen, Last.fm-configuratie, thema, EQ en sneltoetsen op in een .psybkp-bestand. Wachtwoorden worden opgeslagen als leesbare tekst — bewaar het bestand veilig.',
@@ -548,6 +567,11 @@ export const nlTranslation = {
seekbarBar: 'Balk',
seekbarThick: 'Dikke balk',
seekbarSegmented: 'Gesegmenteerd',
seekbarNeon: 'Neon',
seekbarPulsewave: 'Pulsgolf',
seekbarParticletrail: 'Deeltjesspoor',
seekbarLiquidfill: 'Vloeistofbuis',
seekbarRetrotape: 'Retrotape',
},
changelog: {
modalTitle: 'Wat is nieuw',
+27
View File
@@ -147,6 +147,13 @@ export const ruTranslation = {
ratingLabel: 'Оценка',
enlargeCover: 'Увеличить обложку',
},
entityRating: {
albumShort: 'Оценка альбома',
artistShort: 'Оценка исполнителя',
albumAriaLabel: 'Оценка альбома',
artistAriaLabel: 'Оценка исполнителя',
saveFailed: 'Не удалось сохранить оценку.',
},
artistDetail: {
back: 'Назад',
albums: 'Альбомы',
@@ -464,6 +471,9 @@ export const ruTranslation = {
showTrayIconDesc: 'Показывать Psysonic в области уведомлений / строке меню.',
minimizeToTray: 'Сворачивать в трей',
minimizeToTrayDesc: 'При закрытии окна не выходить из приложения, а оставаться в трее.',
useCustomTitlebar: 'Своя строка заголовка',
useCustomTitlebarDesc:
'Заменить системную строку заголовка встроенной, в стиле темы приложения. Отключите, чтобы использовать родную строку GNOME/GTK.',
discordRichPresence: 'Статус в Discord',
discordRichPresenceDesc:
'Показывать текущий трек в профиле и статусе Discord. Нужен запущенный клиент Discord.',
@@ -515,6 +525,18 @@ export const ruTranslation = {
tabServer: 'Сервер',
tabSystem: 'Система',
tabGeneral: 'Общие',
ratingsSectionTitle: 'Рейтинги',
ratingsSkipStarTitle: 'Скипнуть для 1 звезды',
ratingsSkipStarDesc:
'При N скипов подряд ставить 1★ треку. Только для не оцененных ранее.',
ratingsSkipStarThresholdLabel: 'Скипов',
ratingsMixFilterTitle: 'Фильтрация по рейтингу',
ratingsMixFilterDesc:
'Фильтровать с низким рейтингом в «{{mix}}» и «{{albums}}». Повторный клик по выбранной звезде отключает порог.',
ratingsMixMinSong: 'Песни',
ratingsMixMinAlbum: 'Альбомы',
ratingsMixMinArtist: 'Исполнители',
ratingsMixMinThresholdAria: 'Минимум звёзд: {{label}}',
backupTitle: 'Резервная копия',
backupExport: 'Экспорт настроек',
backupExportDesc:
@@ -571,6 +593,11 @@ export const ruTranslation = {
seekbarBar: 'Полоса',
seekbarThick: 'Толстая полоса',
seekbarSegmented: 'Сегменты',
seekbarNeon: 'Неон',
seekbarPulsewave: 'Пульс-волна',
seekbarParticletrail: 'Частицы',
seekbarLiquidfill: 'Жидкость',
seekbarRetrotape: 'Ретро-лента',
},
changelog: {
modalTitle: 'Что нового',
+24
View File
@@ -145,6 +145,13 @@ export const zhTranslation = {
ratingLabel: '评分',
enlargeCover: '放大',
},
entityRating: {
albumShort: '专辑评分',
artistShort: '艺人评分',
albumAriaLabel: '专辑评分',
artistAriaLabel: '艺人评分',
saveFailed: '无法保存评分。',
},
artistDetail: {
back: '返回',
albums: '专辑',
@@ -490,6 +497,18 @@ export const zhTranslation = {
tabServer: '服务器',
tabSystem: '系统',
tabGeneral: '通用',
ratingsSectionTitle: '评分',
ratingsSkipStarTitle: '跳过以评 1 星',
ratingsSkipStarDesc:
'连续跳过 N 次后将曲目设为 1 星。仅适用于此前未评分的曲目。',
ratingsSkipStarThresholdLabel: '跳过次数(至 1★)',
ratingsMixFilterTitle: '按评分筛选',
ratingsMixFilterDesc:
'在{{mix}}与{{albums}}中筛选低评分内容。再次点击所选星标可关闭阈值。',
ratingsMixMinSong: '歌曲',
ratingsMixMinAlbum: '专辑',
ratingsMixMinArtist: '艺人',
ratingsMixMinThresholdAria: '最低星数:{{label}}',
backupTitle: '备份与恢复',
backupExport: '导出设置',
backupExportDesc: '将所有设置、服务器配置、Last.fm 配置、主题、均衡器和快捷键保存到 .psybkp 文件。密码以明文存储——请妥善保管该文件。',
@@ -544,6 +563,11 @@ export const zhTranslation = {
seekbarBar: '条形',
seekbarThick: '粗条形',
seekbarSegmented: '分段式',
seekbarNeon: '霓虹',
seekbarPulsewave: '脉冲波',
seekbarParticletrail: '粒子轨迹',
seekbarLiquidfill: '液体填充',
seekbarRetrotape: '复古磁带',
},
changelog: {
modalTitle: '新功能',