pl-fe: WIP migrate settings store to zustand
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
419877adcc
commit
e4615b70f7
31 changed files with 351 additions and 306 deletions
|
@ -1,10 +1,11 @@
|
|||
import { getSettings, changeSetting } from 'pl-fe/actions/settings';
|
||||
import { changeSetting } from 'pl-fe/actions/settings';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
||||
const toggleMainWindow = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const main = getSettings(getState()).getIn(['chats', 'mainWindow']) as 'minimized' | 'open';
|
||||
const main = useSettingsStore.getState().settings.chats.mainWindow;
|
||||
const state = main === 'minimized' ? 'open' : 'minimized';
|
||||
return dispatch(changeSetting(['chats', 'mainWindow'], state));
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Language } from 'pl-fe/features/preferences';
|
|||
import { selectAccount, selectOwnAccount, makeGetAccount } from 'pl-fe/selectors';
|
||||
import { tagHistory } from 'pl-fe/settings';
|
||||
import { useModalsStore } from 'pl-fe/stores';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import toast from 'pl-fe/toast';
|
||||
import { isLoggedIn } from 'pl-fe/utils/auth';
|
||||
|
||||
|
@ -15,7 +16,6 @@ import { chooseEmoji } from './emojis';
|
|||
import { importFetchedAccounts } from './importer';
|
||||
import { rememberLanguageUse } from './languages';
|
||||
import { uploadFile, updateMedia } from './media';
|
||||
import { getSettings } from './settings';
|
||||
import { createStatus } from './statuses';
|
||||
|
||||
import type { EditorState } from 'lexical';
|
||||
|
@ -178,7 +178,7 @@ const replyCompose = (
|
|||
const state = getState();
|
||||
const client = getClient(state);
|
||||
const { createStatusExplicitAddressing: explicitAddressing } = client.features;
|
||||
const preserveSpoilers = !!getSettings(state).get('preserveSpoilers');
|
||||
const preserveSpoilers = useSettingsStore.getState().settings.preserveSpoilers;
|
||||
const account = selectOwnAccount(state);
|
||||
|
||||
if (!account) return;
|
||||
|
@ -321,7 +321,7 @@ const handleComposeSubmit = (dispatch: AppDispatch, getState: () => RootState, c
|
|||
|
||||
const needsDescriptions = (state: RootState, composeId: string) => {
|
||||
const media = state.compose.get(composeId)!.media_attachments;
|
||||
const missingDescriptionModal = getSettings(state).get('missingDescriptionModal');
|
||||
const missingDescriptionModal = useSettingsStore.getState().settings.missingDescriptionModal;
|
||||
|
||||
const hasMissing = media.filter(item => !item.description).size > 0;
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { selectAccount } from 'pl-fe/selectors';
|
||||
import { setSentryAccount } from 'pl-fe/sentry';
|
||||
import KVStore from 'pl-fe/storage/kv-store';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import { getAuthUserId, getAuthUserUrl } from 'pl-fe/utils/auth';
|
||||
|
||||
import { getClient } from '../api';
|
||||
|
||||
import { loadCredentials } from './auth';
|
||||
import { importFetchedAccount } from './importer';
|
||||
import { FE_NAME } from './settings';
|
||||
|
||||
import type { CredentialAccount, UpdateCredentialsParams } from 'pl-api';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
@ -95,6 +97,8 @@ const fetchMeRequest = () => ({
|
|||
const fetchMeSuccess = (account: CredentialAccount) => {
|
||||
setSentryAccount(account);
|
||||
|
||||
useSettingsStore.getState().loadUserSettings(account.settings_store?.[FE_NAME]);
|
||||
|
||||
return {
|
||||
type: ME_FETCH_SUCCESS,
|
||||
me: account,
|
||||
|
|
|
@ -6,6 +6,7 @@ import { getClient } from 'pl-fe/api';
|
|||
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
|
||||
import { normalizeNotification, normalizeNotifications, type Notification } from 'pl-fe/normalizers';
|
||||
import { getFilters, regexFromFilters } from 'pl-fe/selectors';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import { isLoggedIn } from 'pl-fe/utils/auth';
|
||||
import { compareId } from 'pl-fe/utils/comparators';
|
||||
import { unescapeHTML } from 'pl-fe/utils/html';
|
||||
|
@ -20,7 +21,7 @@ import {
|
|||
importFetchedStatuses,
|
||||
} from './importer';
|
||||
import { saveMarker } from './markers';
|
||||
import { getSettings, saveSettings } from './settings';
|
||||
import { saveSettings } from './settings';
|
||||
|
||||
import type { Account, Notification as BaseNotification, PaginatedResponse, Status } from 'pl-api';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
@ -72,7 +73,8 @@ const fetchRelatedRelationships = (dispatch: AppDispatch, notifications: Array<B
|
|||
|
||||
const updateNotifications = (notification: BaseNotification) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const showInColumn = getSettings(getState()).getIn(['notifications', 'shows', notification.type], true);
|
||||
const selectedFilter = useSettingsStore().settings.notifications.quickFilter.active;
|
||||
const showInColumn = selectedFilter === 'all' ? true : (FILTER_TYPES[selectedFilter as FilterType] || [notification.type]).includes(notification.type);
|
||||
|
||||
if (notification.account) {
|
||||
dispatch(importFetchedAccount(notification.account));
|
||||
|
@ -105,7 +107,7 @@ const updateNotificationsQueue = (notification: BaseNotification, intlMessages:
|
|||
if (notification.type === 'chat_mention') return; // Drop chat notifications, handle them per-chat
|
||||
|
||||
const filters = getFilters(getState(), { contextType: 'notifications' });
|
||||
const playSound = getSettings(getState()).getIn(['notifications', 'sounds', notification.type]);
|
||||
const playSound = useSettingsStore.getState().settings.notifications.sounds[notification.type];
|
||||
|
||||
const status = getNotificationStatus(notification);
|
||||
|
||||
|
@ -195,7 +197,7 @@ const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => an
|
|||
const state = getState();
|
||||
|
||||
const features = state.auth.client.features;
|
||||
const activeFilter = getSettings(state).getIn(['notifications', 'quickFilter', 'active']) as FilterType;
|
||||
const activeFilter = useSettingsStore.getState().settings.notifications.quickFilter.active as FilterType;
|
||||
const notifications = state.notifications;
|
||||
|
||||
if (notifications.isLoading) {
|
||||
|
@ -291,8 +293,8 @@ const scrollTopNotifications = (top: boolean) =>
|
|||
};
|
||||
|
||||
const setFilter = (filterType: FilterType, abort?: boolean) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const activeFilter = getSettings(getState()).getIn(['notifications', 'quickFilter', 'active']);
|
||||
(dispatch: AppDispatch) => {
|
||||
const activeFilter = useSettingsStore.getState().settings.notifications.quickFilter.active as FilterType;
|
||||
|
||||
dispatch({
|
||||
type: NOTIFICATIONS_FILTER_SET,
|
||||
|
|
|
@ -3,6 +3,7 @@ import { createSelector } from 'reselect';
|
|||
import { getHost } from 'pl-fe/actions/instance';
|
||||
import { normalizePlFeConfig } from 'pl-fe/normalizers';
|
||||
import KVStore from 'pl-fe/storage/kv-store';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
|
||||
import { getClient, staticFetch } from '../api';
|
||||
|
||||
|
@ -77,6 +78,9 @@ const importPlFeConfig = (plFeConfig: APIEntity, host: string | null) => {
|
|||
if (!plFeConfig.brandColor) {
|
||||
plFeConfig.brandColor = '#d80482';
|
||||
}
|
||||
|
||||
useSettingsStore.getState().loadDefaultSettings(plFeConfig?.defaultSettings);
|
||||
|
||||
return {
|
||||
type: PLFE_CONFIG_REQUEST_SUCCESS,
|
||||
plFeConfig,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { getSettings, changeSetting } from 'pl-fe/actions/settings';
|
||||
import { changeSetting } from 'pl-fe/actions/settings';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
|
||||
import type { List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
||||
const getPinnedHosts = (state: RootState) => {
|
||||
const settings = getSettings(state);
|
||||
return settings.getIn(['remote_timeline', 'pinnedHosts']) as ImmutableList<string> | ImmutableOrderedSet<string>;
|
||||
const { settings } = useSettingsStore.getState();
|
||||
return settings.remote_timeline.pinnedHosts;
|
||||
};
|
||||
|
||||
const pinHost = (host: string) =>
|
||||
|
@ -13,7 +13,7 @@ const pinHost = (host: string) =>
|
|||
const state = getState();
|
||||
const pinnedHosts = getPinnedHosts(state);
|
||||
|
||||
return dispatch(changeSetting(['remote_timeline', 'pinnedHosts'], pinnedHosts.toOrderedSet().add(host)));
|
||||
return dispatch(changeSetting(['remote_timeline', 'pinnedHosts'], [...pinnedHosts, host]));
|
||||
};
|
||||
|
||||
const unpinHost = (host: string) =>
|
||||
|
@ -21,7 +21,7 @@ const unpinHost = (host: string) =>
|
|||
const state = getState();
|
||||
const pinnedHosts = getPinnedHosts(state);
|
||||
|
||||
return dispatch(changeSetting(['remote_timeline', 'pinnedHosts'], pinnedHosts.toOrderedSet().remove(host)));
|
||||
return dispatch(changeSetting(['remote_timeline', 'pinnedHosts'], pinnedHosts.filter(value => value !== host)));
|
||||
};
|
||||
|
||||
export {
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import { defineMessage } from 'react-intl';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { patchMe } from 'pl-fe/actions/me';
|
||||
import { getClient } from 'pl-fe/api';
|
||||
import messages from 'pl-fe/messages';
|
||||
import { makeGetAccount } from 'pl-fe/selectors';
|
||||
import KVStore from 'pl-fe/storage/kv-store';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import toast from 'pl-fe/toast';
|
||||
import { isLoggedIn } from 'pl-fe/utils/auth';
|
||||
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
||||
const SETTING_CHANGE = 'SETTING_CHANGE' as const;
|
||||
const SETTING_SAVE = 'SETTING_SAVE' as const;
|
||||
const SETTINGS_UPDATE = 'SETTINGS_UPDATE' as const;
|
||||
|
||||
const FE_NAME = 'pl_fe';
|
||||
|
||||
|
@ -28,114 +25,109 @@ type SettingOpts = {
|
|||
|
||||
const saveSuccessMessage = defineMessage({ id: 'settings.save.success', defaultMessage: 'Your preferences have been saved!' });
|
||||
|
||||
const defaultSettings = ImmutableMap({
|
||||
onboarded: false,
|
||||
skinTone: 1,
|
||||
reduceMotion: false,
|
||||
underlineLinks: false,
|
||||
autoPlayGif: true,
|
||||
displayMedia: 'default',
|
||||
displaySpoilers: false,
|
||||
unfollowModal: true,
|
||||
boostModal: false,
|
||||
deleteModal: true,
|
||||
missingDescriptionModal: true,
|
||||
defaultPrivacy: 'public',
|
||||
defaultContentType: 'text/plain',
|
||||
themeMode: 'system',
|
||||
locale: navigator.language || 'en',
|
||||
showExplanationBox: true,
|
||||
explanationBox: true,
|
||||
autoloadTimelines: true,
|
||||
autoloadMore: false,
|
||||
preserveSpoilers: true,
|
||||
autoTranslate: false,
|
||||
knownLanguages: ImmutableOrderedSet(),
|
||||
// const defaultSettings = ImmutableMap({
|
||||
// onboarded: false,
|
||||
// skinTone: 1,
|
||||
// reduceMotion: false,
|
||||
// underlineLinks: false,
|
||||
// autoPlayGif: true,
|
||||
// displayMedia: 'default',
|
||||
// displaySpoilers: false,
|
||||
// unfollowModal: true,
|
||||
// boostModal: false,
|
||||
// deleteModal: true,
|
||||
// missingDescriptionModal: true,
|
||||
// defaultPrivacy: 'public',
|
||||
// defaultContentType: 'text/plain',
|
||||
// themeMode: 'system',
|
||||
// locale: navigator.language || 'en',
|
||||
// showExplanationBox: true,
|
||||
// explanationBox: true,
|
||||
// autoloadTimelines: true,
|
||||
// autoloadMore: false,
|
||||
// preserveSpoilers: true,
|
||||
// autoTranslate: false,
|
||||
// knownLanguages: ImmutableOrderedSet(),
|
||||
|
||||
systemFont: false,
|
||||
demetricator: false,
|
||||
// systemFont: false,
|
||||
// demetricator: false,
|
||||
|
||||
isDeveloper: false,
|
||||
// isDeveloper: false,
|
||||
|
||||
chats: ImmutableMap({
|
||||
panes: ImmutableList(),
|
||||
mainWindow: 'minimized',
|
||||
sound: true,
|
||||
}),
|
||||
// chats: ImmutableMap({
|
||||
// panes: ImmutableList(),
|
||||
// mainWindow: 'minimized',
|
||||
// sound: true,
|
||||
// }),
|
||||
|
||||
home: ImmutableMap({
|
||||
shows: ImmutableMap({
|
||||
reblog: true,
|
||||
reply: true,
|
||||
direct: false,
|
||||
}),
|
||||
}),
|
||||
// home: ImmutableMap({
|
||||
// shows: ImmutableMap({
|
||||
// reblog: true,
|
||||
// reply: true,
|
||||
// direct: false,
|
||||
// }),
|
||||
// }),
|
||||
|
||||
notifications: ImmutableMap({
|
||||
quickFilter: ImmutableMap({
|
||||
active: 'all',
|
||||
show: true,
|
||||
advanced: false,
|
||||
}),
|
||||
// notifications: ImmutableMap({
|
||||
// quickFilter: ImmutableMap({
|
||||
// active: 'all',
|
||||
// show: true,
|
||||
// advanced: false,
|
||||
// }),
|
||||
|
||||
sounds: ImmutableMap({
|
||||
follow: false,
|
||||
follow_request: false,
|
||||
favourite: false,
|
||||
reblog: false,
|
||||
mention: false,
|
||||
poll: false,
|
||||
move: false,
|
||||
emoji_reaction: false,
|
||||
}),
|
||||
}),
|
||||
// sounds: ImmutableMap({
|
||||
// follow: false,
|
||||
// follow_request: false,
|
||||
// favourite: false,
|
||||
// reblog: false,
|
||||
// mention: false,
|
||||
// poll: false,
|
||||
// move: false,
|
||||
// emoji_reaction: false,
|
||||
// }),
|
||||
// }),
|
||||
|
||||
'public:local': ImmutableMap({
|
||||
shows: ImmutableMap({
|
||||
reblog: false,
|
||||
reply: true,
|
||||
direct: false,
|
||||
}),
|
||||
other: ImmutableMap({
|
||||
onlyMedia: false,
|
||||
}),
|
||||
}),
|
||||
// 'public:local': ImmutableMap({
|
||||
// shows: ImmutableMap({
|
||||
// reblog: false,
|
||||
// reply: true,
|
||||
// direct: false,
|
||||
// }),
|
||||
// other: ImmutableMap({
|
||||
// onlyMedia: false,
|
||||
// }),
|
||||
// }),
|
||||
|
||||
public: ImmutableMap({
|
||||
shows: ImmutableMap({
|
||||
reblog: true,
|
||||
reply: true,
|
||||
direct: false,
|
||||
}),
|
||||
other: ImmutableMap({
|
||||
onlyMedia: false,
|
||||
}),
|
||||
}),
|
||||
// public: ImmutableMap({
|
||||
// shows: ImmutableMap({
|
||||
// reblog: true,
|
||||
// reply: true,
|
||||
// direct: false,
|
||||
// }),
|
||||
// other: ImmutableMap({
|
||||
// onlyMedia: false,
|
||||
// }),
|
||||
// }),
|
||||
|
||||
direct: ImmutableMap({
|
||||
}),
|
||||
// direct: ImmutableMap({
|
||||
// }),
|
||||
|
||||
account_timeline: ImmutableMap({
|
||||
shows: ImmutableMap({
|
||||
reblog: true,
|
||||
pinned: true,
|
||||
direct: false,
|
||||
}),
|
||||
}),
|
||||
// account_timeline: ImmutableMap({
|
||||
// shows: ImmutableMap({
|
||||
// reblog: true,
|
||||
// pinned: true,
|
||||
// direct: false,
|
||||
// }),
|
||||
// }),
|
||||
|
||||
trends: ImmutableMap({
|
||||
show: true,
|
||||
}),
|
||||
// trends: ImmutableMap({
|
||||
// show: true,
|
||||
// }),
|
||||
|
||||
remote_timeline: ImmutableMap({
|
||||
pinnedHosts: ImmutableList(),
|
||||
}),
|
||||
});
|
||||
|
||||
const getSettings = createSelector([
|
||||
(state: RootState) => state.plfe.get('defaultSettings'),
|
||||
(state: RootState) => state.settings,
|
||||
], (plFeSettings, settings) => defaultSettings.mergeDeep(plFeSettings).mergeDeep(settings));
|
||||
// remote_timeline: ImmutableMap({
|
||||
// pinnedHosts: ImmutableList(),
|
||||
// }),
|
||||
// });
|
||||
|
||||
interface SettingChangeAction {
|
||||
type: typeof SETTING_CHANGE;
|
||||
|
@ -171,13 +163,13 @@ const saveSettings = (opts?: SettingOpts) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const state = getState();
|
||||
if (getSettings(state).getIn(['saved'])) return;
|
||||
const { userSettings, userSettingsSaving } = useSettingsStore.getState();
|
||||
if (userSettings.saved) return;
|
||||
|
||||
const data = state.settings.delete('saved').toJS();
|
||||
const { saved, ...data } = userSettings;
|
||||
|
||||
dispatch(updateSettingsStore(data)).then(() => {
|
||||
dispatch({ type: SETTING_SAVE });
|
||||
userSettingsSaving();
|
||||
|
||||
if (opts?.showAlert) {
|
||||
toast.success(saveSuccessMessage);
|
||||
|
@ -216,23 +208,18 @@ const updateSettingsStore = (settings: any) =>
|
|||
}
|
||||
};
|
||||
|
||||
const getLocale = (state: RootState, fallback = 'en') => {
|
||||
const localeWithVariant = (getSettings(state).get('locale') as string).replace('_', '-');
|
||||
const getLocale = (fallback = 'en') => {
|
||||
const localeWithVariant = useSettingsStore.getState().settings.locale.replace('_', '-');
|
||||
const locale = localeWithVariant.split('-')[0];
|
||||
return Object.keys(messages).includes(localeWithVariant) ? localeWithVariant : Object.keys(messages).includes(locale) ? locale : fallback;
|
||||
};
|
||||
|
||||
type SettingsAction =
|
||||
| SettingChangeAction
|
||||
| { type: typeof SETTING_SAVE }
|
||||
|
||||
export {
|
||||
SETTING_CHANGE,
|
||||
SETTING_SAVE,
|
||||
SETTINGS_UPDATE,
|
||||
FE_NAME,
|
||||
defaultSettings,
|
||||
getSettings,
|
||||
changeSettingImmediate,
|
||||
changeSetting,
|
||||
saveSettings,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useModalsStore } from 'pl-fe/stores';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import { isLoggedIn } from 'pl-fe/utils/auth';
|
||||
import { shouldHaveCard } from 'pl-fe/utils/status';
|
||||
|
||||
|
@ -6,7 +7,6 @@ import { getClient } from '../api';
|
|||
|
||||
import { setComposeToStatus } from './compose';
|
||||
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
||||
import { getSettings } from './settings';
|
||||
import { deleteFromTimelines } from './timelines';
|
||||
|
||||
import type { CreateStatusParams, Status as BaseStatus } from 'pl-api';
|
||||
|
@ -114,7 +114,7 @@ const fetchStatus = (statusId: string, intl?: IntlShape) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: STATUS_FETCH_REQUEST, statusId });
|
||||
|
||||
const params = intl && getSettings(getState()).get('autoTranslate') ? {
|
||||
const params = intl && useSettingsStore.getState().settings.autoTranslate ? {
|
||||
language: intl.locale,
|
||||
} : undefined;
|
||||
|
||||
|
@ -159,7 +159,7 @@ const fetchContext = (statusId: string, intl?: IntlShape) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: CONTEXT_FETCH_REQUEST, statusId });
|
||||
|
||||
const params = intl && getSettings(getState()).get('autoTranslate') ? {
|
||||
const params = intl && useSettingsStore.getState().settings.autoTranslate ? {
|
||||
language: intl.locale,
|
||||
} : undefined;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { getLocale, getSettings } from 'pl-fe/actions/settings';
|
||||
import { getLocale } from 'pl-fe/actions/settings';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import { shouldFilter } from 'pl-fe/utils/timelines';
|
||||
|
||||
import { getClient } from '../api';
|
||||
|
@ -31,12 +32,12 @@ const processTimelineUpdate = (timeline: string, status: BaseStatus) =>
|
|||
const ownStatus = status.account?.id === me;
|
||||
const hasPendingStatuses = !getState().pending_statuses.isEmpty();
|
||||
|
||||
const columnSettings = getSettings(getState()).get(timeline, ImmutableMap());
|
||||
const columnSettings = useSettingsStore.getState().settings.timelines[timeline];
|
||||
const shouldSkipQueue = shouldFilter({
|
||||
in_reply_to_id: status.in_reply_to_id,
|
||||
visibility: status.visibility,
|
||||
reblog_id: status.reblog?.id || null,
|
||||
}, columnSettings as any);
|
||||
}, columnSettings);
|
||||
|
||||
if (ownStatus && hasPendingStatuses) {
|
||||
// WebSockets push statuses without the Idempotency-Key,
|
||||
|
@ -183,7 +184,7 @@ const fetchHomeTimeline = (expand = false, done = noOp) =>
|
|||
const state = getState();
|
||||
|
||||
const params: HomeTimelineParams = {};
|
||||
if (getSettings(state).get('autoTranslate')) params.language = getLocale(state);
|
||||
if (useSettingsStore.getState().settings.autoTranslate) params.language = getLocale();
|
||||
|
||||
if (expand && state.timelines.get('home')?.isLoading) return;
|
||||
|
||||
|
@ -198,7 +199,7 @@ const fetchPublicTimeline = ({ onlyMedia, local, instance }: Record<string, any>
|
|||
const timelineId = `${instance ? 'remote' : 'public'}${local ? ':local' : ''}${onlyMedia ? ':media' : ''}${instance ? `:${instance}` : ''}`;
|
||||
|
||||
const params: PublicTimelineParams = { only_media: onlyMedia, local: instance ? false : local, instance };
|
||||
if (getSettings(state).get('autoTranslate')) params.language = getLocale(state);
|
||||
if (useSettingsStore.getState().settings.autoTranslate) params.language = getLocale();
|
||||
|
||||
if (expand && state.timelines.get(timelineId)?.isLoading) return;
|
||||
|
||||
|
@ -213,7 +214,7 @@ const fetchBubbleTimeline = ({ onlyMedia }: Record<string, any> = {}, expand = f
|
|||
const timelineId = `bubble${onlyMedia ? ':media' : ''}`;
|
||||
|
||||
const params: PublicTimelineParams = { only_media: onlyMedia };
|
||||
if (getSettings(state).get('autoTranslate')) params.language = getLocale(state);
|
||||
if (useSettingsStore.getState().settings.autoTranslate) params.language = getLocale();
|
||||
|
||||
if (expand && state.timelines.get(timelineId)?.isLoading) return;
|
||||
|
||||
|
@ -229,7 +230,7 @@ const fetchAccountTimeline = (accountId: string, { exclude_replies, pinned, only
|
|||
|
||||
const params: GetAccountStatusesParams = { exclude_replies, pinned, only_media, limit };
|
||||
if (pinned || only_media) params.with_muted = true;
|
||||
if (getSettings(state).get('autoTranslate')) params.language = getLocale(state);
|
||||
if (useSettingsStore.getState().settings.autoTranslate) params.language = getLocale();
|
||||
|
||||
if (expand && state.timelines.get(timelineId)?.isLoading) return;
|
||||
|
||||
|
@ -244,7 +245,7 @@ const fetchListTimeline = (listId: string, expand = false, done = noOp) =>
|
|||
const timelineId = `list:${listId}`;
|
||||
|
||||
const params: ListTimelineParams = {};
|
||||
if (getSettings(state).get('autoTranslate')) params.language = getLocale(state);
|
||||
if (useSettingsStore.getState().settings.autoTranslate) params.language = getLocale();
|
||||
|
||||
if (expand && state.timelines.get(timelineId)?.isLoading) return;
|
||||
|
||||
|
@ -260,7 +261,7 @@ const fetchGroupTimeline = (groupId: string, { only_media, limit }: Record<strin
|
|||
|
||||
const params: GroupTimelineParams = { only_media, limit };
|
||||
if (only_media) params.with_muted = true;
|
||||
if (getSettings(state).get('autoTranslate')) params.language = getLocale(state);
|
||||
if (useSettingsStore.getState().settings.autoTranslate) params.language = getLocale();
|
||||
|
||||
if (expand && state.timelines.get(timelineId)?.isLoading) return;
|
||||
|
||||
|
@ -282,7 +283,7 @@ const fetchHashtagTimeline = (hashtag: string, { tags }: Record<string, any> = {
|
|||
|
||||
if (expand && state.timelines.get(timelineId)?.isLoading) return;
|
||||
|
||||
if (getSettings(state).get('autoTranslate')) params.language = getLocale(state);
|
||||
if (useSettingsStore.getState().settings.autoTranslate) params.language = getLocale();
|
||||
|
||||
const fn = (expand && state.timelines.get(timelineId)?.next?.()) || getClient(state).timelines.hashtagTimeline(hashtag, params);
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { updateConversations } from 'pl-fe/actions/conversations';
|
|||
import { fetchFilters } from 'pl-fe/actions/filters';
|
||||
import { MARKER_FETCH_SUCCESS } from 'pl-fe/actions/markers';
|
||||
import { updateNotificationsQueue } from 'pl-fe/actions/notifications';
|
||||
import { getLocale, getSettings } from 'pl-fe/actions/settings';
|
||||
import { getLocale } from 'pl-fe/actions/settings';
|
||||
import { updateStatus } from 'pl-fe/actions/statuses';
|
||||
import { deleteFromTimelines, processTimelineUpdate } from 'pl-fe/actions/timelines';
|
||||
import { useStatContext } from 'pl-fe/contexts/stat-context';
|
||||
|
@ -14,6 +14,7 @@ import { selectEntity } from 'pl-fe/entity-store/selectors';
|
|||
import { useAppDispatch, useLoggedIn } from 'pl-fe/hooks';
|
||||
import messages from 'pl-fe/messages';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import { getUnreadChatsCount, updateChatListItem } from 'pl-fe/utils/chats';
|
||||
import { play, soundCache } from 'pl-fe/utils/sounds';
|
||||
|
||||
|
@ -101,6 +102,7 @@ const useUserStream = () => {
|
|||
const { isLoggedIn } = useLoggedIn();
|
||||
const dispatch = useAppDispatch();
|
||||
const statContext = useStatContext();
|
||||
const { settings } = useSettingsStore();
|
||||
|
||||
const listener = useCallback((event: StreamingEvent) => {
|
||||
switch (event.event) {
|
||||
|
@ -114,20 +116,17 @@ const useUserStream = () => {
|
|||
dispatch(deleteFromTimelines(event.payload));
|
||||
break;
|
||||
case 'notification':
|
||||
dispatch((dispatch, getState) => {
|
||||
const locale = getLocale(getState());
|
||||
messages[locale]().then(messages => {
|
||||
dispatch(
|
||||
updateNotificationsQueue(
|
||||
event.payload,
|
||||
messages,
|
||||
locale,
|
||||
window.location.pathname,
|
||||
),
|
||||
);
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
messages[getLocale()]().then(messages => {
|
||||
dispatch(
|
||||
updateNotificationsQueue(
|
||||
event.payload,
|
||||
messages,
|
||||
getLocale(),
|
||||
window.location.pathname,
|
||||
),
|
||||
);
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
break;
|
||||
case 'conversation':
|
||||
|
@ -141,13 +140,12 @@ const useUserStream = () => {
|
|||
const chat = event.payload;
|
||||
const me = getState().me;
|
||||
const messageOwned = chat.last_message?.account_id === me;
|
||||
const settings = getSettings(getState());
|
||||
|
||||
// Don't update own messages from streaming
|
||||
if (!messageOwned) {
|
||||
updateChatListItem(chat);
|
||||
|
||||
if (settings.getIn(['chats', 'sound'])) {
|
||||
if (settings.chats.sound) {
|
||||
play(soundCache.chat);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
|||
import { Link, NavLink } from 'react-router-dom';
|
||||
|
||||
import { fetchOwnAccounts, logOut, switchAccount } from 'pl-fe/actions/auth';
|
||||
import { getSettings } from 'pl-fe/actions/settings';
|
||||
import { useAccount } from 'pl-fe/api/hooks';
|
||||
import Account from 'pl-fe/components/account';
|
||||
import { Stack, Divider, HStack, Icon, Text } from 'pl-fe/components/ui';
|
||||
|
@ -13,6 +12,7 @@ import ProfileStats from 'pl-fe/features/ui/components/profile-stats';
|
|||
import { useAppDispatch, useAppSelector, useFeatures, useInstance, useRegistrationStatus } from 'pl-fe/hooks';
|
||||
import { makeGetOtherAccounts } from 'pl-fe/selectors';
|
||||
import { useUiStore } from 'pl-fe/stores';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import sourceCode from 'pl-fe/utils/code';
|
||||
|
||||
import type { List as ImmutableList } from 'immutable';
|
||||
|
@ -86,7 +86,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
|
|||
const me = useAppSelector((state) => state.me);
|
||||
const { account } = useAccount(me || undefined);
|
||||
const otherAccounts: ImmutableList<AccountEntity> = useAppSelector((state) => getOtherAccounts(state));
|
||||
const settings = useAppSelector((state) => getSettings(state));
|
||||
const { settings } = useSettingsStore();
|
||||
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count());
|
||||
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
|
||||
const draftCount = useAppSelector((state) => state.draft_statuses.size);
|
||||
|
@ -343,7 +343,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
|
|||
/>
|
||||
)}
|
||||
|
||||
{settings.get('isDeveloper') && (
|
||||
{settings.isDeveloper && (
|
||||
<SidebarLink
|
||||
to='/developers'
|
||||
icon={require('@tabler/icons/outline/code.svg')}
|
||||
|
|
|
@ -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 = () => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 />;
|
||||
};
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { getLocale } from 'pl-fe/actions/settings';
|
||||
|
||||
import { useAppSelector } from './useAppSelector';
|
||||
|
||||
/** Locales which should be presented in right-to-left. */
|
||||
const RTL_LOCALES = ['ar', 'ckb', 'fa', 'he'];
|
||||
|
||||
|
@ -12,7 +10,8 @@ interface UseLocaleResult {
|
|||
|
||||
/** Get valid locale from settings. */
|
||||
const useLocale = (fallback = 'en'): UseLocaleResult => {
|
||||
const locale = useAppSelector((state) => getLocale(state, fallback));
|
||||
// TODO use useSettingsStore directly
|
||||
const locale = getLocale(fallback);
|
||||
|
||||
const direction: 'ltr' | 'rtl' =
|
||||
RTL_LOCALES.includes(locale)
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { getSettings } from 'pl-fe/actions/settings';
|
||||
import { settingsSchema } from 'pl-fe/schemas/pl-fe/settings';
|
||||
|
||||
import { useAppSelector } from './useAppSelector';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
|
||||
/** Get the user settings from the store */
|
||||
const useSettings = () => {
|
||||
const data = useAppSelector((state) => getSettings(state));
|
||||
return useMemo(() => settingsSchema.parse(data.toJS()), [data]);
|
||||
};
|
||||
const useSettings = () => useSettingsStore().settings;
|
||||
|
||||
export { useSettings };
|
||||
|
|
|
@ -37,7 +37,7 @@ import push_notifications from './push-notifications';
|
|||
import scheduled_statuses from './scheduled-statuses';
|
||||
import search from './search';
|
||||
import security from './security';
|
||||
import settings from './settings';
|
||||
// import settings from './settings';
|
||||
import status_lists from './status-lists';
|
||||
import statuses from './statuses';
|
||||
import suggestions from './suggestions';
|
||||
|
@ -81,7 +81,7 @@ const reducers = {
|
|||
scheduled_statuses,
|
||||
search,
|
||||
security,
|
||||
settings,
|
||||
// settings,
|
||||
status_lists,
|
||||
statuses,
|
||||
suggestions,
|
||||
|
|
|
@ -46,7 +46,7 @@ const persistPlFeConfig = (plFeConfig: ImmutableMap<string, any>, host: string)
|
|||
}
|
||||
};
|
||||
|
||||
const importPlFeConfig = (state: ImmutableMap<string, any>, plFeConfig: ImmutableMap<string, any>, host: string) => {
|
||||
const importPlFeConfig = (plFeConfig: ImmutableMap<string, any>, host: string) => {
|
||||
persistPlFeConfig(plFeConfig, host);
|
||||
return plFeConfig;
|
||||
};
|
||||
|
@ -58,7 +58,7 @@ const plfe = (state = initialState, action: Record<string, any>) => {
|
|||
case PLFE_CONFIG_REMEMBER_SUCCESS:
|
||||
return fromJS(action.plFeConfig);
|
||||
case PLFE_CONFIG_REQUEST_SUCCESS:
|
||||
return importPlFeConfig(state, fromJS(action.plFeConfig) as ImmutableMap<string, any>, action.host);
|
||||
return importPlFeConfig(fromJS(action.plFeConfig) as ImmutableMap<string, any>, action.host);
|
||||
case PLFE_CONFIG_REQUEST_FAIL:
|
||||
return fallbackState.mergeDeep(state);
|
||||
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
||||
|
|
|
@ -1,61 +1,31 @@
|
|||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
import { produce } from 'immer';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { LANGUAGE_USE } from 'pl-fe/actions/languages';
|
||||
import { ME_FETCH_SUCCESS } from 'pl-fe/actions/me';
|
||||
import { settingsSchema, type Settings } from 'pl-fe/schemas/pl-fe/settings';
|
||||
|
||||
import { EMOJI_CHOOSE } from '../actions/emojis';
|
||||
import { NOTIFICATIONS_FILTER_SET } from '../actions/notifications';
|
||||
import { SEARCH_FILTER_SET } from '../actions/search';
|
||||
import {
|
||||
SETTING_CHANGE,
|
||||
SETTING_SAVE,
|
||||
SETTINGS_UPDATE,
|
||||
FE_NAME,
|
||||
} from '../actions/settings';
|
||||
import { SETTING_CHANGE } from '../actions/settings';
|
||||
|
||||
import type { Emoji } from 'pl-fe/features/emoji';
|
||||
import type { APIEntity } from 'pl-fe/types/entities';
|
||||
|
||||
type State = ImmutableMap<string, any>;
|
||||
|
||||
const updateFrequentEmojis = (state: State, emoji: Emoji) =>
|
||||
state.update('frequentlyUsedEmojis', ImmutableMap(), map => map.update(emoji.id, 0, (count: number) => count + 1)).set('saved', false);
|
||||
|
||||
const updateFrequentLanguages = (state: State, language: string) =>
|
||||
state.update('frequentlyUsedLanguages', ImmutableMap<string, number>(), map => map.update(language, 0, (count: number) => count + 1)).set('saved', false);
|
||||
|
||||
const importSettings = (state: State, account: APIEntity) => {
|
||||
account = fromJS(account);
|
||||
const prefs = account.getIn(['settings_store', FE_NAME], ImmutableMap());
|
||||
return state.merge(prefs) as State;
|
||||
};
|
||||
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 = ImmutableMap<string, any>({ saved: true }),
|
||||
state: State = settingsSchema.partial().parse({}),
|
||||
action: AnyAction,
|
||||
): State => {
|
||||
switch (action.type) {
|
||||
case ME_FETCH_SUCCESS:
|
||||
return importSettings(state, action.me);
|
||||
case NOTIFICATIONS_FILTER_SET:
|
||||
case SEARCH_FILTER_SET:
|
||||
case SETTING_CHANGE:
|
||||
return state
|
||||
.setIn(action.path, action.value)
|
||||
.set('saved', false);
|
||||
case EMOJI_CHOOSE:
|
||||
return updateFrequentEmojis(state, action.emoji);
|
||||
case LANGUAGE_USE:
|
||||
return updateFrequentLanguages(state, action.language);
|
||||
case SETTING_SAVE:
|
||||
return state.set('saved', true);
|
||||
case SETTINGS_UPDATE:
|
||||
return ImmutableMap<string, any>(fromJS(action.settings));
|
||||
return produce(state, draft => {
|
||||
// @ts-ignore
|
||||
draft[action.path] = action.value;
|
||||
draft.saved = false;
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -77,9 +77,24 @@ const settingsSchema = z.object({
|
|||
advanced: z.boolean().catch(false),
|
||||
show: z.boolean().catch(true),
|
||||
}),
|
||||
sounds: z.record(z.boolean()).catch({}),
|
||||
}),
|
||||
autoTranslate: z.boolean().catch(false),
|
||||
knownLanguages: z.array(z.string()).catch([]),
|
||||
frequentlyUsedEmojis: z.record(z.number()).catch({}),
|
||||
frequentlyUsedLanguages: z.record(z.number()).catch({}),
|
||||
timelines: z.record(coerceObject({
|
||||
shows: coerceObject({
|
||||
reblog: z.boolean().catch(true),
|
||||
reply: z.boolean().catch(true),
|
||||
direct: z.boolean().catch(false),
|
||||
}),
|
||||
other: coerceObject({
|
||||
onlyMedia: z.boolean().catch(false),
|
||||
}),
|
||||
})).catch({}),
|
||||
|
||||
saved: z.boolean().catch(true),
|
||||
});
|
||||
|
||||
type Settings = z.infer<typeof settingsSchema>;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import {
|
||||
Map as ImmutableMap,
|
||||
List as ImmutableList,
|
||||
OrderedSet as ImmutableOrderedSet,
|
||||
Record as ImmutableRecord,
|
||||
} from 'immutable';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { getLocale, getSettings } from 'pl-fe/actions/settings';
|
||||
import { getLocale } from 'pl-fe/actions/settings';
|
||||
import { Entities } from 'pl-fe/entity-store/entities';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import { getDomain } from 'pl-fe/utils/accounts';
|
||||
import { validId } from 'pl-fe/utils/auth';
|
||||
import ConfigDB from 'pl-fe/utils/config-db';
|
||||
|
@ -137,7 +137,7 @@ const makeGetStatus = () => createSelector(
|
|||
getFilters,
|
||||
(state: RootState) => state.me,
|
||||
(state: RootState) => state.auth.client.features,
|
||||
(state: RootState) => getLocale(state, 'en'),
|
||||
(state: RootState) => getLocale('en'),
|
||||
],
|
||||
|
||||
(statusBase, statusReblog, statusQuote, statusGroup, poll, username, filters, me, features, locale) => {
|
||||
|
@ -333,7 +333,7 @@ const makeGetRemoteInstance = () =>
|
|||
type ColumnQuery = { type: string; prefix?: string };
|
||||
|
||||
const makeGetStatusIds = () => createSelector([
|
||||
(state: RootState, { type, prefix }: ColumnQuery) => getSettings(state).get(prefix || type, ImmutableMap()),
|
||||
(state: RootState, { type, prefix }: ColumnQuery) => useSettingsStore.getState().settings.timelines[prefix || type],
|
||||
(state: RootState, { type }: ColumnQuery) => state.timelines.get(type)?.items || ImmutableOrderedSet(),
|
||||
(state: RootState) => state.statuses,
|
||||
], (columnSettings: any, statusIds: ImmutableOrderedSet<string>, statuses) =>
|
||||
|
|
74
packages/pl-fe/src/stores/settings.ts
Normal file
74
packages/pl-fe/src/stores/settings.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { produce } from 'immer';
|
||||
import { create } from 'zustand';
|
||||
|
||||
import { settingsSchema, type Settings } from 'pl-fe/schemas/pl-fe/settings';
|
||||
|
||||
import type { Emoji } from 'pl-fe/features/emoji';
|
||||
import type { APIEntity } from 'pl-fe/types/entities';
|
||||
|
||||
const settingsSchemaPartial = settingsSchema.partial();
|
||||
|
||||
type State = {
|
||||
defaultSettings: Settings;
|
||||
userSettings: Partial<Settings>;
|
||||
|
||||
settings: Settings;
|
||||
|
||||
loadDefaultSettings: (settings: APIEntity) => void;
|
||||
loadUserSettings: (settings: APIEntity) => void;
|
||||
userSettingsSaving: () => void;
|
||||
rememberEmojiUse: (emoji: Emoji) => void;
|
||||
rememberLanguageUse: (language: string) => void;
|
||||
}
|
||||
|
||||
const mergeSettings = (state: State) => state.settings = { ...state.defaultSettings, ...state.userSettings };
|
||||
|
||||
const useSettingsStore = create<State>((set) => ({
|
||||
defaultSettings: settingsSchema.parse({}),
|
||||
userSettings: {},
|
||||
|
||||
settings: settingsSchema.parse({}),
|
||||
|
||||
loadDefaultSettings: (settings: APIEntity) => set(produce((state: State) => {
|
||||
if (typeof settings !== 'object') return;
|
||||
|
||||
state.defaultSettings = settingsSchema.parse(settings);
|
||||
mergeSettings(state);
|
||||
})),
|
||||
|
||||
loadUserSettings: (settings?: APIEntity) => set(produce((state: State) => {
|
||||
if (typeof settings !== 'object') return;
|
||||
|
||||
state.userSettings = settingsSchemaPartial.parse(settings);
|
||||
mergeSettings(state);
|
||||
})),
|
||||
|
||||
userSettingsSaving: () => set(produce((state: State) => {
|
||||
state.userSettings.saved = true;
|
||||
|
||||
mergeSettings(state);
|
||||
})),
|
||||
|
||||
rememberEmojiUse: (emoji: Emoji) => set(produce((state: State) => {
|
||||
const settings = state.userSettings;
|
||||
if (!settings.frequentlyUsedEmojis) settings.frequentlyUsedEmojis = {};
|
||||
|
||||
settings.frequentlyUsedEmojis[emoji.id] = (settings.frequentlyUsedEmojis[emoji.id] || 0) + 1;
|
||||
settings.saved = false;
|
||||
|
||||
mergeSettings(state);
|
||||
})),
|
||||
|
||||
rememberLanguageUse: (language: string) => set(produce((state: State) => {
|
||||
const settings = state.userSettings;
|
||||
if (!settings.frequentlyUsedLanguages) settings.frequentlyUsedLanguages = {};
|
||||
|
||||
settings.frequentlyUsedLanguages[language] = (settings.frequentlyUsedLanguages[language] || 0) + 1;
|
||||
settings.saved = false;
|
||||
|
||||
mergeSettings(state);
|
||||
})),
|
||||
}));
|
||||
|
||||
export { useSettingsStore };
|
||||
|
|
@ -1,18 +1,24 @@
|
|||
import { Map as ImmutableMap, type Collection } from 'immutable';
|
||||
import { Settings } from 'pl-fe/schemas/pl-fe/settings';
|
||||
|
||||
import type { Status } from 'pl-fe/normalizers';
|
||||
|
||||
const shouldFilter = (
|
||||
status: Pick<Status, 'in_reply_to_id' | 'visibility' | 'reblog_id'>,
|
||||
columnSettings: Collection<any, any>,
|
||||
columnSettings: Settings['timelines'][''],
|
||||
) => {
|
||||
const shows = ImmutableMap({
|
||||
const fallback = {
|
||||
reblog: true,
|
||||
reply: true,
|
||||
direct: false,
|
||||
};
|
||||
|
||||
const shows = {
|
||||
reblog: status.reblog_id !== null,
|
||||
reply: status.in_reply_to_id !== null,
|
||||
direct: status.visibility === 'direct',
|
||||
});
|
||||
};
|
||||
|
||||
return shows.some((value, key) => columnSettings.getIn(['shows', key]) === false && value);
|
||||
return Object.entries(shows).some(([key, value]) => (columnSettings?.shows || fallback)[key as 'reblog' | 'reply' | 'direct'] === false && value);
|
||||
};
|
||||
|
||||
export { shouldFilter };
|
||||
|
|
Loading…
Reference in a new issue