pl-fe: Move user lists away from immutable
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
563e5288fb
commit
0811af7ad7
20 changed files with 197 additions and 157 deletions
|
@ -43,7 +43,7 @@ const expandDirectory = (params: Record<string, any>) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(expandDirectoryRequest());
|
||||
|
||||
const loadedItems = getState().user_lists.directory.items.size;
|
||||
const loadedItems = getState().user_lists.directory.items.length;
|
||||
|
||||
return getClient(getState()).instance.profileDirectory({ ...params, offset: loadedItems, limit: 20 }).then((data) => {
|
||||
dispatch(importEntities({ accounts: data }));
|
||||
|
|
|
@ -268,7 +268,7 @@ const fetchEventParticipationsFail = (statusId: string, error: unknown) => ({
|
|||
|
||||
const expandEventParticipations = (statusId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const next = getState().user_lists.event_participations.get(statusId)?.next || null;
|
||||
const next = getState().user_lists.event_participations[statusId]?.next || null;
|
||||
|
||||
if (next === null) {
|
||||
return dispatch(noOp);
|
||||
|
@ -337,7 +337,7 @@ const fetchEventParticipationRequestsFail = (statusId: string, error: unknown) =
|
|||
|
||||
const expandEventParticipationRequests = (statusId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const next = getState().user_lists.event_participation_requests.get(statusId)?.next || null;
|
||||
const next = getState().user_lists.event_participation_requests[statusId]?.next || null;
|
||||
|
||||
if (next === null) {
|
||||
return dispatch(noOp);
|
||||
|
|
|
@ -28,7 +28,7 @@ const useAccountList = (listKey: string[], entityFn: EntityFn<void>) => {
|
|||
getNextPageParam: (config) => config.next ? config : undefined,
|
||||
});
|
||||
|
||||
const data = flattenPages<Account>(queryInfo.data as any)?.toReversed() || [];
|
||||
const data = flattenPages<Account>(queryInfo.data as any) || [];
|
||||
|
||||
const { relationships } = useRelationships(
|
||||
listKey,
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
import clsx from 'clsx';
|
||||
import { List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import React from 'react';
|
||||
|
||||
import Avatar from 'pl-fe/components/ui/avatar';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { makeGetAccount } from 'pl-fe/selectors';
|
||||
|
||||
import type { Account } from 'pl-fe/normalizers/account';
|
||||
|
||||
const getAccount = makeGetAccount();
|
||||
import { selectAccounts } from 'pl-fe/selectors';
|
||||
|
||||
interface IAvatarStack {
|
||||
accountIds: ImmutableOrderedSet<string>;
|
||||
accountIds: Array<string>;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
const AvatarStack: React.FC<IAvatarStack> = ({ accountIds, limit = 3 }) => {
|
||||
const accounts = useAppSelector(state => ImmutableList(accountIds.slice(0, limit).map(accountId => getAccount(state, accountId)).filter(account => account))) as ImmutableList<Account>;
|
||||
const accounts = useAppSelector(state => selectAccounts(state, accountIds.slice(0, limit)));
|
||||
|
||||
return (
|
||||
<HStack className='relative' aria-hidden>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import React, { useRef } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -22,7 +21,7 @@ interface IBirthdayPanel {
|
|||
const BirthdayPanel = ({ limit }: IBirthdayPanel) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const birthdays: ImmutableOrderedSet<string> = useAppSelector(state => state.user_lists.birthday_reminders.get(state.me as string)?.items || ImmutableOrderedSet());
|
||||
const birthdays = useAppSelector(state => state.user_lists.birthday_reminders[state.me as string]?.items || []);
|
||||
const birthdaysToRender = birthdays.slice(0, limit);
|
||||
|
||||
const timeout = useRef<NodeJS.Timeout>();
|
||||
|
@ -48,7 +47,7 @@ const BirthdayPanel = ({ limit }: IBirthdayPanel) => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
if (birthdaysToRender.isEmpty()) {
|
||||
if (!birthdaysToRender.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import { useSettingsStore } from 'pl-fe/stores/settings';
|
|||
import { useUiStore } from 'pl-fe/stores/ui';
|
||||
import sourceCode from 'pl-fe/utils/code';
|
||||
|
||||
import type { List as ImmutableList } from 'immutable';
|
||||
import type { Account as AccountEntity } from 'pl-fe/normalizers/account';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -95,9 +94,9 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
|
|||
const features = useFeatures();
|
||||
const me = useAppSelector((state) => state.me);
|
||||
const { account } = useAccount(me || undefined);
|
||||
const otherAccounts: ImmutableList<AccountEntity> = useAppSelector((state) => getOtherAccounts(state));
|
||||
const otherAccounts = useAppSelector((state) => getOtherAccounts(state));
|
||||
const { settings } = useSettingsStore();
|
||||
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count());
|
||||
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.length);
|
||||
const interactionRequestsCount = useInteractionRequestsCount().data || 0;
|
||||
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
|
||||
const draftCount = useAppSelector((state) => state.draft_statuses.size);
|
||||
|
|
|
@ -48,7 +48,7 @@ const SidebarNavigation = () => {
|
|||
const logoSrc = useLogo();
|
||||
|
||||
const notificationCount = useAppSelector((state) => state.notifications.unread);
|
||||
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count());
|
||||
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.length);
|
||||
const interactionRequestsCount = useInteractionRequestsCount().data || 0;
|
||||
const dashboardCount = useAppSelector((state) => state.admin.openReports.length + state.admin.awaitingApproval.length);
|
||||
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
|
||||
|
|
|
@ -70,7 +70,7 @@ interface IManagePendingParticipants {
|
|||
const ManagePendingParticipants: React.FC<IManagePendingParticipants> = ({ statusId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const accounts = useAppSelector((state) => state.user_lists.event_participation_requests.get(statusId!)?.items);
|
||||
const accounts = useAppSelector((state) => state.user_lists.event_participation_requests[statusId]?.items);
|
||||
|
||||
useEffect(() => {
|
||||
if (statusId) dispatch(fetchEventParticipationRequests(statusId));
|
||||
|
|
|
@ -66,7 +66,7 @@ const GroupBlockedMembers: React.FC<IGroupBlockedMembers> = ({ params }) => {
|
|||
const groupId = params?.groupId;
|
||||
|
||||
const { group } = useGroup(groupId);
|
||||
const accountIds = useAppSelector((state) => state.user_lists.group_blocks.get(groupId)?.items);
|
||||
const accountIds = useAppSelector((state) => state.user_lists.group_blocks[groupId]?.items);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchGroupBlocks(groupId));
|
||||
|
|
|
@ -10,7 +10,7 @@ import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
|||
import type { BaseModalProps } from '../modal-root';
|
||||
|
||||
const BirthdaysModal = ({ onClose }: BaseModalProps) => {
|
||||
const accountIds = useAppSelector(state => state.user_lists.birthday_reminders.get(state.me as string)?.items);
|
||||
const accountIds = useAppSelector(state => state.user_lists.birthday_reminders[state.me as string]?.items);
|
||||
|
||||
const onClickClose = () => {
|
||||
onClose('BIRTHDAYS');
|
||||
|
|
|
@ -18,7 +18,7 @@ interface DislikesModalProps {
|
|||
const DislikesModal: React.FC<BaseModalProps & DislikesModalProps> = ({ onClose, statusId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const accountIds = useAppSelector((state) => state.user_lists.disliked_by.get(statusId)?.items);
|
||||
const accountIds = useAppSelector((state) => state.user_lists.disliked_by[statusId]?.items);
|
||||
|
||||
const fetchData = () => {
|
||||
dispatch(fetchDislikes(statusId));
|
||||
|
|
|
@ -18,7 +18,7 @@ interface EventParticipantsModalProps {
|
|||
const EventParticipantsModal: React.FC<BaseModalProps & EventParticipantsModalProps> = ({ onClose, statusId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const accountIds = useAppSelector((state) => state.user_lists.event_participations.get(statusId)?.items);
|
||||
const accountIds = useAppSelector((state) => state.user_lists.event_participations[statusId]?.items);
|
||||
|
||||
const fetchData = () => {
|
||||
dispatch(fetchEventParticipations(statusId));
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import React, { useRef } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -21,7 +20,7 @@ interface FamiliarFollowersModalProps {
|
|||
const FamiliarFollowersModal: React.FC<BaseModalProps & FamiliarFollowersModalProps> = ({ accountId, onClose }) => {
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
const account = useAppSelector(state => getAccount(state, accountId));
|
||||
const familiarFollowerIds: ImmutableOrderedSet<string> = useAppSelector(state => state.user_lists.familiar_followers.get(accountId)?.items || ImmutableOrderedSet());
|
||||
const familiarFollowerIds = useAppSelector(state => state.user_lists.familiar_followers[accountId]?.items || []);
|
||||
|
||||
const onClickClose = () => {
|
||||
onClose('FAMILIAR_FOLLOWERS');
|
||||
|
|
|
@ -19,8 +19,8 @@ const FavouritesModal: React.FC<BaseModalProps & FavouritesModalProps> = ({ onCl
|
|||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const accountIds = useAppSelector((state) => state.user_lists.favourited_by.get(statusId)?.items);
|
||||
const next = useAppSelector((state) => state.user_lists.favourited_by.get(statusId)?.next);
|
||||
const accountIds = useAppSelector((state) => state.user_lists.favourited_by[statusId]?.items);
|
||||
const next = useAppSelector((state) => state.user_lists.favourited_by[statusId]?.next);
|
||||
|
||||
const fetchData = () => {
|
||||
dispatch(fetchFavourites(statusId));
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import clsx from 'clsx';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
|
@ -36,7 +35,7 @@ const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClos
|
|||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const [reaction, setReaction] = useState(initialReaction);
|
||||
const reactions = useAppSelector((state) => state.user_lists.reactions.get(statusId)?.items);
|
||||
const reactions = useAppSelector((state) => state.user_lists.reactions[statusId]?.items);
|
||||
|
||||
const onClickClose = () => {
|
||||
onClose('REACTIONS');
|
||||
|
@ -65,15 +64,15 @@ const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClos
|
|||
return <Tabs items={items} activeItem={reaction || 'all'} />;
|
||||
};
|
||||
|
||||
const accounts = useMemo((): ImmutableList<IAccountWithReaction> | undefined => {
|
||||
const accounts = useMemo((): Array<IAccountWithReaction> | undefined => {
|
||||
if (!reactions) return;
|
||||
|
||||
if (reaction) {
|
||||
const reactionRecord = reactions.find(({ name }) => name === reaction);
|
||||
|
||||
if (reactionRecord) return reactionRecord.accounts.map(account => ({ id: account, reaction: reaction, reactionUrl: reactionRecord.url || undefined })).toList();
|
||||
if (reactionRecord) return reactionRecord.accounts.map(account => ({ id: account, reaction: reaction, reactionUrl: reactionRecord.url || undefined }));
|
||||
} else {
|
||||
return reactions.map(({ accounts, name, url }) => accounts.map(account => ({ id: account, reaction: name, reactionUrl: url }))).flatten() as ImmutableList<IAccountWithReaction>;
|
||||
return reactions.map(({ accounts, name, url }) => accounts.map(account => ({ id: account, reaction: name, reactionUrl: url || undefined }))).flat();
|
||||
}
|
||||
}, [reactions, reaction]);
|
||||
|
||||
|
@ -89,11 +88,11 @@ const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClos
|
|||
const emptyMessage = <FormattedMessage id='status.reactions.empty' defaultMessage='No one has reacted to this post yet. When someone does, they will show up here.' />;
|
||||
|
||||
body = (<>
|
||||
{reactions.size > 0 && renderFilterBar()}
|
||||
{reactions.length > 0 && renderFilterBar()}
|
||||
<ScrollableList
|
||||
emptyMessage={emptyMessage}
|
||||
listClassName={clsx('max-w-full', {
|
||||
'mt-4': reactions.size > 0,
|
||||
'mt-4': reactions.length > 0,
|
||||
})}
|
||||
itemClassName='pb-3'
|
||||
style={{ height: 'calc(80vh - 88px)' }}
|
||||
|
|
|
@ -19,8 +19,8 @@ interface ReblogsModalProps {
|
|||
const ReblogsModal: React.FC<BaseModalProps & ReblogsModalProps> = ({ onClose, statusId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const accountIds = useAppSelector((state) => state.user_lists.reblogged_by.get(statusId)?.items);
|
||||
const next = useAppSelector((state) => state.user_lists.reblogged_by.get(statusId)?.next);
|
||||
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 fetchData = () => {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import React, { useEffect } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -19,13 +18,13 @@ interface IPinnedAccountsPanel {
|
|||
|
||||
const PinnedAccountsPanel: React.FC<IPinnedAccountsPanel> = ({ account, limit }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const pinned = useAppSelector((state) => state.user_lists.pinned.get(account.id)?.items || ImmutableOrderedSet<string>()).slice(0, limit);
|
||||
const pinned = useAppSelector((state) => state.user_lists.pinned[account.id]?.items || []).slice(0, limit);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchPinnedAccounts(account.id));
|
||||
}, []);
|
||||
|
||||
if (pinned.isEmpty()) {
|
||||
if (!pinned.length) {
|
||||
return (
|
||||
<WhoToFollowPanel limit={limit} />
|
||||
);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import React, { useEffect } from 'react';
|
||||
import { FormattedList, FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
@ -29,8 +28,8 @@ const ProfileFamiliarFollowers: React.FC<IProfileFamiliarFollowers> = ({ account
|
|||
const dispatch = useAppDispatch();
|
||||
const me = useAppSelector((state) => state.me);
|
||||
const features = useFeatures();
|
||||
const familiarFollowerIds = useAppSelector(state => state.user_lists.familiar_followers.get(account.id)?.items || ImmutableOrderedSet<string>());
|
||||
const familiarFollowers: ImmutableOrderedSet<Account | null> = useAppSelector(state => familiarFollowerIds.slice(0, 2).map(accountId => getAccount(state, accountId)));
|
||||
const familiarFollowerIds = useAppSelector(state => state.user_lists.familiar_followers[account.id]?.items || []);
|
||||
const familiarFollowers = useAppSelector(state => familiarFollowerIds.slice(0, 2).map(accountId => getAccount(state, accountId)));
|
||||
|
||||
useEffect(() => {
|
||||
if (me && features.familiarFollowers) {
|
||||
|
@ -44,7 +43,7 @@ const ProfileFamiliarFollowers: React.FC<IProfileFamiliarFollowers> = ({ account
|
|||
});
|
||||
};
|
||||
|
||||
if (familiarFollowerIds.size === 0) {
|
||||
if (familiarFollowerIds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -60,15 +59,15 @@ const ProfileFamiliarFollowers: React.FC<IProfileFamiliarFollowers> = ({ account
|
|||
</HStack>
|
||||
</Link>
|
||||
</HoverAccountWrapper>
|
||||
)).toArray().filter(Boolean);
|
||||
)).filter(Boolean);
|
||||
|
||||
if (familiarFollowerIds.size > 2) {
|
||||
if (familiarFollowerIds.length > 2) {
|
||||
accounts.push(
|
||||
<span key='_' className='cursor-pointer hover:underline' role='presentation' onClick={openFamiliarFollowersModal}>
|
||||
<FormattedMessage
|
||||
id='account.familiar_followers.more'
|
||||
defaultMessage='{count, plural, one {# other} other {# others}} you follow'
|
||||
values={{ count: familiarFollowerIds.size - familiarFollowers.size }}
|
||||
values={{ count: familiarFollowerIds.length - familiarFollowers.length }}
|
||||
/>
|
||||
</span>,
|
||||
);
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
Map as ImmutableMap,
|
||||
OrderedSet as ImmutableOrderedSet,
|
||||
Record as ImmutableRecord,
|
||||
} from 'immutable';
|
||||
import { create } from 'mutative';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import {
|
||||
|
@ -44,95 +40,123 @@ import {
|
|||
FAVOURITES_EXPAND_SUCCESS,
|
||||
DISLIKES_FETCH_SUCCESS,
|
||||
REACTIONS_FETCH_SUCCESS,
|
||||
InteractionsAction,
|
||||
} from 'pl-fe/actions/interactions';
|
||||
import { NOTIFICATIONS_UPDATE } from 'pl-fe/actions/notifications';
|
||||
|
||||
import type { Account, Notification, PaginatedResponse } from 'pl-api';
|
||||
import type { Account, EmojiReaction, Notification, PaginatedResponse } from 'pl-api';
|
||||
import type { APIEntity } from 'pl-fe/types/entities';
|
||||
|
||||
const ListRecord = ImmutableRecord({
|
||||
next: null as (() => Promise<PaginatedResponse<Account>>) | null,
|
||||
items: ImmutableOrderedSet<string>(),
|
||||
isLoading: false,
|
||||
});
|
||||
interface List {
|
||||
next: (() => Promise<PaginatedResponse<Account>>) | null;
|
||||
items: Array<string>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const ReactionRecord = ImmutableRecord({
|
||||
accounts: ImmutableOrderedSet<string>(),
|
||||
count: 0,
|
||||
name: '',
|
||||
url: null as string | null,
|
||||
});
|
||||
interface Reaction {
|
||||
accounts: Array<string>;
|
||||
count: number;
|
||||
name: string;
|
||||
url: string | null;
|
||||
}
|
||||
|
||||
const ReactionListRecord = ImmutableRecord({
|
||||
next: null as (() => Promise<PaginatedResponse<Reaction>>) | null,
|
||||
items: ImmutableOrderedSet<Reaction>(),
|
||||
isLoading: false,
|
||||
});
|
||||
interface ReactionList {
|
||||
next: (() => Promise<PaginatedResponse<Reaction>>) | null;
|
||||
items: Array<Reaction>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const ParticipationRequestRecord = ImmutableRecord({
|
||||
account: '',
|
||||
participation_message: null as string | null,
|
||||
});
|
||||
interface ParticipationRequest {
|
||||
account: string;
|
||||
participation_message: string | null;
|
||||
}
|
||||
|
||||
const ParticipationRequestListRecord = ImmutableRecord({
|
||||
next: null as (() => Promise<PaginatedResponse<any>>) | null,
|
||||
items: ImmutableOrderedSet<ParticipationRequest>(),
|
||||
isLoading: false,
|
||||
});
|
||||
interface ParticipationRequestList {
|
||||
next: (() => Promise<PaginatedResponse<any>>) | null;
|
||||
items: Array<ParticipationRequest>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const ReducerRecord = ImmutableRecord({
|
||||
followers: ImmutableMap<string, List>(),
|
||||
following: ImmutableMap<string, List>(),
|
||||
reblogged_by: ImmutableMap<string, List>(),
|
||||
favourited_by: ImmutableMap<string, List>(),
|
||||
disliked_by: ImmutableMap<string, List>(),
|
||||
reactions: ImmutableMap<string, ReactionList>(),
|
||||
follow_requests: ListRecord(),
|
||||
mutes: ListRecord(),
|
||||
directory: ListRecord({ isLoading: true }),
|
||||
pinned: ImmutableMap<string, List>(),
|
||||
birthday_reminders: ImmutableMap<string, List>(),
|
||||
familiar_followers: ImmutableMap<string, List>(),
|
||||
event_participations: ImmutableMap<string, List>(),
|
||||
event_participation_requests: ImmutableMap<string, ParticipationRequestList>(),
|
||||
membership_requests: ImmutableMap<string, List>(),
|
||||
group_blocks: ImmutableMap<string, List>(),
|
||||
});
|
||||
type ListKey = 'follow_requests' | 'directory';
|
||||
type NestedListKey = 'reblogged_by' | 'favourited_by' | 'disliked_by' | 'pinned' | 'birthday_reminders' | 'familiar_followers' | 'event_participations' | 'membership_requests' | 'group_blocks';
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
type List = ReturnType<typeof ListRecord>;
|
||||
type Reaction = ReturnType<typeof ReactionRecord>;
|
||||
type ReactionList = ReturnType<typeof ReactionListRecord>;
|
||||
type ParticipationRequest = ReturnType<typeof ParticipationRequestRecord>;
|
||||
type ParticipationRequestList = ReturnType<typeof ParticipationRequestListRecord>;
|
||||
type Items = ImmutableOrderedSet<string>;
|
||||
type NestedListPath = ['followers' | 'following' | 'reblogged_by' | 'favourited_by' | 'disliked_by' | 'reactions' | 'pinned' | 'birthday_reminders' | 'familiar_followers' | 'event_participations' | 'event_participation_requests' | 'membership_requests' | 'group_blocks', string];
|
||||
type ListPath = ['follow_requests' | 'mutes' | 'directory'];
|
||||
type State = Record<ListKey, List> & Record<NestedListKey, Record<string, List>> & {
|
||||
reactions: Record<string, ReactionList>;
|
||||
event_participation_requests: Record<string, ParticipationRequestList>;
|
||||
};
|
||||
|
||||
const normalizeList = (state: State, path: NestedListPath | ListPath, accounts: Array<Pick<Account, 'id'>>, next?: (() => any) | null) =>
|
||||
state.setIn(path, ListRecord({
|
||||
next,
|
||||
items: ImmutableOrderedSet(accounts.map(item => item.id)),
|
||||
}));
|
||||
const initialState: State = {
|
||||
reblogged_by: {},
|
||||
favourited_by: {},
|
||||
disliked_by: {},
|
||||
reactions: {},
|
||||
follow_requests: { next: null, items: [], isLoading: false },
|
||||
directory: { next: null, items: [], isLoading: true },
|
||||
pinned: {},
|
||||
birthday_reminders: {},
|
||||
familiar_followers: {},
|
||||
event_participations: {},
|
||||
event_participation_requests: {},
|
||||
membership_requests: {},
|
||||
group_blocks: {},
|
||||
};
|
||||
|
||||
const appendToList = (state: State, path: NestedListPath | ListPath, accounts: Array<Pick<Account, 'id'>>, next: (() => any) | null) =>
|
||||
state.updateIn(path, map => (map as List)
|
||||
.set('next', next)
|
||||
.set('isLoading', false)
|
||||
.update('items', list => (list as Items).concat(accounts.map(item => item.id))),
|
||||
);
|
||||
type NestedListPath = [NestedListKey, string];
|
||||
type ListPath = [ListKey];
|
||||
|
||||
const normalizeList = (state: State, path: NestedListPath | ListPath, accounts: Array<Pick<Account, 'id'>>, next: (() => Promise<PaginatedResponse<any>>) | null = null) =>
|
||||
create(state, (draft) => {
|
||||
let list: List;
|
||||
|
||||
if (path.length === 1) {
|
||||
list = draft[path[0]];
|
||||
} else {
|
||||
list = draft[path[0]][path[1]];
|
||||
}
|
||||
|
||||
const newList = { ...list, next, items: accounts.map(item => item.id), isLoading: false };
|
||||
|
||||
if (path.length === 1) {
|
||||
draft[path[0]] = newList;
|
||||
} else {
|
||||
draft[path[0]][path[1]] = newList;
|
||||
}
|
||||
});
|
||||
|
||||
const appendToList = (state: State, path: NestedListPath | ListPath, accounts: Array<Pick<Account, 'id'>>, next: (() => any) | null = null) =>
|
||||
create(state, (draft) => {
|
||||
let list: List;
|
||||
|
||||
if (path.length === 1) {
|
||||
list = draft[path[0]];
|
||||
} else {
|
||||
list = draft[path[0]][path[1]];
|
||||
}
|
||||
|
||||
list.next = next;
|
||||
list.isLoading = false;
|
||||
list.items = [...new Set([...list.items, ...accounts.map(item => item.id)])];
|
||||
});
|
||||
|
||||
const removeFromList = (state: State, path: NestedListPath | ListPath, accountId: string) =>
|
||||
state.updateIn(path, map =>
|
||||
(map as List).update('items', list => (list as Items).filterNot(item => item === accountId)),
|
||||
);
|
||||
create(state, (draft) => {
|
||||
let list: List;
|
||||
|
||||
if (path.length === 1) {
|
||||
list = draft[path[0]];
|
||||
} else {
|
||||
list = draft[path[0]][path[1]];
|
||||
}
|
||||
|
||||
list.items = list.items.filter(item => item !== accountId);
|
||||
});
|
||||
|
||||
const normalizeFollowRequest = (state: State, notification: Notification) =>
|
||||
state.updateIn(['follow_requests', 'items'], list =>
|
||||
ImmutableOrderedSet([notification.account.id]).union(list as Items),
|
||||
);
|
||||
create(state, (draft) => {
|
||||
draft.follow_requests.items = [...new Set([notification.account.id, ...draft.follow_requests.items])];
|
||||
});
|
||||
|
||||
const userLists = (state = ReducerRecord(), action: DirectoryAction | AnyAction) => {
|
||||
const userLists = (state = initialState, action: DirectoryAction | InteractionsAction | AnyAction): State => {
|
||||
switch (action.type) {
|
||||
case REBLOGS_FETCH_SUCCESS:
|
||||
return normalizeList(state, ['reblogged_by', action.statusId], action.accounts, action.next);
|
||||
|
@ -145,12 +169,13 @@ const userLists = (state = ReducerRecord(), action: DirectoryAction | AnyAction)
|
|||
case DISLIKES_FETCH_SUCCESS:
|
||||
return normalizeList(state, ['disliked_by', action.statusId], action.accounts);
|
||||
case REACTIONS_FETCH_SUCCESS:
|
||||
return state.setIn(['reactions', action.statusId], ReactionListRecord({
|
||||
items: ImmutableOrderedSet<Reaction>(action.reactions.map(({ accounts, ...reaction }: APIEntity) => ReactionRecord({
|
||||
...reaction,
|
||||
accounts: ImmutableOrderedSet(accounts.map((account: APIEntity) => account.id)),
|
||||
}))),
|
||||
}));
|
||||
return create(state, (draft) => {
|
||||
draft.reactions[action.statusId] = {
|
||||
items: action.reactions.map((reaction: EmojiReaction) => ({ ...reaction, accounts: reaction.accounts.map(({ id }) => id) })),
|
||||
next: null,
|
||||
isLoading: false,
|
||||
};
|
||||
});
|
||||
case NOTIFICATIONS_UPDATE:
|
||||
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
|
||||
case FOLLOW_REQUESTS_FETCH_SUCCESS:
|
||||
|
@ -166,10 +191,14 @@ const userLists = (state = ReducerRecord(), action: DirectoryAction | AnyAction)
|
|||
return appendToList(state, ['directory'], action.accounts, null);
|
||||
case DIRECTORY_FETCH_REQUEST:
|
||||
case DIRECTORY_EXPAND_REQUEST:
|
||||
return state.setIn(['directory', 'isLoading'], true);
|
||||
return create(state, (draft) => {
|
||||
draft.directory.isLoading = true;
|
||||
});
|
||||
case DIRECTORY_FETCH_FAIL:
|
||||
case DIRECTORY_EXPAND_FAIL:
|
||||
return state.setIn(['directory', 'isLoading'], false);
|
||||
return create(state, (draft) => {
|
||||
draft.directory.isLoading = false;
|
||||
});
|
||||
case PINNED_ACCOUNTS_FETCH_SUCCESS:
|
||||
return normalizeList(state, ['pinned', action.accountId], action.accounts, action.next);
|
||||
case BIRTHDAY_REMINDERS_FETCH_SUCCESS:
|
||||
|
@ -181,43 +210,60 @@ const userLists = (state = ReducerRecord(), action: DirectoryAction | AnyAction)
|
|||
case EVENT_PARTICIPATIONS_EXPAND_SUCCESS:
|
||||
return appendToList(state, ['event_participations', action.statusId], action.accounts, action.next);
|
||||
case EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS:
|
||||
return state.setIn(['event_participation_requests', action.statusId], ParticipationRequestListRecord({
|
||||
next: action.next,
|
||||
items: ImmutableOrderedSet(action.participations.map(({ account, participation_message }: APIEntity) => ParticipationRequestRecord({
|
||||
account: account.id,
|
||||
participation_message,
|
||||
}))),
|
||||
}));
|
||||
case EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS:
|
||||
return state.updateIn(
|
||||
['event_participation_requests', action.statusId, 'items'],
|
||||
(items) => (items as ImmutableOrderedSet<ParticipationRequest>)
|
||||
.union(action.participations.map(({ account, participation_message }: APIEntity) => ParticipationRequestRecord({
|
||||
return create(state, (draft) => {
|
||||
draft.event_participation_requests[action.statusId] = {
|
||||
next: action.next,
|
||||
items: action.participations.map(({ account, participation_message }: APIEntity) => ({
|
||||
account: account.id,
|
||||
participation_message,
|
||||
}))),
|
||||
);
|
||||
})),
|
||||
isLoading: false,
|
||||
};
|
||||
});
|
||||
case EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS:
|
||||
return create(state, (draft) => {
|
||||
const list = draft.event_participation_requests[action.statusId];
|
||||
list.next = action.next;
|
||||
list.items = [...list.items, ...action.participations.map(({ account, participation_message }: APIEntity) => ({
|
||||
account: account.id,
|
||||
participation_message,
|
||||
}))];
|
||||
list.isLoading = false;
|
||||
});
|
||||
case EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS:
|
||||
case EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS:
|
||||
return state.updateIn(
|
||||
['event_participation_requests', action.statusId, 'items'],
|
||||
items => (items as ImmutableOrderedSet<ParticipationRequest>).filter(({ account }) => account !== action.accountId),
|
||||
);
|
||||
return create(state, (draft) => {
|
||||
const list = draft.event_participation_requests[action.statusId];
|
||||
if (list.items) list.items = list.items.filter(item => item !== action.accountId);
|
||||
});
|
||||
case GROUP_BLOCKS_FETCH_SUCCESS:
|
||||
return normalizeList(state, ['group_blocks', action.groupId], action.accounts, action.next);
|
||||
case GROUP_BLOCKS_FETCH_REQUEST:
|
||||
return state.setIn(['group_blocks', action.groupId, 'isLoading'], true);
|
||||
return create(state, (draft) => {
|
||||
draft.group_blocks[action.groupId] = {
|
||||
items: [],
|
||||
next: null,
|
||||
isLoading: true,
|
||||
};
|
||||
});
|
||||
case GROUP_BLOCKS_FETCH_FAIL:
|
||||
return state.setIn(['group_blocks', action.groupId, 'isLoading'], false);
|
||||
return create(state, (draft) => {
|
||||
draft.group_blocks[action.groupId] = {
|
||||
items: [],
|
||||
next: null,
|
||||
isLoading: false,
|
||||
};
|
||||
});
|
||||
case GROUP_UNBLOCK_SUCCESS:
|
||||
return state.updateIn(['group_blocks', action.groupId, 'items'], list => (list as ImmutableOrderedSet<string>).filterNot(item => item === action.accountId));
|
||||
return create(state, (draft) => {
|
||||
const list = draft.group_blocks[action.groupId];
|
||||
if (list.items) list.items = list.items.filter(item => item !== action.accountId);
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
ListRecord,
|
||||
ReducerRecord,
|
||||
userLists as default,
|
||||
};
|
||||
|
|
|
@ -51,6 +51,8 @@ const makeGetAccount = () => createSelector([
|
|||
};
|
||||
});
|
||||
|
||||
type SelectedAccount = Exclude<ReturnType<ReturnType<typeof makeGetAccount>>, null>;
|
||||
|
||||
const toServerSideType = (columnType: string): Filter['context'][0] => {
|
||||
switch (columnType) {
|
||||
case 'home':
|
||||
|
@ -277,11 +279,12 @@ const makeGetOtherAccounts = () => createSelector([
|
|||
getAuthUserIds,
|
||||
(state: RootState) => state.me,
|
||||
], (accounts, authUserIds, me) =>
|
||||
authUserIds.reduce((list: ImmutableList<any>, id: string) => {
|
||||
authUserIds.reduce<Array<Account>>((list, id) => {
|
||||
if (id === me) return list;
|
||||
const account = accounts?.[id];
|
||||
return account ? list.push(account) : list;
|
||||
}, ImmutableList()),
|
||||
if (account) list.push(account);
|
||||
return list;
|
||||
}, []),
|
||||
);
|
||||
|
||||
const getSimplePolicy = createSelector([
|
||||
|
@ -353,8 +356,10 @@ const makeGetStatusIds = () => createSelector([
|
|||
export {
|
||||
type RemoteInstance,
|
||||
selectAccount,
|
||||
selectAccounts,
|
||||
selectOwnAccount,
|
||||
makeGetAccount,
|
||||
type SelectedAccount,
|
||||
getFilters,
|
||||
regexFromFilters,
|
||||
makeGetStatus,
|
||||
|
|
Loading…
Reference in a new issue