pl-fe: Move user lists away from immutable

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-11-06 12:56:09 +01:00
parent 563e5288fb
commit 0811af7ad7
20 changed files with 197 additions and 157 deletions

View file

@ -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 }));

View file

@ -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);

View file

@ -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,

View file

@ -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>

View file

@ -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;
}

View file

@ -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);

View file

@ -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);

View file

@ -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));

View file

@ -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));

View file

@ -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');

View file

@ -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));

View file

@ -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));

View file

@ -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');

View file

@ -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));

View file

@ -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)' }}

View file

@ -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 = () => {

View file

@ -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} />
);

View file

@ -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>,
);

View file

@ -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,
};

View file

@ -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,