diff --git a/packages/pl-fe/src/actions/conversations.ts b/packages/pl-fe/src/actions/conversations.ts index 31c43b8e4..ae273676f 100644 --- a/packages/pl-fe/src/actions/conversations.ts +++ b/packages/pl-fe/src/actions/conversations.ts @@ -21,10 +21,15 @@ const mountConversations = () => ({ type: CONVERSATIONS_MOUNT }); const unmountConversations = () => ({ type: CONVERSATIONS_UNMOUNT }); +interface ConversationsReadAction { + type: typeof CONVERSATIONS_READ; + conversationId: string; +} + const markConversationRead = (conversationId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch({ + dispatch({ type: CONVERSATIONS_READ, conversationId, }); @@ -71,18 +76,32 @@ const expandConversationsFail = (error: unknown) => ({ error, }); +interface ConversataionsUpdateAction { + type: typeof CONVERSATIONS_UPDATE; + conversation: Conversation; +} + const updateConversations = (conversation: Conversation) => (dispatch: AppDispatch) => { dispatch(importEntities({ accounts: conversation.accounts, statuses: [conversation.last_status], })); - return dispatch({ + return dispatch({ type: CONVERSATIONS_UPDATE, conversation, }); }; +type ConversationsAction = + | ReturnType + | ReturnType + | ConversationsReadAction + | ReturnType + | ReturnType + | ReturnType + | ConversataionsUpdateAction + export { CONVERSATIONS_MOUNT, CONVERSATIONS_UNMOUNT, @@ -99,4 +118,5 @@ export { expandConversationsSuccess, expandConversationsFail, updateConversations, + type ConversationsAction, }; diff --git a/packages/pl-fe/src/features/conversations/components/conversations-list.tsx b/packages/pl-fe/src/features/conversations/components/conversations-list.tsx index a163fa40e..ac920c852 100644 --- a/packages/pl-fe/src/features/conversations/components/conversations-list.tsx +++ b/packages/pl-fe/src/features/conversations/components/conversations-list.tsx @@ -45,7 +45,7 @@ const ConversationsList: React.FC = () => { onLoadMore={handleLoadOlder} id='direct-list' isLoading={isLoading} - showLoading={isLoading && conversations.size === 0} + showLoading={isLoading && conversations.length === 0} emptyMessage={} > {conversations.map((item: any) => ( diff --git a/packages/pl-fe/src/features/notifications/index.tsx b/packages/pl-fe/src/features/notifications/index.tsx index 2af3eb865..4b682d0b8 100644 --- a/packages/pl-fe/src/features/notifications/index.tsx +++ b/packages/pl-fe/src/features/notifications/index.tsx @@ -142,7 +142,7 @@ const Notifications = () => { 'animate-pulse': notifications.size === 0, })} > - {scrollableContent as ImmutableList} + {scrollableContent!} ); diff --git a/packages/pl-fe/src/features/ui/components/modals/mentions-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/mentions-modal.tsx index 3285a1888..39347a0f5 100644 --- a/packages/pl-fe/src/features/ui/components/modals/mentions-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/mentions-modal.tsx @@ -1,4 +1,3 @@ -import { OrderedSet as ImmutableOrderedSet } from 'immutable'; import React, { useCallback, useEffect, useRef } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; @@ -24,7 +23,7 @@ const MentionsModal: React.FC = ({ onClose, const getStatus = useCallback(makeGetStatus(), []); const status = useAppSelector((state) => getStatus(state, { id: statusId })); - const accountIds = status ? ImmutableOrderedSet(status.mentions.map(m => m.id)) : null; + const accountIds = status ? status.mentions.map(m => m.id) : null; const fetchData = () => { dispatch(fetchStatusWithContext(statusId, intl)); diff --git a/packages/pl-fe/src/reducers/conversations.ts b/packages/pl-fe/src/reducers/conversations.ts index dbb15142c..4d2aa7daf 100644 --- a/packages/pl-fe/src/reducers/conversations.ts +++ b/packages/pl-fe/src/reducers/conversations.ts @@ -1,5 +1,5 @@ -import { List as ImmutableList, Record as ImmutableRecord } from 'immutable'; import pick from 'lodash/pick'; +import { create } from 'mutative'; import { CONVERSATIONS_MOUNT, @@ -9,21 +9,27 @@ import { CONVERSATIONS_FETCH_FAIL, CONVERSATIONS_UPDATE, CONVERSATIONS_READ, + type ConversationsAction, } from '../actions/conversations'; import { compareDate } from '../utils/comparators'; import type { Conversation, PaginatedResponse } from 'pl-api'; -import type { AnyAction } from 'redux'; -const ReducerRecord = ImmutableRecord({ - items: ImmutableList(), +interface State { + items: Array; + isLoading: boolean; + hasMore: boolean; + next: (() => Promise>) | null; + mounted: number; +} + +const initialState: State = { + items: [], isLoading: false, hasMore: true, - next: null as (() => Promise>) | null, + next: null, mounted: 0, -}); - -type State = ReturnType; +}; const minifyConversation = (conversation: Conversation) => ({ ...(pick(conversation, ['id', 'unread'])), @@ -34,79 +40,85 @@ const minifyConversation = (conversation: Conversation) => ({ type MinifiedConversation = ReturnType; -const updateConversation = (state: State, item: Conversation) => state.update('items', list => { - const index = list.findIndex(x => x.id === item.id); +const updateConversation = (state: State, item: Conversation) => { + const index = state.items.findIndex(x => x.id === item.id); const newItem = minifyConversation(item); if (index === -1) { - return list.unshift(newItem); + state.items = [newItem, ...state.items]; } else { - return list.set(index, newItem); + state.items[index] = newItem; } -}); - -const expandNormalizedConversations = (state: State, conversations: Conversation[], next: (() => Promise>) | null, isLoadingRecent?: boolean) => { - let items = ImmutableList(conversations.map(minifyConversation)); - - return state.withMutations(mutable => { - if (!items.isEmpty()) { - mutable.update('items', list => { - list = list.map(oldItem => { - const newItemIndex = items.findIndex(x => x.id === oldItem.id); - - if (newItemIndex === -1) { - return oldItem; - } - - const newItem = items.get(newItemIndex); - items = items.delete(newItemIndex); - - return newItem!; - }); - - list = list.concat(items); - - return list.sortBy(x => x.last_status_created_at, (a, b) => { - if (a === null || b === null) { - return -1; - } - - return compareDate(a, b); - }); - }); - } - - if (!next && !isLoadingRecent) { - mutable.set('hasMore', false); - } - - mutable.set('next', next); - mutable.set('isLoading', false); - }); }; -const conversations = (state = ReducerRecord(), action: AnyAction) => { +const expandNormalizedConversations = (state: State, conversations: Conversation[], next: (() => Promise>) | null, isLoadingRecent?: boolean) => { + let items = conversations.map(minifyConversation); + + if (items.length) { + let list = state.items.map(oldItem => { + const newItemIndex = items.findIndex(x => x.id === oldItem.id); + + if (newItemIndex === -1) { + return oldItem; + } + + const newItem = items[newItemIndex]; + items = items.filter((_, index) => index !== newItemIndex); + + return newItem!; + }); + + list = list.concat(items); + + state.items = list.toSorted((a, b) => { + if (a.last_status_created_at === null || b.last_status_created_at === null) { + return -1; + } + + return compareDate(a.last_status_created_at, b.last_status_created_at); + }); + } + + if (!next && !isLoadingRecent) { + state.hasMore = false; + } + + state.next = next; + state.isLoading = false; +}; + +const conversations = (state = initialState, action: ConversationsAction): State => { switch (action.type) { case CONVERSATIONS_FETCH_REQUEST: - return state.set('isLoading', true); + return create(state, (draft) => { + draft.isLoading = true; + }); case CONVERSATIONS_FETCH_FAIL: - return state.set('isLoading', false); + return create(state, (draft) => { + draft.isLoading = false; + }); case CONVERSATIONS_FETCH_SUCCESS: - return expandNormalizedConversations(state, action.conversations, action.next, action.isLoadingRecent); + return create(state, (draft) => expandNormalizedConversations(draft, action.conversations, action.next, action.isLoadingRecent)); case CONVERSATIONS_UPDATE: - return updateConversation(state, action.conversation); + return create(state, (draft) => updateConversation(state, action.conversation)); case CONVERSATIONS_MOUNT: - return state.update('mounted', count => count + 1); + return create(state, (draft) => { + draft.mounted += 1; + }); case CONVERSATIONS_UNMOUNT: - return state.update('mounted', count => count - 1); + return create(state, (draft) => { + draft.mounted -= 1; + }); case CONVERSATIONS_READ: - return state.update('items', list => list.map(item => { - if (item.id === action.conversationId) { - return { ...item, unread: false }; - } + return create(state, (draft) => { + state.items = state.items.map(item => { + if (item.id === action.conversationId) { + return { ...item, unread: false }; + } - return item; - })); + return item; + }); + }); default: return state; }