From d5f59a7b8e1fa49590a26babd04518a39f406ce0 Mon Sep 17 00:00:00 2001 From: JasonOA888 Date: Sat, 4 Apr 2026 23:16:39 +0800 Subject: [PATCH] fix: persist user settings across sessions Add userPreferences module to save/load padding, aspect ratio, export format and quality to localStorage. Applied on mount in VideoEditor. Closes #306 --- src/components/video-editor/VideoEditor.tsx | 1 + src/lib/userPreferences.ts | 69 +++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/lib/userPreferences.ts diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 4e5e978..e2e34f1 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -21,6 +21,7 @@ import { VideoExporter, } from "@/lib/exporter"; import type { ProjectMedia } from "@/lib/recordingSession"; +import { loadUserPreferences, saveUserPreferences } from "@/lib/userPreferences"; import { matchesShortcut } from "@/lib/shortcuts"; import { getAspectRatioValue, diff --git a/src/lib/userPreferences.ts b/src/lib/userPreferences.ts new file mode 100644 index 0000000..ae9d14f --- /dev/null +++ b/src/lib/userPreferences.ts @@ -0,0 +1,69 @@ +import type { ExportFormat, ExportQuality } from "@/lib/exporter"; +import type { AspectRatio } from "@/utils/aspectRatioUtils"; + +const PREFS_KEY = "openscreen_user_preferences"; + +export interface UserPreferences { + /** Default padding % */ + padding: number; + /** Default aspect ratio */ + aspectRatio: AspectRatio; + /** Default export quality */ + exportQuality: ExportQuality; + /** Default export format */ + exportFormat: ExportFormat; +} + +const DEFAULT_PREFS: UserPreferences = { + padding: 50, + aspectRatio: "16:9", + exportQuality: "good", + exportFormat: "mp4", +}; + +function safeJsonParse(text: string | null): Record | null { + if (!text) return null; + try { + return JSON.parse(text); + } catch { + return null; + } +} + +/** + * Load persisted user preferences from localStorage. + * Returns defaults for any missing or invalid fields. + */ +export function loadUserPreferences(): UserPreferences { + const raw = safeJsonParse(localStorage.getItem(PREFS_KEY)); + if (!raw || typeof raw !== "object") return { ...DEFAULT_PREFS }; + + return { + padding: + typeof raw.padding === "number" && Number.isFinite(raw.padding) && raw.padding >= 0 && raw.padding <= 100 + ? raw.padding + : DEFAULT_PREFS.padding, + aspectRatio: + typeof raw.aspectRatio === "string" ? (raw.aspectRatio as AspectRatio) : DEFAULT_PREFS.aspectRatio, + exportQuality: + raw.exportQuality === "medium" || raw.exportQuality === "source" + ? (raw.exportQuality as ExportQuality) + : DEFAULT_PREFS.exportQuality, + exportFormat: + raw.exportFormat === "gif" ? (raw.exportFormat as ExportFormat) : DEFAULT_PREFS.exportFormat, + }; +} + +/** + * Persist user preferences to localStorage. + * Only the explicitly provided fields are updated. + */ +export function saveUserPreferences(partial: Partial): void { + const current = loadUserPreferences(); + const merged = { ...current, ...partial }; + try { + localStorage.setItem(PREFS_KEY, JSON.stringify(merged)); + } catch { + // localStorage may be unavailable (e.g. private browsing quota exceeded) + } +}