diff --git a/web/chat/chat-message-list-container.css b/web/chat/chat-message-list-container.css new file mode 100644 --- /dev/null +++ b/web/chat/chat-message-list-container.css @@ -0,0 +1,12 @@ +div.container { + margin-left: 400px; + height: 100%; + background-color: var(--bg); + display: flex; + flex-direction: column; + box-sizing: border-box; +} +div.activeContainer { + border: 2px solid #5989d6; + margin-left: 402px; +} diff --git a/web/chat/chat-message-list-container.react.js b/web/chat/chat-message-list-container.react.js new file mode 100644 --- /dev/null +++ b/web/chat/chat-message-list-container.react.js @@ -0,0 +1,109 @@ +// @flow + +import classNames from 'classnames'; +import invariant from 'invariant'; +import * as React from 'react'; +import { useDrop } from 'react-dnd'; +import { NativeTypes } from 'react-dnd-html5-backend'; + +import { threadInfoSelector } from 'lib/selectors/thread-selectors'; +import { + useWatchThread, + useExistingThreadInfoFinder, +} from 'lib/shared/thread-utils'; + +import { InputStateContext } from '../input/input-state'; +import { useSelector } from '../redux/redux-utils'; +import ChatInputBar from './chat-input-bar.react'; +import css from './chat-message-list-container.css'; +import ChatMessageList from './chat-message-list.react'; +import ThreadTopBar from './thread-top-bar.react'; + +function ChatMessageListContainer(): React.Node { + const activeChatThreadID = useSelector( + state => state.navInfo.activeChatThreadID, + ); + const baseThreadInfo = useSelector(state => { + if (!activeChatThreadID) { + return null; + } + return ( + threadInfoSelector(state)[activeChatThreadID] ?? + state.navInfo.pendingThread + ); + }); + const existingThreadInfoFinder = useExistingThreadInfoFinder(baseThreadInfo); + const threadInfo = React.useMemo( + () => + existingThreadInfoFinder({ + searching: false, + userInfoInputArray: [], + }), + [existingThreadInfoFinder], + ); + invariant(threadInfo, 'ThreadInfo should be set'); + + const inputState = React.useContext(InputStateContext); + invariant(inputState, 'InputState should be set'); + const [{ isActive }, connectDropTarget] = useDrop({ + accept: NativeTypes.FILE, + drop: item => { + const { files } = item; + if (inputState && files.length > 0) { + inputState.appendFiles(files); + } + }, + collect: monitor => ({ + isActive: monitor.isOver() && monitor.canDrop(), + }), + }); + + useWatchThread(threadInfo); + + const containerStyle = classNames({ + [css.container]: true, + [css.activeContainer]: isActive, + }); + + const containerRef = React.useRef(); + + const onPaste = React.useCallback( + (e: ClipboardEvent) => { + if (!inputState) { + return; + } + const { clipboardData } = e; + if (!clipboardData) { + return; + } + const { files } = clipboardData; + if (files.length === 0) { + return; + } + e.preventDefault(); + inputState.appendFiles([...files]); + }, + [inputState], + ); + + React.useEffect(() => { + const currentContainerRef = containerRef.current; + if (!currentContainerRef) { + return; + } + currentContainerRef.addEventListener('paste', onPaste); + return () => { + currentContainerRef.removeEventListener('paste', onPaste); + }; + }, [onPaste]); + + return connectDropTarget( +
+ + + +
, + ); +} + +export default ChatMessageListContainer; diff --git a/web/chat/chat-message-list.css b/web/chat/chat-message-list.css --- a/web/chat/chat-message-list.css +++ b/web/chat/chat-message-list.css @@ -1,15 +1,3 @@ -div.container { - margin-left: 400px; - height: 100%; - background-color: var(--bg); - display: flex; - flex-direction: column; - box-sizing: border-box; -} -div.activeContainer { - border: 2px solid #5989d6; - margin-left: 402px; -} div.outerMessageContainer { position: relative; height: calc(100vh - 128px); diff --git a/web/chat/chat-message-list.react.js b/web/chat/chat-message-list.react.js --- a/web/chat/chat-message-list.react.js +++ b/web/chat/chat-message-list.react.js @@ -4,8 +4,6 @@ import { detect as detectBrowser } from 'detect-browser'; import invariant from 'invariant'; import * as React from 'react'; -import { useDrop } from 'react-dnd'; -import { NativeTypes } from 'react-dnd-html5-backend'; import { fetchMessagesBeforeCursorActionTypes, @@ -18,13 +16,8 @@ type ChatMessageItem, useMessageListData, } from 'lib/selectors/chat-selectors'; -import { threadInfoSelector } from 'lib/selectors/thread-selectors'; import { messageKey } from 'lib/shared/message-utils'; -import { - useWatchThread, - useExistingThreadInfoFinder, - threadIsPending, -} from 'lib/shared/thread-utils'; +import { threadIsPending } from 'lib/shared/thread-utils'; import type { FetchMessageInfosPayload } from 'lib/types/message-types'; import { type ThreadInfo } from 'lib/types/thread-types'; import { @@ -37,7 +30,6 @@ import LoadingIndicator from '../loading-indicator.react'; import { useTextMessageRulesFunc } from '../markdown/rules.react'; import { useSelector } from '../redux/redux-utils'; -import ChatInputBar from './chat-input-bar.react'; import css from './chat-message-list.css'; import { MessageListContext } from './message-list-types'; import Message from './message.react'; @@ -46,12 +38,15 @@ MessagePositionInfo, } from './position-types'; import RelationshipPrompt from './relationship-prompt/relationship-prompt'; -import ThreadTopBar from './thread-top-bar.react'; -type PassedProps = { +type BaseProps = { + +threadInfo: ThreadInfo, +}; + +type Props = { + ...BaseProps, // Redux state +activeChatThreadID: ?string, - +threadInfo: ?ThreadInfo, +messageListData: ?$ReadOnlyArray, +startReached: boolean, +timeZone: ?string, @@ -69,14 +64,6 @@ // withInputState +inputState: ?InputState, }; -type ReactDnDProps = { - +isActive: boolean, - +connectDropTarget: (node: React.Node) => React.Node, -}; -type Props = { - ...PassedProps, - ...ReactDnDProps, -}; type State = { +mouseOverMessagePosition: ?OnMessagePositionWithContainerInfo, }; @@ -243,73 +230,32 @@ }; render() { - const { - messageListData, - threadInfo, - inputState, - connectDropTarget, - isActive, - } = this.props; + const { messageListData, threadInfo, inputState } = this.props; if (!messageListData) { return
; } - invariant(threadInfo, 'ThreadInfo should be set if messageListData is'); invariant(inputState, 'InputState should be set'); const messages = messageListData.map(this.renderItem); - const containerStyle = classNames({ - [css.container]: true, - [css.activeContainer]: isActive, - }); let relationshipPrompt; - if (this.props.threadInfo) { - relationshipPrompt = ( - - ); + if (threadInfo) { + relationshipPrompt = ; } const messageContainerStyle = classNames({ [css.messageContainer]: true, [css.mirroredMessageContainer]: !this.props.supportsReverseFlex, }); - return connectDropTarget( -
- -
- {relationshipPrompt} -
- {messages} -
+ return ( +
+ {relationshipPrompt} +
+ {messages}
- -
, +
); } - containerRef = (container: ?HTMLDivElement) => { - if (container) { - container.addEventListener('paste', this.onPaste); - } - this.container = container; - }; - - onPaste = (e: ClipboardEvent) => { - const { inputState } = this.props; - if (!inputState) { - return; - } - const { clipboardData } = e; - if (!clipboardData) { - return; - } - const { files } = clipboardData; - if (files.length === 0) { - return; - } - e.preventDefault(); - inputState.appendFiles([...files]); - }; - messageContainerRef = (messageContainer: ?HTMLDivElement) => { this.messageContainer = messageContainer; // In case we already have all the most recent messages, @@ -379,8 +325,9 @@ registerFetchKey(fetchMessagesBeforeCursorActionTypes); registerFetchKey(fetchMostRecentMessagesActionTypes); -const ConnectedChatMessageList: React.ComponentType<{}> = React.memo<{}>( - function ConnectedChatMessageList(): React.Node { +const ConnectedChatMessageList: React.ComponentType = React.memo( + function ConnectedChatMessageList(props: BaseProps): React.Node { + const { threadInfo } = props; const userAgent = useSelector(state => state.userAgent); const supportsReverseFlex = React.useMemo(() => { const browser = detectBrowser(userAgent); @@ -393,41 +340,19 @@ const timeZone = useSelector(state => state.timeZone); - const activeChatThreadID = useSelector( - state => state.navInfo.activeChatThreadID, - ); - const baseThreadInfo = useSelector(state => { - const activeID = state.navInfo.activeChatThreadID; - if (!activeID) { - return null; - } - return threadInfoSelector(state)[activeID] ?? state.navInfo.pendingThread; - }); - const existingThreadInfoFinder = useExistingThreadInfoFinder( - baseThreadInfo, - ); - const threadInfo = React.useMemo( - () => - existingThreadInfoFinder({ - searching: false, - userInfoInputArray: [], - }), - [existingThreadInfoFinder], - ); - const messageListData = useMessageListData({ threadInfo, searching: false, userInfoInputArray: [], }); - const startReached = useSelector(state => { - const activeID = state.navInfo.activeChatThreadID; + const startReached = !!useSelector(state => { + const activeID = threadInfo.id; if (!activeID) { return null; } - if (threadIsPending(threadInfo?.id)) { + if (threadIsPending(activeID)) { return true; } @@ -445,20 +370,8 @@ const callFetchMostRecentMessages = useServerCall(fetchMostRecentMessages); const inputState = React.useContext(InputStateContext); - const [dndProps, connectDropTarget] = useDrop({ - accept: NativeTypes.FILE, - drop: item => { - const { files } = item; - if (inputState && files.length > 0) { - inputState.appendFiles(files); - } - }, - collect: monitor => ({ - isActive: monitor.isOver() && monitor.canDrop(), - }), - }); - const getTextMessageMarkdownRules = useTextMessageRulesFunc(threadInfo?.id); + const getTextMessageMarkdownRules = useTextMessageRulesFunc(threadInfo.id); const messageListContext = React.useMemo(() => { if (!getTextMessageMarkdownRules) { return undefined; @@ -466,12 +379,10 @@ return { getTextMessageMarkdownRules }; }, [getTextMessageMarkdownRules]); - useWatchThread(threadInfo); - return ( ); diff --git a/web/chat/chat.react.js b/web/chat/chat.react.js --- a/web/chat/chat.react.js +++ b/web/chat/chat.react.js @@ -2,7 +2,7 @@ import * as React from 'react'; -import ChatMessageList from './chat-message-list.react'; +import ChatMessageListContainer from './chat-message-list-container.react'; import ChatTabs from './chat-tabs.react'; import { ThreadListProvider } from './thread-list-provider'; @@ -12,7 +12,7 @@ - + ); }