pl-fe: migrate custom emojis to tanstack query

Signed-off-by: mkljczk <git@mkljczk.pl>
This commit is contained in:
mkljczk 2024-12-05 11:50:29 +01:00
parent ab70e2a257
commit f541ba1f11
12 changed files with 56 additions and 114 deletions

View file

@ -19,7 +19,7 @@ import { saveSettings } from './settings';
import { createStatus } from './statuses';
import type { EditorState } from 'lexical';
import type { Account as BaseAccount, CreateStatusParams, Group, MediaAttachment, Status as BaseStatus, Tag, Poll, ScheduledStatus } from 'pl-api';
import type { Account as BaseAccount, CreateStatusParams, CustomEmoji, Group, MediaAttachment, Status as BaseStatus, Tag, Poll, ScheduledStatus } from 'pl-api';
import type { AutoSuggestion } from 'pl-fe/components/autosuggest-input';
import type { Emoji } from 'pl-fe/features/emoji';
import type { Account } from 'pl-fe/normalizers/account';
@ -595,9 +595,9 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, composeId,
});
}, 200, { leading: true, trailing: true });
const fetchComposeSuggestionsEmojis = (dispatch: AppDispatch, getState: () => RootState, composeId: string, token: string) => {
const state = getState();
const results = emojiSearch(token.replace(':', ''), { maxResults: 10 }, state.custom_emojis);
const fetchComposeSuggestionsEmojis = (dispatch: AppDispatch, composeId: string, token: string) => {
const customEmojis = queryClient.getQueryData<Array<CustomEmoji>>(['instance', 'customEmojis']);
const results = emojiSearch(token.replace(':', ''), { maxResults: 10 }, customEmojis);
dispatch(readyComposeSuggestionsEmojis(composeId, token, results));
};
@ -633,7 +633,7 @@ const fetchComposeSuggestions = (composeId: string, token: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
switch (token[0]) {
case ':':
fetchComposeSuggestionsEmojis(dispatch, getState, composeId, token);
fetchComposeSuggestionsEmojis(dispatch, composeId, token);
break;
case '#':
fetchComposeSuggestionsTags(dispatch, getState, composeId, token);

View file

@ -1,49 +0,0 @@
import { getClient } from '../api';
import type { CustomEmoji } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
const CUSTOM_EMOJIS_FETCH_REQUEST = 'CUSTOM_EMOJIS_FETCH_REQUEST' as const;
const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS' as const;
const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL' as const;
const fetchCustomEmojis = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
const me = getState().me;
if (!me) return;
dispatch(fetchCustomEmojisRequest());
return getClient(getState()).instance.getCustomEmojis().then(response => {
dispatch(fetchCustomEmojisSuccess(response));
}).catch(error => {
dispatch(fetchCustomEmojisFail(error));
});
};
const fetchCustomEmojisRequest = () => ({
type: CUSTOM_EMOJIS_FETCH_REQUEST,
});
const fetchCustomEmojisSuccess = (custom_emojis: Array<CustomEmoji>) => ({
type: CUSTOM_EMOJIS_FETCH_SUCCESS,
custom_emojis,
});
const fetchCustomEmojisFail = (error: unknown) => ({
type: CUSTOM_EMOJIS_FETCH_FAIL,
error,
});
type CustomEmojisAction =
ReturnType<typeof fetchCustomEmojisRequest>
| ReturnType<typeof fetchCustomEmojisSuccess>
| ReturnType<typeof fetchCustomEmojisFail>;
export {
CUSTOM_EMOJIS_FETCH_REQUEST,
CUSTOM_EMOJIS_FETCH_SUCCESS,
CUSTOM_EMOJIS_FETCH_FAIL,
fetchCustomEmojis,
type CustomEmojisAction,
};

View file

@ -2,23 +2,21 @@ import clsx from 'clsx';
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import ReactSwipeableViews from 'react-swipeable-views';
import { createSelector } from 'reselect';
import Card from 'pl-fe/components/ui/card';
import HStack from 'pl-fe/components/ui/hstack';
import Widget from 'pl-fe/components/ui/widget';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useAnnouncements } from 'pl-fe/queries/announcements/use-announcements';
import { useCustomEmojis } from 'pl-fe/queries/instance/use-custom-emojis';
import Announcement from './announcement';
import type { CustomEmoji } from 'pl-api';
import type { RootState } from 'pl-fe/store';
const customEmojiMap = createSelector([(state: RootState) => state.custom_emojis], items => items.reduce<Record<string, CustomEmoji>>((map, emoji) => (map[emoji.shortcode] = emoji, map), {}));
const makeCustomEmojiMap = (items: Array<CustomEmoji>) => items.reduce<Record<string, CustomEmoji>>((map, emoji) => (map[emoji.shortcode] = emoji, map), {});
const AnnouncementsPanel = () => {
const emojiMap = useAppSelector(state => customEmojiMap(state));
const { data: emojiMap = {} } = useCustomEmojis(makeCustomEmojiMap);
const [index, setIndex] = useState(0);
const { data: announcements } = useAnnouncements();

View file

@ -1,8 +1,7 @@
import { GroupRoles } from 'pl-api';
import { type CustomEmoji, GroupRoles } from 'pl-api';
import React, { useMemo } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useHistory, useRouteMatch } from 'react-router-dom';
import { createSelector } from 'reselect';
import { blockAccount } from 'pl-fe/actions/accounts';
import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'pl-fe/actions/compose';
@ -30,8 +29,8 @@ import { useOwnAccount } from 'pl-fe/hooks/use-own-account';
import { useSettings } from 'pl-fe/hooks/use-settings';
import { useChats } from 'pl-fe/queries/chats';
import { useBlockGroupUserMutation } from 'pl-fe/queries/groups/use-group-blocks';
import { useCustomEmojis } from 'pl-fe/queries/instance/use-custom-emojis';
import { useTranslationLanguages } from 'pl-fe/queries/instance/use-translation-languages';
import { RootState } from 'pl-fe/store';
import { useModalsStore } from 'pl-fe/stores/modals';
import { useStatusMetaStore } from 'pl-fe/stores/status-meta';
import toast from 'pl-fe/toast';
@ -463,10 +462,7 @@ const DislikeButton: React.FC<IActionButton> = ({
);
};
const getLongerWrench = createSelector(
[(state: RootState) => state.custom_emojis],
(emojis) => emojis.find(({ shortcode }) => shortcode === 'longestest_wrench') || emojis.find(({ shortcode }) => shortcode === 'longest_wrench'),
);
const getLongerWrench = (emojis: Array<CustomEmoji>) => emojis.find(({ shortcode }) => shortcode === 'longestest_wrench') || emojis.find(({ shortcode }) => shortcode === 'longest_wrench');
const WrenchButton: React.FC<IActionButton> = ({
status,
@ -481,7 +477,7 @@ const WrenchButton: React.FC<IActionButton> = ({
const { openModal } = useModalsStore();
const { showWrenchButton } = useSettings();
const hasLongerWrench = useAppSelector(getLongerWrench);
const { data: hasLongerWrench } = useCustomEmojis(getLongerWrench);
if (!me || withLabels || !features.emojiReacts || !showWrenchButton) return;

View file

@ -9,7 +9,7 @@ import HStack from 'pl-fe/components/ui/hstack';
import Spinner from 'pl-fe/components/ui/spinner';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import { useBackupsQuery, useCreateBackupMutation } from 'pl-fe/queries/settings/use-backups';
import { useBackups, useCreateBackupMutation } from 'pl-fe/queries/settings/use-backups';
import type { Backup as BackupEntity } from 'pl-api';
@ -62,7 +62,7 @@ const Backup: React.FC<IBackup> = ({ backup }) => {
const Backups = () => {
const intl = useIntl();
const { data: backups = [], isLoading } = useBackupsQuery();
const { data: backups = [], isLoading } = useBackups();
const { mutate: createBackup } = useCreateBackupMutation();
const handleCreateBackup: React.MouseEventHandler = e => {

View file

@ -1,18 +1,17 @@
import React, { useEffect, useState, useLayoutEffect, Suspense, useMemo } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { createSelector } from 'reselect';
import { changeSetting, saveSettings } from 'pl-fe/actions/settings';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useSettings } from 'pl-fe/hooks/use-settings';
import { useTheme } from 'pl-fe/hooks/use-theme';
import { RootState } from 'pl-fe/store';
import { useCustomEmojis } from 'pl-fe/queries/instance/use-custom-emojis';
import { useSettingsStore } from 'pl-fe/stores/settings';
import { buildCustomEmojis } from '../../emoji';
import { EmojiPicker } from '../../ui/util/async-components';
import type { CustomEmoji as BaseCustomEmoji } from 'pl-api';
import type { Emoji, CustomEmoji, NativeEmoji } from 'pl-fe/features/emoji';
const messages = defineMessages({
@ -87,9 +86,7 @@ const getFrequentlyUsedEmojis = (emojiCounters: Record<string, number>) => {
return emojis;
};
const getCustomEmojis = createSelector([
(state: RootState) => state.custom_emojis,
], emojis => emojis.filter(e => e.visible_in_picker).toSorted((a, b) => {
const getCustomEmojis = (emojis: Array<BaseCustomEmoji>) => emojis.filter(e => e.visible_in_picker).toSorted((a, b) => {
const aShort = a.shortcode.toLowerCase();
const bShort = b.shortcode.toLowerCase();
@ -100,7 +97,7 @@ const getCustomEmojis = createSelector([
} else {
return 0;
}
}));
});
// Fixes render bug where popover has a delayed position update
const RenderAfter = ({ children, update }: any) => {
@ -130,7 +127,7 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({
const theme = useTheme();
const { rememberEmojiUse } = useSettingsStore();
const customEmojis = useAppSelector((state) => getCustomEmojis(state));
const { data: customEmojis } = useCustomEmojis(getCustomEmojis);
const settings = useSettings();
const frequentlyUsedEmojis = useMemo(() => getFrequentlyUsedEmojis(settings.frequentlyUsedEmojis), [settings.frequentlyUsedEmojis]);
@ -217,7 +214,7 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({
<RenderAfter update={update}>
<Suspense>
<EmojiPicker
custom={withCustom ? [{ emojis: buildCustomEmojis(customEmojis) }] : undefined}
custom={withCustom ? [{ emojis: buildCustomEmojis(customEmojis || []) }] : undefined}
title={title}
onEmojiSelect={handlePick}
recent={frequentlyUsedEmojis}

View file

@ -3,7 +3,6 @@ import React, { Suspense, lazy, useEffect, useRef } from 'react';
import { Redirect, Switch, useHistory, useLocation } from 'react-router-dom';
import { fetchConfig, fetchReports, fetchUsers } from 'pl-fe/actions/admin';
import { fetchCustomEmojis } from 'pl-fe/actions/custom-emojis';
import { fetchDraftStatuses } from 'pl-fe/actions/draft-statuses';
import { fetchFilters } from 'pl-fe/actions/filters';
import { fetchMarker } from 'pl-fe/actions/markers';
@ -41,6 +40,7 @@ import RemoteInstanceLayout from 'pl-fe/layouts/remote-instance-layout';
import SearchLayout from 'pl-fe/layouts/search-layout';
import StatusLayout from 'pl-fe/layouts/status-layout';
import { prefetchFollowRequests } from 'pl-fe/queries/accounts/use-follow-requests';
import { prefetchCustomEmojis } from 'pl-fe/queries/instance/use-custom-emojis';
import { useUiStore } from 'pl-fe/stores/ui';
import { getVapidKey } from 'pl-fe/utils/auth';
import { isStandalone } from 'pl-fe/utils/state';
@ -445,7 +445,7 @@ const UI: React.FC<IUI> = ({ children }) => {
// The user has logged in
useEffect(() => {
loadAccountData();
dispatch(fetchCustomEmojis());
prefetchCustomEmojis(client);
}, [!!account]);
useEffect(() => {

View file

@ -0,0 +1,30 @@
import { queryOptions, useQuery } from '@tanstack/react-query';
import { buildCustomEmojis } from 'pl-fe/features/emoji';
import { addCustomToPool } from 'pl-fe/features/emoji/search';
import { useClient } from 'pl-fe/hooks/use-client';
import { queryClient } from '../client';
import type { CustomEmoji, PlApiClient } from 'pl-api';
const customEmojisQueryOptions = (client: PlApiClient) => queryOptions({
queryKey: ['instance', 'customEmojis'],
queryFn: () => client.instance.getCustomEmojis().then((emojis) => {
addCustomToPool(buildCustomEmojis(emojis));
return emojis;
}),
});
const useCustomEmojis = <T>(select?: (data: Array<CustomEmoji>) => T) => {
const client = useClient();
return useQuery({
...customEmojisQueryOptions(client),
select: select ?? ((data) => data as T),
});
};
const prefetchCustomEmojis = (client: PlApiClient) => queryClient.prefetchQuery(customEmojisQueryOptions(client));
export { useCustomEmojis, prefetchCustomEmojis };

View file

@ -4,7 +4,7 @@ import { useClient } from 'pl-fe/hooks/use-client';
import { queryClient } from '../client';
const useBackupsQuery = () => {
const useBackups = () => {
const client = useClient();
return useQuery({
@ -26,4 +26,4 @@ const useCreateBackupMutation = () => {
});
};
export { useBackupsQuery, useCreateBackupMutation };
export { useBackups, useCreateBackupMutation };

View file

@ -1,9 +0,0 @@
import { List as ImmutableList } from 'immutable';
import reducer from './custom-emojis';
describe('custom_emojis reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {} as any)).toEqual(ImmutableList());
});
});

View file

@ -1,19 +0,0 @@
import { buildCustomEmojis } from 'pl-fe/features/emoji';
import { addCustomToPool } from 'pl-fe/features/emoji/search';
import { CUSTOM_EMOJIS_FETCH_SUCCESS, type CustomEmojisAction } from '../actions/custom-emojis';
import type { CustomEmoji } from 'pl-api';
const initialState: Array<CustomEmoji> = [];
const custom_emojis = (state = initialState, action: CustomEmojisAction) => {
if (action.type === CUSTOM_EMOJIS_FETCH_SUCCESS) {
addCustomToPool(buildCustomEmojis(action.custom_emojis));
return action.custom_emojis;
}
return state;
};
export { custom_emojis as default };

View file

@ -12,7 +12,6 @@ import auth from './auth';
import compose from './compose';
import contexts from './contexts';
import conversations from './conversations';
import custom_emojis from './custom-emojis';
import domain_lists from './domain-lists';
import draft_statuses from './draft-statuses';
import filters from './filters';
@ -45,7 +44,6 @@ const reducers = {
compose,
contexts,
conversations,
custom_emojis,
domain_lists,
draft_statuses,
entities,
@ -83,8 +81,8 @@ const logOut = (state: AppState): ReturnType<typeof appReducer> => {
const newState = rootReducer(undefined, { type: '' });
const { instance, plfe, custom_emojis, auth } = state;
return { ...newState, instance, plfe, custom_emojis, auth };
const { instance, plfe, auth } = state;
return { ...newState, instance, plfe, auth };
};
const rootReducer: typeof appReducer = (state, action) => {