Changeset View
Changeset View
Standalone View
Standalone View
web/chat/chat-message-list-container.react.js
// @flow | // @flow | ||||
import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
import invariant from 'invariant'; | import invariant from 'invariant'; | ||||
import _isEqual from 'lodash/fp/isEqual'; | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { useDrop } from 'react-dnd'; | import { useDrop } from 'react-dnd'; | ||||
import { NativeTypes } from 'react-dnd-html5-backend'; | import { NativeTypes } from 'react-dnd-html5-backend'; | ||||
import { useDispatch } from 'react-redux'; | import { useDispatch } from 'react-redux'; | ||||
import { threadInfoSelector } from 'lib/selectors/thread-selectors'; | import { threadInfoSelector } from 'lib/selectors/thread-selectors'; | ||||
import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors'; | import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors'; | ||||
import { | import { | ||||
useWatchThread, | useWatchThread, | ||||
useExistingThreadInfoFinder, | useExistingThreadInfoFinder, | ||||
createPendingThread, | createPendingThread, | ||||
threadIsPending, | threadIsPending, | ||||
} from 'lib/shared/thread-utils'; | } from 'lib/shared/thread-utils'; | ||||
import { threadTypes } from 'lib/types/thread-types'; | import { threadTypes } from 'lib/types/thread-types'; | ||||
import type { AccountUserInfo } from 'lib/types/user-types'; | |||||
import { InputStateContext } from '../input/input-state'; | import { InputStateContext } from '../input/input-state'; | ||||
import { updateNavInfoActionType } from '../redux/action-types'; | import { updateNavInfoActionType } from '../redux/action-types'; | ||||
import { useSelector } from '../redux/redux-utils'; | import { useSelector } from '../redux/redux-utils'; | ||||
import ChatInputBar from './chat-input-bar.react'; | import ChatInputBar from './chat-input-bar.react'; | ||||
import css from './chat-message-list-container.css'; | import css from './chat-message-list-container.css'; | ||||
import ChatMessageList from './chat-message-list.react'; | import ChatMessageList from './chat-message-list.react'; | ||||
import ChatThreadComposer from './chat-thread-composer.react'; | import ChatThreadComposer from './chat-thread-composer.react'; | ||||
import ThreadTopBar from './thread-top-bar.react'; | import ThreadTopBar from './thread-top-bar.react'; | ||||
type Props = { | type Props = { | ||||
+activeChatThreadID: string, | +activeChatThreadID: string, | ||||
}; | }; | ||||
function ChatMessageListContainer(props: Props): React.Node { | function ChatMessageListContainer(props: Props): React.Node { | ||||
const { activeChatThreadID } = props; | const { activeChatThreadID } = props; | ||||
const isChatCreation = | const isChatCreation = | ||||
useSelector(state => state.navInfo.chatMode) === 'create'; | useSelector(state => state.navInfo.chatMode) === 'create'; | ||||
const selectedUserIDs = useSelector(state => state.navInfo.selectedUserList); | const selectedUserIDs = useSelector( | ||||
state => state.navInfo.selectedUserList ?? [], | |||||
); | |||||
const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); | const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); | ||||
const userInfoInputArray: $ReadOnlyArray<AccountUserInfo> = React.useMemo( | const [userInfoInputArray, setUserInfoInputArray] = React.useState(() => | ||||
() => selectedUserIDs?.map(id => otherUserInfos[id]).filter(Boolean) ?? [], | selectedUserIDs.map(id => otherUserInfos[id]).filter(Boolean), | ||||
[otherUserInfos, selectedUserIDs], | |||||
); | ); | ||||
React.useEffect(() => { | |||||
if (!isChatCreation) { | |||||
setUserInfoInputArray([]); | |||||
} | |||||
}, [isChatCreation]); | |||||
const dispatch = useDispatch(); | |||||
React.useEffect(() => { | |||||
if (isChatCreation) { | |||||
dispatch({ | |||||
type: updateNavInfoActionType, | |||||
payload: { | |||||
selectedUserList: userInfoInputArray.map(user => user.id), | |||||
}, | |||||
}); | |||||
} | |||||
}, [dispatch, isChatCreation, userInfoInputArray]); | |||||
const viewerID = useSelector(state => state.currentUserInfo?.id); | const viewerID = useSelector(state => state.currentUserInfo?.id); | ||||
invariant(viewerID, 'should be set'); | invariant(viewerID, 'should be set'); | ||||
const pendingPrivateThread = React.useRef( | const pendingPrivateThread = React.useRef( | ||||
createPendingThread({ | createPendingThread({ | ||||
viewerID, | viewerID, | ||||
threadType: threadTypes.PRIVATE, | threadType: threadTypes.PRIVATE, | ||||
}), | }), | ||||
▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | const threadInfo = React.useMemo(() => { | ||||
existingThreadInfoFinder, | existingThreadInfoFinder, | ||||
existingThreadInfoFinderForCreatingThread, | existingThreadInfoFinderForCreatingThread, | ||||
isChatCreation, | isChatCreation, | ||||
userInfoInputArray, | userInfoInputArray, | ||||
pendingNewThread, | pendingNewThread, | ||||
]); | ]); | ||||
invariant(threadInfo, 'ThreadInfo should be set'); | invariant(threadInfo, 'ThreadInfo should be set'); | ||||
const dispatch = useDispatch(); | |||||
// The effect removes members from list in navInfo | |||||
// if some of the user IDs don't exist in redux store | |||||
React.useEffect(() => { | |||||
if (!isChatCreation) { | |||||
return; | |||||
} | |||||
const existingSelectedUsersSet = new Set( | |||||
userInfoInputArray.map(userInfo => userInfo.id), | |||||
); | |||||
if ( | |||||
selectedUserIDs?.length !== existingSelectedUsersSet.size || | |||||
!_isEqual(new Set(selectedUserIDs), existingSelectedUsersSet) | |||||
) { | |||||
dispatch({ | |||||
type: updateNavInfoActionType, | |||||
payload: { | |||||
selectedUserList: Array.from(existingSelectedUsersSet), | |||||
}, | |||||
}); | |||||
} | |||||
}, [ | |||||
dispatch, | |||||
isChatCreation, | |||||
otherUserInfos, | |||||
selectedUserIDs, | |||||
userInfoInputArray, | |||||
]); | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
if (isChatCreation && activeChatThreadID !== threadInfo?.id) { | if (isChatCreation && activeChatThreadID !== threadInfo?.id) { | ||||
let payload = { | let payload = { | ||||
activeChatThreadID: threadInfo?.id, | activeChatThreadID: threadInfo?.id, | ||||
}; | }; | ||||
if (threadIsPending(threadInfo?.id)) { | if (threadIsPending(threadInfo?.id)) { | ||||
payload = { | payload = { | ||||
...payload, | ...payload, | ||||
▲ Show 20 Lines • Show All 75 Lines • ▼ Show 20 Lines | if (!isChatCreation) { | ||||
{topBar} | {topBar} | ||||
{messageListAndInput} | {messageListAndInput} | ||||
</> | </> | ||||
); | ); | ||||
} | } | ||||
const chatUserSelection = ( | const chatUserSelection = ( | ||||
<ChatThreadComposer | <ChatThreadComposer | ||||
userInfoInputArray={userInfoInputArray} | userInfoInputArray={userInfoInputArray} | ||||
setUserInfoInputArray={setUserInfoInputArray} | |||||
otherUserInfos={otherUserInfos} | otherUserInfos={otherUserInfos} | ||||
threadID={threadInfo.id} | threadID={threadInfo.id} | ||||
inputState={inputState} | inputState={inputState} | ||||
/> | /> | ||||
); | ); | ||||
if (!userInfoInputArray.length) { | if (!userInfoInputArray.length) { | ||||
return chatUserSelection; | return chatUserSelection; | ||||
Show All 24 Lines |