Initial commit: Shelfless – alternative Audiobookshelf frontend
React + Vite + TypeScript SPA covering the full ABS feature set (library browsing, item detail, metadata/cover editing, podcasts, player with session sync, admin: users/libraries/scanner/server settings). Dev uses a dynamic CORS proxy; production is served by server/index.mjs (static + reverse proxy to ABS_URL). Includes systemd unit and installer under deploy/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
93
src/store/settingsStore.ts
Normal file
93
src/store/settingsStore.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
export type AccentName = 'amber' | 'teal' | 'coral' | 'green' | 'blue'
|
||||
|
||||
export const ACCENTS: { name: AccentName; label: string; swatch: string }[] = [
|
||||
{ name: 'amber', label: 'Amber', swatch: '#f59e0b' },
|
||||
{ name: 'teal', label: 'Türkis', swatch: '#14b8a6' },
|
||||
{ name: 'coral', label: 'Koralle', swatch: '#fb7185' },
|
||||
{ name: 'green', label: 'Grün', swatch: '#22c55e' },
|
||||
{ name: 'blue', label: 'Blau', swatch: '#3b82f6' },
|
||||
]
|
||||
|
||||
export const PLAYBACK_SPEEDS = [0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3] as const
|
||||
|
||||
interface SettingsState {
|
||||
accent: AccentName
|
||||
/** When set, a freely chosen hex color overrides the named preset. */
|
||||
customAccent: string | null
|
||||
defaultSpeed: number
|
||||
sidebarCollapsed: boolean
|
||||
setAccent: (accent: AccentName) => void
|
||||
setCustomAccent: (hex: string) => void
|
||||
setDefaultSpeed: (speed: number) => void
|
||||
toggleSidebar: () => void
|
||||
}
|
||||
|
||||
export const useSettingsStore = create<SettingsState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
accent: 'amber',
|
||||
customAccent: null,
|
||||
defaultSpeed: 1,
|
||||
sidebarCollapsed: false,
|
||||
setAccent: (accent) => {
|
||||
applyTheme(accent, null)
|
||||
set({ accent, customAccent: null })
|
||||
},
|
||||
setCustomAccent: (hex) => {
|
||||
applyTheme('amber', hex)
|
||||
set({ customAccent: hex })
|
||||
},
|
||||
setDefaultSpeed: (defaultSpeed) => set({ defaultSpeed }),
|
||||
toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })),
|
||||
}),
|
||||
{
|
||||
name: 'shelfless.settings',
|
||||
onRehydrateStorage: () => (state) => {
|
||||
if (state) applyTheme(state.accent, state.customAccent)
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
// ── Theme application ─────────────────────────────────────────────────────
|
||||
function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
|
||||
let h = hex.trim().replace(/^#/, '')
|
||||
if (h.length === 3) h = h.split('').map((c) => c + c).join('')
|
||||
if (!/^[0-9a-fA-F]{6}$/.test(h)) return null
|
||||
return {
|
||||
r: parseInt(h.slice(0, 2), 16),
|
||||
g: parseInt(h.slice(2, 4), 16),
|
||||
b: parseInt(h.slice(4, 6), 16),
|
||||
}
|
||||
}
|
||||
|
||||
/** Pick readable foreground (near-black or near-white) for a given accent color. */
|
||||
function onAccentFor(r: number, g: number, b: number): string {
|
||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
|
||||
return luminance > 0.6 ? '#0c0a09' : '#fafaf9'
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflect the chosen accent onto the document. A named preset uses the
|
||||
* `[data-accent]` CSS rules; a custom hex sets inline CSS variables (which win).
|
||||
*/
|
||||
export function applyTheme(accent: AccentName, customAccent: string | null) {
|
||||
if (typeof document === 'undefined') return
|
||||
const root = document.documentElement
|
||||
const rgb = customAccent ? hexToRgb(customAccent) : null
|
||||
|
||||
if (customAccent && rgb) {
|
||||
root.style.setProperty('--accent', customAccent)
|
||||
root.style.setProperty('--accent-soft', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.14)`)
|
||||
root.style.setProperty('--on-accent', onAccentFor(rgb.r, rgb.g, rgb.b))
|
||||
root.setAttribute('data-accent', 'custom')
|
||||
} else {
|
||||
root.style.removeProperty('--accent')
|
||||
root.style.removeProperty('--accent-soft')
|
||||
root.style.removeProperty('--on-accent')
|
||||
root.setAttribute('data-accent', accent)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user