mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
225f7c1406
* feat(themes): add Kanagawa, Atom One, 1984 palettes; group OSS Classics by family Adds three upstream-faithful theme families to Open Source Classics and restructures the picker so the section is no longer alphabetically wild. New themes (9 total): - Kanagawa (rebelot/kanagawa.nvim): Wave, Dragon, Lotus - Atom One (Th3Whit3Wolf/one-nvim): Dark, Light - 1984 (juanmnl/vs-1984): Default, Cyberpunk, Light, Orwell (Fancy + Unbolded skipped — identical palette to Default, style-only) Each theme defines the full token set (--bg-*, --accent, --text-*, all --ctp-*, --waveform-*, --positive/warning/danger, --select-arrow), so login screen, queue sidebar tabs, and all subpages inherit the palette without component-level overrides. Picker restructure: - ThemeDef gains optional `family?: string` - Open Source Classics regrouped: 1984, Atom One, Catppuccin, Dracula, Gruvbox, Kanagawa, Nightfox, Nord - Family headings rendered inline (grid-column: 1 / -1) when family changes; new .theme-family-header style in components.css - Theme scheduler dropdown labels prefixed with family for context Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(release): sync Cargo.lock to 1.45.0-dev
80 lines
4.0 KiB
TypeScript
80 lines
4.0 KiB
TypeScript
import { create } from 'zustand';
|
|
import { persist } from 'zustand/middleware';
|
|
|
|
type Theme = 'mocha' | 'macchiato' | 'frappe' | 'latte' | 'nord' | 'nord-snowstorm' | 'nord-frost' | 'nord-aurora' | 'psychowave' | 'wnamp' | 'poison' | 'nucleo' | 'muma-jukebox' | 'winmedplayer' | 'p-dvd' | 'vintage-tube-radio' | 'neon-drift' | 'aero-glass' | 'luna-teal' | 'w98' | 'cupertino-light' | 'cupertino-dark' | 'gruvbox-dark-hard' | 'gruvbox-dark-medium' | 'gruvbox-dark-soft' | 'gruvbox-light-hard' | 'gruvbox-light-medium' | 'gruvbox-light-soft' | 'spotless' | 'dzr0' | 'cupertino-beats' | 'lambda-17' | 'gw1' | 'grand-theft-audio' | 'v-tactical' | 'nightcity-2077' | 'middle-earth' | 'morpheus' | 'stark-hud' | 'blade' | 'heisenberg' | 'ice-and-fire' | 'doh-matic' | 't-800' | 'dune' | 'tetrastack' | 'the-book' | 'readit' | 'insta' | 'hill-valley-85' | 'turtle-power' | 'w3-1' | 'aqua-quartz' | 'spider-tech' | 'dos' | 'unix' | 'jayfin' | 'horde' | 'alliance' | 'w11' | 'w10' | 'north-park' | 'dark-side-of-the-moon' | 'powerslave' | 'nightfox' | 'dayfox' | 'dawnfox' | 'duskfox' | 'nordfox' | 'terafox' | 'carbonfox' | 'dracula' | 'kanagawa-wave' | 'kanagawa-dragon' | 'kanagawa-lotus' | 'one-dark' | 'one-light' | 'vs-1984' | 'vs-1984-cyberpunk' | 'vs-1984-light' | 'vs-1984-orwell' | 'vision-dark' | 'vision-navy';
|
|
|
|
interface ThemeState {
|
|
theme: Theme;
|
|
setTheme: (theme: Theme) => void;
|
|
enableThemeScheduler: boolean;
|
|
setEnableThemeScheduler: (v: boolean) => void;
|
|
themeDay: string;
|
|
setThemeDay: (v: string) => void;
|
|
themeNight: string;
|
|
setThemeNight: (v: string) => void;
|
|
timeDayStart: string;
|
|
setTimeDayStart: (v: string) => void;
|
|
timeNightStart: string;
|
|
setTimeNightStart: (v: string) => void;
|
|
enableCoverArtBackground: boolean;
|
|
setEnableCoverArtBackground: (v: boolean) => void;
|
|
enablePlaylistCoverPhoto: boolean;
|
|
setEnablePlaylistCoverPhoto: (v: boolean) => void;
|
|
showBitrate: boolean;
|
|
setShowBitrate: (v: boolean) => void;
|
|
showRemainingTime: boolean;
|
|
setShowRemainingTime: (v: boolean) => void;
|
|
expandReplayGain: boolean;
|
|
setExpandReplayGain: (v: boolean) => void;
|
|
floatingPlayerBar: boolean;
|
|
setFloatingPlayerBar: (v: boolean) => void;
|
|
}
|
|
|
|
export function getScheduledTheme(state: Pick<ThemeState, 'enableThemeScheduler' | 'theme' | 'themeDay' | 'themeNight' | 'timeDayStart' | 'timeNightStart'>): string {
|
|
if (!state.enableThemeScheduler) return state.theme;
|
|
const now = new Date();
|
|
const nowMins = now.getHours() * 60 + now.getMinutes();
|
|
const [dh, dm] = state.timeDayStart.split(':').map(Number);
|
|
const [nh, nm] = state.timeNightStart.split(':').map(Number);
|
|
const dayMins = dh * 60 + dm;
|
|
const nightMins = nh * 60 + nm;
|
|
const isDay = dayMins < nightMins
|
|
? nowMins >= dayMins && nowMins < nightMins
|
|
: nowMins >= dayMins || nowMins < nightMins;
|
|
return isDay ? state.themeDay : state.themeNight;
|
|
}
|
|
|
|
export const useThemeStore = create<ThemeState>()(
|
|
persist(
|
|
(set) => ({
|
|
theme: 'mocha',
|
|
setTheme: (theme) => set({ theme }),
|
|
enableThemeScheduler: false,
|
|
setEnableThemeScheduler: (v) => set({ enableThemeScheduler: v }),
|
|
themeDay: 'latte',
|
|
setThemeDay: (v) => set({ themeDay: v }),
|
|
themeNight: 'mocha',
|
|
setThemeNight: (v) => set({ themeNight: v }),
|
|
timeDayStart: '07:00',
|
|
setTimeDayStart: (v) => set({ timeDayStart: v }),
|
|
timeNightStart: '19:00',
|
|
setTimeNightStart: (v) => set({ timeNightStart: v }),
|
|
enableCoverArtBackground: true,
|
|
setEnableCoverArtBackground: (v) => set({ enableCoverArtBackground: v }),
|
|
enablePlaylistCoverPhoto: true,
|
|
setEnablePlaylistCoverPhoto: (v) => set({ enablePlaylistCoverPhoto: v }),
|
|
showBitrate: true,
|
|
setShowBitrate: (v) => set({ showBitrate: v }),
|
|
showRemainingTime: false,
|
|
setShowRemainingTime: (v) => set({ showRemainingTime: v }),
|
|
expandReplayGain: false,
|
|
setExpandReplayGain: (v) => set({ expandReplayGain: v }),
|
|
floatingPlayerBar: false,
|
|
setFloatingPlayerBar: (v) => set({ floatingPlayerBar: v }),
|
|
}),
|
|
{
|
|
name: 'psysonic_theme',
|
|
}
|
|
)
|
|
);
|