Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32320230
D5463.1765300444.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
D5463.1765300444.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D5463: [web] Call searchUsers in chat composer
Attached
Detach File
Event Timeline
Log In to Comment