Changeset View
Changeset View
Standalone View
Standalone View
web/chat/chat-thread-composer.react.js
// @flow | // @flow | ||||
import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { useDispatch } from 'react-redux'; | import { useDispatch } from 'react-redux'; | ||||
import { searchUsers } from 'lib/actions/user-actions.js'; | import { searchUsers } from 'lib/actions/user-actions.js'; | ||||
import { useModalContext } from 'lib/components/modal-provider.react.js'; | |||||
import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; | import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; | ||||
import { useENSNames } from 'lib/hooks/ens-cache.js'; | import { useENSNames } from 'lib/hooks/ens-cache.js'; | ||||
import { | import { | ||||
filterPotentialMembers, | filterPotentialMembers, | ||||
userSearchIndexForPotentialMembers, | userSearchIndexForPotentialMembers, | ||||
searchIndexFromUserInfos, | searchIndexFromUserInfos, | ||||
} from 'lib/selectors/user-selectors.js'; | } from 'lib/selectors/user-selectors.js'; | ||||
import { getPotentialMemberItems } from 'lib/shared/search-utils.js'; | import { | ||||
getPotentialMemberItems, | |||||
notFriendNotice, | |||||
} from 'lib/shared/search-utils.js'; | |||||
import { threadIsPending } from 'lib/shared/thread-utils.js'; | import { threadIsPending } from 'lib/shared/thread-utils.js'; | ||||
import type { ExistingThreadInfoFinder } from 'lib/shared/thread-utils.js'; | |||||
import type { SetState } from 'lib/types/hook-types.js'; | import type { SetState } from 'lib/types/hook-types.js'; | ||||
import type { | import type { | ||||
AccountUserInfo, | AccountUserInfo, | ||||
UserListItem, | UserListItem, | ||||
GlobalAccountUserInfo, | GlobalAccountUserInfo, | ||||
} from 'lib/types/user-types.js'; | } from 'lib/types/user-types.js'; | ||||
import { useServerCall } from 'lib/utils/action-utils.js'; | import { useServerCall } from 'lib/utils/action-utils.js'; | ||||
import css from './chat-thread-composer.css'; | import css from './chat-thread-composer.css'; | ||||
import Button from '../components/button.react.js'; | import Button from '../components/button.react.js'; | ||||
import Label from '../components/label.react.js'; | import Label from '../components/label.react.js'; | ||||
import Search from '../components/search.react.js'; | import Search from '../components/search.react.js'; | ||||
import type { InputState } from '../input/input-state.js'; | import type { InputState } from '../input/input-state.js'; | ||||
import Alert from '../modals/alert.react.js'; | |||||
import { updateNavInfoActionType } from '../redux/action-types.js'; | import { updateNavInfoActionType } from '../redux/action-types.js'; | ||||
import { useSelector } from '../redux/redux-utils.js'; | import { useSelector } from '../redux/redux-utils.js'; | ||||
type Props = { | type Props = { | ||||
+userInfoInputArray: $ReadOnlyArray<AccountUserInfo>, | +userInfoInputArray: $ReadOnlyArray<AccountUserInfo>, | ||||
+setUserInfoInputArray: SetState<$ReadOnlyArray<AccountUserInfo>>, | +setUserInfoInputArray: SetState<$ReadOnlyArray<AccountUserInfo>>, | ||||
+existingThreadInfoFinderForCreatingThread: ExistingThreadInfoFinder, | |||||
+otherUserInfos: { [id: string]: AccountUserInfo }, | +otherUserInfos: { [id: string]: AccountUserInfo }, | ||||
+threadID: string, | +threadID: string, | ||||
+inputState: InputState, | +inputState: InputState, | ||||
}; | }; | ||||
type ActiveThreadBehavior = | type ActiveThreadBehavior = | ||||
| 'reset-active-thread-if-pending' | | 'reset-active-thread-if-pending' | ||||
| 'keep-active-thread'; | | 'keep-active-thread'; | ||||
function ChatThreadComposer(props: Props): React.Node { | function ChatThreadComposer(props: Props): React.Node { | ||||
const { | const { | ||||
userInfoInputArray, | userInfoInputArray, | ||||
setUserInfoInputArray, | setUserInfoInputArray, | ||||
existingThreadInfoFinderForCreatingThread, | |||||
otherUserInfos, | otherUserInfos, | ||||
threadID, | threadID, | ||||
inputState, | inputState, | ||||
} = props; | } = props; | ||||
const [usernameInputText, setUsernameInputText] = React.useState(''); | const [usernameInputText, setUsernameInputText] = React.useState(''); | ||||
const userInfos = useSelector(state => state.userStore.userInfos); | const userInfos = useSelector(state => state.userStore.userInfos); | ||||
▲ Show 20 Lines • Show All 55 Lines • ▼ Show 20 Lines | [ | ||||
mergedUserInfos, | mergedUserInfos, | ||||
userSearchIndex, | userSearchIndex, | ||||
filteredServerUsersSearchIndex, | filteredServerUsersSearchIndex, | ||||
userInfoInputIDs, | userInfoInputIDs, | ||||
], | ], | ||||
); | ); | ||||
const userListItemsWithENSNames = useENSNames(userListItems); | const userListItemsWithENSNames = useENSNames(userListItems); | ||||
const dispatch = useDispatch(); | |||||
const { pushModal } = useModalContext(); | |||||
const onSelectUserFromSearch = React.useCallback( | const onSelectUserFromSearch = React.useCallback( | ||||
(id: string, username: string) => { | (userItem: UserListItem) => { | ||||
setUsernameInputText(''); | |||||
if (!userItem.alert) { | |||||
setUserInfoInputArray(previousUserInfoInputArray => [ | setUserInfoInputArray(previousUserInfoInputArray => [ | ||||
...previousUserInfoInputArray, | ...previousUserInfoInputArray, | ||||
{ id, username }, | { id: userItem.id, username: userItem.username }, | ||||
]); | ]); | ||||
setUsernameInputText(''); | } else if ( | ||||
userItem.notice === notFriendNotice && | |||||
userInfoInputArray.length === 0 | |||||
) { | |||||
const newUserInfoInputArray = [ | |||||
{ id: userItem.id, username: userItem.username }, | |||||
]; | |||||
setUserInfoInputArray(newUserInfoInputArray); | |||||
const threadInfo = existingThreadInfoFinderForCreatingThread({ | |||||
searching: true, | |||||
userInfoInputArray: newUserInfoInputArray, | |||||
}); | |||||
dispatch({ | |||||
type: updateNavInfoActionType, | |||||
payload: { | |||||
chatMode: 'view', | |||||
activeChatThreadID: threadInfo?.id, | |||||
pendingThread: threadInfo, | |||||
}, | }, | ||||
[setUserInfoInputArray], | }); | ||||
} else { | |||||
pushModal( | |||||
<Alert title={userItem.alert.title}>{userItem.alert.text}</Alert>, | |||||
); | |||||
} | |||||
}, | |||||
[ | |||||
dispatch, | |||||
existingThreadInfoFinderForCreatingThread, | |||||
pushModal, | |||||
setUserInfoInputArray, | |||||
userInfoInputArray.length, | |||||
], | |||||
); | ); | ||||
const onRemoveUserFromSelected = React.useCallback( | const onRemoveUserFromSelected = React.useCallback( | ||||
(id: string) => { | (id: string) => { | ||||
setUserInfoInputArray(previousUserInfoInputArray => | setUserInfoInputArray(previousUserInfoInputArray => | ||||
previousUserInfoInputArray.filter(user => user.id !== id), | previousUserInfoInputArray.filter(user => user.id !== id), | ||||
); | ); | ||||
}, | }, | ||||
Show All 9 Lines | const userSearchResultList = React.useMemo(() => { | ||||
} | } | ||||
return ( | return ( | ||||
<ul className={css.searchResultsContainer}> | <ul className={css.searchResultsContainer}> | ||||
{userListItemsWithENSNames.map((userSearchResult: UserListItem) => ( | {userListItemsWithENSNames.map((userSearchResult: UserListItem) => ( | ||||
<li key={userSearchResult.id} className={css.searchResultsItem}> | <li key={userSearchResult.id} className={css.searchResultsItem}> | ||||
<Button | <Button | ||||
variant="text" | variant="text" | ||||
onClick={() => | onClick={() => onSelectUserFromSearch(userSearchResult)} | ||||
onSelectUserFromSearch( | |||||
userSearchResult.id, | |||||
userSearchResult.username, | |||||
) | |||||
} | |||||
className={css.searchResultsButton} | className={css.searchResultsButton} | ||||
> | > | ||||
<div className={css.userName}>{userSearchResult.username}</div> | <div className={css.userName}>{userSearchResult.username}</div> | ||||
<div className={css.userInfo}> | <div className={css.userInfo}>{userSearchResult.notice}</div> | ||||
{userSearchResult.alert?.title} | |||||
</div> | |||||
</Button> | </Button> | ||||
</li> | </li> | ||||
))} | ))} | ||||
</ul> | </ul> | ||||
); | ); | ||||
}, [ | }, [ | ||||
onSelectUserFromSearch, | onSelectUserFromSearch, | ||||
userInfoInputArray.length, | userInfoInputArray.length, | ||||
userListItemsWithENSNames, | userListItemsWithENSNames, | ||||
usernameInputText, | usernameInputText, | ||||
]); | ]); | ||||
const dispatch = useDispatch(); | |||||
const hideSearch = React.useCallback( | const hideSearch = React.useCallback( | ||||
(threadBehavior: ActiveThreadBehavior = 'keep-active-thread') => { | (threadBehavior: ActiveThreadBehavior = 'keep-active-thread') => { | ||||
dispatch({ | dispatch({ | ||||
type: updateNavInfoActionType, | type: updateNavInfoActionType, | ||||
payload: { | payload: { | ||||
chatMode: 'view', | chatMode: 'view', | ||||
activeChatThreadID: | activeChatThreadID: | ||||
threadBehavior === 'keep-active-thread' || | threadBehavior === 'keep-active-thread' || | ||||
▲ Show 20 Lines • Show All 61 Lines • Show Last 20 Lines |