pl-fe: WIP migrate settings store to zustand

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak
2024-10-07 01:32:55 +02:00
parent 419877adcc
commit e4615b70f7
31 changed files with 351 additions and 306 deletions

View File

@@ -10,7 +10,6 @@ import { blockDomain, unblockDomain } from 'pl-fe/actions/domain-blocks';
import { initMuteModal } from 'pl-fe/actions/mutes';
import { initReport, ReportableEntities } from 'pl-fe/actions/reports';
import { setSearchAccount } from 'pl-fe/actions/search';
import { getSettings } from 'pl-fe/actions/settings';
import { useFollow } from 'pl-fe/api/hooks';
import Badge from 'pl-fe/components/badge';
import DropdownMenu, { Menu } from 'pl-fe/components/dropdown-menu';
@@ -24,6 +23,7 @@ import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'pl-f
import { useChats } from 'pl-fe/queries/chats';
import { queryClient } from 'pl-fe/queries/client';
import { useModalsStore } from 'pl-fe/stores';
import { useSettingsStore } from 'pl-fe/stores/settings';
import toast from 'pl-fe/toast';
import { isDefaultHeader } from 'pl-fe/utils/accounts';
import copy from 'pl-fe/utils/copy';
@@ -90,6 +90,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
const { account: ownAccount } = useOwnAccount();
const { follow } = useFollow();
const { openModal } = useModalsStore();
const { settings } = useSettingsStore();
const { software } = useAppSelector((state) => state.auth.client.features.version);
@@ -221,19 +222,17 @@ const Header: React.FC<IHeader> = ({ account }) => {
};
const onRemoveFromFollowers = () => {
dispatch((_, getState) => {
const unfollowModal = getSettings(getState()).get('unfollowModal');
if (unfollowModal) {
openModal('CONFIRM', {
heading: <FormattedMessage id='confirmations.remove_from_followers.heading' defaultMessage='Remove {name} from followers' values={{ name: <strong className='break-words'>@{account.acct}</strong> }} />,
message: <FormattedMessage id='confirmations.remove_from_followers.message' defaultMessage='Are you sure you want to remove {name} from your followers?' values={{ name: <strong className='break-words'>@{account.acct}</strong> }} />,
confirm: intl.formatMessage(messages.removeFromFollowersConfirm),
onConfirm: () => dispatch(removeFromFollowers(account.id)),
});
} else {
dispatch(removeFromFollowers(account.id));
}
});
const unfollowModal = settings.unfollowModal;
if (unfollowModal) {
openModal('CONFIRM', {
heading: <FormattedMessage id='confirmations.remove_from_followers.heading' defaultMessage='Remove {name} from followers' values={{ name: <strong className='break-words'>@{account.acct}</strong> }} />,
message: <FormattedMessage id='confirmations.remove_from_followers.message' defaultMessage='Are you sure you want to remove {name} from your followers?' values={{ name: <strong className='break-words'>@{account.acct}</strong> }} />,
confirm: intl.formatMessage(messages.removeFromFollowersConfirm),
onConfirm: () => dispatch(removeFromFollowers(account.id)),
});
} else {
dispatch(removeFromFollowers(account.id));
}
};
const onSearch = () => {

View File

@@ -1,24 +1,19 @@
import clsx from 'clsx';
import fuzzysort from 'fuzzysort';
import { Map as ImmutableMap } from 'immutable';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { createSelector } from 'reselect';
import { addComposeLanguage, changeComposeLanguage, changeComposeModifiedLanguage, deleteComposeLanguage } from 'pl-fe/actions/compose';
import DropdownMenu from 'pl-fe/components/dropdown-menu';
import { Button, Icon, Input } from 'pl-fe/components/ui';
import { type Language, languages as languagesObject } from 'pl-fe/features/preferences';
import { useAppDispatch, useAppSelector, useCompose, useFeatures } from 'pl-fe/hooks';
import { useAppDispatch, useCompose, useFeatures, useSettings } from 'pl-fe/hooks';
const getFrequentlyUsedLanguages = createSelector([
state => state.settings.get('frequentlyUsedLanguages', ImmutableMap()),
], (languageCounters: ImmutableMap<Language, number>) => (
languageCounters.keySeq()
.sort((a, b) => languageCounters.get(a, 0) - languageCounters.get(b, 0))
.reverse()
.toArray()
));
const getFrequentlyUsedLanguages = (languageCounters: Record<string, number>) => (
Object.keys(languageCounters)
.toSorted((a, b) => languageCounters[a] - languageCounters[b])
.toReversed()
);
const languages = Object.entries(languagesObject) as Array<[Language, string]>;
@@ -39,7 +34,8 @@ const getLanguageDropdown = (composeId: string): React.FC<ILanguageDropdown> =>
const intl = useIntl();
const features = useFeatures();
const dispatch = useAppDispatch();
const frequentlyUsedLanguages = useAppSelector(getFrequentlyUsedLanguages);
const settings = useSettings();
const frequentlyUsedLanguages = useMemo(() => getFrequentlyUsedLanguages(settings.frequentlyUsedLanguages), [settings.frequentlyUsedLanguages]);
const node = useRef<HTMLDivElement>(null);
const focusedItem = useRef<HTMLButtonElement>(null);

View File

@@ -1,13 +1,12 @@
import React from 'react';
import { getSettings } from 'pl-fe/actions/settings';
import { useAppSelector } from 'pl-fe/hooks';
import { useSettingsStore } from 'pl-fe/stores/settings';
import DevelopersChallenge from './developers-challenge';
import DevelopersMenu from './developers-menu';
const Developers: React.FC = () => {
const isDeveloper = useAppSelector((state) => getSettings(state).get('isDeveloper'));
const { isDeveloper } = useSettingsStore().settings;
return isDeveloper ? <DevelopersMenu /> : <DevelopersChallenge />;
};

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
import { SETTINGS_UPDATE, changeSetting, updateSettingsStore } from 'pl-fe/actions/settings';
import { changeSetting, updateSettingsStore } from 'pl-fe/actions/settings';
import List, { ListItem } from 'pl-fe/components/list';
import {
CardHeader,
@@ -14,7 +14,8 @@ import {
Textarea,
} from 'pl-fe/components/ui';
import SettingToggle from 'pl-fe/features/notifications/components/setting-toggle';
import { useAppSelector, useAppDispatch, useSettings } from 'pl-fe/hooks';
import { useAppDispatch } from 'pl-fe/hooks';
import { useSettingsStore } from 'pl-fe/stores/settings';
import toast from 'pl-fe/toast';
const isJSONValid = (text: any): boolean => {
@@ -35,10 +36,9 @@ const messages = defineMessages({
const SettingsStore: React.FC = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const settings = useSettings();
const settingsStore = useAppSelector(state => state.settings);
const { settings, userSettings, loadUserSettings } = useSettingsStore();
const [rawJSON, setRawJSON] = useState<string>(JSON.stringify(settingsStore, null, 2));
const [rawJSON, setRawJSON] = useState<string>(JSON.stringify(userSettings, null, 2));
const [jsonValid, setJsonValid] = useState(true);
const [isLoading, setLoading] = useState(false);
@@ -57,7 +57,7 @@ const SettingsStore: React.FC = () => {
setLoading(true);
dispatch(updateSettingsStore(settings)).then(() => {
dispatch({ type: SETTINGS_UPDATE, settings });
loadUserSettings(settings);
setLoading(false);
}).catch(error => {
toast.showAlertForError(error);
@@ -66,9 +66,9 @@ const SettingsStore: React.FC = () => {
};
useEffect(() => {
setRawJSON(JSON.stringify(settingsStore, null, 2));
setRawJSON(JSON.stringify(userSettings, null, 2));
setJsonValid(true);
}, [settingsStore]);
}, [userSettings]);
return (
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>

View File

@@ -2,7 +2,6 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { getSettings } from 'pl-fe/actions/settings';
import { useAccount } from 'pl-fe/api/hooks';
import Account from 'pl-fe/components/account';
import Badge from 'pl-fe/components/badge';
@@ -12,6 +11,7 @@ import RelativeTimestamp from 'pl-fe/components/relative-timestamp';
import { Avatar, Stack, Text } from 'pl-fe/components/ui';
import ActionButton from 'pl-fe/features/ui/components/action-button';
import { useAppSelector } from 'pl-fe/hooks';
import { useSettingsStore } from 'pl-fe/stores/settings';
import { shortNumberFormat } from 'pl-fe/utils/numbers';
interface IAccountCard {
@@ -21,7 +21,7 @@ interface IAccountCard {
const AccountCard: React.FC<IAccountCard> = ({ id }) => {
const me = useAppSelector((state) => state.me);
const { account } = useAccount(id);
const autoPlayGif = useAppSelector((state) => getSettings(state).get('autoPlayGif'));
const { autoPlayGif } = useSettingsStore().settings;
if (!account) return null;

View File

@@ -3,10 +3,10 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { setComposeToStatus } from 'pl-fe/actions/compose';
import { cancelDraftStatus } from 'pl-fe/actions/draft-statuses';
import { getSettings } from 'pl-fe/actions/settings';
import { Button, HStack } from 'pl-fe/components/ui';
import { useAppDispatch } from 'pl-fe/hooks';
import { useModalsStore } from 'pl-fe/stores';
import { useSettingsStore } from 'pl-fe/stores/settings';
import type { Status as StatusEntity } from 'pl-fe/normalizers';
import type { DraftStatus } from 'pl-fe/reducers/draft-statuses';
@@ -26,12 +26,13 @@ const DraftStatusActionBar: React.FC<IDraftStatusActionBar> = ({ source, status
const intl = useIntl();
const { openModal } = useModalsStore();
const { settings } = useSettingsStore();
const dispatch = useAppDispatch();
const handleCancelClick = () => {
dispatch((_, getState) => {
const deleteModal = getSettings(getState()).get('deleteModal');
const deleteModal = settings.deleteModal;
if (!deleteModal) {
dispatch(cancelDraftStatus(source.draft_id));
} else {

View File

@@ -1,11 +1,10 @@
import { Map as ImmutableMap } from 'immutable';
import React, { useEffect, useState, useLayoutEffect, Suspense } from 'react';
import React, { useEffect, useState, useLayoutEffect, Suspense, useMemo } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { createSelector } from 'reselect';
import { chooseEmoji } from 'pl-fe/actions/emojis';
import { changeSetting } from 'pl-fe/actions/settings';
import { useAppDispatch, useAppSelector, useTheme } from 'pl-fe/hooks';
import { useAppDispatch, useAppSelector, useSettings, useTheme } from 'pl-fe/hooks';
import { RootState } from 'pl-fe/store';
import { buildCustomEmojis } from '../../emoji';
@@ -71,15 +70,11 @@ const DEFAULTS = [
'ok_hand',
];
const getFrequentlyUsedEmojis = createSelector([
(state: RootState) => state.settings.get('frequentlyUsedEmojis', ImmutableMap()),
], (emojiCounters: ImmutableMap<string, number>) => {
let emojis = emojiCounters
.keySeq()
.sort((a, b) => emojiCounters.get(a)! - emojiCounters.get(b)!)
.reverse()
.slice(0, perLine * lines)
.toArray();
const getFrequentlyUsedEmojis = (emojiCounters: Record<string, number>) => {
let emojis = Object.keys(emojiCounters)
.toSorted((a, b) => emojiCounters[a] - emojiCounters[b])
.toReversed()
.slice(0, perLine * lines);
if (emojis.length < DEFAULTS.length) {
const uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji));
@@ -87,7 +82,7 @@ const getFrequentlyUsedEmojis = createSelector([
}
return emojis;
});
};
const getCustomEmojis = createSelector([
(state: RootState) => state.custom_emojis,
@@ -132,7 +127,9 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({
const theme = useTheme();
const customEmojis = useAppSelector((state) => getCustomEmojis(state));
const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state));
const settings = useSettings();
const frequentlyUsedEmojis = useMemo(() => getFrequentlyUsedEmojis(settings.frequentlyUsedEmojis), [settings.frequentlyUsedEmojis]);
const handlePick = (emoji: any) => {
setVisible(false);
@@ -238,6 +235,5 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({
export {
messages,
type IEmojiPickerDropdown,
getFrequentlyUsedEmojis,
EmojiPickerDropdown as default,
};

View File

@@ -4,7 +4,6 @@ import { Link, useHistory } from 'react-router-dom';
import { mentionCompose } from 'pl-fe/actions/compose';
import { reblog, favourite, unreblog, unfavourite } from 'pl-fe/actions/interactions';
import { getSettings } from 'pl-fe/actions/settings';
import { toggleStatusMediaHidden } from 'pl-fe/actions/statuses';
import Icon from 'pl-fe/components/icon';
import RelativeTimestamp from 'pl-fe/components/relative-timestamp';
@@ -15,6 +14,7 @@ import { HotKeys } from 'pl-fe/features/ui/components/hotkeys';
import { useAppDispatch, useAppSelector, useInstance, useLoggedIn } from 'pl-fe/hooks';
import { makeGetNotification } from 'pl-fe/selectors';
import { useModalsStore } from 'pl-fe/stores';
import { useSettingsStore } from 'pl-fe/stores/settings';
import { NotificationType } from 'pl-fe/utils/notification';
import type { Notification as BaseNotification } from 'pl-api';
@@ -196,6 +196,7 @@ const Notification: React.FC<INotification> = (props) => {
const { me } = useLoggedIn();
const { openModal } = useModalsStore();
const { settings } = useSettingsStore();
const notification = useAppSelector((state) => getNotification(state, props.notification));
const history = useHistory();
@@ -252,23 +253,21 @@ const Notification: React.FC<INotification> = (props) => {
const handleHotkeyBoost = useCallback((e?: KeyboardEvent) => {
if (status && typeof status === 'object') {
dispatch((_, getState) => {
const boostModal = getSettings(getState()).get('boostModal');
if (status.reblogged) {
dispatch(unreblog(status));
const boostModal = settings.boostModal;
if (status.reblogged) {
dispatch(unreblog(status));
} else {
if (e?.shiftKey || !boostModal) {
dispatch(reblog(status));
} else {
if (e?.shiftKey || !boostModal) {
dispatch(reblog(status));
} else {
openModal('BOOST', {
statusId: status.id,
onReblog: (status) => {
dispatch(reblog(status));
},
});
}
openModal('BOOST', {
statusId: status.id,
onReblog: (status) => {
dispatch(reblog(status));
},
});
}
});
}
}
}, [status]);

View File

@@ -2,10 +2,10 @@ import clsx from 'clsx';
import React, { useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import { defaultSettings } from 'pl-fe/actions/settings';
import BackgroundShapes from 'pl-fe/features/ui/components/background-shapes';
import { useSystemTheme } from 'pl-fe/hooks';
import { normalizePlFeConfig } from 'pl-fe/normalizers';
import { useSettingsStore } from 'pl-fe/stores/settings';
import { generateThemeCss } from 'pl-fe/utils/theme';
interface ISitePreview {
@@ -16,9 +16,9 @@ interface ISitePreview {
/** Renders a preview of the website's style with the configuration applied. */
const SitePreview: React.FC<ISitePreview> = ({ plFe }) => {
const plFeConfig = useMemo(() => normalizePlFeConfig(plFe), [plFe]);
const settings = defaultSettings.mergeDeep(plFeConfig.defaultSettings);
const { defaultSettings } = useSettingsStore();
const userTheme = settings.get('themeMode');
const userTheme = defaultSettings.themeMode;
const systemTheme = useSystemTheme();
const dark = ['dark', 'black'].includes(userTheme as string) || (userTheme === 'system' && systemTheme === 'black');

View File

@@ -2,10 +2,10 @@ import React from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { cancelScheduledStatus } from 'pl-fe/actions/scheduled-statuses';
import { getSettings } from 'pl-fe/actions/settings';
import { Button, HStack } from 'pl-fe/components/ui';
import { useAppDispatch } from 'pl-fe/hooks';
import { useModalsStore } from 'pl-fe/stores';
import { useSettingsStore } from 'pl-fe/stores/settings';
import type { Status as StatusEntity } from 'pl-fe/normalizers';
@@ -25,11 +25,12 @@ const ScheduledStatusActionBar: React.FC<IScheduledStatusActionBar> = ({ status
const dispatch = useAppDispatch();
const { openModal } = useModalsStore();
const { settings } = useSettingsStore();
const handleCancelClick = () => {
dispatch((_, getState) => {
const deleteModal = getSettings(getState()).get('deleteModal');
const deleteModal = settings.deleteModal;
if (!deleteModal) {
dispatch(cancelScheduledStatus(status.id));
} else {

View File

@@ -8,7 +8,6 @@ import { useHistory } from 'react-router-dom';
import { type ComposeReplyAction, mentionCompose, replyCompose } from 'pl-fe/actions/compose';
import { reblog, toggleFavourite, unreblog } from 'pl-fe/actions/interactions';
import { getSettings } from 'pl-fe/actions/settings';
import { toggleStatusMediaHidden } from 'pl-fe/actions/statuses';
import ScrollableList from 'pl-fe/components/scrollable-list';
import StatusActionBar from 'pl-fe/components/status-action-bar';
@@ -20,6 +19,7 @@ import PendingStatus from 'pl-fe/features/ui/components/pending-status';
import { useAppDispatch, useAppSelector } from 'pl-fe/hooks';
import { RootState } from 'pl-fe/store';
import { useModalsStore } from 'pl-fe/stores';
import { useSettingsStore } from 'pl-fe/stores/settings';
import { textForScreenReader } from 'pl-fe/utils/status';
import DetailedStatus from './detailed-status';
@@ -93,6 +93,7 @@ const Thread: React.FC<IThread> = ({
const intl = useIntl();
const { openModal } = useModalsStore();
const { settings } = useSettingsStore();
const { ancestorsIds, descendantsIds } = useAppSelector((state) => {
let ancestorsIds = ImmutableOrderedSet<string>();
@@ -136,7 +137,7 @@ const Thread: React.FC<IThread> = ({
const handleReblogClick = (status: SelectedStatus, e?: React.MouseEvent) => {
dispatch((_, getState) => {
const boostModal = getSettings(getState()).get('boostModal');
const boostModal = settings.boostModal;
if (status.reblogged) {
dispatch(unreblog(status));
} else {