zustand stores

This commit is contained in:
2026-03-27 03:32:48 +02:00
parent df17c7ab95
commit b0c2e48224
20 changed files with 1279 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
import { create } from 'zustand'
/**
* Per-session player state - things that should reset on a new playback
* session and shouldn't pollute the persisted preferences store.
*
* Examples:
* - A-B loop markers (per item)
* - Theater / zen mode toggle (transient)
* - Per-session subtitle / audio delay overrides on top of the persisted
* defaults from `usePreferencesStore`
* - Secondary subtitle index
* - Picture filters during this session (overrides persisted defaults)
*
* Anything you'd want to persist across episodes / app restarts belongs
* in `usePreferencesStore`, not here.
*/
export interface PlayerRuntimeState {
// A-B loop
loopA: number | null
loopB: number | null
// Subtitle + audio offsets in ms. These add to the persisted default
// from settings; combined effective offset = settings + runtime.
subtitleOffsetMs: number
audioOffsetMs: number
// Secondary subtitle stream index (Jellyfin stream Index, not array index)
secondarySubtitleIndex: number | null
// Theater / zen mode
theaterMode: boolean
// Picture filters - if a value is null we fall back to the persisted pref.
// Overrides for *this* session only.
brightnessOverride: number | null
contrastOverride: number | null
saturationOverride: number | null
// Auto-recap dismissal flag - true once the user has dismissed (or
// skipped) the recap card for the current item, so it doesn't reappear
// if playback restarts.
recapDismissed: boolean
setLoopA(t: number | null): void
setLoopB(t: number | null): void
clearLoop(): void
setSubtitleOffsetMs(ms: number): void
bumpSubtitleOffsetMs(deltaMs: number): void
setAudioOffsetMs(ms: number): void
bumpAudioOffsetMs(deltaMs: number): void
setSecondarySubtitleIndex(i: number | null): void
setTheaterMode(on: boolean): void
setBrightnessOverride(v: number | null): void
setContrastOverride(v: number | null): void
setSaturationOverride(v: number | null): void
setRecapDismissed(v: boolean): void
/** Reset everything except theaterMode (which is window-scoped, not item-scoped). */
resetForNewItem(): void
}
export const usePlayerRuntimeStore = create<PlayerRuntimeState>(set => ({
loopA: null,
loopB: null,
subtitleOffsetMs: 0,
audioOffsetMs: 0,
secondarySubtitleIndex: null,
theaterMode: false,
brightnessOverride: null,
contrastOverride: null,
saturationOverride: null,
recapDismissed: false,
setLoopA: t => set({ loopA: t }),
setLoopB: t => set({ loopB: t }),
clearLoop: () => set({ loopA: null, loopB: null }),
setSubtitleOffsetMs: ms => set({ subtitleOffsetMs: ms }),
bumpSubtitleOffsetMs: deltaMs => set(s => ({ subtitleOffsetMs: s.subtitleOffsetMs + deltaMs })),
setAudioOffsetMs: ms => set({ audioOffsetMs: ms }),
bumpAudioOffsetMs: deltaMs => set(s => ({ audioOffsetMs: s.audioOffsetMs + deltaMs })),
setSecondarySubtitleIndex: i => set({ secondarySubtitleIndex: i }),
setTheaterMode: on => set({ theaterMode: on }),
setBrightnessOverride: v => set({ brightnessOverride: v }),
setContrastOverride: v => set({ contrastOverride: v }),
setSaturationOverride: v => set({ saturationOverride: v }),
setRecapDismissed: v => set({ recapDismissed: v }),
resetForNewItem: () =>
set({
loopA: null,
loopB: null,
subtitleOffsetMs: 0,
audioOffsetMs: 0,
secondarySubtitleIndex: null,
brightnessOverride: null,
contrastOverride: null,
saturationOverride: null,
recapDismissed: false,
}),
}))