ChatList: use Virtuoso
This commit is contained in:
parent
712b4c5a42
commit
05068460e0
4 changed files with 51 additions and 41 deletions
|
@ -61,14 +61,14 @@ export function fetchChatsV2() {
|
||||||
export function fetchChats() {
|
export function fetchChats() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const instance = state.get('instance');
|
const { instance } = state;
|
||||||
const features = getFeatures(instance);
|
const features = getFeatures(instance);
|
||||||
|
|
||||||
dispatch({ type: CHATS_FETCH_REQUEST });
|
dispatch({ type: CHATS_FETCH_REQUEST });
|
||||||
if (features.chatsV2) {
|
if (features.chatsV2) {
|
||||||
dispatch(fetchChatsV2());
|
return dispatch(fetchChatsV2());
|
||||||
} else {
|
} else {
|
||||||
dispatch(fetchChatsV1());
|
return dispatch(fetchChatsV1());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
import { debounce } from 'lodash';
|
import React, { useCallback } from 'react';
|
||||||
import React from 'react';
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { expandChats } from 'soapbox/actions/chats';
|
import { fetchChats, expandChats } from 'soapbox/actions/chats';
|
||||||
import ScrollableList from 'soapbox/components/scrollable_list';
|
import PullToRefresh from 'soapbox/components/pull-to-refresh';
|
||||||
|
import { Text } from 'soapbox/components/ui';
|
||||||
import PlaceholderChat from 'soapbox/features/placeholder/components/placeholder_chat';
|
import PlaceholderChat from 'soapbox/features/placeholder/components/placeholder_chat';
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
@ -16,10 +17,6 @@ const messages = defineMessages({
|
||||||
emptyMessage: { id: 'chat_panels.main_window.empty', defaultMessage: 'No chats found. To start a chat, visit a user\'s profile' },
|
emptyMessage: { id: 'chat_panels.main_window.empty', defaultMessage: 'No chats found. To start a chat, visit a user\'s profile' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleLoadMore = debounce((dispatch) => {
|
|
||||||
dispatch(expandChats());
|
|
||||||
}, 300, { leading: true });
|
|
||||||
|
|
||||||
const getSortedChatIds = (chats: ImmutableMap<string, any>) => (
|
const getSortedChatIds = (chats: ImmutableMap<string, any>) => (
|
||||||
chats
|
chats
|
||||||
.toList()
|
.toList()
|
||||||
|
@ -45,10 +42,10 @@ const sortedChatIdsSelector = createSelector(
|
||||||
|
|
||||||
interface IChatList {
|
interface IChatList {
|
||||||
onClickChat: (chat: any) => void,
|
onClickChat: (chat: any) => void,
|
||||||
onRefresh: () => void,
|
useWindowScroll?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatList: React.FC<IChatList> = ({ onClickChat, onRefresh }) => {
|
const ChatList: React.FC<IChatList> = ({ onClickChat, useWindowScroll = false }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
@ -56,28 +53,41 @@ const ChatList: React.FC<IChatList> = ({ onClickChat, onRefresh }) => {
|
||||||
const hasMore = useAppSelector(state => !!state.chats.get('next'));
|
const hasMore = useAppSelector(state => !!state.chats.get('next'));
|
||||||
const isLoading = useAppSelector(state => state.chats.get('isLoading'));
|
const isLoading = useAppSelector(state => state.chats.get('isLoading'));
|
||||||
|
|
||||||
|
const handleLoadMore = useCallback(() => {
|
||||||
|
if (hasMore && !isLoading) {
|
||||||
|
dispatch(expandChats());
|
||||||
|
}
|
||||||
|
}, [dispatch, hasMore, isLoading]);
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
return dispatch(fetchChats()) as any;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollableList
|
<PullToRefresh onRefresh={handleRefresh}>
|
||||||
|
<Virtuoso
|
||||||
className='chat-list'
|
className='chat-list'
|
||||||
scrollKey='awaiting-approval'
|
useWindowScroll={useWindowScroll}
|
||||||
emptyMessage={intl.formatMessage(messages.emptyMessage)}
|
data={chatIds.toArray()}
|
||||||
hasMore={hasMore}
|
endReached={handleLoadMore}
|
||||||
isLoading={isLoading}
|
itemContent={(_index, chatId) => (
|
||||||
showLoading={isLoading && chatIds.size === 0}
|
<Chat chatId={chatId} onClick={onClickChat} />
|
||||||
onLoadMore={() => handleLoadMore(dispatch)}
|
)}
|
||||||
onRefresh={onRefresh}
|
components={{
|
||||||
placeholderComponent={PlaceholderChat}
|
ScrollSeekPlaceholder: () => <PlaceholderChat />,
|
||||||
placeholderCount={20}
|
Footer: () => hasMore ? <PlaceholderChat /> : null,
|
||||||
>
|
EmptyPlaceholder: () => {
|
||||||
{chatIds.map((chatId: string) => (
|
if (isLoading) {
|
||||||
<div key={chatId} className='chat-list-item'>
|
return (
|
||||||
<Chat
|
<>{Array(20).fill(<PlaceholderChat />)}</>
|
||||||
chatId={chatId}
|
);
|
||||||
onClick={onClickChat}
|
} else {
|
||||||
|
return <Text>{intl.formatMessage(messages.emptyMessage)}</Text>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</PullToRefresh>
|
||||||
))}
|
|
||||||
</ScrollableList>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import { fetchChats, launchChat } from 'soapbox/actions/chats';
|
import { launchChat } from 'soapbox/actions/chats';
|
||||||
import AccountSearch from 'soapbox/components/account_search';
|
import AccountSearch from 'soapbox/components/account_search';
|
||||||
import AudioToggle from 'soapbox/features/chats/components/audio_toggle';
|
import AudioToggle from 'soapbox/features/chats/components/audio_toggle';
|
||||||
|
|
||||||
|
@ -29,10 +29,6 @@ const ChatIndex: React.FC = () => {
|
||||||
history.push(`/chats/${chat.id}`);
|
history.push(`/chats/${chat.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = () => {
|
|
||||||
return dispatch(fetchChats());
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.title)}>
|
<Column label={intl.formatMessage(messages.title)}>
|
||||||
<div className='column__switch'>
|
<div className='column__switch'>
|
||||||
|
@ -46,7 +42,7 @@ const ChatIndex: React.FC = () => {
|
||||||
|
|
||||||
<ChatList
|
<ChatList
|
||||||
onClickChat={handleClickChat}
|
onClickChat={handleClickChat}
|
||||||
onRefresh={handleRefresh}
|
useWindowScroll
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -90,12 +90,16 @@
|
||||||
&__content {
|
&__content {
|
||||||
@apply flex flex-1 flex-col overflow-hidden bg-white dark:bg-slate-800;
|
@apply flex flex-1 flex-col overflow-hidden bg-white dark:bg-slate-800;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
@apply max-h-full;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-box {
|
.chat-box {
|
||||||
@apply flex flex-1 flex-col overflow-hidden;
|
@apply flex flex-1 flex-col overflow-hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-list {
|
.chat-list {
|
||||||
@apply overflow-y-auto;
|
@apply overflow-y-auto max-h-full;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue