Page MenuHomePhorge

D5463.1765300444.diff
No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None

D5463.1765300444.diff

diff --git a/lib/selectors/user-selectors.js b/lib/selectors/user-selectors.js
--- a/lib/selectors/user-selectors.js
+++ b/lib/selectors/user-selectors.js
@@ -113,31 +113,33 @@
baseRelativeMemberInfoSelectorForMembersOfThread,
);
-const userInfoSelectorForPotentialMembers: (state: BaseAppState<*>) => {
- [id: string]: AccountUserInfo,
-} = createSelector(
+function filterPotentialMembers(
+ userInfos: UserInfos,
+ currentUserID: ?string,
+): { [id: string]: AccountUserInfo } {
+ const availableUsers: { [id: string]: AccountUserInfo } = {};
+
+ for (const id in userInfos) {
+ const { username, relationshipStatus } = userInfos[id];
+ if (id === currentUserID || !username) {
+ continue;
+ }
+ if (
+ relationshipStatus !== userRelationshipStatus.BLOCKED_VIEWER &&
+ relationshipStatus !== userRelationshipStatus.BOTH_BLOCKED
+ ) {
+ availableUsers[id] = { id, username, relationshipStatus };
+ }
+ }
+ return availableUsers;
+}
+
+const userInfoSelectorForPotentialMembers: (
+ state: BaseAppState<*>,
+) => { [id: string]: AccountUserInfo } = createSelector(
(state: BaseAppState<*>) => state.userStore.userInfos,
(state: BaseAppState<*>) => state.currentUserInfo && state.currentUserInfo.id,
- (
- userInfos: UserInfos,
- currentUserID: ?string,
- ): { [id: string]: AccountUserInfo } => {
- const availableUsers: { [id: string]: AccountUserInfo } = {};
-
- for (const id in userInfos) {
- const { username, relationshipStatus } = userInfos[id];
- if (id === currentUserID || !username) {
- continue;
- }
- if (
- relationshipStatus !== userRelationshipStatus.BLOCKED_VIEWER &&
- relationshipStatus !== userRelationshipStatus.BOTH_BLOCKED
- ) {
- availableUsers[id] = { id, username, relationshipStatus };
- }
- }
- return availableUsers;
- },
+ filterPotentialMembers,
);
function searchIndexFromUserInfos(userInfos: {
@@ -214,4 +216,5 @@
isLoggedIn,
userStoreSearchIndex,
usersWithPersonalThreadSelector,
+ filterPotentialMembers,
};
diff --git a/web/chat/chat-message-list-container.react.js b/web/chat/chat-message-list-container.react.js
--- a/web/chat/chat-message-list-container.react.js
+++ b/web/chat/chat-message-list-container.react.js
@@ -38,16 +38,24 @@
const isChatCreation =
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 userInfoInputArray: $ReadOnlyArray<AccountUserInfo> = React.useMemo(
- () => selectedUserIDs?.map(id => otherUserInfos[id]).filter(Boolean) ?? [],
- [otherUserInfos, selectedUserIDs],
+ const [userInfoInputArray, setUserInfoInputArray] = React.useState(() =>
+ selectedUserIDs.map(id => otherUserInfos[id]).filter(Boolean),
);
+ React.useEffect(() => {
+ if (!isChatCreation) {
+ setUserInfoInputArray([]);
+ }
+ }, [isChatCreation]);
+
+
const loggedInUserInfo = useLoggedInUserInfo();
invariant(loggedInUserInfo, 'loggedInUserInfo should be set');
-
+
const pendingPrivateThread = React.useRef(
createPendingThread({
viewerID: loggedInUserInfo.id,
@@ -110,52 +118,44 @@
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,
- ]);
+ if (isChatCreation) {
+ let payload = {};
- React.useEffect(() => {
- if (isChatCreation && activeChatThreadID !== threadInfo?.id) {
- let payload = {
- activeChatThreadID: threadInfo?.id,
- };
- if (threadIsPending(threadInfo?.id)) {
+ const newSelectedUserIDs = userInfoInputArray.map(user => user.id);
+ if (!_isEqual(new Set(selectedUserIDs), new Set(newSelectedUserIDs))) {
payload = {
...payload,
- pendingThread: threadInfo,
+ selectedUserList: newSelectedUserIDs,
};
}
+
+ if (activeChatThreadID !== threadInfo?.id) {
+ payload = {
+ ...payload,
+ activeChatThreadID: threadInfo?.id,
+ };
+ if (threadIsPending(threadInfo?.id)) {
+ payload = {
+ ...payload,
+ pendingThread: threadInfo,
+ };
+ }
+ }
+
dispatch({
type: updateNavInfoActionType,
payload,
});
}
- }, [activeChatThreadID, dispatch, isChatCreation, threadInfo]);
+ }, [
+ activeChatThreadID,
+ dispatch,
+ isChatCreation,
+ selectedUserIDs,
+ threadInfo,
+ userInfoInputArray,
+ ]);
const inputState = React.useContext(InputStateContext);
invariant(inputState, 'InputState should be set');
@@ -230,6 +230,7 @@
const chatUserSelection = (
<ChatThreadComposer
userInfoInputArray={userInfoInputArray}
+ setUserInfoInputArray={setUserInfoInputArray}
otherUserInfos={otherUserInfos}
threadID={threadInfo.id}
inputState={inputState}
diff --git a/web/chat/chat-thread-composer.react.js b/web/chat/chat-thread-composer.react.js
--- a/web/chat/chat-thread-composer.react.js
+++ b/web/chat/chat-thread-composer.react.js
@@ -1,14 +1,23 @@
// @flow
import classNames from 'classnames';
+import _cloneDeep from 'lodash/fp/cloneDeep';
import * as React from 'react';
import { useDispatch } from 'react-redux';
-import SWMansionIcon from 'lib/components/SWMansionIcon.react.js';
-import { useENSNames } from 'lib/hooks/ens-cache.js';
-import { userSearchIndexForPotentialMembers } from 'lib/selectors/user-selectors.js';
-import { getPotentialMemberItems } from 'lib/shared/search-utils.js';
-import { threadIsPending } from 'lib/shared/thread-utils.js';
-import type { AccountUserInfo, UserListItem } from 'lib/types/user-types.js';
+import { searchUsers } from 'lib/actions/user-actions';
+import {
+ filterPotentialMembers,
+ userSearchIndexForPotentialMembers,
+} from 'lib/selectors/user-selectors';
+import { getPotentialMemberItems } from 'lib/shared/search-utils';
+import { threadIsPending } from 'lib/shared/thread-utils';
+import type { SetState } from 'lib/types/hook-types';
+import type {
+ AccountUserInfo,
+ UserListItem,
+ GlobalAccountUserInfo,
+} from 'lib/types/user-types';
+import { useServerCall } from 'lib/utils/action-utils';
import css from './chat-thread-composer.css';
import Button from '../components/button.react.js';
@@ -20,6 +29,7 @@
type Props = {
+userInfoInputArray: $ReadOnlyArray<AccountUserInfo>,
+ +setUserInfoInputArray: SetState<$ReadOnlyArray<AccountUserInfo>>,
+otherUserInfos: { [id: string]: AccountUserInfo },
+threadID: string,
+inputState: InputState,
@@ -30,12 +40,59 @@
| 'keep-active-thread';
function ChatThreadComposer(props: Props): React.Node {
- const { userInfoInputArray, otherUserInfos, threadID, inputState } = props;
+ const {
+ userInfoInputArray,
+ setUserInfoInputArray,
+ otherUserInfos,
+ threadID,
+ inputState,
+ } = props;
const [usernameInputText, setUsernameInputText] = React.useState('');
- const dispatch = useDispatch();
+ const userInfos = useSelector(state => state.userStore.userInfos);
+ const viewerID = useSelector(state => state.currentUserInfo?.id);
+
+ const [serverSearchUserInfos, setServerSearchUserInfos] = React.useState<
+ $ReadOnlyArray<GlobalAccountUserInfo>,
+ >([]);
+ const callSearchUsers = useServerCall(searchUsers);
+ React.useEffect(() => {
+ (async () => {
+ if (usernameInputText.length === 0) {
+ setServerSearchUserInfos([]);
+ } else {
+ const { userInfos: serverUserInfos } = await callSearchUsers(
+ usernameInputText,
+ );
+ setServerSearchUserInfos(serverUserInfos);
+ }
+ })();
+ }, [callSearchUsers, usernameInputText]);
+
+ const filteredServerUserInfos = React.useMemo(() => {
+ const result = {};
+ for (const user of serverSearchUserInfos) {
+ if (!(user.id in userInfos)) {
+ result[user.id] = user;
+ }
+ }
+ return filterPotentialMembers(result, viewerID);
+ }, [serverSearchUserInfos, userInfos, viewerID]);
+
+ const mergedUserInfos = React.useMemo(
+ () => ({ ...filteredServerUserInfos, ...otherUserInfos }),
+ [filteredServerUserInfos, otherUserInfos],
+ );
+
const userSearchIndex = useSelector(userSearchIndexForPotentialMembers);
+ const mergedUserSearchIndex = React.useMemo(() => {
+ const searchIndex = _cloneDeep(userSearchIndex);
+ for (const id in filteredServerUserInfos) {
+ searchIndex.addEntry(id, filteredServerUserInfos[id].username);
+ }
+ return searchIndex;
+ }, [filteredServerUserInfos, userSearchIndex]);
const userInfoInputIDs = React.useMemo(
() => userInfoInputArray.map(userInfo => userInfo.id),
@@ -46,42 +103,37 @@
() =>
getPotentialMemberItems(
usernameInputText,
- otherUserInfos,
- userSearchIndex,
+ mergedUserInfos,
+ mergedUserSearchIndex,
userInfoInputIDs,
),
- [usernameInputText, otherUserInfos, userSearchIndex, userInfoInputIDs],
+ [
+ usernameInputText,
+ mergedUserInfos,
+ mergedUserSearchIndex,
+ userInfoInputIDs,
+ ],
);
const userListItemsWithENSNames = useENSNames(userListItems);
const onSelectUserFromSearch = React.useCallback(
- (id: string) => {
- const selectedUserIDs = userInfoInputArray.map(user => user.id);
- dispatch({
- type: updateNavInfoActionType,
- payload: {
- selectedUserList: [...selectedUserIDs, id],
- },
- });
+ (id: string, username: string) => {
+ setUserInfoInputArray(previousUserInfoInputArray => [
+ ...previousUserInfoInputArray,
+ { id, username },
+ ]);
setUsernameInputText('');
},
- [dispatch, userInfoInputArray],
+ [setUserInfoInputArray],
);
const onRemoveUserFromSelected = React.useCallback(
(id: string) => {
- const selectedUserIDs = userInfoInputArray.map(user => user.id);
- if (!selectedUserIDs.includes(id)) {
- return;
- }
- dispatch({
- type: updateNavInfoActionType,
- payload: {
- selectedUserList: selectedUserIDs.filter(userID => userID !== id),
- },
- });
+ setUserInfoInputArray(previousUserInfoInputArray =>
+ previousUserInfoInputArray.filter(user => user.id !== id),
+ );
},
- [dispatch, userInfoInputArray],
+ [setUserInfoInputArray],
);
const userSearchResultList = React.useMemo(() => {
@@ -98,7 +150,12 @@
<li key={userSearchResult.id} className={css.searchResultsItem}>
<Button
variant="text"
- onClick={() => onSelectUserFromSearch(userSearchResult.id)}
+ onClick={() =>
+ onSelectUserFromSearch(
+ userSearchResult.id,
+ userSearchResult.username,
+ )
+ }
className={css.searchResultsButton}
>
<div className={css.userName}>{userSearchResult.username}</div>
@@ -115,6 +172,7 @@
usernameInputText,
]);
+ const dispatch = useDispatch();
const hideSearch = React.useCallback(
(threadBehavior: ActiveThreadBehavior = 'keep-active-thread') => {
dispatch({

File Metadata

Mime Type
text/plain
Expires
Tue, Dec 9, 5:14 PM (13 h, 21 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5855976
Default Alt Text
D5463.1765300444.diff (11 KB)

Event Timeline