mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-16 17:16:19 +00:00
refactor: replace useLocalStorage with useUiPreferences for better state management in AppContent
This commit is contained in:
176
src/hooks/useUiPreferences.ts
Normal file
176
src/hooks/useUiPreferences.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { useEffect, useReducer } from 'react';
|
||||
|
||||
type UiPreferences = {
|
||||
autoExpandTools: boolean;
|
||||
showRawParameters: boolean;
|
||||
showThinking: boolean;
|
||||
autoScrollToBottom: boolean;
|
||||
sendByCtrlEnter: boolean;
|
||||
sidebarVisible: boolean;
|
||||
};
|
||||
|
||||
type UiPreferenceKey = keyof UiPreferences;
|
||||
|
||||
type SetPreferenceAction = {
|
||||
type: 'set';
|
||||
key: UiPreferenceKey;
|
||||
value: unknown;
|
||||
};
|
||||
|
||||
type SetManyPreferencesAction = {
|
||||
type: 'set_many';
|
||||
value?: Partial<Record<UiPreferenceKey, unknown>>;
|
||||
};
|
||||
|
||||
type ResetPreferencesAction = {
|
||||
type: 'reset';
|
||||
value?: Partial<UiPreferences>;
|
||||
};
|
||||
|
||||
type UiPreferencesAction =
|
||||
| SetPreferenceAction
|
||||
| SetManyPreferencesAction
|
||||
| ResetPreferencesAction;
|
||||
|
||||
const DEFAULTS: UiPreferences = {
|
||||
autoExpandTools: false,
|
||||
showRawParameters: false,
|
||||
showThinking: true,
|
||||
autoScrollToBottom: true,
|
||||
sendByCtrlEnter: false,
|
||||
sidebarVisible: true,
|
||||
};
|
||||
|
||||
const PREFERENCE_KEYS = Object.keys(DEFAULTS) as UiPreferenceKey[];
|
||||
const VALID_KEYS = new Set<UiPreferenceKey>(PREFERENCE_KEYS); // prevents unknown keys from being written
|
||||
|
||||
const parseBoolean = (value: unknown, fallback: boolean): boolean => {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
if (value === 'true') return true;
|
||||
if (value === 'false') return false;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
};
|
||||
|
||||
const readLegacyPreference = (key: UiPreferenceKey, fallback: boolean): boolean => {
|
||||
try {
|
||||
const raw = localStorage.getItem(key);
|
||||
if (raw === null) return fallback;
|
||||
|
||||
// Supports values written by both JSON.stringify and plain strings.
|
||||
const parsed = JSON.parse(raw);
|
||||
return parseBoolean(parsed, fallback);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
const readInitialPreferences = (storageKey: string): UiPreferences => {
|
||||
if (typeof window === 'undefined') {
|
||||
return DEFAULTS;
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = localStorage.getItem(storageKey);
|
||||
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
const parsedRecord = parsed as Record<string, unknown>;
|
||||
|
||||
return PREFERENCE_KEYS.reduce((acc, key) => {
|
||||
acc[key] = parseBoolean(parsedRecord[key], DEFAULTS[key]);
|
||||
return acc;
|
||||
}, { ...DEFAULTS });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Fall back to legacy keys when unified key is missing or invalid.
|
||||
}
|
||||
|
||||
return PREFERENCE_KEYS.reduce((acc, key) => {
|
||||
acc[key] = readLegacyPreference(key, DEFAULTS[key]);
|
||||
return acc;
|
||||
}, { ...DEFAULTS });
|
||||
};
|
||||
|
||||
function reducer(state: UiPreferences, action: UiPreferencesAction): UiPreferences {
|
||||
switch (action.type) {
|
||||
case 'set': {
|
||||
const { key, value } = action;
|
||||
if (!VALID_KEYS.has(key)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const nextValue = parseBoolean(value, state[key]);
|
||||
if (state[key] === nextValue) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return { ...state, [key]: nextValue };
|
||||
}
|
||||
case 'set_many': {
|
||||
const updates = action.value || {};
|
||||
let changed = false;
|
||||
const nextState = { ...state };
|
||||
|
||||
for (const key of PREFERENCE_KEYS) {
|
||||
if (!(key in updates)) continue;
|
||||
|
||||
const value = updates[key];
|
||||
const nextValue = parseBoolean(value, state[key]);
|
||||
if (nextState[key] !== nextValue) {
|
||||
nextState[key] = nextValue;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed ? nextState : state;
|
||||
}
|
||||
case 'reset':
|
||||
return { ...DEFAULTS, ...(action.value || {}) };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export function useUiPreferences(storageKey = 'uiPreferences') {
|
||||
const [state, dispatch] = useReducer(
|
||||
reducer,
|
||||
storageKey,
|
||||
readInitialPreferences
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(storageKey, JSON.stringify(state));
|
||||
}, [state, storageKey]);
|
||||
|
||||
const setPreference = (key: UiPreferenceKey, value: unknown) => {
|
||||
dispatch({ type: 'set', key, value });
|
||||
};
|
||||
|
||||
const setPreferences = (value: Partial<Record<UiPreferenceKey, unknown>>) => {
|
||||
dispatch({ type: 'set_many', value });
|
||||
};
|
||||
|
||||
const resetPreferences = (value?: Partial<UiPreferences>) => {
|
||||
dispatch({ type: 'reset', value });
|
||||
};
|
||||
|
||||
return {
|
||||
preferences: state,
|
||||
setPreference,
|
||||
setPreferences,
|
||||
resetPreferences,
|
||||
dispatch,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user