mirror of
https://git.um-react.app/um/um-react.git
synced 2025-11-28 03:23:02 +00:00
feat: setup redux store for settings
This commit is contained in:
24
src/Loader.tsx
Normal file
24
src/Loader.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import App from './App';
|
||||
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { theme } from './theme';
|
||||
import { persistSettings } from './features/settings/persistSettings';
|
||||
import type { AppStore } from './store';
|
||||
|
||||
export function Loader({ store }: { store: AppStore }) {
|
||||
useEffect(() => {
|
||||
return persistSettings(store);
|
||||
}, [store]);
|
||||
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<ChakraProvider theme={theme}>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</ChakraProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
}
|
||||
45
src/features/settings/persistSettings.ts
Normal file
45
src/features/settings/persistSettings.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { debounce } from 'radash';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import type { AppStore } from '~/store';
|
||||
import { SettingsState, settingsSlice, updateSettings } from './settingsSlice';
|
||||
import { enumObject } from '~/util/objects';
|
||||
import { getLogger } from '~/util/logUtils';
|
||||
|
||||
const DEFAULT_STORAGE_KEY = 'um-react-settings';
|
||||
|
||||
function mergeSettings(settings: SettingsState): SettingsState {
|
||||
return produce(settingsSlice.getInitialState(), (draft) => {
|
||||
for (const [k, v] of enumObject(settings.qmc2?.keys)) {
|
||||
if (typeof v === 'string') {
|
||||
draft.qmc2.keys[k] = v;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function persistSettings(store: AppStore, storageKey = DEFAULT_STORAGE_KEY) {
|
||||
let lastSettings: unknown;
|
||||
|
||||
try {
|
||||
const loadedSettings: SettingsState = JSON.parse(localStorage.getItem(storageKey) ?? '');
|
||||
if (loadedSettings) {
|
||||
const mergedSettings = mergeSettings(loadedSettings);
|
||||
store.dispatch(updateSettings(mergedSettings));
|
||||
getLogger().debug('settings loaded');
|
||||
}
|
||||
} catch {
|
||||
// load failed, ignore.
|
||||
}
|
||||
|
||||
return store.subscribe(
|
||||
debounce({ delay: 150 }, () => {
|
||||
const currentSettings = store.getState().settings;
|
||||
if (lastSettings !== currentSettings) {
|
||||
lastSettings = currentSettings;
|
||||
localStorage.setItem(storageKey, JSON.stringify(currentSettings));
|
||||
getLogger().debug('settings saved');
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
31
src/features/settings/settingsSlice.ts
Normal file
31
src/features/settings/settingsSlice.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
export interface QMCSettings {
|
||||
keys: Record<string, string>; // { [fileName]: ekey }
|
||||
}
|
||||
|
||||
export interface SettingsState {
|
||||
qmc2: QMCSettings;
|
||||
}
|
||||
|
||||
const initialState: SettingsState = {
|
||||
qmc2: { keys: {} },
|
||||
};
|
||||
|
||||
export const settingsSlice = createSlice({
|
||||
name: 'settings',
|
||||
initialState,
|
||||
reducers: {
|
||||
updateSettings: (_state, { payload }: PayloadAction<SettingsState>) => {
|
||||
return payload;
|
||||
},
|
||||
resetConfig: () => {
|
||||
return initialState;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { updateSettings, resetConfig } = settingsSlice.actions;
|
||||
|
||||
export default settingsSlice.reducer;
|
||||
16
src/main.tsx
16
src/main.tsx
@@ -1,21 +1,9 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { setupStore } from './store';
|
||||
import { theme } from './theme';
|
||||
import { Loader } from './Loader';
|
||||
|
||||
// Private to this file only.
|
||||
const store = setupStore();
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<ChakraProvider theme={theme}>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</ChakraProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<Loader store={store} />);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { PreloadedState, combineReducers, configureStore } from '@reduxjs/toolkit';
|
||||
import fileListingReducer from './features/file-listing/fileListingSlice';
|
||||
import settingsReducer from './features/settings/settingsSlice';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
fileListing: fileListingReducer,
|
||||
settings: settingsReducer,
|
||||
});
|
||||
|
||||
export const setupStore = (preloadedState?: PreloadedState<RootState>) =>
|
||||
|
||||
@@ -23,3 +23,23 @@ export function withGroupedLogs<R = unknown>(label: string, fn: () => R): R {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const noop = (..._args: unknown[]) => {
|
||||
// noop
|
||||
};
|
||||
|
||||
const dummyLogger = {
|
||||
log: noop,
|
||||
info: noop,
|
||||
warn: noop,
|
||||
debug: noop,
|
||||
trace: noop,
|
||||
};
|
||||
|
||||
export function getLogger() {
|
||||
if (import.meta.env.ENABLE_PERF_LOG === '1') {
|
||||
return window.console;
|
||||
} else {
|
||||
return dummyLogger;
|
||||
}
|
||||
}
|
||||
|
||||
7
src/util/objects.ts
Normal file
7
src/util/objects.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export function* enumObject<T>(obj: Record<string, T> | null | void): Generator<[string, T]> {
|
||||
if (obj && typeof obj === 'object') {
|
||||
for (const key in obj) {
|
||||
yield [key, obj[key]];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user