Skip to content

Commit f3d761b

Browse files
Merge pull request #324 from JasonOA888/fix/306-persist-user-settings
fix: persist user settings across sessions
2 parents ae971bc + a8427b9 commit f3d761b

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

src/components/video-editor/VideoEditor.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import { computeFrameStepTime } from "@/lib/frameStep";
2424
import type { ProjectMedia } from "@/lib/recordingSession";
2525
import { matchesShortcut } from "@/lib/shortcuts";
26+
import { loadUserPreferences, saveUserPreferences } from "@/lib/userPreferences";
2627
import {
2728
getAspectRatioValue,
2829
getNativeAspectRatioValue,
@@ -366,6 +367,28 @@ export default function VideoEditor() {
366367
loadInitialData();
367368
}, [applyLoadedProject]);
368369

370+
// Track whether user preferences have been loaded to avoid
371+
// overwriting saved prefs with defaults on the first render
372+
const [prefsHydrated, setPrefsHydrated] = useState(false);
373+
374+
// Load persisted user preferences on mount (intentionally runs once)
375+
useEffect(() => {
376+
const prefs = loadUserPreferences();
377+
updateState({
378+
padding: prefs.padding,
379+
aspectRatio: prefs.aspectRatio,
380+
});
381+
setExportQuality(prefs.exportQuality);
382+
setExportFormat(prefs.exportFormat);
383+
setPrefsHydrated(true);
384+
}, [updateState]);
385+
386+
// Auto-save user preferences when settings change
387+
useEffect(() => {
388+
if (!prefsHydrated) return;
389+
saveUserPreferences({ padding, aspectRatio, exportQuality, exportFormat });
390+
}, [prefsHydrated, padding, aspectRatio, exportQuality, exportFormat]);
391+
369392
const saveProject = useCallback(
370393
async (forceSaveAs: boolean) => {
371394
if (!videoPath) {

src/lib/userPreferences.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import type { ExportFormat, ExportQuality } from "@/lib/exporter";
2+
import type { AspectRatio } from "@/utils/aspectRatioUtils";
3+
4+
const PREFS_KEY = "openscreen_user_preferences";
5+
6+
const VALID_ASPECT_RATIOS: readonly string[] = [
7+
"16:9",
8+
"9:16",
9+
"1:1",
10+
"4:3",
11+
"4:5",
12+
"16:10",
13+
"10:16",
14+
"native",
15+
];
16+
17+
export interface UserPreferences {
18+
/** Default padding % */
19+
padding: number;
20+
/** Default aspect ratio */
21+
aspectRatio: AspectRatio;
22+
/** Default export quality */
23+
exportQuality: ExportQuality;
24+
/** Default export format */
25+
exportFormat: ExportFormat;
26+
}
27+
28+
const DEFAULT_PREFS: UserPreferences = {
29+
padding: 50,
30+
aspectRatio: "16:9",
31+
exportQuality: "good",
32+
exportFormat: "mp4",
33+
};
34+
35+
function safeJsonParse(text: string | null): Record<string, unknown> | null {
36+
if (!text) return null;
37+
try {
38+
return JSON.parse(text);
39+
} catch {
40+
return null;
41+
}
42+
}
43+
44+
/**
45+
* Load persisted user preferences from localStorage.
46+
* Returns defaults for any missing or invalid fields.
47+
*/
48+
export function loadUserPreferences(): UserPreferences {
49+
let raw: Record<string, unknown> | null = null;
50+
try {
51+
raw = safeJsonParse(localStorage.getItem(PREFS_KEY));
52+
} catch {
53+
return { ...DEFAULT_PREFS };
54+
}
55+
if (!raw || typeof raw !== "object") return { ...DEFAULT_PREFS };
56+
57+
return {
58+
padding:
59+
typeof raw.padding === "number" &&
60+
Number.isFinite(raw.padding) &&
61+
raw.padding >= 0 &&
62+
raw.padding <= 100
63+
? raw.padding
64+
: DEFAULT_PREFS.padding,
65+
aspectRatio:
66+
typeof raw.aspectRatio === "string" && VALID_ASPECT_RATIOS.includes(raw.aspectRatio)
67+
? (raw.aspectRatio as AspectRatio)
68+
: DEFAULT_PREFS.aspectRatio,
69+
exportQuality:
70+
raw.exportQuality === "medium" ||
71+
raw.exportQuality === "good" ||
72+
raw.exportQuality === "source"
73+
? (raw.exportQuality as ExportQuality)
74+
: DEFAULT_PREFS.exportQuality,
75+
exportFormat:
76+
raw.exportFormat === "gif" || raw.exportFormat === "mp4"
77+
? (raw.exportFormat as ExportFormat)
78+
: DEFAULT_PREFS.exportFormat,
79+
};
80+
}
81+
82+
/**
83+
* Persist user preferences to localStorage.
84+
* Only the explicitly provided fields are updated.
85+
*/
86+
export function saveUserPreferences(partial: Partial<UserPreferences>): void {
87+
const current = loadUserPreferences();
88+
const merged = { ...current, ...partial };
89+
try {
90+
localStorage.setItem(PREFS_KEY, JSON.stringify(merged));
91+
} catch {
92+
// localStorage may be unavailable (e.g. private browsing quota exceeded)
93+
}
94+
}

0 commit comments

Comments
 (0)