Files
MultiRoombaRover/webui/src/settings/SettingsProvider.jsx
T
2026-04-29 18:31:02 -04:00

97 lines
2.9 KiB
React

// Settings Provider
// Purpose: Supplies app-wide settings state and namespace-scoped update APIs. Scope: Bridges persistence helpers with React context for consistent settings access.
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { loadSettings, saveSettings } from './persistence.js';
const SettingsContext = createContext(null);
export function SettingsProvider({ children }) {
const [state, setState] = useState(() => ({ status: 'loading', data: {} }));
useEffect(() => {
const loaded = loadSettings();
setState({ status: 'ready', data: loaded ?? {} });
}, []);
const reload = useCallback(() => {
const loaded = loadSettings();
setState({ status: 'ready', data: loaded ?? {} });
}, []);
const saveAll = useCallback((nextData) => {
const success = saveSettings(nextData ?? {});
if (success) {
setState({ status: 'ready', data: nextData ?? {} });
} else {
setState((prev) => ({ ...prev, status: 'error' }));
}
return success;
}, []);
const setNamespace = useCallback((namespace, updater) => {
setState((prev) => {
const current = prev.data[namespace] || {};
const nextValue = typeof updater === 'function' ? updater(current) : { ...current, ...(updater ?? {}) };
const nextData = { ...prev.data, [namespace]: nextValue };
const success = saveSettings(nextData);
if (!success) {
return { ...prev, status: 'error' };
}
return { status: 'ready', data: nextData };
});
}, []);
const contextValue = useMemo(
() => ({
data: state.data,
status: state.status,
reload,
saveAll,
setNamespace,
}),
[state, reload, saveAll, setNamespace],
);
return <SettingsContext.Provider value={contextValue}>{children}</SettingsContext.Provider>;
}
export function useSettings() {
const ctx = useContext(SettingsContext);
if (!ctx) {
throw new Error('useSettings must be used within SettingsProvider');
}
return ctx;
}
export function useSettingsNamespace(namespace, defaults = {}) {
const { data, status, setNamespace } = useSettings();
const value = data[namespace] ?? defaults ?? {};
const save = useCallback(
(update) => {
setNamespace(namespace, (current) => {
const base = current ?? {};
if (typeof update === 'function') {
return update(base);
}
return { ...base, ...(update ?? {}) };
});
},
[namespace, setNamespace],
);
const replace = useCallback(
(nextValue) => {
setNamespace(namespace, () => nextValue ?? {});
},
[namespace, setNamespace],
);
const reset = useCallback(() => {
const base = typeof defaults === 'object' ? { ...defaults } : defaults;
setNamespace(namespace, () => base ?? {});
}, [defaults, namespace, setNamespace]);
return { value, status, save, replace, reset };
}