import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { invoke } from '@tauri-apps/api/core'; import { Clock, LayoutGrid, Maximize2, Palette, Sliders, Type, ZoomIn } from 'lucide-react'; import { useAuthStore } from '../../store/authStore'; import { LIBRARY_GRID_MAX_COLUMNS_MAX, LIBRARY_GRID_MAX_COLUMNS_MIN, } from '../../store/authStoreDefaults'; import type { SeekbarStyle } from '../../store/authStoreTypes'; import { useFontStore, FontId } from '../../store/fontStore'; import { useThemeStore } from '../../store/themeStore'; import { IS_LINUX, IS_WINDOWS } from '../../utils/platform'; import CustomSelect from '../CustomSelect'; import SettingsSubSection from '../SettingsSubSection'; import ThemePicker, { THEME_GROUPS } from '../ThemePicker'; import { SeekbarPreview } from '../WaveformSeekPreview'; export function AppearanceTab() { const { t, i18n } = useTranslation(); const auth = useAuthStore(); const theme = useThemeStore(); const fontStore = useFontStore(); const [isTilingWm, setIsTilingWm] = useState(false); useEffect(() => { if (!IS_LINUX) return; invoke('is_tiling_wm_cmd').then(setIsTilingWm).catch(() => {}); }, []); return ( <> } >
{theme.enableThemeScheduler && (
{t('settings.themeSchedulerActiveHint')}
)} theme.setTheme(v as any)} />
} >
{t('settings.themeSchedulerEnable')}
{t('settings.themeSchedulerEnableSub')}
{theme.enableThemeScheduler && (() => { const themeOptions = THEME_GROUPS.flatMap(g => g.themes.map(th => ({ value: th.id, label: th.family ? `${th.family} ${th.label}` : th.label, group: g.group, })) ); const use12h = i18n.language === 'en'; const hourOptions = Array.from({ length: 24 }, (_, i) => { const value = String(i).padStart(2, '0'); const label = use12h ? `${i % 12 === 0 ? 12 : i % 12} ${i < 12 ? 'AM' : 'PM'}` : value; return { value, label }; }); const minuteOptions = ['00', '05', '10', '15', '20', '25', '30', '35', '40', '45', '50', '55'].map(m => ({ value: m, label: m })); const dayH = theme.timeDayStart.split(':')[0]; const dayM = theme.timeDayStart.split(':')[1]; const nightH = theme.timeNightStart.split(':')[0]; const nightM = theme.timeNightStart.split(':')[1]; return (
theme.setTimeDayStart(`${v}:${dayM}`)} options={hourOptions} /> : theme.setTimeDayStart(`${dayH}:${v}`)} options={minuteOptions} />
theme.setTimeNightStart(`${v}:${nightM}`)} options={hourOptions} /> : theme.setTimeNightStart(`${nightH}:${v}`)} options={minuteOptions} />
); })()}
} >
{t('settings.libraryGridMaxColumnsPerfHint')}
auth.setLibraryGridMaxColumns(Number(e.target.value))} style={{ flex: 1, maxWidth: 360 }} aria-valuemin={LIBRARY_GRID_MAX_COLUMNS_MIN} aria-valuemax={LIBRARY_GRID_MAX_COLUMNS_MAX} aria-valuenow={auth.libraryGridMaxColumns} /> {auth.libraryGridMaxColumns}

{t('settings.libraryGridMaxColumnsDesc')}

} >
{t('settings.coverArtBackground')}
{t('settings.coverArtBackgroundSub')}
{t('settings.playlistCoverPhoto')}
{t('settings.playlistCoverPhotoSub')}
{t('settings.showBitrate')}
{t('settings.showBitrateSub')}
{t('settings.floatingPlayerBar')}
{t('settings.floatingPlayerBarSub')}
{t('settings.showArtistImages')}
{t('settings.showArtistImagesDesc')}
{t('settings.showOrbitTrigger')}
{t('settings.showOrbitTriggerDesc')}
{!IS_WINDOWS && ( <>
{t('settings.preloadMiniPlayer')}
{t('settings.preloadMiniPlayerDesc')}
)} {IS_LINUX && !isTilingWm && ( <>
{t('settings.useCustomTitlebar')}
{t('settings.useCustomTitlebarDesc')}
)}
} >
{t('settings.uiScaleLabel')} {Math.round(fontStore.uiScale * 100)}%
{(() => { const presets = [80, 90, 100, 110, 125, 150]; const currentPct = Math.round(fontStore.uiScale * 100); let idx = presets.indexOf(currentPct); if (idx < 0) { // Snap legacy off-preset values to the closest preset. idx = presets.reduce((best, p, i) => Math.abs(p - currentPct) < Math.abs(presets[best] - currentPct) ? i : best, 0); } return ( <> fontStore.setUiScale(presets[parseInt(e.target.value, 10)] / 100)} className="ui-scale-slider" />
{presets.map(p => { const active = currentPct === p; return ( ); })}
); })()}
} >
{( [ // Accessibility-first: OpenDyslexic at the top so dyslexic // readers don't have to scroll past 14 sans-serifs to find it. { id: 'opendyslexic', label: 'OpenDyslexic', stack: "'OpenDyslexic', sans-serif", hint: t('settings.fontHintOpenDyslexic') }, { id: 'inter', label: 'Inter', stack: "'Inter Variable', sans-serif" }, { id: 'outfit', label: 'Outfit', stack: "'Outfit Variable', sans-serif" }, { id: 'dm-sans', label: 'DM Sans', stack: "'DM Sans Variable', sans-serif" }, { id: 'nunito', label: 'Nunito', stack: "'Nunito Variable', sans-serif" }, { id: 'rubik', label: 'Rubik', stack: "'Rubik Variable', sans-serif" }, { id: 'space-grotesk', label: 'Space Grotesk', stack: "'Space Grotesk Variable', sans-serif" }, { id: 'figtree', label: 'Figtree', stack: "'Figtree Variable', sans-serif" }, { id: 'manrope', label: 'Manrope', stack: "'Manrope Variable', sans-serif" }, { id: 'plus-jakarta-sans', label: 'Plus Jakarta Sans', stack: "'Plus Jakarta Sans Variable', sans-serif" }, { id: 'lexend', label: 'Lexend', stack: "'Lexend Variable', sans-serif" }, { id: 'geist', label: 'Geist', stack: "'Geist Variable', sans-serif" }, { id: 'jetbrains-mono', label: 'JetBrains Mono', stack: "'JetBrains Mono Variable', monospace" }, { id: 'golos-text', label: 'Golos Text', stack: "'Golos Text Variable', sans-serif" }, { id: 'unbounded', label: 'Unbounded', stack: "'Unbounded Variable', sans-serif" }, ] as { id: FontId; label: string; stack: string; hint?: string }[] ).map(f => ( ))}
} >
{t('settings.fsShowArtistPortrait')}
{t('settings.fsShowArtistPortraitDesc')}
{auth.showFsArtistPortrait && (
{t('settings.fsPortraitDim')} {auth.fsPortraitDim}%
auth.setFsPortraitDim(parseInt(e.target.value, 10))} className="ui-scale-slider" />
)}
} >
{t('settings.seekbarStyleDesc')}
{(['truewave', 'pseudowave', 'linedot', 'bar', 'thick', 'segmented', 'neon', 'pulsewave', 'particletrail', 'liquidfill', 'retrotape'] as SeekbarStyle[]).map(style => ( auth.setSeekbarStyle(style)} /> ))}
); }