added themes and theme selection to settings page

This commit is contained in:
2026-04-15 14:47:58 -07:00
parent 694eb23e7b
commit ca559de2c0
5 changed files with 320 additions and 53 deletions
+78 -15
View File
@@ -28,21 +28,27 @@ import PlayArrowIcon from '@mui/icons-material/PlayArrow'
import EditIcon from '@mui/icons-material/Edit'
import AddIcon from '@mui/icons-material/Add'
import CloudDownloadIcon from '@mui/icons-material/CloudDownload'
import type { LibraryPrimaryAction } from '../appPreferences'
import type { Server } from '../context/ServersContext'
import { useServers } from '../context/ServersContext'
import { buildLibraryCacheKey, getLibraryCacheStats, pruneLibraryCache } from '../libraryCache'
import { syncLibraryCache } from '../librarySync'
import { APP_THEME_PRESETS, type AppThemeId } from '../themes'
const DEFAULT_SERVER_FORM = { name: '', host: '', port: undefined, apiKey: '', ssl: false, forceApiKeyInQuery: false }
type SettingsPageProps = {
onClose?: () => void
devOverlayEnabled: boolean
onDevOverlayEnabledChange: (enabled: boolean) => void
appTheme: AppThemeId
onAppThemeChange: (theme: AppThemeId) => void
libraryDisplayMode: 'grid' | 'table'
onLibraryDisplayModeChange: (mode: 'grid' | 'table') => void
libraryPrimaryAction: LibraryPrimaryAction
onLibraryPrimaryActionChange: (action: LibraryPrimaryAction) => void
}
export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayEnabledChange, libraryDisplayMode, onLibraryDisplayModeChange }: SettingsPageProps) {
export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayEnabledChange, appTheme, onAppThemeChange, libraryDisplayMode, onLibraryDisplayModeChange, libraryPrimaryAction, onLibraryPrimaryActionChange }: SettingsPageProps) {
const { servers, addServer, updateServer, removeServer, testServerById, testServerConfig, setActiveServerId, activeServerId } = useServers()
const [editing, setEditing] = useState<Server | null>(null)
const [form, setForm] = useState<Omit<Server, 'id' | 'lastTest'>>(DEFAULT_SERVER_FORM)
@@ -51,7 +57,9 @@ export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayE
const [lastTest, setLastTest] = useState<string | null>(null)
const [detailsOpen, setDetailsOpen] = useState(false)
const [detailsText, setDetailsText] = useState<string | null>(null)
const [draftAppTheme, setDraftAppTheme] = useState<AppThemeId>(appTheme)
const [draftLibraryDisplayMode, setDraftLibraryDisplayMode] = useState<'grid' | 'table'>(libraryDisplayMode)
const [draftLibraryPrimaryAction, setDraftLibraryPrimaryAction] = useState<LibraryPrimaryAction>(libraryPrimaryAction)
const [draftDevOverlayEnabled, setDraftDevOverlayEnabled] = useState(devOverlayEnabled)
const [syncCompletionNotices, setSyncCompletionNotices] = useState<Record<string, string>>({})
const [cacheStorageText, setCacheStorageText] = useState('0 B')
@@ -70,10 +78,18 @@ export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayE
setDetailsOpen(false)
}, [])
useEffect(() => {
setDraftAppTheme(appTheme)
}, [appTheme])
useEffect(() => {
setDraftLibraryDisplayMode(libraryDisplayMode)
}, [libraryDisplayMode])
useEffect(() => {
setDraftLibraryPrimaryAction(libraryPrimaryAction)
}, [libraryPrimaryAction])
useEffect(() => {
setDraftDevOverlayEnabled(devOverlayEnabled)
}, [devOverlayEnabled])
@@ -84,7 +100,7 @@ export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayE
}
}, [])
const preferencesDirty = draftLibraryDisplayMode !== libraryDisplayMode || draftDevOverlayEnabled !== devOverlayEnabled
const preferencesDirty = draftAppTheme !== appTheme || draftLibraryDisplayMode !== libraryDisplayMode || draftLibraryPrimaryAction !== libraryPrimaryAction || draftDevOverlayEnabled !== devOverlayEnabled
const formatBytes = (value: number) => {
if (!value || value <= 0) return '0 B'
@@ -226,10 +242,18 @@ export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayE
}, [currentCacheKey])
const handleSavePreferences = () => {
if (draftAppTheme !== appTheme) {
onAppThemeChange(draftAppTheme)
}
if (draftLibraryDisplayMode !== libraryDisplayMode) {
onLibraryDisplayModeChange(draftLibraryDisplayMode)
}
if (draftLibraryPrimaryAction !== libraryPrimaryAction) {
onLibraryPrimaryActionChange(draftLibraryPrimaryAction)
}
if (draftDevOverlayEnabled !== devOverlayEnabled) {
onDevOverlayEnabledChange(draftDevOverlayEnabled)
}
@@ -252,7 +276,7 @@ export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayE
<Box>
<Typography variant="subtitle1" sx={{ mb: 0.5 }}>Interface preferences</Typography>
<Typography variant="body2" color="text.secondary">
Save Library layout and development UI changes together.
Personalize the look and default actions without affecting your cached library.
</Typography>
</Box>
<Button variant="contained" onClick={handleSavePreferences} disabled={!preferencesDirty}>
@@ -260,18 +284,57 @@ export default function SettingsPage({ onClose, devOverlayEnabled, onDevOverlayE
</Button>
</Box>
<FormControl size="small" sx={{ minWidth: 180, maxWidth: 240, mb: import.meta.env.DEV ? 2 : 0 }}>
<InputLabel id="settings-library-display-mode-label">Display</InputLabel>
<Select
labelId="settings-library-display-mode-label"
value={draftLibraryDisplayMode}
label="Display"
onChange={(event) => setDraftLibraryDisplayMode(event.target.value as 'grid' | 'table')}
>
<MenuItem value="grid">Grid</MenuItem>
<MenuItem value="table">Table</MenuItem>
</Select>
</FormControl>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: 'repeat(3, minmax(0, 240px))' }, gap: 1.5, mb: import.meta.env.DEV ? 2 : 0 }}>
<FormControl size="small">
<InputLabel id="settings-app-theme-label">Theme</InputLabel>
<Select
labelId="settings-app-theme-label"
value={draftAppTheme}
label="Theme"
onChange={(event) => setDraftAppTheme(event.target.value as AppThemeId)}
>
{APP_THEME_PRESETS.map((theme) => (
<MenuItem key={theme.id} value={theme.id}>{theme.label}</MenuItem>
))}
</Select>
</FormControl>
<FormControl size="small">
<InputLabel id="settings-library-display-mode-label">Display</InputLabel>
<Select
labelId="settings-library-display-mode-label"
value={draftLibraryDisplayMode}
label="Display"
onChange={(event) => setDraftLibraryDisplayMode(event.target.value as 'grid' | 'table')}
>
<MenuItem value="grid">Grid</MenuItem>
<MenuItem value="table">Table</MenuItem>
</Select>
</FormControl>
<FormControl size="small">
<InputLabel id="settings-library-primary-action-label">Track tap</InputLabel>
<Select
labelId="settings-library-primary-action-label"
value={draftLibraryPrimaryAction}
label="Track tap"
onChange={(event) => setDraftLibraryPrimaryAction(event.target.value as LibraryPrimaryAction)}
>
<MenuItem value="details">Open details popup</MenuItem>
<MenuItem value="stream">Stream immediately</MenuItem>
</Select>
</FormControl>
</Box>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', mb: import.meta.env.DEV ? 2 : 0.5 }}>
{APP_THEME_PRESETS.map((theme) => (
<Chip key={theme.id} label={theme.label} color={draftAppTheme === theme.id ? 'primary' : 'default'} variant={draftAppTheme === theme.id ? 'filled' : 'outlined'} />
))}
</Box>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: import.meta.env.DEV ? 0.5 : 0 }}>
{APP_THEME_PRESETS.find((theme) => theme.id === draftAppTheme)?.description}
</Typography>
{import.meta.env.DEV && (
<>