diff --git a/web/chat/chat-thread-list-search.react.js b/web/chat/chat-thread-list-search.react.js new file mode 100644 --- /dev/null +++ b/web/chat/chat-thread-list-search.react.js @@ -0,0 +1,29 @@ +// @flow + +import invariant from 'invariant'; +import * as React from 'react'; + +import { ThreadListContext } from './thread-list-provider.js'; +import Search from '../components/search.react.js'; + +function ChatThreadListSearch(): React.Node { + const threadListContext = React.useContext(ThreadListContext); + invariant( + threadListContext, + 'threadListContext should be set in ChatThreadListSearch', + ); + const { setSearchText, searchText } = threadListContext; + + return React.useMemo( + () => ( + + ), + [searchText, setSearchText], + ); +} + +export default ChatThreadListSearch; diff --git a/web/chat/chat-thread-list.css b/web/chat/chat-thread-list.css --- a/web/chat/chat-thread-list.css +++ b/web/chat/chat-thread-list.css @@ -15,9 +15,6 @@ top: -7px; left: 30px; } -.thread:first-child { - padding-top: 6px; -} .activeThread, .threadListSidebar:hover { @@ -265,6 +262,7 @@ display: flex; flex-direction: column; overflow: auto; + flex: 1; } div.createNewThread { diff --git a/web/chat/chat-thread-list.react.js b/web/chat/chat-thread-list.react.js --- a/web/chat/chat-thread-list.react.js +++ b/web/chat/chat-thread-list.react.js @@ -1,27 +1,62 @@ // @flow import invariant from 'invariant'; +import _sum from 'lodash/fp/sum.js'; import * as React from 'react'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { VariableSizeList } from 'react-window'; +import type { ChatThreadItem } from 'lib/selectors/chat-selectors.js'; import { emptyItemText } from 'lib/shared/thread-utils.js'; import ChatThreadListItem from './chat-thread-list-item.react.js'; +import ChatThreadListSearch from './chat-thread-list-search.react.js'; import css from './chat-thread-list.css'; import { ThreadListContext } from './thread-list-provider.js'; import BackgroundIllustration from '../assets/background-illustration.react.js'; import Button from '../components/button.react.js'; -import Search from '../components/search.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useOnClickNewThread } from '../selectors/thread-selectors.js'; +type Item = ChatThreadItem | { +type: 'search' } | { +type: 'empty' }; + +const sizes = { + search: 68, + empty: 249, + thread: 81, + sidebars: { sidebar: 32, seeMore: 22, spacer: 6 }, +}; + +const itemKey = (index: number, data: $ReadOnlyArray) => { + if (data[index].type === 'search') { + return 'search'; + } else if (data[index].type === 'empty') { + return 'empty'; + } else { + return data[index].threadInfo.id; + } +}; + +const renderItem = ({ index, data, style }) => { + let item; + if (data[index].type === 'search') { + item = ; + } else if (data[index].type === 'empty') { + item = ; + } else { + item = ; + } + + return
{item}
; +}; + function ChatThreadList(): React.Node { const threadListContext = React.useContext(ThreadListContext); invariant( threadListContext, 'threadListContext should be set in ChatThreadList', ); - const { activeTab, threadList, setSearchText, searchText } = - threadListContext; + const { activeTab, threadList } = threadListContext; const onClickNewThread = useOnClickNewThread(); @@ -33,31 +68,66 @@ const communityID = useSelector(state => state.communityPickerStore.chat); - const threadComponents: React.Node[] = React.useMemo(() => { - const threads = threadList - .filter( + const threadListContainerRef = React.useRef(); + + const threads = React.useMemo( + () => + threadList.filter( item => !communityID || item.threadInfo.community === communityID || item.threadInfo.id === communityID, - ) - .map(item => ); - if (threads.length === 0 && isBackground) { - threads.push(); + ), + [communityID, threadList], + ); + + React.useEffect(() => { + if (threadListContainerRef.current) { + threadListContainerRef.current.resetAfterIndex(0, false); } - return threads; - }, [threadList, isBackground, communityID]); + }, [threads]); + + const threadListContainer = React.useMemo(() => { + const items: Item[] = [{ type: 'search' }, ...threads]; + + if (isBackground && threads.length === 0) { + items.push({ type: 'empty' }); + } + + const itemSize = index => { + if (items[index].type === 'search') { + return sizes.search; + } else if (items[index].type === 'empty') { + return sizes.empty; + } + + const sidebarHeight = _sum( + items[index].sidebars.map(s => sizes.sidebars[s.type]), + ); + return sizes.thread + sidebarHeight; + }; + + return ( + + {({ height }) => ( + + {renderItem} + + )} + + ); + }, [isBackground, threads]); return ( <> -
- -
{threadComponents}
-
+
{threadListContainer}