added themes and theme selection to settings page
This commit is contained in:
+78
-15
@@ -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 && (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user