pl-fe: Complete migration of settings store

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-10-07 18:37:50 +02:00
parent e4615b70f7
commit 97d1ce2f47
12 changed files with 51 additions and 110 deletions

View file

@ -294,14 +294,14 @@ const scrollTopNotifications = (top: boolean) =>
const setFilter = (filterType: FilterType, abort?: boolean) => const setFilter = (filterType: FilterType, abort?: boolean) =>
(dispatch: AppDispatch) => { (dispatch: AppDispatch) => {
const activeFilter = useSettingsStore.getState().settings.notifications.quickFilter.active as FilterType; const settingsStore = useSettingsStore.getState();
const activeFilter = settingsStore.settings.notifications.quickFilter.active as FilterType;
dispatch({ settingsStore.changeSetting(['notifications', 'quickFilter', 'active'], filterType);
type: NOTIFICATIONS_FILTER_SET,
path: ['notifications', 'quickFilter', 'active'], dispatch({ type: NOTIFICATIONS_FILTER_SET });
value: filterType,
});
dispatch(expandNotifications(undefined, undefined, abort)); dispatch(expandNotifications(undefined, undefined, abort));
if (activeFilter !== filterType) dispatch(saveSettings()); if (activeFilter !== filterType) dispatch(saveSettings());
}; };

View file

@ -1,3 +1,5 @@
import { useSettingsStore } from 'pl-fe/stores/settings';
import { getClient } from '../api'; import { getClient } from '../api';
import { fetchRelationships } from './accounts'; import { fetchRelationships } from './accounts';
@ -88,9 +90,10 @@ const setFilter = (value: string, filterType: SearchFilter) =>
(dispatch: AppDispatch) => { (dispatch: AppDispatch) => {
dispatch(submitSearch(value, filterType)); dispatch(submitSearch(value, filterType));
useSettingsStore.getState().changeSetting(['search', 'filter'], filterType);
return dispatch({ return dispatch({
type: SEARCH_FILTER_SET, type: SEARCH_FILTER_SET,
path: ['search', 'filter'],
value: filterType, value: filterType,
}); });
}; };

View file

@ -11,8 +11,6 @@ import { isLoggedIn } from 'pl-fe/utils/auth';
import type { AppDispatch, RootState } from 'pl-fe/store'; import type { AppDispatch, RootState } from 'pl-fe/store';
const SETTING_CHANGE = 'SETTING_CHANGE' as const;
const FE_NAME = 'pl_fe'; const FE_NAME = 'pl_fe';
const getAccount = makeGetAccount(); const getAccount = makeGetAccount();
@ -129,33 +127,9 @@ const saveSuccessMessage = defineMessage({ id: 'settings.save.success', defaultM
// }), // }),
// }); // });
interface SettingChangeAction {
type: typeof SETTING_CHANGE;
path: string[];
value: any;
}
const changeSettingImmediate = (path: string[], value: any, opts?: SettingOpts) =>
(dispatch: AppDispatch) => {
const action: SettingChangeAction = {
type: SETTING_CHANGE,
path,
value,
};
dispatch(action);
dispatch(saveSettings(opts));
};
const changeSetting = (path: string[], value: any, opts?: SettingOpts) => const changeSetting = (path: string[], value: any, opts?: SettingOpts) =>
(dispatch: AppDispatch) => { (dispatch: AppDispatch) => {
const action: SettingChangeAction = { useSettingsStore.getState().changeSetting(path, value);
type: SETTING_CHANGE,
path,
value,
};
dispatch(action);
return dispatch(saveSettings(opts)); return dispatch(saveSettings(opts));
}; };
@ -214,16 +188,10 @@ const getLocale = (fallback = 'en') => {
return Object.keys(messages).includes(localeWithVariant) ? localeWithVariant : Object.keys(messages).includes(locale) ? locale : fallback; return Object.keys(messages).includes(localeWithVariant) ? localeWithVariant : Object.keys(messages).includes(locale) ? locale : fallback;
}; };
type SettingsAction =
| SettingChangeAction
export { export {
SETTING_CHANGE,
FE_NAME, FE_NAME,
changeSettingImmediate,
changeSetting, changeSetting,
saveSettings, saveSettings,
updateSettingsStore, updateSettingsStore,
getLocale, getLocale,
type SettingsAction,
}; };

View file

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { changeSettingImmediate } from 'pl-fe/actions/settings'; import { changeSetting } from 'pl-fe/actions/settings';
import { Column, Button, Form, FormActions, FormGroup, Input, Text } from 'pl-fe/components/ui'; import { Column, Button, Form, FormActions, FormGroup, Input, Text } from 'pl-fe/components/ui';
import { useAppDispatch } from 'pl-fe/hooks'; import { useAppDispatch } from 'pl-fe/hooks';
import toast from 'pl-fe/toast'; import toast from 'pl-fe/toast';
@ -26,7 +26,7 @@ const DevelopersChallenge = () => {
const handleSubmit = () => { const handleSubmit = () => {
if (answer === 'fe-pl') { if (answer === 'fe-pl') {
dispatch(changeSettingImmediate(['isDeveloper'], true)); dispatch(changeSetting(['isDeveloper'], true));
toast.success(intl.formatMessage(messages.success)); toast.success(intl.formatMessage(messages.success));
} else { } else {
toast.error(intl.formatMessage(messages.fail)); toast.error(intl.formatMessage(messages.fail));

View file

@ -2,7 +2,7 @@ import React from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { changeSettingImmediate } from 'pl-fe/actions/settings'; import { changeSetting } from 'pl-fe/actions/settings';
import { Column, Text } from 'pl-fe/components/ui'; import { Column, Text } from 'pl-fe/components/ui';
import SvgIcon from 'pl-fe/components/ui/icon/svg-icon'; import SvgIcon from 'pl-fe/components/ui/icon/svg-icon';
import { useAppDispatch } from 'pl-fe/hooks'; import { useAppDispatch } from 'pl-fe/hooks';
@ -38,7 +38,7 @@ const Developers: React.FC = () => {
const leaveDevelopers = (e: React.MouseEvent<HTMLButtonElement>) => { const leaveDevelopers = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault(); e.preventDefault();
dispatch(changeSettingImmediate(['isDeveloper'], false)); dispatch(changeSetting(['isDeveloper'], false));
toast.success(intl.formatMessage(messages.leave)); toast.success(intl.formatMessage(messages.leave));
history.push('/'); history.push('/');
}; };

View file

@ -22,8 +22,8 @@ const STATUS_TYPE_ICONS: Record<string, string> = {
direct: require('@tabler/icons/outline/mail.svg'), direct: require('@tabler/icons/outline/mail.svg'),
private: require('@tabler/icons/outline/lock.svg'), private: require('@tabler/icons/outline/lock.svg'),
mutuals_only: require('@tabler/icons/outline/users-group.svg'), mutuals_only: require('@tabler/icons/outline/users-group.svg'),
local: require('@tabler/icons/outline/affiliate.svg'), local: require('@tabler/icons/outline/affiliate.svg'),
list: require('@tabler/icons/outline/list.svg'), list: require('@tabler/icons/outline/list.svg'),
}; };
const StatusTypeIcon: React.FC<IStatusTypeIcon> = ({ status }) => { const StatusTypeIcon: React.FC<IStatusTypeIcon> = ({ status }) => {

View file

@ -2,7 +2,7 @@
* globals: do things through the console. * globals: do things through the console.
* This feature is for developers. * This feature is for developers.
*/ */
import { changeSettingImmediate } from 'pl-fe/actions/settings'; import { changeSetting } from 'pl-fe/actions/settings';
import type { Store } from 'pl-fe/store'; import type { Store } from 'pl-fe/store';
@ -14,7 +14,7 @@ const createGlobals = (store: Store) => {
if (![true, false].includes(bool)) { if (![true, false].includes(bool)) {
throw `Invalid option ${bool}. Must be true or false.`; throw `Invalid option ${bool}. Must be true or false.`;
} }
store.dispatch(changeSettingImmediate(['isDeveloper'], bool) as any); store.dispatch(changeSetting(['isDeveloper'], bool) as any);
return bool; return bool;
}, },
}; };

View file

@ -62,7 +62,7 @@ import {
} from '../actions/compose'; } from '../actions/compose';
import { EVENT_COMPOSE_CANCEL, EVENT_FORM_SET, type EventsAction } from '../actions/events'; import { EVENT_COMPOSE_CANCEL, EVENT_FORM_SET, type EventsAction } from '../actions/events';
import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS, MeAction } from '../actions/me'; import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS, MeAction } from '../actions/me';
import { SETTING_CHANGE, FE_NAME, SettingsAction } from '../actions/settings'; import { FE_NAME } from '../actions/settings';
import { TIMELINE_DELETE, TimelineAction } from '../actions/timelines'; import { TIMELINE_DELETE, TimelineAction } from '../actions/timelines';
import { unescapeHTML } from '../utils/html'; import { unescapeHTML } from '../utils/html';
@ -263,17 +263,17 @@ const importAccount = (compose: Compose, account: CredentialAccount) => {
}); });
}; };
const updateSetting = (compose: Compose, path: string[], value: string) => { // const updateSetting = (compose: Compose, path: string[], value: string) => {
const pathString = path.join(','); // const pathString = path.join(',');
switch (pathString) { // switch (pathString) {
case 'defaultPrivacy': // case 'defaultPrivacy':
return compose.set('privacy', value); // return compose.set('privacy', value);
case 'defaultContentType': // case 'defaultContentType':
return compose.set('content_type', value); // return compose.set('content_type', value);
default: // default:
return compose; // return compose;
} // }
}; // };
const updateCompose = (state: State, key: string, updater: (compose: Compose) => Compose) => const updateCompose = (state: State, key: string, updater: (compose: Compose) => Compose) =>
state.update(key, state.get('default')!, updater); state.update(key, state.get('default')!, updater);
@ -282,7 +282,7 @@ const initialState: State = ImmutableMap({
default: ReducerCompose({ idempotencyKey: crypto.randomUUID(), resetFileKey: getResetFileKey() }), default: ReducerCompose({ idempotencyKey: crypto.randomUUID(), resetFileKey: getResetFileKey() }),
}); });
const compose = (state = initialState, action: ComposeAction | EventsAction | MeAction | SettingsAction | TimelineAction) => { const compose = (state = initialState, action: ComposeAction | EventsAction | MeAction | TimelineAction) => {
switch (action.type) { switch (action.type) {
case COMPOSE_TYPE_CHANGE: case COMPOSE_TYPE_CHANGE:
return updateCompose(state, action.composeId, compose => compose.withMutations(map => { return updateCompose(state, action.composeId, compose => compose.withMutations(map => {
@ -544,8 +544,8 @@ const compose = (state = initialState, action: ComposeAction | EventsAction | Me
case ME_FETCH_SUCCESS: case ME_FETCH_SUCCESS:
case ME_PATCH_SUCCESS: case ME_PATCH_SUCCESS:
return updateCompose(state, 'default', compose => importAccount(compose, action.me)); return updateCompose(state, 'default', compose => importAccount(compose, action.me));
case SETTING_CHANGE: // case SETTING_CHANGE:
return updateCompose(state, 'default', compose => updateSetting(compose, action.path, action.value)); // return updateCompose(state, 'default', compose => updateSetting(compose, action.path, action.value));
case COMPOSE_EDITOR_STATE_SET: case COMPOSE_EDITOR_STATE_SET:
return updateCompose(state, action.composeId, compose => compose return updateCompose(state, action.composeId, compose => compose
.setIn(!compose.modified_language || compose.modified_language === compose.language ? ['editorState'] : ['editorStateMap', compose.modified_language], action.editorState as string) .setIn(!compose.modified_language || compose.modified_language === compose.language ? ['editorState'] : ['editorStateMap', compose.modified_language], action.editorState as string)

View file

@ -37,7 +37,6 @@ import push_notifications from './push-notifications';
import scheduled_statuses from './scheduled-statuses'; import scheduled_statuses from './scheduled-statuses';
import search from './search'; import search from './search';
import security from './security'; import security from './security';
// import settings from './settings';
import status_lists from './status-lists'; import status_lists from './status-lists';
import statuses from './statuses'; import statuses from './statuses';
import suggestions from './suggestions'; import suggestions from './suggestions';
@ -81,7 +80,6 @@ const reducers = {
scheduled_statuses, scheduled_statuses,
search, search,
security, security,
// settings,
status_lists, status_lists,
statuses, statuses,
suggestions, suggestions,

View file

@ -1,11 +0,0 @@
import { Map as ImmutableMap } from 'immutable';
import reducer from './settings';
describe('settings reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {} as any)).toEqual(ImmutableMap({
saved: true,
}));
});
});

View file

@ -1,34 +0,0 @@
import { produce } from 'immer';
import { AnyAction } from 'redux';
import { settingsSchema, type Settings } from 'pl-fe/schemas/pl-fe/settings';
import { NOTIFICATIONS_FILTER_SET } from '../actions/notifications';
import { SEARCH_FILTER_SET } from '../actions/search';
import { SETTING_CHANGE } from '../actions/settings';
type State = Partial<Settings>;
// Default settings are in action/settings.js
//
// Settings should be accessed with `getSettings(getState()).getIn(...)`
// instead of directly from the state.
const settings = (
state: State = settingsSchema.partial().parse({}),
action: AnyAction,
): State => {
switch (action.type) {
case NOTIFICATIONS_FILTER_SET:
case SEARCH_FILTER_SET:
case SETTING_CHANGE:
return produce(state, draft => {
// @ts-ignore
draft[action.path] = action.value;
draft.saved = false;
});
default:
return state;
}
};
export { settings as default };

View file

@ -17,10 +17,21 @@ type State = {
loadDefaultSettings: (settings: APIEntity) => void; loadDefaultSettings: (settings: APIEntity) => void;
loadUserSettings: (settings: APIEntity) => void; loadUserSettings: (settings: APIEntity) => void;
userSettingsSaving: () => void; userSettingsSaving: () => void;
changeSetting: (path: string[], value: any) => void;
rememberEmojiUse: (emoji: Emoji) => void; rememberEmojiUse: (emoji: Emoji) => void;
rememberLanguageUse: (language: string) => void; rememberLanguageUse: (language: string) => void;
} }
const changeSetting = (object: APIEntity, path: string[], value: any) => {
if (path.length === 1) {
object[path[0]] = value;
return;
}
if (typeof object[path[0]] !== 'object') object[path[0]] = {};
return changeSetting(object[path[0]], path.slice(1), value);
};
const mergeSettings = (state: State) => state.settings = { ...state.defaultSettings, ...state.userSettings }; const mergeSettings = (state: State) => state.settings = { ...state.defaultSettings, ...state.userSettings };
const useSettingsStore = create<State>((set) => ({ const useSettingsStore = create<State>((set) => ({
@ -49,6 +60,12 @@ const useSettingsStore = create<State>((set) => ({
mergeSettings(state); mergeSettings(state);
})), })),
changeSetting: (path: string[], value: any) => set(produce((state: State) => {
changeSetting(state.userSettings, path, value);
mergeSettings(state);
})),
rememberEmojiUse: (emoji: Emoji) => set(produce((state: State) => { rememberEmojiUse: (emoji: Emoji) => set(produce((state: State) => {
const settings = state.userSettings; const settings = state.userSettings;
if (!settings.frequentlyUsedEmojis) settings.frequentlyUsedEmojis = {}; if (!settings.frequentlyUsedEmojis) settings.frequentlyUsedEmojis = {};