From bc862960a77d101c76a1bb713d1c59bcac45c699 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Mon, 1 Jun 2026 01:32:36 +0200 Subject: [PATCH] fix(settings): persist pause menu preferences --- src/managers/stores/useSettingsStore.ts | 71 ++++++---- src/managers/stores/useWorldSettingsStore.ts | 129 +++++++++++-------- src/utils/debug/Debug.ts | 15 ++- 3 files changed, 129 insertions(+), 86 deletions(-) diff --git a/src/managers/stores/useSettingsStore.ts b/src/managers/stores/useSettingsStore.ts index 5bafec8..c46a147 100644 --- a/src/managers/stores/useSettingsStore.ts +++ b/src/managers/stores/useSettingsStore.ts @@ -1,4 +1,5 @@ import { create } from "zustand"; +import { createJSONStorage, persist } from "zustand/middleware"; import { AudioManager } from "@/managers/AudioManager"; import type { AudioCategory } from "@/managers/AudioManager"; import type { SubtitleLanguage } from "@/types/settings/settings"; @@ -33,6 +34,8 @@ const DEFAULT_SETTINGS: SettingsState = { subtitleLanguage: "fr", }; +const SETTINGS_STORAGE_KEY = "la-fabrik-settings"; + function clampVolume(volume: number): number { return Math.max(0, Math.min(1, volume)); } @@ -46,36 +49,50 @@ function setAudioCategoryVolume( return nextVolume; } -function applyDefaultAudioSettings(): void { - AudioManager.getInstance().setCategoryVolume( - "music", - DEFAULT_SETTINGS.musicVolume, - ); - AudioManager.getInstance().setCategoryVolume( - "sfx", - DEFAULT_SETTINGS.sfxVolume, - ); +function applyAudioSettings( + settings: Pick, +): void { + AudioManager.getInstance().setCategoryVolume("music", settings.musicVolume); + AudioManager.getInstance().setCategoryVolume("sfx", settings.sfxVolume); AudioManager.getInstance().setCategoryVolume( "dialogue", - DEFAULT_SETTINGS.dialogueVolume, + settings.dialogueVolume, ); } -applyDefaultAudioSettings(); +applyAudioSettings(DEFAULT_SETTINGS); -export const useSettingsStore = create()((set) => ({ - ...DEFAULT_SETTINGS, - setSettingsMenuOpen: (isSettingsMenuOpen) => set({ isSettingsMenuOpen }), - setMusicVolume: (volume) => - set({ musicVolume: setAudioCategoryVolume("music", volume) }), - setSfxVolume: (volume) => - set({ sfxVolume: setAudioCategoryVolume("sfx", volume) }), - setDialogueVolume: (volume) => - set({ dialogueVolume: setAudioCategoryVolume("dialogue", volume) }), - setSubtitlesEnabled: (subtitlesEnabled) => set({ subtitlesEnabled }), - setSubtitleLanguage: (subtitleLanguage) => set({ subtitleLanguage }), - resetSettings: () => { - applyDefaultAudioSettings(); - set(DEFAULT_SETTINGS); - }, -})); +export const useSettingsStore = create()( + persist( + (set) => ({ + ...DEFAULT_SETTINGS, + setSettingsMenuOpen: (isSettingsMenuOpen) => set({ isSettingsMenuOpen }), + setMusicVolume: (volume) => + set({ musicVolume: setAudioCategoryVolume("music", volume) }), + setSfxVolume: (volume) => + set({ sfxVolume: setAudioCategoryVolume("sfx", volume) }), + setDialogueVolume: (volume) => + set({ dialogueVolume: setAudioCategoryVolume("dialogue", volume) }), + setSubtitlesEnabled: (subtitlesEnabled) => set({ subtitlesEnabled }), + setSubtitleLanguage: (subtitleLanguage) => set({ subtitleLanguage }), + resetSettings: () => { + applyAudioSettings(DEFAULT_SETTINGS); + set(DEFAULT_SETTINGS); + }, + }), + { + name: SETTINGS_STORAGE_KEY, + storage: createJSONStorage(() => window.localStorage), + partialize: (state) => ({ + dialogueVolume: state.dialogueVolume, + musicVolume: state.musicVolume, + sfxVolume: state.sfxVolume, + subtitleLanguage: state.subtitleLanguage, + subtitlesEnabled: state.subtitlesEnabled, + }), + onRehydrateStorage: () => (state) => { + if (state) applyAudioSettings(state); + }, + }, + ), +); diff --git a/src/managers/stores/useWorldSettingsStore.ts b/src/managers/stores/useWorldSettingsStore.ts index db1d71a..bdf6ad5 100644 --- a/src/managers/stores/useWorldSettingsStore.ts +++ b/src/managers/stores/useWorldSettingsStore.ts @@ -1,4 +1,5 @@ import { create } from "zustand"; +import { createJSONStorage, persist } from "zustand/middleware"; import { CLOUD_DEFAULTS, type CloudState } from "@/data/world/cloudConfig"; import { FOG_CONFIG, type FogState } from "@/data/world/fogConfig"; import { WIND_DEFAULTS, type WindState } from "@/data/world/windConfig"; @@ -46,73 +47,89 @@ const DEFAULT_STATE: WorldSettingsState = { graphics: { ...GRAPHICS_DEFAULTS }, }; -export const useWorldSettingsStore = create()((set) => ({ - ...DEFAULT_STATE, +const WORLD_SETTINGS_STORAGE_KEY = "la-fabrik-world-settings"; - setClouds: (cloudsUpdate) => - set((state) => ({ - clouds: { ...state.clouds, ...cloudsUpdate }, - })), +export const useWorldSettingsStore = create()( + persist( + (set) => ({ + ...DEFAULT_STATE, - setFog: (fogUpdate) => - set((state) => ({ - fog: { ...state.fog, ...fogUpdate }, - })), + setClouds: (cloudsUpdate) => + set((state) => ({ + clouds: { ...state.clouds, ...cloudsUpdate }, + })), - setWind: (windUpdate) => - set((state) => ({ - wind: { ...state.wind, ...windUpdate }, - })), + setFog: (fogUpdate) => + set((state) => ({ + fog: { ...state.fog, ...fogUpdate }, + })), - setWindSpeed: (speed) => - set((state) => ({ - wind: { ...state.wind, speed }, - })), + setWind: (windUpdate) => + set((state) => ({ + wind: { ...state.wind, ...windUpdate }, + })), - setWindDirection: (direction) => - set((state) => ({ - wind: { ...state.wind, direction }, - })), + setWindSpeed: (speed) => + set((state) => ({ + wind: { ...state.wind, speed }, + })), - setWindStrength: (strength) => - set((state) => ({ - wind: { ...state.wind, strength }, - })), + setWindDirection: (direction) => + set((state) => ({ + wind: { ...state.wind, direction }, + })), - setGraphics: (graphicsUpdate) => - set((state) => ({ - graphics: { ...state.graphics, ...graphicsUpdate }, - })), + setWindStrength: (strength) => + set((state) => ({ + wind: { ...state.wind, strength }, + })), - setGraphicsPreset: (preset) => - set((state) => ({ - graphics: { ...state.graphics, preset }, - })), + setGraphics: (graphicsUpdate) => + set((state) => ({ + graphics: { ...state.graphics, ...graphicsUpdate }, + })), - setDynamicGrass: (dynamicGrass) => - set((state) => ({ - graphics: { ...state.graphics, dynamicGrass }, - })), + setGraphicsPreset: (preset) => + set((state) => ({ + graphics: { ...state.graphics, preset }, + })), - setDynamicTrees: (dynamicTrees) => - set((state) => ({ - graphics: { ...state.graphics, dynamicTrees }, - })), + setDynamicGrass: (dynamicGrass) => + set((state) => ({ + graphics: { ...state.graphics, dynamicGrass }, + })), - setDynamicClouds: (dynamicClouds) => - set((state) => ({ - graphics: { ...state.graphics, dynamicClouds }, - })), + setDynamicTrees: (dynamicTrees) => + set((state) => ({ + graphics: { ...state.graphics, dynamicTrees }, + })), - setShadowsEnabled: (shadowsEnabled) => - set((state) => ({ - graphics: { ...state.graphics, shadowsEnabled }, - })), + setDynamicClouds: (dynamicClouds) => + set((state) => ({ + graphics: { ...state.graphics, dynamicClouds }, + })), - setGrassDensity: (grassDensity) => - set((state) => ({ - graphics: { ...state.graphics, grassDensity }, - })), + setShadowsEnabled: (shadowsEnabled) => + set((state) => ({ + graphics: { ...state.graphics, shadowsEnabled }, + })), - resetToDefaults: () => set(DEFAULT_STATE), -})); + setGrassDensity: (grassDensity) => + set((state) => ({ + graphics: { ...state.graphics, grassDensity }, + })), + + resetToDefaults: () => set(DEFAULT_STATE), + }), + { + name: WORLD_SETTINGS_STORAGE_KEY, + storage: createJSONStorage(() => window.localStorage), + partialize: (state) => ({ + clouds: state.clouds, + fog: state.fog, + graphics: state.graphics, + wind: state.wind, + }), + }, + ), +); diff --git a/src/utils/debug/Debug.ts b/src/utils/debug/Debug.ts index 026b9d1..e848f15 100644 --- a/src/utils/debug/Debug.ts +++ b/src/utils/debug/Debug.ts @@ -9,6 +9,7 @@ const DEBUG_CONTROLS_STORAGE_KEY = "la-fabrik-debug-controls"; interface StoredDebugControls { cameraMode: CameraMode; + handTrackingSource: HandTrackingSource; sceneMode: SceneMode; } @@ -39,6 +40,10 @@ function isSceneMode(value: unknown): value is SceneMode { return value === "game" || value === "physics"; } +function isHandTrackingSource(value: unknown): value is HandTrackingSource { + return value === "browser" || value === "backend"; +} + function getStoredDebugControls(): Partial { try { const rawValue = window.localStorage.getItem(DEBUG_CONTROLS_STORAGE_KEY); @@ -51,6 +56,9 @@ function getStoredDebugControls(): Partial { ...(isCameraMode(parsedValue.cameraMode) ? { cameraMode: parsedValue.cameraMode } : {}), + ...(isHandTrackingSource(parsedValue.handTrackingSource) + ? { handTrackingSource: parsedValue.handTrackingSource } + : {}), ...(isSceneMode(parsedValue.sceneMode) ? { sceneMode: parsedValue.sceneMode } : {}), @@ -94,7 +102,7 @@ export class Debug { this.controls = { cameraMode: storedControls.cameraMode ?? "player", fogEnabled: FOG_CONFIG.enabled, - handTrackingSource: "browser", + handTrackingSource: storedControls.handTrackingSource ?? "browser", showDebugOverlay: true, showHandTrackingSvg: false, showInteractionSpheres: false, @@ -159,7 +167,7 @@ export class Debug { .name("Source") .onChange((value: HandTrackingSource) => { this.controls.handTrackingSource = value; - this.emit(); + this.saveAndEmit(); }); } } @@ -246,7 +254,7 @@ export class Debug { setHandTrackingSource(value: HandTrackingSource): void { this.controls.handTrackingSource = value; - this.emit(); + this.saveAndEmit(); } getFogEnabled(): boolean { @@ -285,6 +293,7 @@ export class Debug { DEBUG_CONTROLS_STORAGE_KEY, JSON.stringify({ cameraMode: this.controls.cameraMode, + handTrackingSource: this.controls.handTrackingSource, sceneMode: this.controls.sceneMode, }), );