Files
psysonic/src/components/settings/settingsTabs.ts
T
Frank Stellmacher dc4eef1a97 feat(fullscreen-player): rebuilt static fullscreen player (#1001)
* feat(player): static media-center fullscreen player (v1)

New lean fullscreen player: sharp full-bleed background (artist photo, cover
fallback), no blur / no continuous animations. Bottom-left big cover bottom-
flush with a full-width semi-transparent text bar (title with queue position,
artist, year/genre + rating stars, next-up); control row of transport · center
time · actions; full-width seekbar; live clock. Only the seekbar, time readout
and clock update at runtime, each owning its state (no per-tick re-render).
Reuses FsSeekbar/FsPlayBtn, the cover/artist hooks, idle-fade and queue
helpers. Wired in AppShell; old player kept for A/B.

* feat(fullscreen-player): true waveform seekbar instead of thin bar

Replace FsSeekbar with the real WaveformSeek (cucadmuh's idea). The taller
canvas grows the bottom cluster upward, shifting the info/control rows up.
Height clamped to 32-52px.

* feat(fullscreen-player): up-next popover + control-bar styling

- Queue button opens a semi-transparent 'Up next' popover anchored bottom
  right; clicking a row jumps to that queue item.
- Larger bottom-right action buttons.
- Control row gets its own darker semi-transparent bar (touches the info bar
  above) for contrast; play button matches the plain transport buttons
  (no white circle, same size).

* feat(fullscreen-player): high-res (2000px) background cover

Fetch the full-screen background cover at the 2000px tier via the existing
on-demand fullRes path (same getCoverArt fetch, saved as a high-res WebP
outside the backfill pipeline) instead of the low-res 500px pipeline tier.
usePlaybackCoverArt now forwards a fullRes option to useCoverArt.

* feat(fullscreen-player): scrolling lyrics overlay + control-bar polish

- Lyrics button next to Queue toggles a centered, dark semi-transparent
  scrolling-lyrics overlay (reuses FsLyricsApple).
- Control bar darkened to match the lyrics overlay (0.88).
- Close button sized down to harmonize with the clock.

* feat(fullscreen-player): make rating stars clickable

The rating stars next to the year were display-only. Wire them to
queueSongRating (same path as the player bar / context menu): click to
set, click the current value to clear, with a hover preview. Keeps the
lucide outline look to match the rest of the control bar.

* fix(fullscreen-player): stable, high-res album cover

The foreground cover was keyed per track, so Navidrome's per-track
`mf-<id>` coverArt re-triggered the distinct-disc heuristic and reloaded
the cover on every song change within the same album. Key it on albumId
(via useAlbumCoverRef) so it stays put while the album is unchanged.

It also used the low-res tier; reuse the fullRes 2000px cover already
fetched for the background so the foreground is crisp and both share a
single fetch/decode.

* refactor(fullscreen-player): remove the old player and its settings

The static rebuild is now the only fullscreen player. Delete the old
FullscreenPlayer component, its old-only parts (FsArt, FsPortrait,
FsSeekbar, FsLyricsRail, FsLyricsMenu, useFsDynamicAccent) and its test.
Remove the now-orphaned settings (showFullscreenLyrics, fsLyricsStyle,
showFsArtistPortrait, fsPortraitDim) along with the Appearance
'Fullscreen player' section and its search entry. The new player always
shows the artist photo with cover fallback and has its own lyrics toggle.

Shared building blocks used by the static player (FsLyricsApple,
FsPlayBtn, FsClock, FsTimeReadout, FsQueueModal, useFsArtistPortrait)
are kept.

* i18n(fullscreen-player): translate the new player's strings

Wire the static player's previously English-only strings through i18n
across all 9 locales: now-playing label, track position, up-next label,
queue/lyrics/shuffle controls, and the queue overlay (title, empty,
close). Reuses existing queue.title / common.close keys; adds six new
keys to the player namespace.

* docs(changelog): note fullscreen player rebuild (#1001)
2026-06-05 14:23:47 +02:00

102 lines
7.8 KiB
TypeScript

export type Tab =
| 'library'
| 'servers'
| 'audio'
| 'lyrics'
| 'appearance'
| 'personalisation'
| 'integrations'
| 'input'
| 'storage'
| 'system'
| 'users';
// Legacy Tab-IDs die via Route-State oder persisted State noch aufschlagen koennen
// auf die neue Struktur mappen. Gibt es keinen Match, faellt die Settings-Page
// einfach auf 'library' zurueck.
const LEGACY_TAB_ALIAS: Record<string, Tab> = {
general: 'library',
server: 'servers',
};
export function resolveTab(input: string | undefined | null): Tab {
if (!input) return 'servers';
const aliased = LEGACY_TAB_ALIAS[input];
if (aliased) return aliased;
const known: Tab[] = ['library', 'servers', 'audio', 'lyrics', 'appearance', 'personalisation', 'integrations', 'input', 'storage', 'system', 'users'];
return (known as string[]).includes(input) ? (input as Tab) : 'servers';
}
// Statischer Suchindex ueber alle Sub-Sections aller Tabs. Mitpflegen, wenn eine
// neue SettingsSubSection hinzukommt — sonst taucht sie nicht in der Suche auf.
export type SearchIndexEntry = { tab: Tab; titleKey: string; keywords?: string; focusTitleKey?: string };
export const SETTINGS_INDEX: SearchIndexEntry[] = [
{ tab: 'audio', titleKey: 'settings.audioOutputDevice', keywords: 'output device speakers headphones alsa wasapi coreaudio' },
{ tab: 'audio', titleKey: 'settings.hiResTitle', keywords: 'hi-res hires resampling bit depth sample rate dsd 24bit' },
{ tab: 'audio', titleKey: 'settings.eqTitle', keywords: 'equalizer eq bass treble autoeq filter pre-gain' },
{ tab: 'audio', titleKey: 'settings.playbackRateTitle', keywords: 'speed playback rate tempo pitch varispeed preserve corrected time stretch' },
{ tab: 'audio', titleKey: 'settings.playbackTitle', keywords: 'playback crossfade gapless replaygain replay gain volume' },
{ tab: 'lyrics', titleKey: 'settings.lyricsSourcesTitle', keywords: 'lyrics sources providers lrclib netease server youlyplus karaoke standard static' },
{ tab: 'lyrics', titleKey: 'settings.sidebarLyricsStyle', keywords: 'lyrics scroll style classic apple music' },
{ tab: 'integrations', titleKey: 'settings.lfmTitle', keywords: 'last.fm lastfm scrobble' },
{ tab: 'integrations', titleKey: 'settings.discordRichPresence', keywords: 'discord rich presence rpc' },
{ tab: 'integrations', titleKey: 'settings.enableBandsintown', keywords: 'bandsintown concerts tours events' },
{ tab: 'integrations', titleKey: 'settings.nowPlayingEnabled', keywords: 'now playing share dropdown presence' },
{ tab: 'personalisation',titleKey: 'settings.sidebarTitle', keywords: 'sidebar nav navigation items reorder customize' },
{ tab: 'personalisation',titleKey: 'settings.artistLayoutTitle', keywords: 'artist page layout sections order' },
{ tab: 'personalisation',titleKey: 'settings.homeCustomizerTitle', keywords: 'mainstage home page customize sections' },
{ tab: 'personalisation',titleKey: 'settings.queueToolbarTitle', keywords: 'queue toolbar buttons reorder customize shuffle save load' },
{ tab: 'personalisation',titleKey: 'settings.playlistLayoutTitle', keywords: 'playlist page layout add songs import csv download zip cache offline suggestions controls hide show' },
{ tab: 'personalisation',titleKey: 'settings.playerBarTitle', keywords: 'player bar playback favorites stars rating lastfm love equalizer mini player controls hide show' },
{ tab: 'appearance', titleKey: 'settings.libraryGridMaxColumnsTitle', keywords: 'grid columns album artist playlist cards layout appearance performance scroll paint' },
{ tab: 'servers', titleKey: 'settings.servers', keywords: 'local library index sync resync verify integrity offline delta background sqlite search' },
{ tab: 'servers', titleKey: 'settings.audiomuseTitle', keywords: 'audiomuse audio muse navidrome plugin instant mix similar songs lucky mix' },
{ tab: 'library', titleKey: 'settings.analyticsStrategyTitle', keywords: 'analytics strategy analysis bpm enrichment waveform lazy advanced library backfill' },
{ tab: 'library', titleKey: 'settings.randomMixTitle', keywords: 'random mix blacklist genre keywords filter audiobook' },
{ tab: 'library', titleKey: 'settings.ratingsSectionTitle', keywords: 'ratings stars skip threshold manual' },
{ tab: 'storage', titleKey: 'settings.coverCacheStrategyTitle', keywords: 'cover art cache webp aggressive lazy disk per server' },
{ tab: 'storage', titleKey: 'settings.offlineDirTitle', keywords: 'offline library download directory folder cache' },
{ tab: 'storage', titleKey: 'settings.nextTrackBufferingTitle', keywords: 'next track buffering preload hot cache streaming' },
{ tab: 'storage', titleKey: 'settings.downloadsTitle', keywords: 'downloads zip export archive folder' },
{ tab: 'appearance', titleKey: 'settings.theme', keywords: 'theme color palette dark light' },
{ tab: 'appearance', titleKey: 'settings.themeSchedulerTitle', keywords: 'theme scheduler auto time dark mode sunset' },
{ tab: 'appearance', titleKey: 'settings.visualOptionsTitle', keywords: 'visual options animations effects titlebar mini player' },
{ tab: 'appearance', titleKey: 'settings.uiScaleTitle', keywords: 'ui scale zoom dpi size' },
{ tab: 'appearance', titleKey: 'settings.font', keywords: 'font typography typeface' },
{ tab: 'appearance', titleKey: 'settings.seekbarStyle', keywords: 'seekbar progress bar waveform reduced animations performance gpu fps low-end framerate cap' },
{ tab: 'input', titleKey: 'settings.inputKeybindingsTitle', keywords: 'keybindings shortcuts hotkeys keyboard' },
{ tab: 'input', titleKey: 'settings.globalShortcutsTitle', keywords: 'global shortcuts hotkeys system-wide media keys' },
{ tab: 'system', titleKey: 'settings.language', keywords: 'language locale translation i18n' },
{ tab: 'system', titleKey: 'settings.behavior', keywords: 'behavior tray minimize close start smooth scroll linux' },
{ tab: 'system', titleKey: 'settings.backupTitle', keywords: 'backup export import settings restore' },
{ tab: 'system', titleKey: 'settings.loggingTitle', keywords: 'log logs diagnostic debug verbose' },
{ tab: 'system', titleKey: 'settings.aboutTitle', keywords: 'about version update changelog release notes' },
{ tab: 'system', titleKey: 'settings.aboutContributorsLabel', keywords: 'contributors credits maintainers' },
{ tab: 'system', titleKey: 'licenses.title', keywords: 'licenses license open source attribution copyright third party dependencies oss' },
];
// Substring-first, compact fuzzy fallback (query chars in order within a
// short span). Returns 0 = no match. Higher = better.
export function matchScore(haystack: string, needle: string): number {
if (!needle) return 0;
const h = haystack.toLowerCase();
const n = needle.toLowerCase();
const idx = h.indexOf(n);
if (idx >= 0) return 1000 - Math.min(999, idx);
// Repeated single-char queries ("aaaaaaa") must not match via sparse fuzzy hits.
if (n.length >= 4 && /^(.)\1+$/.test(n)) return 0;
let hi = 0;
let start = -1;
for (const ch of n) {
const j = h.indexOf(ch, hi);
if (j < 0) return 0;
if (start < 0) start = j;
hi = j + 1;
}
const span = hi - start;
if (span > n.length * 2) return 0;
if (n.length >= 4 && span > n.length + 3) return 0;
return Math.max(1, 100 - Math.min(99, span - n.length));
}