WIP hooks migration

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak
2024-09-28 22:10:31 +02:00
parent ba0fb61374
commit 14ecf62f4e
14 changed files with 249 additions and 114 deletions

View File

@ -60,7 +60,7 @@
"@reach/popover": "^0.18.0",
"@reach/rect": "^0.18.0",
"@reach/tabs": "^0.18.0",
"@reduxjs/toolkit": "^2.0.1",
"@reduxjs/toolkit": "^2.2.7",
"@sentry/browser": "^7.74.1",
"@sentry/react": "^7.74.1",
"@tabler/icons": "^3.18.0",
@ -121,7 +121,7 @@
"react-inlinesvg": "^4.0.0",
"react-intl": "^6.7.0",
"react-motion": "^0.5.2",
"react-redux": "^9.0.4",
"react-redux": "^9.1.2",
"react-router-dom": "^5.3.4",
"react-router-dom-v5-compat": "^6.24.1",
"react-router-scroll-4": "^1.0.0-beta.2",
@ -129,7 +129,7 @@
"react-sparklines": "^1.7.0",
"react-sticky-box": "^2.0.0",
"react-swipeable-views": "^0.14.0",
"redux": "^5.0.0",
"redux": "^5.0.1",
"redux-immutable": "^4.0.0",
"redux-thunk": "^3.1.0",
"reselect": "^5.0.0",

View File

@ -1,14 +1,11 @@
import { PLEROMA, type UpdateNotificationSettingsParams, type Account, type CreateAccountParams, type PaginatedResponse, type Relationship } from 'pl-api';
import { importEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { selectAccount } from 'pl-fe/selectors';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient, type PlfeResponse } from '../api';
import { importFetchedAccount, importFetchedAccounts } from './importer';
import type { Map as ImmutableMap } from 'immutable';
import type { MinifiedStatus } from 'pl-fe/reducers/statuses';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -128,7 +125,7 @@ const fetchAccount = (accountId: string) =>
return getClient(getState()).accounts.getAccount(accountId)
.then(response => {
dispatch(importFetchedAccount(response));
importEntities({ accounts: [response] });
dispatch(fetchAccountSuccess(response));
})
.catch(error => {
@ -143,8 +140,8 @@ const fetchAccountByUsername = (username: string, history?: History) =>
if (features.accountByUsername && (me || !features.accountLookup)) {
return getClient(getState()).accounts.getAccount(username).then(response => {
importEntities({ accounts: [response] });
dispatch(fetchRelationships([response.id]));
dispatch(importFetchedAccount(response));
dispatch(fetchAccountSuccess(response));
}).catch(error => {
dispatch(fetchAccountFail(null, error));
@ -198,7 +195,7 @@ const blockAccount = (accountId: string) =>
return getClient(getState).filtering.blockAccount(accountId)
.then(response => {
dispatch(importEntities([response], Entities.RELATIONSHIPS));
importEntities({ relationships: [response] });
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
return dispatch(blockAccountSuccess(response, getState().statuses));
}).catch(error => dispatch(blockAccountFail(error)));
@ -212,7 +209,7 @@ const unblockAccount = (accountId: string) =>
return getClient(getState).filtering.unblockAccount(accountId)
.then(response => {
dispatch(importEntities([response], Entities.RELATIONSHIPS));
importEntities({ relationships: [response] });
return dispatch(unblockAccountSuccess(response));
})
.catch(error => dispatch(unblockAccountFail(error)));
@ -273,7 +270,7 @@ const muteAccount = (accountId: string, notifications?: boolean, duration = 0) =
return client.filtering.muteAccount(accountId, params)
.then(response => {
dispatch(importEntities([response], Entities.RELATIONSHIPS));
importEntities({ relationships: [response] });
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
return dispatch(muteAccountSuccess(response, getState().statuses));
})
@ -288,7 +285,7 @@ const unmuteAccount = (accountId: string) =>
return getClient(getState()).filtering.unmuteAccount(accountId)
.then(response => {
dispatch(importEntities([response], Entities.RELATIONSHIPS));
importEntities({ relationships: [response] });
return dispatch(unmuteAccountSuccess(response));
})
.catch(error => dispatch(unmuteAccountFail(accountId, error)));
@ -369,7 +366,7 @@ const fetchRelationships = (accountIds: string[]) =>
return getClient(getState()).accounts.getRelationships(newAccountIds)
.then(response => {
dispatch(importEntities(response, Entities.RELATIONSHIPS));
importEntities({ relationships: response });
dispatch(fetchRelationshipsSuccess(response));
})
.catch(error => dispatch(fetchRelationshipsFail(error)));
@ -398,7 +395,7 @@ const fetchFollowRequests = () =>
return getClient(getState()).myAccount.getFollowRequests()
.then(response => {
dispatch(importFetchedAccounts(response.items));
importEntities({ accounts: response.items });
dispatch(fetchFollowRequestsSuccess(response.items, response.next));
})
.catch(error => dispatch(fetchFollowRequestsFail(error)));
@ -430,7 +427,7 @@ const expandFollowRequests = () =>
dispatch(expandFollowRequestsRequest());
return next().then(response => {
dispatch(importFetchedAccounts(response.items));
importEntities({ accounts: response.items });
dispatch(expandFollowRequestsSuccess(response.items, response.next));
}).catch(error => dispatch(expandFollowRequestsFail(error)));
};
@ -576,7 +573,7 @@ const fetchPinnedAccounts = (accountId: string) =>
dispatch(fetchPinnedAccountsRequest(accountId));
return getClient(getState).accounts.getAccountEndorsements(accountId).then(response => {
dispatch(importFetchedAccounts(response));
importEntities({ accounts: response });
dispatch(fetchPinnedAccountsSuccess(accountId, response, null));
}).catch(error => {
dispatch(fetchPinnedAccountsFail(accountId, error));
@ -604,10 +601,10 @@ const fetchPinnedAccountsFail = (accountId: string, error: unknown) => ({
const accountSearch = (q: string, signal?: AbortSignal) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: ACCOUNT_SEARCH_REQUEST, params: { q } });
return getClient(getState()).accounts.searchAccounts(q, { resolve: false, limit: 4, following: true }, { signal }).then((accounts) => {
dispatch(importFetchedAccounts(accounts));
dispatch({ type: ACCOUNT_SEARCH_SUCCESS, accounts });
return accounts;
return getClient(getState()).accounts.searchAccounts(q, { resolve: false, limit: 4, following: true }, { signal }).then((response) => {
importEntities({ accounts: response });
dispatch({ type: ACCOUNT_SEARCH_SUCCESS, accounts: response });
return response;
}).catch(error => {
dispatch({ type: ACCOUNT_SEARCH_FAIL, skipAlert: true });
throw error;
@ -618,7 +615,7 @@ const accountLookup = (acct: string, signal?: AbortSignal) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: ACCOUNT_LOOKUP_REQUEST, acct });
return getClient(getState()).accounts.lookupAccount(acct, { signal }).then((account) => {
if (account && account.id) dispatch(importFetchedAccount(account));
if (account && account.id) importEntities({ accounts: [account] });
dispatch({ type: ACCOUNT_LOOKUP_SUCCESS, account });
return account;
}).catch(error => {
@ -636,7 +633,7 @@ const fetchBirthdayReminders = (month: number, day: number) =>
dispatch({ type: BIRTHDAY_REMINDERS_FETCH_REQUEST, day, month, accountId: me });
return getClient(getState).accounts.getBirthdays(day, month).then(response => {
dispatch(importFetchedAccounts(response));
importEntities({ accounts: response });
dispatch({
type: BIRTHDAY_REMINDERS_FETCH_SUCCESS,
accounts: response,

View File

@ -1,7 +1,8 @@
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { getClient } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import type { Account, ProfileDirectoryParams } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -19,7 +20,7 @@ const fetchDirectory = (params: ProfileDirectoryParams) =>
dispatch(fetchDirectoryRequest());
return getClient(getState()).instance.profileDirectory({ ...params, limit: 20 }).then((data) => {
dispatch(importFetchedAccounts(data));
importEntities({ accounts: data });
dispatch(fetchDirectorySuccess(data));
dispatch(fetchRelationships(data.map((x) => x.id)));
}).catch(error => dispatch(fetchDirectoryFail(error)));
@ -46,7 +47,7 @@ const expandDirectory = (params: Record<string, any>) =>
const loadedItems = getState().user_lists.directory.items.size;
return getClient(getState()).instance.profileDirectory({ ...params, offset: loadedItems, limit: 20 }).then((data) => {
dispatch(importFetchedAccounts(data));
importEntities({ accounts: data });
dispatch(expandDirectorySuccess(data));
dispatch(fetchRelationships(data.map((x) => x.id)));
}).catch(error => dispatch(expandDirectoryFail(error)));

View File

@ -1,10 +1,11 @@
import { defineMessages } from 'react-intl';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { useModalsStore } from 'pl-fe/stores';
import toast from 'pl-fe/toast';
import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer';
import { importFetchedStatus, importFetchedStatuses } from './importer';
import { STATUS_FETCH_SOURCE_FAIL, STATUS_FETCH_SOURCE_REQUEST, STATUS_FETCH_SOURCE_SUCCESS } from './statuses';
import type { Account, CreateEventParams, Location, MediaAttachment, PaginatedResponse, Status } from 'pl-api';
@ -241,7 +242,7 @@ const fetchEventParticipations = (statusId: string) =>
dispatch(fetchEventParticipationsRequest(statusId));
return getClient(getState).events.getEventParticipations(statusId).then(response => {
dispatch(importFetchedAccounts(response.items));
importEntities({ accounts: response.items });
return dispatch(fetchEventParticipationsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(fetchEventParticipationsFail(statusId, error));
@ -277,7 +278,7 @@ const expandEventParticipations = (statusId: string) =>
dispatch(expandEventParticipationsRequest(statusId));
return next().then(response => {
dispatch(importFetchedAccounts(response.items));
importEntities({ accounts: response.items });
return dispatch(expandEventParticipationsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(expandEventParticipationsFail(statusId, error));
@ -307,7 +308,7 @@ const fetchEventParticipationRequests = (statusId: string) =>
dispatch(fetchEventParticipationRequestsRequest(statusId));
return getClient(getState).events.getEventParticipationRequests(statusId).then(response => {
dispatch(importFetchedAccounts(response.items.map(({ account }) => account)));
importEntities({ accounts: response.items.map(({ account }) => account) });
return dispatch(fetchEventParticipationRequestsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(fetchEventParticipationRequestsFail(statusId, error));
@ -346,7 +347,7 @@ const expandEventParticipationRequests = (statusId: string) =>
dispatch(expandEventParticipationRequestsRequest(statusId));
return next().then(response => {
dispatch(importFetchedAccounts(response.items.map(({ account }) => account)));
importEntities({ accounts: response.items.map(({ account }) => account) });
return dispatch(expandEventParticipationRequestsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(expandEventParticipationRequestsFail(statusId, error));

View File

@ -1,9 +1,9 @@
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { AppDispatch, RootState } from 'pl-fe/store';
import { getClient } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
const FAMILIAR_FOLLOWERS_FETCH_REQUEST = 'FAMILIAR_FOLLOWERS_FETCH_REQUEST' as const;
const FAMILIAR_FOLLOWERS_FETCH_SUCCESS = 'FAMILIAR_FOLLOWERS_FETCH_SUCCESS' as const;
@ -19,7 +19,7 @@ const fetchAccountFamiliarFollowers = (accountId: string) => (dispatch: AppDispa
.then((data) => {
const accounts = data.find(({ id }: { id: string }) => id === accountId)!.accounts;
dispatch(importFetchedAccounts(accounts));
importEntities({ accounts });
dispatch(fetchRelationships(accounts.map((item) => item.id)));
dispatch({
type: FAMILIAR_FOLLOWERS_FETCH_SUCCESS,

View File

@ -1,6 +1,6 @@
import { getClient } from '../api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { importFetchedAccounts } from './importer';
import { getClient } from '../api';
import type { Account, PaginatedResponse } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -23,7 +23,7 @@ const fetchGroupBlocks = (groupId: string) =>
dispatch(fetchGroupBlocksRequest(groupId));
return getClient(getState).experimental.groups.getGroupBlocks(groupId).then(response => {
dispatch(importFetchedAccounts(response.items));
importEntities({ accounts: response.items });
dispatch(fetchGroupBlocksSuccess(groupId, response.items, response.next));
}).catch(error => {
dispatch(fetchGroupBlocksFail(groupId, error));

View File

@ -1,6 +1,5 @@
import { getClient } from 'pl-fe/api';
import { importFetchedAccounts } from './importer';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import type { StatusEdit } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -18,7 +17,7 @@ const fetchHistory = (statusId: string) =>
dispatch(fetchHistoryRequest(statusId));
return getClient(getState()).statuses.getStatusHistory(statusId).then(data => {
dispatch(importFetchedAccounts(data.map((x) => x.account)));
importEntities({ accounts: data.map((x) => x.account) });
dispatch(fetchHistorySuccess(statusId, data));
}).catch(error => dispatch(fetchHistoryFail(statusId, error)));
};

View File

@ -1,8 +1,8 @@
import { importEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import { normalizeAccount, normalizeGroup } from 'pl-fe/normalizers';
import { normalizeAccount, normalizeGroup, type Account, type Group } from 'pl-fe/normalizers';
import type { Account as BaseAccount, Group, Poll, Status as BaseStatus } from 'pl-api';
import type { Account as BaseAccount, Group as BaseGroup, Poll, Status as BaseStatus } from 'pl-api';
import type { AppDispatch } from 'pl-fe/store';
const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';
@ -13,25 +13,29 @@ const POLLS_IMPORT = 'POLLS_IMPORT';
const importAccount = (data: BaseAccount) => importAccounts([data]);
const importAccounts = (data: Array<BaseAccount>) => (dispatch: AppDispatch) => {
dispatch({ type: ACCOUNTS_IMPORT, accounts: data });
const importAccounts = (data: Array<BaseAccount>) => {
let accounts: Array<Account> = [];
try {
const accounts = data.map(normalizeAccount);
dispatch(importEntities(accounts, Entities.ACCOUNTS));
accounts = data.map(normalizeAccount);
} catch (e) {
//
}
return importEntities(accounts, Entities.ACCOUNTS);
};
const importGroup = (data: Group) => importGroups([data]);
const importGroup = (data: BaseGroup) => importGroups([data]);
const importGroups = (data: Array<Group>) => (dispatch: AppDispatch) => {
const importGroups = (data: Array<BaseGroup>) => {
let groups: Array<Group> = [];
try {
const groups = data.map(normalizeGroup);
dispatch(importEntities(groups, Entities.GROUPS));
groups = data.map(normalizeGroup);
} catch (e) {
//
}
return importEntities(groups, Entities.GROUPS);
};
const importStatus = (status: BaseStatus & { expectsCard?: boolean }, idempotencyKey?: string) => ({ type: STATUS_IMPORT, status, idempotencyKey });

View File

@ -1,5 +1,6 @@
import { defineMessages } from 'react-intl';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { useModalsStore } from 'pl-fe/stores';
import toast, { type IToastOptions } from 'pl-fe/toast';
import { isLoggedIn } from 'pl-fe/utils/auth';
@ -7,7 +8,6 @@ import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatus } from './importer';
import type { Account, EmojiReaction, PaginatedResponse, Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -97,7 +97,7 @@ const reblog = (status: Pick<Status, 'id'>) =>
return getClient(getState()).statuses.reblogStatus(status.id).then((response) => {
// The reblog API method returns a new status wrapped around the original. In this case we are only
// interested in how the original is modified, hence passing it skipping the wrapper
if (response.reblog) dispatch(importFetchedStatus(response.reblog as Status));
if (response.reblog) importEntities({ statuses: [response] });
dispatch(reblogSuccess(response));
}).catch(error => {
dispatch(reblogFail(status.id, error));
@ -110,8 +110,8 @@ const unreblog = (status: Pick<Status, 'id'>) =>
dispatch(unreblogRequest(status.id));
return getClient(getState()).statuses.unreblogStatus(status.id).then((status) => {
dispatch(unreblogSuccess(status));
return getClient(getState()).statuses.unreblogStatus(status.id).then((response) => {
dispatch(unreblogSuccess(response));
}).catch(error => {
dispatch(unreblogFail(status.id, error));
});
@ -306,9 +306,9 @@ const bookmark = (status: Pick<Status, 'id'>, folderId?: string) =>
dispatch(bookmarkRequest(status.id));
return getClient(getState()).statuses.bookmarkStatus(status.id, folderId).then((response) => {
dispatch(importFetchedStatus(response));
dispatch(bookmarkSuccess(response));
return getClient(getState()).statuses.bookmarkStatus(status.id, folderId).then((status) => {
importEntities({ statuses: [status] });
dispatch(bookmarkSuccess(status));
let opts: IToastOptions = {
actionLabel: messages.view,
@ -335,7 +335,7 @@ const unbookmark = (status: Pick<Status, 'id'>) =>
dispatch(unbookmarkRequest(status.id));
return getClient(getState()).statuses.unbookmarkStatus(status.id).then(response => {
dispatch(importFetchedStatus(response));
importEntities({ statuses: [response] });
dispatch(unbookmarkSuccess(response));
toast.success(messages.bookmarkRemoved);
}).catch(error => {
@ -391,7 +391,7 @@ const fetchReblogs = (statusId: string) =>
dispatch(fetchReblogsRequest(statusId));
return getClient(getState()).statuses.getRebloggedBy(statusId).then(response => {
dispatch(importFetchedAccounts(response.items));
importEntities({ accounts: response.items });
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(fetchReblogsSuccess(statusId, response.items, response.next));
}).catch(error => {
@ -420,7 +420,7 @@ const fetchReblogsFail = (statusId: string, error: unknown) => ({
const expandReblogs = (statusId: string, next: AccountListLink) =>
(dispatch: AppDispatch, getState: () => RootState) => {
next().then(response => {
dispatch(importFetchedAccounts(response.items));
importEntities({ accounts: response.items });
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(expandReblogsSuccess(statusId, response.items, response.next));
}).catch(error => {
@ -446,7 +446,7 @@ const fetchFavourites = (statusId: string) =>
dispatch(fetchFavouritesRequest(statusId));
return getClient(getState()).statuses.getFavouritedBy(statusId).then(response => {
dispatch(importFetchedAccounts(response.items));
importEntities({ accounts: response.items });
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(fetchFavouritesSuccess(statusId, response.items, response.next));
}).catch(error => {
@ -475,7 +475,7 @@ const fetchFavouritesFail = (statusId: string, error: unknown) => ({
const expandFavourites = (statusId: string, next: AccountListLink) =>
(dispatch: AppDispatch) => {
next().then(response => {
dispatch(importFetchedAccounts(response.items));
importEntities({ accounts: response.items });
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(expandFavouritesSuccess(statusId, response.items, response.next));
}).catch(error => {
@ -501,7 +501,7 @@ const fetchDislikes = (statusId: string) =>
dispatch(fetchDislikesRequest(statusId));
return getClient(getState).statuses.getDislikedBy(statusId).then(response => {
dispatch(importFetchedAccounts(response));
importEntities({ accounts: response });
dispatch(fetchRelationships(response.map((item) => item.id)));
dispatch(fetchDislikesSuccess(statusId, response));
}).catch(error => {
@ -531,7 +531,7 @@ const fetchReactions = (statusId: string) =>
dispatch(fetchReactionsRequest(statusId));
return getClient(getState).statuses.getStatusReactions(statusId).then(response => {
dispatch(importFetchedAccounts((response).map(({ accounts }) => accounts).flat()));
importEntities({ accounts: (response).map(({ accounts }) => accounts).flat() });
dispatch(fetchReactionsSuccess(statusId, response));
}).catch(error => {
dispatch(fetchReactionsFail(statusId, error));
@ -562,7 +562,7 @@ const pin = (status: Pick<Status, 'id'>, accountId: string) =>
dispatch(pinRequest(status.id, accountId));
return getClient(getState()).statuses.pinStatus(status.id).then(response => {
dispatch(importFetchedStatus(response));
importEntities({ statuses: [response] });
dispatch(pinSuccess(response, accountId));
}).catch(error => {
dispatch(pinFail(status.id, error, accountId));
@ -596,7 +596,7 @@ const unpin = (status: Pick<Status, 'id'>, accountId: string) =>
dispatch(unpinRequest(status.id, accountId));
return getClient(getState()).statuses.unpinStatus(status.id).then(response => {
dispatch(importFetchedStatus(response));
importEntities({ statuses: [response] });
dispatch(unpinSuccess(response, accountId));
}).catch(error => {
dispatch(unpinFail(status.id, error, accountId));

View File

@ -34,7 +34,7 @@ const AccountTimeline: React.FC<IAccountTimeline> = ({ params, withReplies = fal
const statusIds = useAppSelector(state => getStatusIds(state, { type: `account:${path}`, prefix: 'account_timeline' }));
const featuredStatusIds = useAppSelector(state => getStatusIds(state, { type: `account:${account?.id}:with_replies:pinned`, prefix: 'account_timeline' }));
const isBlocked = useAppSelector(state => state.relationships.getIn([account?.id, 'blocked_by']) === true);
const isBlocked = account?.relationship?.blocked_by;
const unavailable = isBlocked && !features.blockersVisible;
const isLoading = useAppSelector(state => state.timelines.get(`account:${path}`)?.isLoading === true);
const hasMore = useAppSelector(state => state.timelines.get(`account:${path}`)?.hasMore === true);

View File

@ -1,20 +1,16 @@
import { useInfiniteQuery } from '@tanstack/react-query';
import { importFetchedAccounts, importFetchedStatuses } from 'pl-fe/actions/importer';
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
import { useClient } from 'pl-fe/hooks';
import { normalizeNotifications } from 'pl-fe/normalizers';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { queryClient } from 'pl-fe/queries/client';
import { AppDispatch, store } from 'pl-fe/store';
import { flattenPages } from 'pl-fe/utils/queries';
import { importNotification, minifyNotification } from './useNotification';
import type {
Account as BaseAccount,
Notification as BaseNotification,
PaginatedResponse,
PlApiClient,
Status as BaseStatus,
} from 'pl-api';
import type { NotificationType } from 'pl-fe/utils/notification';
@ -29,31 +25,59 @@ const getQueryKey = (params: UseNotificationParams) => [
params.types ? params.types.join('|') : params.excludeTypes ? ('exclude:' + params.excludeTypes.join('|')) : 'all',
];
const importNotifications = (response: PaginatedResponse<BaseNotification>) => {
const accounts: Record<string, BaseAccount> = {};
const statuses: Record<string, BaseStatus> = {};
type DeduplicatedNotification = BaseNotification & {
accounts: Array<BaseAccount>;
duplicate?: boolean;
}
response.items.forEach((notification) => {
accounts[notification.account.id] = notification.account;
const STATUS_NOTIFICATION_TYPES = [
'favourite',
'reblog',
'emoji_reaction',
'event_reminder',
'participation_accepted',
'participation_request',
];
if (notification.type === 'move') accounts[notification.target.id] = notification.target;
const deduplicateNotifications = (notifications: Array<BaseNotification>) => {
const deduplicatedNotifications: DeduplicatedNotification[] = [];
// @ts-ignore
if (notification.status?.id) {
// @ts-ignore
statuses[notification.status.id] = notification.status;
for (const notification of notifications) {
if (STATUS_NOTIFICATION_TYPES.includes(notification.type)) {
const existingNotification = deduplicatedNotifications
.find(deduplicated =>
deduplicated.type === notification.type
&& ((notification.type === 'emoji_reaction' && deduplicated.type === 'emoji_reaction') ? notification.emoji === deduplicated.emoji : true)
&& getNotificationStatus(deduplicated)?.id === getNotificationStatus(notification)?.id,
);
if (existingNotification) {
existingNotification.accounts.push(notification.account);
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: true });
} else {
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: false });
}
} else {
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: false });
}
}
return deduplicatedNotifications;
};
const importNotifications = (response: PaginatedResponse<BaseNotification>) => {
const deduplicatedNotifications = deduplicateNotifications(response.items);
importEntities({
notifications: deduplicatedNotifications,
});
store.dispatch<AppDispatch>(importFetchedStatuses(Object.values(statuses)));
store.dispatch<AppDispatch>(importFetchedAccounts(Object.values(accounts)));
// const normalizedNotifications = normalizeNotifications(response.items);
const normalizedNotifications = normalizeNotifications(response.items);
normalizedNotifications.map(minifyNotification).forEach(importNotification);
// normalizedNotifications.map(minifyNotification).forEach(importNotification);
return {
items: normalizedNotifications.filter(({ duplicate }) => !duplicate).map(({ id }) => id),
items: deduplicatedNotifications.filter(({ duplicate }) => !duplicate).map(({ id }) => id),
previous: response.previous,
next: response.next,
};
@ -91,4 +115,4 @@ const prefetchNotifications = (client: PlApiClient, params: UseNotificationParam
getNextPageParam: (response) => response,
});
export { useNotifications, prefetchNotifications };
export { useNotifications, prefetchNotifications, type DeduplicatedNotification };

View File

@ -1,28 +1,125 @@
import omit from 'lodash/omit';
import { importAccounts, importGroups, importPolls, importStatuses } from 'pl-fe/actions/importer';
import { importEntities as importEntityStoreEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import { queryClient } from 'pl-fe/queries/client';
import { store } from 'pl-fe/store';
import { DeduplicatedNotification } from './hooks/notifications/useNotifications';
import type {
AccountWarning,
Account as BaseAccount,
Group as BaseGroup,
Notification as BaseNotification,
Poll as BasePoll,
Relationship as BaseRelationship,
Status as BaseStatus,
RelationshipSeveranceEvent,
} from 'pl-api';
const minifyNotification = (notification: DeduplicatedNotification) => {
// @ts-ignore
const minifiedNotification: {
duplicate: boolean;
account_id: string;
account_ids: string[];
created_at: string;
id: string;
group_key: string;
} & (
| { type: 'follow' | 'follow_request' | 'admin.sign_up' | 'bite' }
| {
type: 'mention';
subtype?: 'reply';
status_id: string;
}
| {
type: 'status' | 'reblog' | 'favourite' | 'poll' | 'update' | 'event_reminder';
status_id: string;
}
| {
type: 'admin.report';
report: Report;
}
| {
type: 'severed_relationships';
relationship_severance_event: RelationshipSeveranceEvent;
}
| {
type: 'moderation_warning';
moderation_warning: AccountWarning;
}
| {
type: 'move';
target_id: string;
}
| {
type: 'emoji_reaction';
emoji: string;
emoji_url: string | null;
status_id: string;
}
| {
type: 'chat_mention';
chat_message_id: string;
}
| {
type: 'participation_accepted' | 'participation_request';
status_id: string;
participation_message: string | null;
}
) = {
...omit(notification, ['account', 'accounts', 'status', 'target', 'chat_message']),
account_id: notification.account.id,
account_ids: notification.accounts.map(({ id }) => id),
created_at: notification.created_at,
id: notification.id,
type: notification.type,
};
// @ts-ignore
if (notification.status) minifiedNotification.status_id = notification.status.id;
// @ts-ignore
if (notification.target) minifiedNotification.target_id = notification.target.id;
// @ts-ignore
if (notification.chat_message) minifiedNotification.chat_message_id = notification.chat_message.id;
return minifiedNotification;
};
type MinifiedNotification = ReturnType<typeof minifyNotification>;
const importNotification = (notification: DeduplicatedNotification) => {
queryClient.setQueryData<MinifiedNotification>(
['notifications', 'entities', notification.id],
existingNotification => existingNotification?.duplicate ? existingNotification : minifyNotification(notification),
);
};
const importEntities = (entities: {
accounts?: Array<BaseAccount>;
notifications?: Array<BaseNotification>;
notifications?: Array<DeduplicatedNotification>;
statuses?: Array<BaseStatus>;
relationships?: Array<BaseRelationship>;
}) => {
const { dispatch } = store;
const accounts: Record<string, BaseAccount> = {};
const groups: Record<string, BaseGroup> = {};
const notifications: Record<string, BaseNotification> = {};
const notifications: Record<string, DeduplicatedNotification> = {};
const polls: Record<string, BasePoll> = {};
const relationships: Record<string, BaseRelationship> = {};
const statuses: Record<string, BaseStatus> = {};
const processAccount = (account: BaseAccount) => {
accounts[account.id] = account;
if (account.moved) processAccount(account.moved);
if (account.relationship) relationships[account.relationship.id] = account.relationship;
};
const processNotification = (notification: BaseNotification) => {
const processNotification = (notification: DeduplicatedNotification) => {
notifications[notification.id] = notification;
processAccount(notification.account);
@ -38,11 +135,20 @@ const importEntities = (entities: {
if (status.quote) processStatus(status.quote);
if (status.reblog) processStatus(status.reblog);
if (status.poll) polls[status.poll.id] = status.poll;
if (status.group) groups[status.group.id] = status.group;
};
entities.accounts?.forEach(processAccount);
entities.notifications?.forEach(processNotification);
entities.statuses?.forEach(processStatus);
dispatch(importAccounts(Object.values(accounts)));
dispatch(importGroups(Object.values(groups)));
Object.values(notifications).forEach(importNotification);
dispatch(importPolls(Object.values(polls)));
dispatch(importStatuses(Object.values(statuses)));
dispatch(importEntityStoreEntities(Object.values(relationships), Entities.RELATIONSHIPS));
};
export { importEntities };

View File

@ -2,11 +2,11 @@ import { InfiniteData, keepPreviousData, useInfiniteQuery, useMutation, useQuery
import sumBy from 'lodash/sumBy';
import { type Chat, type ChatMessage as BaseChatMessage, type PaginatedResponse, chatMessageSchema } from 'pl-api';
import { importFetchedAccount, importFetchedAccounts } from 'pl-fe/actions/importer';
import { ChatWidgetScreens, useChatContext } from 'pl-fe/contexts/chat-context';
import { useStatContext } from 'pl-fe/contexts/stat-context';
import { useAppDispatch, useAppSelector, useClient, useFeatures, useLoggedIn, useOwnAccount } from 'pl-fe/hooks';
import { useAppSelector, useClient, useFeatures, useLoggedIn, useOwnAccount } from 'pl-fe/hooks';
import { type ChatMessage, normalizeChatMessage } from 'pl-fe/normalizers';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { reOrderChatListItems } from 'pl-fe/utils/chats';
import { flattenPages, updatePageItem } from 'pl-fe/utils/queries';
@ -51,7 +51,6 @@ const useChatMessages = (chat: Chat) => {
const useChats = () => {
const client = useClient();
const dispatch = useAppDispatch();
const features = useFeatures();
const { setUnreadChatsCount } = useStatContext();
const fetchRelationships = useFetchRelationships();
@ -65,7 +64,7 @@ const useChats = () => {
// Set the relationships to these users in the redux store.
fetchRelationships.mutate({ accountIds: items.map((item) => item.account.id) });
dispatch(importFetchedAccounts(items.map((item) => item.account)));
importEntities({ accounts: items.map((item) => item.account) });
return response;
};
@ -94,7 +93,6 @@ const useChats = () => {
const useChat = (chatId?: string) => {
const client = useClient();
const dispatch = useAppDispatch();
const fetchRelationships = useFetchRelationships();
const getChat = async () => {
@ -102,7 +100,7 @@ const useChat = (chatId?: string) => {
const data = await client.chats.getChat(chatId);
fetchRelationships.mutate({ accountIds: [data.account.id] });
dispatch(importFetchedAccount(data.account));
importEntities({ accounts: [data.account] });
return data;
}

View File

@ -2171,15 +2171,15 @@
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.18.0.tgz#4f3cebe093dd436eeaff633809bf0f68f4f9d2ee"
integrity sha512-KdVMdpTgDyK8FzdKO9SCpiibuy/kbv3pwgfXshTI6tEcQT1OOwj7BAksnzGC0rPz0UholwC+AgkqEl3EJX3M1A==
"@reduxjs/toolkit@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.0.1.tgz#0a5233c1e35c1941b03aece39cceade3467a1062"
integrity sha512-fxIjrR9934cmS8YXIGd9e7s1XRsEU++aFc9DVNMFMRTM5Vtsg2DCRMj21eslGtDt43IUf9bJL3h5bwUlZleibA==
"@reduxjs/toolkit@^2.2.7":
version "2.2.7"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.2.7.tgz#199e3d10ccb39267cb5aee92c0262fd9da7fdfb2"
integrity sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==
dependencies:
immer "^10.0.3"
redux "^5.0.0"
redux "^5.0.1"
redux-thunk "^3.1.0"
reselect "^5.0.1"
reselect "^5.1.0"
"@remix-run/router@1.18.0":
version "1.18.0"
@ -9713,10 +9713,10 @@ react-property@2.0.2:
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.2.tgz#d5ac9e244cef564880a610bc8d868bd6f60fdda6"
integrity sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==
react-redux@^9.0.4:
version "9.0.4"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.0.4.tgz#6892d465f086507a517d4b53eb589876e6bc8344"
integrity sha512-9J1xh8sWO0vYq2sCxK2My/QO7MzUMRi3rpiILP/+tDr8krBHixC6JMM17fMK88+Oh3e4Ae6/sHIhNBgkUivwFA==
react-redux@^9.1.2:
version "9.1.2"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.1.2.tgz#deba38c64c3403e9abd0c3fbeab69ffd9d8a7e4b"
integrity sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==
dependencies:
"@types/use-sync-external-store" "^0.0.3"
use-sync-external-store "^1.0.0"
@ -9908,10 +9908,10 @@ redux@^4.0.5:
dependencies:
"@babel/runtime" "^7.9.2"
redux@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.0.tgz#29572e29a439e094ff8fec46883fc45053f6736d"
integrity sha512-blLIYmYetpZMET6Q6uCY7Jtl/Im5OBldy+vNPauA8vvsdqyt66oep4EUpAMWNHauTC6xa9JuRPhRB72rY82QGA==
redux@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b"
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
reflect.getprototypeof@^1.0.4:
version "1.0.4"
@ -10051,11 +10051,16 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
reselect@^5.0.0, reselect@^5.0.1:
reselect@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.0.1.tgz#587cdaaeb4e0e8927cff80ebe2bbef05f74b1648"
integrity sha512-D72j2ubjgHpvuCiORWkOUxndHJrxDaSolheiz5CO+roz8ka97/4msh2E8F5qay4GawR5vzBt5MkbDHT+Rdy/Wg==
reselect@^5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.1.tgz#c766b1eb5d558291e5e550298adb0becc24bb72e"
integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"