pl-fe: migrate status interaction lists to tanstack query
Signed-off-by: mkljczk <git@mkljczk.pl>
This commit is contained in:
parent
fac30c2ea9
commit
16ea9f13e2
10 changed files with 83 additions and 356 deletions
|
@ -2214,11 +2214,8 @@ class PlApiClient {
|
||||||
* Requires features{@link Features['statusDislikes']}.
|
* Requires features{@link Features['statusDislikes']}.
|
||||||
* @see {@link https://github.com/friendica/friendica/blob/2024.06-rc/doc/API-Friendica.md#get-apifriendicastatusesiddisliked_by}
|
* @see {@link https://github.com/friendica/friendica/blob/2024.06-rc/doc/API-Friendica.md#get-apifriendicastatusesiddisliked_by}
|
||||||
*/
|
*/
|
||||||
getDislikedBy: async (statusId: string) => {
|
getDislikedBy: async (statusId: string) =>
|
||||||
const response = await this.request(`/api/friendica/statuses/${statusId}/disliked_by`);
|
this.#paginatedGet(`/api/v1/statuses/${statusId}/disliked_by`, {}, accountSchema),
|
||||||
|
|
||||||
return v.parse(filteredArray(accountSchema), response.json);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the given status as disliked by this user
|
* Marks the given status as disliked by this user
|
||||||
|
|
|
@ -343,7 +343,6 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) =>
|
||||||
|
|
||||||
const compose = state.compose[composeId]!;
|
const compose = state.compose[composeId]!;
|
||||||
|
|
||||||
|
|
||||||
const status = compose.text;
|
const status = compose.text;
|
||||||
const media = compose.media_attachments;
|
const media = compose.media_attachments;
|
||||||
const statusId = compose.id;
|
const statusId = compose.id;
|
||||||
|
@ -782,7 +781,6 @@ const deleteComposeLanguage = (composeId: string, value: Language) => ({
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const addPoll = (composeId: string) => ({
|
const addPoll = (composeId: string) => ({
|
||||||
type: COMPOSE_POLL_ADD,
|
type: COMPOSE_POLL_ADD,
|
||||||
composeId,
|
composeId,
|
||||||
|
|
|
@ -6,10 +6,9 @@ import { isLoggedIn } from 'pl-fe/utils/auth';
|
||||||
|
|
||||||
import { getClient } from '../api';
|
import { getClient } from '../api';
|
||||||
|
|
||||||
import { fetchRelationships } from './accounts';
|
|
||||||
import { importEntities } from './importer';
|
import { importEntities } from './importer';
|
||||||
|
|
||||||
import type { Account, EmojiReaction, PaginatedResponse, Status } from 'pl-api';
|
import type { Status } from 'pl-api';
|
||||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||||
|
|
||||||
const REBLOG_REQUEST = 'REBLOG_REQUEST' as const;
|
const REBLOG_REQUEST = 'REBLOG_REQUEST' as const;
|
||||||
|
@ -36,22 +35,6 @@ const UNDISLIKE_REQUEST = 'UNDISLIKE_REQUEST' as const;
|
||||||
const UNDISLIKE_SUCCESS = 'UNDISLIKE_SUCCESS' as const;
|
const UNDISLIKE_SUCCESS = 'UNDISLIKE_SUCCESS' as const;
|
||||||
const UNDISLIKE_FAIL = 'UNDISLIKE_FAIL' as const;
|
const UNDISLIKE_FAIL = 'UNDISLIKE_FAIL' as const;
|
||||||
|
|
||||||
const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST' as const;
|
|
||||||
const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS' as const;
|
|
||||||
const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL' as const;
|
|
||||||
|
|
||||||
const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST' as const;
|
|
||||||
const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS' as const;
|
|
||||||
const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL' as const;
|
|
||||||
|
|
||||||
const DISLIKES_FETCH_REQUEST = 'DISLIKES_FETCH_REQUEST' as const;
|
|
||||||
const DISLIKES_FETCH_SUCCESS = 'DISLIKES_FETCH_SUCCESS' as const;
|
|
||||||
const DISLIKES_FETCH_FAIL = 'DISLIKES_FETCH_FAIL' as const;
|
|
||||||
|
|
||||||
const REACTIONS_FETCH_REQUEST = 'REACTIONS_FETCH_REQUEST' as const;
|
|
||||||
const REACTIONS_FETCH_SUCCESS = 'REACTIONS_FETCH_SUCCESS' as const;
|
|
||||||
const REACTIONS_FETCH_FAIL = 'REACTIONS_FETCH_FAIL' as const;
|
|
||||||
|
|
||||||
const PIN_REQUEST = 'PIN_REQUEST' as const;
|
const PIN_REQUEST = 'PIN_REQUEST' as const;
|
||||||
const PIN_SUCCESS = 'PIN_SUCCESS' as const;
|
const PIN_SUCCESS = 'PIN_SUCCESS' as const;
|
||||||
const PIN_FAIL = 'PIN_FAIL' as const;
|
const PIN_FAIL = 'PIN_FAIL' as const;
|
||||||
|
@ -80,8 +63,6 @@ const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL' as const;
|
||||||
|
|
||||||
const noOp = () => new Promise(f => f(undefined));
|
const noOp = () => new Promise(f => f(undefined));
|
||||||
|
|
||||||
type AccountListLink = () => Promise<PaginatedResponse<Account>>;
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' },
|
bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' },
|
||||||
bookmarkRemoved: { id: 'status.unbookmarked', defaultMessage: 'Bookmark removed.' },
|
bookmarkRemoved: { id: 'status.unbookmarked', defaultMessage: 'Bookmark removed.' },
|
||||||
|
@ -386,175 +367,6 @@ const unbookmarkFail = (statusId: string, error: unknown) => ({
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchReblogs = (statusId: string) =>
|
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
dispatch(fetchReblogsRequest(statusId));
|
|
||||||
|
|
||||||
return getClient(getState()).statuses.getRebloggedBy(statusId).then(response => {
|
|
||||||
dispatch(importEntities({ accounts: response.items }));
|
|
||||||
dispatch(fetchRelationships(response.items.map((item) => item.id)));
|
|
||||||
dispatch(fetchReblogsSuccess(statusId, response.items, response.next));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(fetchReblogsFail(statusId, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchReblogsRequest = (statusId: string) => ({
|
|
||||||
type: REBLOGS_FETCH_REQUEST,
|
|
||||||
statusId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchReblogsSuccess = (statusId: string, accounts: Array<Account>, next: AccountListLink | null) => ({
|
|
||||||
type: REBLOGS_FETCH_SUCCESS,
|
|
||||||
statusId,
|
|
||||||
accounts,
|
|
||||||
next,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchReblogsFail = (statusId: string, error: unknown) => ({
|
|
||||||
type: REBLOGS_FETCH_FAIL,
|
|
||||||
statusId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandReblogs = (statusId: string, next: AccountListLink) =>
|
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
next().then(response => {
|
|
||||||
dispatch(importEntities({ accounts: response.items }));
|
|
||||||
dispatch(fetchRelationships(response.items.map((item) => item.id)));
|
|
||||||
dispatch(expandReblogsSuccess(statusId, response.items, response.next));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(expandReblogsFail(statusId, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const expandReblogsSuccess = (statusId: string, accounts: Array<Account>, next: AccountListLink | null) => ({
|
|
||||||
type: REBLOGS_EXPAND_SUCCESS,
|
|
||||||
statusId,
|
|
||||||
accounts,
|
|
||||||
next,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandReblogsFail = (statusId: string, error: unknown) => ({
|
|
||||||
type: REBLOGS_EXPAND_FAIL,
|
|
||||||
statusId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchFavourites = (statusId: string) =>
|
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
dispatch(fetchFavouritesRequest(statusId));
|
|
||||||
|
|
||||||
return getClient(getState()).statuses.getFavouritedBy(statusId).then(response => {
|
|
||||||
dispatch(importEntities({ accounts: response.items }));
|
|
||||||
dispatch(fetchRelationships(response.items.map((item) => item.id)));
|
|
||||||
dispatch(fetchFavouritesSuccess(statusId, response.items, response.next));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(fetchFavouritesFail(statusId, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchFavouritesRequest = (statusId: string) => ({
|
|
||||||
type: FAVOURITES_FETCH_REQUEST,
|
|
||||||
statusId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchFavouritesSuccess = (statusId: string, accounts: Array<Account>, next: AccountListLink | null) => ({
|
|
||||||
type: FAVOURITES_FETCH_SUCCESS,
|
|
||||||
statusId,
|
|
||||||
accounts,
|
|
||||||
next,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchFavouritesFail = (statusId: string, error: unknown) => ({
|
|
||||||
type: FAVOURITES_FETCH_FAIL,
|
|
||||||
statusId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandFavourites = (statusId: string, next: AccountListLink) =>
|
|
||||||
(dispatch: AppDispatch) => {
|
|
||||||
next().then(response => {
|
|
||||||
dispatch(importEntities({ accounts: response.items }));
|
|
||||||
dispatch(fetchRelationships(response.items.map((item) => item.id)));
|
|
||||||
dispatch(expandFavouritesSuccess(statusId, response.items, response.next));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(expandFavouritesFail(statusId, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const expandFavouritesSuccess = (statusId: string, accounts: Array<Account>, next: AccountListLink | null) => ({
|
|
||||||
type: FAVOURITES_EXPAND_SUCCESS,
|
|
||||||
statusId,
|
|
||||||
accounts,
|
|
||||||
next,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandFavouritesFail = (statusId: string, error: unknown) => ({
|
|
||||||
type: FAVOURITES_EXPAND_FAIL,
|
|
||||||
statusId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchDislikes = (statusId: string) =>
|
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
dispatch(fetchDislikesRequest(statusId));
|
|
||||||
|
|
||||||
return getClient(getState).statuses.getDislikedBy(statusId).then(response => {
|
|
||||||
dispatch(importEntities({ accounts: response }));
|
|
||||||
dispatch(fetchRelationships(response.map((item) => item.id)));
|
|
||||||
dispatch(fetchDislikesSuccess(statusId, response));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(fetchDislikesFail(statusId, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchDislikesRequest = (statusId: string) => ({
|
|
||||||
type: DISLIKES_FETCH_REQUEST,
|
|
||||||
statusId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchDislikesSuccess = (statusId: string, accounts: Array<Account>) => ({
|
|
||||||
type: DISLIKES_FETCH_SUCCESS,
|
|
||||||
statusId,
|
|
||||||
accounts,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchDislikesFail = (statusId: string, error: unknown) => ({
|
|
||||||
type: DISLIKES_FETCH_FAIL,
|
|
||||||
statusId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchReactions = (statusId: string) =>
|
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
dispatch(fetchReactionsRequest(statusId));
|
|
||||||
|
|
||||||
return getClient(getState).statuses.getStatusReactions(statusId).then(response => {
|
|
||||||
dispatch(importEntities({ accounts: (response).map(({ accounts }) => accounts).flat() }));
|
|
||||||
dispatch(fetchReactionsSuccess(statusId, response));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(fetchReactionsFail(statusId, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchReactionsRequest = (statusId: string) => ({
|
|
||||||
type: REACTIONS_FETCH_REQUEST,
|
|
||||||
statusId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchReactionsSuccess = (statusId: string, reactions: EmojiReaction[]) => ({
|
|
||||||
type: REACTIONS_FETCH_SUCCESS,
|
|
||||||
statusId,
|
|
||||||
reactions,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchReactionsFail = (statusId: string, error: unknown) => ({
|
|
||||||
type: REACTIONS_FETCH_FAIL,
|
|
||||||
statusId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
const pin = (status: Pick<Status, 'id'>, accountId: string) =>
|
const pin = (status: Pick<Status, 'id'>, accountId: string) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
if (!isLoggedIn(getState)) return;
|
if (!isLoggedIn(getState)) return;
|
||||||
|
@ -695,22 +507,6 @@ type InteractionsAction =
|
||||||
| ReturnType<typeof unbookmarkRequest>
|
| ReturnType<typeof unbookmarkRequest>
|
||||||
| ReturnType<typeof unbookmarkSuccess>
|
| ReturnType<typeof unbookmarkSuccess>
|
||||||
| ReturnType<typeof unbookmarkFail>
|
| ReturnType<typeof unbookmarkFail>
|
||||||
| ReturnType<typeof fetchReblogsRequest>
|
|
||||||
| ReturnType<typeof fetchReblogsSuccess>
|
|
||||||
| ReturnType<typeof fetchReblogsFail>
|
|
||||||
| ReturnType<typeof expandReblogsSuccess>
|
|
||||||
| ReturnType<typeof expandReblogsFail>
|
|
||||||
| ReturnType<typeof fetchFavouritesRequest>
|
|
||||||
| ReturnType<typeof fetchFavouritesSuccess>
|
|
||||||
| ReturnType<typeof fetchFavouritesFail>
|
|
||||||
| ReturnType<typeof expandFavouritesSuccess>
|
|
||||||
| ReturnType<typeof expandFavouritesFail>
|
|
||||||
| ReturnType<typeof fetchDislikesRequest>
|
|
||||||
| ReturnType<typeof fetchDislikesSuccess>
|
|
||||||
| ReturnType<typeof fetchDislikesFail>
|
|
||||||
| ReturnType<typeof fetchReactionsRequest>
|
|
||||||
| ReturnType<typeof fetchReactionsSuccess>
|
|
||||||
| ReturnType<typeof fetchReactionsFail>
|
|
||||||
| ReturnType<typeof pinRequest>
|
| ReturnType<typeof pinRequest>
|
||||||
| ReturnType<typeof pinSuccess>
|
| ReturnType<typeof pinSuccess>
|
||||||
| ReturnType<typeof pinFail>
|
| ReturnType<typeof pinFail>
|
||||||
|
@ -740,18 +536,6 @@ export {
|
||||||
UNDISLIKE_REQUEST,
|
UNDISLIKE_REQUEST,
|
||||||
UNDISLIKE_SUCCESS,
|
UNDISLIKE_SUCCESS,
|
||||||
UNDISLIKE_FAIL,
|
UNDISLIKE_FAIL,
|
||||||
REBLOGS_FETCH_REQUEST,
|
|
||||||
REBLOGS_FETCH_SUCCESS,
|
|
||||||
REBLOGS_FETCH_FAIL,
|
|
||||||
FAVOURITES_FETCH_REQUEST,
|
|
||||||
FAVOURITES_FETCH_SUCCESS,
|
|
||||||
FAVOURITES_FETCH_FAIL,
|
|
||||||
DISLIKES_FETCH_REQUEST,
|
|
||||||
DISLIKES_FETCH_SUCCESS,
|
|
||||||
DISLIKES_FETCH_FAIL,
|
|
||||||
REACTIONS_FETCH_REQUEST,
|
|
||||||
REACTIONS_FETCH_SUCCESS,
|
|
||||||
REACTIONS_FETCH_FAIL,
|
|
||||||
PIN_REQUEST,
|
PIN_REQUEST,
|
||||||
PIN_SUCCESS,
|
PIN_SUCCESS,
|
||||||
PIN_FAIL,
|
PIN_FAIL,
|
||||||
|
@ -783,12 +567,6 @@ export {
|
||||||
bookmark,
|
bookmark,
|
||||||
unbookmark,
|
unbookmark,
|
||||||
toggleBookmark,
|
toggleBookmark,
|
||||||
fetchReblogs,
|
|
||||||
expandReblogs,
|
|
||||||
fetchFavourites,
|
|
||||||
expandFavourites,
|
|
||||||
fetchDislikes,
|
|
||||||
fetchReactions,
|
|
||||||
pin,
|
pin,
|
||||||
unpin,
|
unpin,
|
||||||
togglePin,
|
togglePin,
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { importEntities } from 'pl-fe/actions/importer';
|
||||||
|
import { minifyAccountList } from 'pl-fe/api/normalizers/minify-list';
|
||||||
|
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||||
|
import { useClient } from 'pl-fe/hooks/use-client';
|
||||||
|
|
||||||
|
import type { PaginatedResponse } from 'pl-api';
|
||||||
|
|
||||||
|
const useStatusInteractions = (statusId: string, method: 'getDislikedBy' | 'getFavouritedBy' | 'getRebloggedBy') => {
|
||||||
|
const client = useClient();
|
||||||
|
|
||||||
|
const queryKey = {
|
||||||
|
getDislikedBy: 'statusDislikes',
|
||||||
|
getFavouritedBy: 'statusFavourites',
|
||||||
|
getRebloggedBy: 'statusReblogs',
|
||||||
|
}[method];
|
||||||
|
|
||||||
|
return useInfiniteQuery({
|
||||||
|
queryKey: ['accountsLists', queryKey, statusId],
|
||||||
|
queryFn: ({ pageParam }) => pageParam.next?.() || client.statuses[method](statusId).then(minifyAccountList),
|
||||||
|
initialPageParam: { previous: null, next: null, items: [], partial: false } as PaginatedResponse<string>,
|
||||||
|
getNextPageParam: (page) => page.next ? page : undefined,
|
||||||
|
select: (data) => data.pages.map(page => page.items).flat(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStatusDislikes = (statusId: string) => useStatusInteractions(statusId, 'getDislikedBy');
|
||||||
|
const useStatusFavourites = (statusId: string) => useStatusInteractions(statusId, 'getFavouritedBy');
|
||||||
|
const useStatusReblogs = (statusId: string) => useStatusInteractions(statusId, 'getRebloggedBy');
|
||||||
|
|
||||||
|
const useStatusReactions = (statusId: string, emoji?: string) => {
|
||||||
|
const client = useClient();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['accountsLists', 'statusReactions', statusId, emoji],
|
||||||
|
queryFn: () => client.statuses.getStatusReactions(statusId, emoji).then((reactions) => {
|
||||||
|
dispatch(importEntities({ accounts: reactions.map(({ accounts }) => accounts).flat() }));
|
||||||
|
|
||||||
|
return reactions.map(({ accounts, ...reactions }) => reactions);
|
||||||
|
}),
|
||||||
|
placeholderData: (previousData) => previousData?.filter(({ name }) => name === emoji),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useStatusDislikes, useStatusFavourites, useStatusReactions, useStatusReblogs };
|
|
@ -1,13 +1,11 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { fetchDislikes } from 'pl-fe/actions/interactions';
|
import { useStatusDislikes } from 'pl-fe/api/hooks/account-lists/use-status-interactions';
|
||||||
import ScrollableList from 'pl-fe/components/scrollable-list';
|
import ScrollableList from 'pl-fe/components/scrollable-list';
|
||||||
import Modal from 'pl-fe/components/ui/modal';
|
import Modal from 'pl-fe/components/ui/modal';
|
||||||
import Spinner from 'pl-fe/components/ui/spinner';
|
import Spinner from 'pl-fe/components/ui/spinner';
|
||||||
import AccountContainer from 'pl-fe/containers/account-container';
|
import AccountContainer from 'pl-fe/containers/account-container';
|
||||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
|
||||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
|
||||||
|
|
||||||
import type { BaseModalProps } from '../modal-root';
|
import type { BaseModalProps } from '../modal-root';
|
||||||
|
|
||||||
|
@ -16,17 +14,9 @@ interface DislikesModalProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DislikesModal: React.FC<BaseModalProps & DislikesModalProps> = ({ onClose, statusId }) => {
|
const DislikesModal: React.FC<BaseModalProps & DislikesModalProps> = ({ onClose, statusId }) => {
|
||||||
const dispatch = useAppDispatch();
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const accountIds = useAppSelector((state) => state.user_lists.disliked_by[statusId]?.items);
|
const { data: accountIds, isLoading, hasNextPage, fetchNextPage } = useStatusDislikes(statusId);
|
||||||
|
|
||||||
const fetchData = () => {
|
|
||||||
dispatch(fetchDislikes(statusId));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onClickClose = () => {
|
const onClickClose = () => {
|
||||||
onClose('DISLIKES');
|
onClose('DISLIKES');
|
||||||
|
@ -44,7 +34,12 @@ const DislikesModal: React.FC<BaseModalProps & DislikesModalProps> = ({ onClose,
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
listClassName='max-w-full'
|
listClassName='max-w-full'
|
||||||
itemClassName='pb-3'
|
itemClassName='pb-3'
|
||||||
|
style={{ height: 'calc(80vh - 88px)' }}
|
||||||
|
hasMore={hasNextPage}
|
||||||
|
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||||
|
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
|
||||||
estimatedSize={42}
|
estimatedSize={42}
|
||||||
|
parentRef={modalRef}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} />,
|
<AccountContainer key={id} id={id} />,
|
||||||
|
@ -57,6 +52,7 @@ const DislikesModal: React.FC<BaseModalProps & DislikesModalProps> = ({ onClose,
|
||||||
<Modal
|
<Modal
|
||||||
title={<FormattedMessage id='column.dislikes' defaultMessage='Dislikes' />}
|
title={<FormattedMessage id='column.dislikes' defaultMessage='Dislikes' />}
|
||||||
onClose={onClickClose}
|
onClose={onClickClose}
|
||||||
|
ref={modalRef}
|
||||||
>
|
>
|
||||||
{body}
|
{body}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { fetchFavourites, expandFavourites } from 'pl-fe/actions/interactions';
|
import { useStatusFavourites } from 'pl-fe/api/hooks/account-lists/use-status-interactions';
|
||||||
import ScrollableList from 'pl-fe/components/scrollable-list';
|
import ScrollableList from 'pl-fe/components/scrollable-list';
|
||||||
import Modal from 'pl-fe/components/ui/modal';
|
import Modal from 'pl-fe/components/ui/modal';
|
||||||
import Spinner from 'pl-fe/components/ui/spinner';
|
import Spinner from 'pl-fe/components/ui/spinner';
|
||||||
import AccountContainer from 'pl-fe/containers/account-container';
|
import AccountContainer from 'pl-fe/containers/account-container';
|
||||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
|
||||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
|
||||||
|
|
||||||
import type { BaseModalProps } from '../modal-root';
|
import type { BaseModalProps } from '../modal-root';
|
||||||
|
|
||||||
|
@ -17,29 +15,13 @@ interface FavouritesModalProps {
|
||||||
|
|
||||||
const FavouritesModal: React.FC<BaseModalProps & FavouritesModalProps> = ({ onClose, statusId }) => {
|
const FavouritesModal: React.FC<BaseModalProps & FavouritesModalProps> = ({ onClose, statusId }) => {
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const accountIds = useAppSelector((state) => state.user_lists.favourited_by[statusId]?.items);
|
const { data: accountIds, isLoading, hasNextPage, fetchNextPage } = useStatusFavourites(statusId);
|
||||||
const next = useAppSelector((state) => state.user_lists.favourited_by[statusId]?.next);
|
|
||||||
|
|
||||||
const fetchData = () => {
|
|
||||||
dispatch(fetchFavourites(statusId));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onClickClose = () => {
|
const onClickClose = () => {
|
||||||
onClose('FAVOURITES');
|
onClose('FAVOURITES');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoadMore = () => {
|
|
||||||
if (next) {
|
|
||||||
dispatch(expandFavourites(statusId, next!));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let body;
|
let body;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
|
@ -53,8 +35,9 @@ const FavouritesModal: React.FC<BaseModalProps & FavouritesModalProps> = ({ onCl
|
||||||
listClassName='max-w-full'
|
listClassName='max-w-full'
|
||||||
itemClassName='pb-3'
|
itemClassName='pb-3'
|
||||||
style={{ height: 'calc(80vh - 88px)' }}
|
style={{ height: 'calc(80vh - 88px)' }}
|
||||||
onLoadMore={handleLoadMore}
|
hasMore={hasNextPage}
|
||||||
hasMore={!!next}
|
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||||
|
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
|
||||||
estimatedSize={42}
|
estimatedSize={42}
|
||||||
parentRef={modalRef}
|
parentRef={modalRef}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useMemo, useRef, useState } from 'react';
|
||||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { fetchReactions } from 'pl-fe/actions/interactions';
|
import { useStatusReactions } from 'pl-fe/api/hooks/account-lists/use-status-interactions';
|
||||||
import ScrollableList from 'pl-fe/components/scrollable-list';
|
import ScrollableList from 'pl-fe/components/scrollable-list';
|
||||||
import Emoji from 'pl-fe/components/ui/emoji';
|
import Emoji from 'pl-fe/components/ui/emoji';
|
||||||
import Modal from 'pl-fe/components/ui/modal';
|
import Modal from 'pl-fe/components/ui/modal';
|
||||||
import Spinner from 'pl-fe/components/ui/spinner';
|
import Spinner from 'pl-fe/components/ui/spinner';
|
||||||
import Tabs from 'pl-fe/components/ui/tabs';
|
import Tabs from 'pl-fe/components/ui/tabs';
|
||||||
import AccountContainer from 'pl-fe/containers/account-container';
|
import AccountContainer from 'pl-fe/containers/account-container';
|
||||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
|
||||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
|
||||||
|
|
||||||
import type { BaseModalProps } from '../modal-root';
|
import type { BaseModalProps } from '../modal-root';
|
||||||
import type { Item } from 'pl-fe/components/ui/tabs';
|
import type { Item } from 'pl-fe/components/ui/tabs';
|
||||||
|
@ -32,10 +30,10 @@ interface ReactionsModalProps {
|
||||||
|
|
||||||
const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClose, statusId, reaction: initialReaction }) => {
|
const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClose, statusId, reaction: initialReaction }) => {
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [reaction, setReaction] = useState(initialReaction);
|
const [reaction, setReaction] = useState(initialReaction);
|
||||||
const reactions = useAppSelector((state) => state.user_lists.reactions[statusId]?.items);
|
|
||||||
|
const { data: reactions, isLoading } = useStatusReactions(statusId);
|
||||||
|
|
||||||
const onClickClose = () => {
|
const onClickClose = () => {
|
||||||
onClose('REACTIONS');
|
onClose('REACTIONS');
|
||||||
|
@ -70,16 +68,12 @@ const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClos
|
||||||
if (reaction) {
|
if (reaction) {
|
||||||
const reactionRecord = reactions.find(({ name }) => name === reaction);
|
const reactionRecord = reactions.find(({ name }) => name === reaction);
|
||||||
|
|
||||||
if (reactionRecord) return reactionRecord.accounts.map(account => ({ id: account, reaction: reaction, reactionUrl: reactionRecord.url || undefined }));
|
if (reactionRecord) return reactionRecord.account_ids.map(account => ({ id: account, reaction: reaction, reactionUrl: reactionRecord.url || undefined }));
|
||||||
} else {
|
} else {
|
||||||
return reactions.map(({ accounts, name, url }) => accounts.map(account => ({ id: account, reaction: name, reactionUrl: url || undefined }))).flat();
|
return reactions.map(({ account_ids, name, url }) => account_ids.map(account => ({ id: account, reaction: name, reactionUrl: url || undefined }))).flat();
|
||||||
}
|
}
|
||||||
}, [reactions, reaction]);
|
}, [reactions, reaction]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(fetchReactions(statusId));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
let body;
|
let body;
|
||||||
|
|
||||||
if (!accounts || !reactions) {
|
if (!accounts || !reactions) {
|
||||||
|
@ -96,6 +90,7 @@ const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClos
|
||||||
})}
|
})}
|
||||||
itemClassName='pb-3'
|
itemClassName='pb-3'
|
||||||
style={{ height: 'calc(80vh - 88px)' }}
|
style={{ height: 'calc(80vh - 88px)' }}
|
||||||
|
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||||
estimatedSize={42}
|
estimatedSize={42}
|
||||||
parentRef={modalRef}
|
parentRef={modalRef}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { FormattedMessage, useIntl } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { fetchReblogs, expandReblogs } from 'pl-fe/actions/interactions';
|
import { useStatusReblogs } from 'pl-fe/api/hooks/account-lists/use-status-interactions';
|
||||||
import { fetchStatus } from 'pl-fe/actions/statuses';
|
|
||||||
import ScrollableList from 'pl-fe/components/scrollable-list';
|
import ScrollableList from 'pl-fe/components/scrollable-list';
|
||||||
import Modal from 'pl-fe/components/ui/modal';
|
import Modal from 'pl-fe/components/ui/modal';
|
||||||
import Spinner from 'pl-fe/components/ui/spinner';
|
import Spinner from 'pl-fe/components/ui/spinner';
|
||||||
import AccountContainer from 'pl-fe/containers/account-container';
|
import AccountContainer from 'pl-fe/containers/account-container';
|
||||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
|
||||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
|
||||||
|
|
||||||
import type { BaseModalProps } from '../modal-root';
|
import type { BaseModalProps } from '../modal-root';
|
||||||
|
|
||||||
|
@ -17,31 +14,14 @@ interface ReblogsModalProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReblogsModal: React.FC<BaseModalProps & ReblogsModalProps> = ({ onClose, statusId }) => {
|
const ReblogsModal: React.FC<BaseModalProps & ReblogsModalProps> = ({ onClose, statusId }) => {
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const intl = useIntl();
|
|
||||||
const accountIds = useAppSelector((state) => state.user_lists.reblogged_by[statusId]?.items);
|
|
||||||
const next = useAppSelector((state) => state.user_lists.reblogged_by[statusId]?.next);
|
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const fetchData = () => {
|
const { data: accountIds, isLoading, hasNextPage, fetchNextPage } = useStatusReblogs(statusId);
|
||||||
dispatch(fetchReblogs(statusId));
|
|
||||||
dispatch(fetchStatus(statusId, intl));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onClickClose = () => {
|
const onClickClose = () => {
|
||||||
onClose('REBLOGS');
|
onClose('REBLOGS');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoadMore = () => {
|
|
||||||
if (next) {
|
|
||||||
dispatch(expandReblogs(statusId, next!));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let body;
|
let body;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
|
@ -55,8 +35,9 @@ const ReblogsModal: React.FC<BaseModalProps & ReblogsModalProps> = ({ onClose, s
|
||||||
listClassName='max-w-full'
|
listClassName='max-w-full'
|
||||||
itemClassName='pb-3'
|
itemClassName='pb-3'
|
||||||
style={{ height: 'calc(80vh - 88px)' }}
|
style={{ height: 'calc(80vh - 88px)' }}
|
||||||
onLoadMore={handleLoadMore}
|
hasMore={hasNextPage}
|
||||||
hasMore={!!next}
|
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||||
|
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
|
||||||
estimatedSize={42}
|
estimatedSize={42}
|
||||||
parentRef={modalRef}
|
parentRef={modalRef}
|
||||||
>
|
>
|
||||||
|
|
|
@ -13,9 +13,7 @@ describe('user_lists reducer', () => {
|
||||||
follow_requests: { next: null, items: ImmutableOrderedSet(), isLoading: false },
|
follow_requests: { next: null, items: ImmutableOrderedSet(), isLoading: false },
|
||||||
blocks: { next: null, items: ImmutableOrderedSet(), isLoading: false },
|
blocks: { next: null, items: ImmutableOrderedSet(), isLoading: false },
|
||||||
mutes: { next: null, items: ImmutableOrderedSet(), isLoading: false },
|
mutes: { next: null, items: ImmutableOrderedSet(), isLoading: false },
|
||||||
directory: { next: null, items: ImmutableOrderedSet(), isLoading: true },
|
|
||||||
pinned: {},
|
pinned: {},
|
||||||
birthday_reminders: {},
|
|
||||||
familiar_followers: {},
|
familiar_followers: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,15 +16,6 @@ import {
|
||||||
GROUP_UNBLOCK_SUCCESS,
|
GROUP_UNBLOCK_SUCCESS,
|
||||||
type GroupsAction,
|
type GroupsAction,
|
||||||
} from 'pl-fe/actions/groups';
|
} from 'pl-fe/actions/groups';
|
||||||
import {
|
|
||||||
REBLOGS_FETCH_SUCCESS,
|
|
||||||
REBLOGS_EXPAND_SUCCESS,
|
|
||||||
FAVOURITES_FETCH_SUCCESS,
|
|
||||||
FAVOURITES_EXPAND_SUCCESS,
|
|
||||||
DISLIKES_FETCH_SUCCESS,
|
|
||||||
REACTIONS_FETCH_SUCCESS,
|
|
||||||
type InteractionsAction,
|
|
||||||
} from 'pl-fe/actions/interactions';
|
|
||||||
import { NOTIFICATIONS_UPDATE, type NotificationsAction } from 'pl-fe/actions/notifications';
|
import { NOTIFICATIONS_UPDATE, type NotificationsAction } from 'pl-fe/actions/notifications';
|
||||||
|
|
||||||
import type { Account, NotificationGroup, PaginatedResponse } from 'pl-api';
|
import type { Account, NotificationGroup, PaginatedResponse } from 'pl-api';
|
||||||
|
@ -35,31 +26,12 @@ interface List {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Reaction {
|
|
||||||
accounts: Array<string>;
|
|
||||||
count: number | null;
|
|
||||||
name: string;
|
|
||||||
url: string | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ReactionList {
|
|
||||||
next: (() => Promise<PaginatedResponse<Reaction>>) | null;
|
|
||||||
items: Array<Reaction>;
|
|
||||||
isLoading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListKey = 'follow_requests';
|
type ListKey = 'follow_requests';
|
||||||
type NestedListKey = 'reblogged_by' | 'favourited_by' | 'disliked_by' | 'pinned' | 'familiar_followers' | 'membership_requests' | 'group_blocks';
|
type NestedListKey = 'pinned' | 'familiar_followers' | 'membership_requests' | 'group_blocks';
|
||||||
|
|
||||||
type State = Record<ListKey, List> & Record<NestedListKey, Record<string, List>> & {
|
type State = Record<ListKey, List> & Record<NestedListKey, Record<string, List>>;
|
||||||
reactions: Record<string, ReactionList>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
reblogged_by: {},
|
|
||||||
favourited_by: {},
|
|
||||||
disliked_by: {},
|
|
||||||
reactions: {},
|
|
||||||
follow_requests: { next: null, items: [], isLoading: false },
|
follow_requests: { next: null, items: [], isLoading: false },
|
||||||
pinned: {},
|
pinned: {},
|
||||||
familiar_followers: {},
|
familiar_followers: {},
|
||||||
|
@ -122,26 +94,8 @@ const normalizeFollowRequest = (state: State, notification: NotificationGroup) =
|
||||||
draft.follow_requests.items = [...new Set([...notification.sample_account_ids, ...draft.follow_requests.items])];
|
draft.follow_requests.items = [...new Set([...notification.sample_account_ids, ...draft.follow_requests.items])];
|
||||||
});
|
});
|
||||||
|
|
||||||
const userLists = (state = initialState, action: AccountsAction | FamiliarFollowersAction | GroupsAction | InteractionsAction | NotificationsAction): State => {
|
const userLists = (state = initialState, action: AccountsAction | FamiliarFollowersAction | GroupsAction | NotificationsAction): State => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case REBLOGS_FETCH_SUCCESS:
|
|
||||||
return normalizeList(state, ['reblogged_by', action.statusId], action.accounts, action.next);
|
|
||||||
case REBLOGS_EXPAND_SUCCESS:
|
|
||||||
return appendToList(state, ['reblogged_by', action.statusId], action.accounts, action.next);
|
|
||||||
case FAVOURITES_FETCH_SUCCESS:
|
|
||||||
return normalizeList(state, ['favourited_by', action.statusId], action.accounts, action.next);
|
|
||||||
case FAVOURITES_EXPAND_SUCCESS:
|
|
||||||
return appendToList(state, ['favourited_by', action.statusId], action.accounts, action.next);
|
|
||||||
case DISLIKES_FETCH_SUCCESS:
|
|
||||||
return normalizeList(state, ['disliked_by', action.statusId], action.accounts);
|
|
||||||
case REACTIONS_FETCH_SUCCESS:
|
|
||||||
return create(state, (draft) => {
|
|
||||||
draft.reactions[action.statusId] = {
|
|
||||||
items: action.reactions.map((reaction) => ({ ...reaction, accounts: reaction.accounts.map(({ id }) => id) })),
|
|
||||||
next: null,
|
|
||||||
isLoading: false,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
case NOTIFICATIONS_UPDATE:
|
case NOTIFICATIONS_UPDATE:
|
||||||
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
|
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
|
||||||
case FOLLOW_REQUESTS_FETCH_SUCCESS:
|
case FOLLOW_REQUESTS_FETCH_SUCCESS:
|
||||||
|
|
Loading…
Reference in a new issue