Refactor user preferences (#4667)

# Description of Changes
Refactor user preferences to all be in one service and all stored in
localStorage instead of indexeddb. This allows simpler & quicker
accessing of them, and ensures that they're all neatly stored in one
consistent place instead of spread out over local storage.
This commit is contained in:
James Brunton
2025-10-15 11:53:00 +01:00
committed by GitHub
parent af57ae02dd
commit 28e45917a2
15 changed files with 136 additions and 330 deletions
+17 -34
View File
@@ -1,53 +1,37 @@
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
import { preferencesService, UserPreferences, DEFAULT_PREFERENCES } from '../services/preferencesService';
import React, { createContext, useContext, useState, useCallback } from 'react';
import { preferencesService, UserPreferences } from '../services/preferencesService';
interface PreferencesContextValue {
preferences: UserPreferences;
updatePreference: <K extends keyof UserPreferences>(
key: K,
value: UserPreferences[K]
) => Promise<void>;
resetPreferences: () => Promise<void>;
isLoading: boolean;
) => void;
resetPreferences: () => void;
}
const PreferencesContext = createContext<PreferencesContextValue | undefined>(undefined);
export const PreferencesProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [preferences, setPreferences] = useState<UserPreferences>(DEFAULT_PREFERENCES);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const loadPreferences = async () => {
try {
await preferencesService.initialize();
const loadedPreferences = await preferencesService.getAllPreferences();
setPreferences(loadedPreferences);
} catch (error) {
console.error('Failed to load preferences:', error);
// Keep default preferences on error
} finally {
setIsLoading(false);
}
};
loadPreferences();
}, []);
const [preferences, setPreferences] = useState<UserPreferences>(() => {
// Load preferences synchronously on mount
return preferencesService.getAllPreferences();
});
const updatePreference = useCallback(
async <K extends keyof UserPreferences>(key: K, value: UserPreferences[K]) => {
await preferencesService.setPreference(key, value);
setPreferences((prev) => ({
...prev,
[key]: value,
}));
<K extends keyof UserPreferences>(key: K, value: UserPreferences[K]) => {
preferencesService.setPreference(key, value);
setPreferences((prev) => ({
...prev,
[key]: value,
}));
},
[]
);
const resetPreferences = useCallback(async () => {
await preferencesService.clearAllPreferences();
setPreferences(DEFAULT_PREFERENCES);
const resetPreferences = useCallback(() => {
preferencesService.clearAllPreferences();
setPreferences(preferencesService.getAllPreferences());
}, []);
return (
@@ -56,7 +40,6 @@ export const PreferencesProvider: React.FC<{ children: React.ReactNode }> = ({ c
preferences,
updatePreference,
resetPreferences,
isLoading,
}}
>
{children}
+6 -18
View File
@@ -14,11 +14,10 @@ import { filterToolRegistryByQuery } from '../utils/toolSearch';
import { useToolHistory } from '../hooks/tools/useUserToolActivity';
import {
ToolWorkflowState,
TOOL_PANEL_MODE_STORAGE_KEY,
createInitialState,
toolWorkflowReducer,
ToolPanelMode,
} from './toolWorkflow/toolWorkflowState';
import type { ToolPanelMode } from '../constants/toolPanel';
import { usePreferences } from './PreferencesContext';
// State interface
@@ -74,7 +73,7 @@ interface ToolWorkflowProviderProps {
export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
const [state, dispatch] = useReducer(toolWorkflowReducer, undefined, createInitialState);
const { preferences } = usePreferences();
const { preferences, updatePreference } = usePreferences();
// Store reset functions for tools
const [toolResetFunctions, setToolResetFunctions] = React.useState<Record<string, () => void>>({});
@@ -118,7 +117,8 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
const setToolPanelMode = useCallback((mode: ToolPanelMode) => {
dispatch({ type: 'SET_TOOL_PANEL_MODE', payload: mode });
}, []);
updatePreference('defaultToolPanelMode', mode);
}, [updatePreference]);
const setPreviewFile = useCallback((file: File | null) => {
@@ -136,27 +136,15 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
dispatch({ type: 'SET_SEARCH_QUERY', payload: query });
}, []);
useEffect(() => {
if (typeof window === 'undefined') {
return;
}
window.localStorage.setItem(TOOL_PANEL_MODE_STORAGE_KEY, state.toolPanelMode);
}, [state.toolPanelMode]);
// Keep tool panel mode in sync with user preference. This ensures the
// Config setting (Default tool picker mode) immediately affects the app
// and persists across reloads.
useEffect(() => {
if (!preferences) return;
const preferredMode = preferences.defaultToolPanelMode;
if (preferredMode && preferredMode !== state.toolPanelMode) {
if (preferredMode !== state.toolPanelMode) {
dispatch({ type: 'SET_TOOL_PANEL_MODE', payload: preferredMode });
if (typeof window !== 'undefined') {
window.localStorage.setItem(TOOL_PANEL_MODE_STORAGE_KEY, preferredMode);
}
}
}, [preferences.defaultToolPanelMode]);
}, [preferences.defaultToolPanelMode, state.toolPanelMode]);
// Tool reset methods
const registerToolReset = useCallback((toolId: string, resetFunction: () => void) => {
@@ -1,7 +1,5 @@
import { PageEditorFunctions } from '../../types/pageEditor';
// State & Modes
export type ToolPanelMode = 'sidebar' | 'fullscreen';
import { type ToolPanelMode, DEFAULT_TOOL_PANEL_MODE } from '../../constants/toolPanel';
export interface ToolWorkflowState {
// UI State
@@ -28,22 +26,6 @@ export type ToolWorkflowAction =
| { type: 'SET_SEARCH_QUERY'; payload: string }
| { type: 'RESET_UI_STATE' };
// Storage keys
export const TOOL_PANEL_MODE_STORAGE_KEY = 'toolPanelModePreference';
export const getStoredToolPanelMode = (): ToolPanelMode => {
if (typeof window === 'undefined') {
return 'sidebar';
}
const stored = window.localStorage.getItem(TOOL_PANEL_MODE_STORAGE_KEY);
if (stored === 'fullscreen') {
return 'fullscreen';
}
return 'sidebar';
};
export const baseState: Omit<ToolWorkflowState, 'toolPanelMode'> = {
sidebarsVisible: true,
leftPanelView: 'toolPicker',
@@ -55,7 +37,7 @@ export const baseState: Omit<ToolWorkflowState, 'toolPanelMode'> = {
export const createInitialState = (): ToolWorkflowState => ({
...baseState,
toolPanelMode: getStoredToolPanelMode(),
toolPanelMode: DEFAULT_TOOL_PANEL_MODE,
});
export function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkflowAction): ToolWorkflowState {