From 0811af7ad7897bdd19e41e7af4d3c06aca415da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 6 Nov 2024 12:56:09 +0100 Subject: [PATCH] pl-fe: Move user lists away from immutable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/actions/directory.ts | 2 +- packages/pl-fe/src/actions/events.ts | 4 +- .../api/hooks/accounts/use-account-list.ts | 2 +- .../pl-fe/src/components/avatar-stack.tsx | 11 +- .../pl-fe/src/components/birthday-panel.tsx | 5 +- .../pl-fe/src/components/sidebar-menu.tsx | 5 +- .../src/components/sidebar-navigation.tsx | 2 +- .../tabs/manage-pending-participants.tsx | 2 +- .../features/group/group-blocked-members.tsx | 2 +- .../ui/components/modals/birthdays-modal.tsx | 2 +- .../ui/components/modals/dislikes-modal.tsx | 2 +- .../modals/event-participants-modal.tsx | 2 +- .../modals/familiar-followers-modal.tsx | 3 +- .../ui/components/modals/favourites-modal.tsx | 4 +- .../ui/components/modals/reactions-modal.tsx | 13 +- .../ui/components/modals/reblogs-modal.tsx | 4 +- .../panels/pinned-accounts-panel.tsx | 5 +- .../components/profile-familiar-followers.tsx | 13 +- packages/pl-fe/src/reducers/user-lists.ts | 260 +++++++++++------- packages/pl-fe/src/selectors/index.ts | 11 +- 20 files changed, 197 insertions(+), 157 deletions(-) diff --git a/packages/pl-fe/src/actions/directory.ts b/packages/pl-fe/src/actions/directory.ts index 58398b47d..11acd887f 100644 --- a/packages/pl-fe/src/actions/directory.ts +++ b/packages/pl-fe/src/actions/directory.ts @@ -43,7 +43,7 @@ const expandDirectory = (params: Record) => (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 })); diff --git a/packages/pl-fe/src/actions/events.ts b/packages/pl-fe/src/actions/events.ts index 3a858ab26..9e07e7f48 100644 --- a/packages/pl-fe/src/actions/events.ts +++ b/packages/pl-fe/src/actions/events.ts @@ -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); diff --git a/packages/pl-fe/src/api/hooks/accounts/use-account-list.ts b/packages/pl-fe/src/api/hooks/accounts/use-account-list.ts index a0ea24fe1..62f808def 100644 --- a/packages/pl-fe/src/api/hooks/accounts/use-account-list.ts +++ b/packages/pl-fe/src/api/hooks/accounts/use-account-list.ts @@ -28,7 +28,7 @@ const useAccountList = (listKey: string[], entityFn: EntityFn) => { getNextPageParam: (config) => config.next ? config : undefined, }); - const data = flattenPages(queryInfo.data as any)?.toReversed() || []; + const data = flattenPages(queryInfo.data as any) || []; const { relationships } = useRelationships( listKey, diff --git a/packages/pl-fe/src/components/avatar-stack.tsx b/packages/pl-fe/src/components/avatar-stack.tsx index 6cfd85065..8d1bd01e4 100644 --- a/packages/pl-fe/src/components/avatar-stack.tsx +++ b/packages/pl-fe/src/components/avatar-stack.tsx @@ -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; + accountIds: Array; limit?: number; } const AvatarStack: React.FC = ({ accountIds, limit = 3 }) => { - const accounts = useAppSelector(state => ImmutableList(accountIds.slice(0, limit).map(accountId => getAccount(state, accountId)).filter(account => account))) as ImmutableList; + const accounts = useAppSelector(state => selectAccounts(state, accountIds.slice(0, limit))); return ( diff --git a/packages/pl-fe/src/components/birthday-panel.tsx b/packages/pl-fe/src/components/birthday-panel.tsx index 1e1d579a4..68115e5db 100644 --- a/packages/pl-fe/src/components/birthday-panel.tsx +++ b/packages/pl-fe/src/components/birthday-panel.tsx @@ -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 = 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(); @@ -48,7 +47,7 @@ const BirthdayPanel = ({ limit }: IBirthdayPanel) => { }; }, []); - if (birthdaysToRender.isEmpty()) { + if (!birthdaysToRender.length) { return null; } diff --git a/packages/pl-fe/src/components/sidebar-menu.tsx b/packages/pl-fe/src/components/sidebar-menu.tsx index f2338dcc1..ca4129b89 100644 --- a/packages/pl-fe/src/components/sidebar-menu.tsx +++ b/packages/pl-fe/src/components/sidebar-menu.tsx @@ -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 = 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); diff --git a/packages/pl-fe/src/components/sidebar-navigation.tsx b/packages/pl-fe/src/components/sidebar-navigation.tsx index 7ca5c403c..c440866ed 100644 --- a/packages/pl-fe/src/components/sidebar-navigation.tsx +++ b/packages/pl-fe/src/components/sidebar-navigation.tsx @@ -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); diff --git a/packages/pl-fe/src/features/compose-event/tabs/manage-pending-participants.tsx b/packages/pl-fe/src/features/compose-event/tabs/manage-pending-participants.tsx index b38198e10..90ffeac59 100644 --- a/packages/pl-fe/src/features/compose-event/tabs/manage-pending-participants.tsx +++ b/packages/pl-fe/src/features/compose-event/tabs/manage-pending-participants.tsx @@ -70,7 +70,7 @@ interface IManagePendingParticipants { const ManagePendingParticipants: React.FC = ({ 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)); diff --git a/packages/pl-fe/src/features/group/group-blocked-members.tsx b/packages/pl-fe/src/features/group/group-blocked-members.tsx index 97037adbe..641d2981f 100644 --- a/packages/pl-fe/src/features/group/group-blocked-members.tsx +++ b/packages/pl-fe/src/features/group/group-blocked-members.tsx @@ -66,7 +66,7 @@ const GroupBlockedMembers: React.FC = ({ 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)); diff --git a/packages/pl-fe/src/features/ui/components/modals/birthdays-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/birthdays-modal.tsx index 25383ca16..654042c22 100644 --- a/packages/pl-fe/src/features/ui/components/modals/birthdays-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/birthdays-modal.tsx @@ -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'); diff --git a/packages/pl-fe/src/features/ui/components/modals/dislikes-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/dislikes-modal.tsx index 7c3809c24..f204c2dde 100644 --- a/packages/pl-fe/src/features/ui/components/modals/dislikes-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/dislikes-modal.tsx @@ -18,7 +18,7 @@ interface DislikesModalProps { const DislikesModal: React.FC = ({ 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)); diff --git a/packages/pl-fe/src/features/ui/components/modals/event-participants-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/event-participants-modal.tsx index 3389461ac..e8586a13a 100644 --- a/packages/pl-fe/src/features/ui/components/modals/event-participants-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/event-participants-modal.tsx @@ -18,7 +18,7 @@ interface EventParticipantsModalProps { const EventParticipantsModal: React.FC = ({ 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)); diff --git a/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx index ba5fd0aad..d90147511 100644 --- a/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx @@ -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 = ({ accountId, onClose }) => { const modalRef = useRef(null); const account = useAppSelector(state => getAccount(state, accountId)); - const familiarFollowerIds: ImmutableOrderedSet = 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'); diff --git a/packages/pl-fe/src/features/ui/components/modals/favourites-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/favourites-modal.tsx index ad33c3e0b..bf9de64fa 100644 --- a/packages/pl-fe/src/features/ui/components/modals/favourites-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/favourites-modal.tsx @@ -19,8 +19,8 @@ const FavouritesModal: React.FC = ({ onCl const modalRef = useRef(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)); diff --git a/packages/pl-fe/src/features/ui/components/modals/reactions-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/reactions-modal.tsx index b2ddd8702..c91d0b7ce 100644 --- a/packages/pl-fe/src/features/ui/components/modals/reactions-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/reactions-modal.tsx @@ -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 = ({ 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 = ({ onClos return ; }; - const accounts = useMemo((): ImmutableList | undefined => { + const accounts = useMemo((): Array | 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; + 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 = ({ onClos const emptyMessage = ; body = (<> - {reactions.size > 0 && renderFilterBar()} + {reactions.length > 0 && renderFilterBar()} 0, + 'mt-4': reactions.length > 0, })} itemClassName='pb-3' style={{ height: 'calc(80vh - 88px)' }} diff --git a/packages/pl-fe/src/features/ui/components/modals/reblogs-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/reblogs-modal.tsx index cd2ac31b6..45fc91a84 100644 --- a/packages/pl-fe/src/features/ui/components/modals/reblogs-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/reblogs-modal.tsx @@ -19,8 +19,8 @@ interface ReblogsModalProps { const ReblogsModal: React.FC = ({ 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(null); const fetchData = () => { diff --git a/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx index 6950fd3f6..b4b7a5582 100644 --- a/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx +++ b/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx @@ -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 = ({ account, limit }) => { const dispatch = useAppDispatch(); - const pinned = useAppSelector((state) => state.user_lists.pinned.get(account.id)?.items || ImmutableOrderedSet()).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 ( ); diff --git a/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx b/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx index 10c11e834..6730f1eaf 100644 --- a/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx +++ b/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx @@ -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 = ({ 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()); - const familiarFollowers: ImmutableOrderedSet = 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 = ({ account }); }; - if (familiarFollowerIds.size === 0) { + if (familiarFollowerIds.length === 0) { return null; } @@ -60,15 +59,15 @@ const ProfileFamiliarFollowers: React.FC = ({ account - )).toArray().filter(Boolean); + )).filter(Boolean); - if (familiarFollowerIds.size > 2) { + if (familiarFollowerIds.length > 2) { accounts.push( , ); diff --git a/packages/pl-fe/src/reducers/user-lists.ts b/packages/pl-fe/src/reducers/user-lists.ts index ecd5884d0..b27673c68 100644 --- a/packages/pl-fe/src/reducers/user-lists.ts +++ b/packages/pl-fe/src/reducers/user-lists.ts @@ -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>) | null, - items: ImmutableOrderedSet(), - isLoading: false, -}); +interface List { + next: (() => Promise>) | null; + items: Array; + isLoading: boolean; +} -const ReactionRecord = ImmutableRecord({ - accounts: ImmutableOrderedSet(), - count: 0, - name: '', - url: null as string | null, -}); +interface Reaction { + accounts: Array; + count: number; + name: string; + url: string | null; +} -const ReactionListRecord = ImmutableRecord({ - next: null as (() => Promise>) | null, - items: ImmutableOrderedSet(), - isLoading: false, -}); +interface ReactionList { + next: (() => Promise>) | null; + items: Array; + 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>) | null, - items: ImmutableOrderedSet(), - isLoading: false, -}); +interface ParticipationRequestList { + next: (() => Promise>) | null; + items: Array; + isLoading: boolean; +} -const ReducerRecord = ImmutableRecord({ - followers: ImmutableMap(), - following: ImmutableMap(), - reblogged_by: ImmutableMap(), - favourited_by: ImmutableMap(), - disliked_by: ImmutableMap(), - reactions: ImmutableMap(), - follow_requests: ListRecord(), - mutes: ListRecord(), - directory: ListRecord({ isLoading: true }), - pinned: ImmutableMap(), - birthday_reminders: ImmutableMap(), - familiar_followers: ImmutableMap(), - event_participations: ImmutableMap(), - event_participation_requests: ImmutableMap(), - membership_requests: ImmutableMap(), - group_blocks: ImmutableMap(), -}); +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; -type List = ReturnType; -type Reaction = ReturnType; -type ReactionList = ReturnType; -type ParticipationRequest = ReturnType; -type ParticipationRequestList = ReturnType; -type Items = ImmutableOrderedSet; -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 & Record> & { + reactions: Record; + event_participation_requests: Record; +}; -const normalizeList = (state: State, path: NestedListPath | ListPath, accounts: Array>, 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>, 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>, next: (() => Promise>) | 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>, 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(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) - .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).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).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, }; diff --git a/packages/pl-fe/src/selectors/index.ts b/packages/pl-fe/src/selectors/index.ts index a1ba3374d..e6c729dd0 100644 --- a/packages/pl-fe/src/selectors/index.ts +++ b/packages/pl-fe/src/selectors/index.ts @@ -51,6 +51,8 @@ const makeGetAccount = () => createSelector([ }; }); +type SelectedAccount = Exclude>, 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, id: string) => { + authUserIds.reduce>((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,