Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32089079
chat-thread-composer.react.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
7 KB
Referenced Files
None
Subscribers
None
chat-thread-composer.react.js
View Options
// @flow
import
classNames
from
'classnames'
;
import
invariant
from
'invariant'
;
import
_isEqual
from
'lodash/fp/isEqual.js'
;
import
*
as
React
from
'react'
;
import
{
useModalContext
}
from
'lib/components/modal-provider.react.js'
;
import
SWMansionIcon
from
'lib/components/swmansion-icon.react.js'
;
import
{
useLoggedInUserInfo
}
from
'lib/hooks/account-hooks.js'
;
import
{
useENSNames
}
from
'lib/hooks/ens-cache.js'
;
import
{
usePotentialMemberItems
,
useSearchUsers
,
notFriendNotice
,
}
from
'lib/shared/search-utils.js'
;
import
{
createPendingThread
,
threadIsPending
,
useExistingThreadInfoFinder
,
}
from
'lib/shared/thread-utils.js'
;
import
{
threadTypes
}
from
'lib/types/thread-types-enum.js'
;
import
type
{
AccountUserInfo
,
UserListItem
}
from
'lib/types/user-types.js'
;
import
{
useDispatch
}
from
'lib/utils/redux-utils.js'
;
import
css
from
'./chat-thread-composer.css'
;
import
UserAvatar
from
'../avatars/user-avatar.react.js'
;
import
Button
from
'../components/button.react.js'
;
import
Label
from
'../components/label.react.js'
;
import
Search
from
'../components/search.react.js'
;
import
type
{
InputState
}
from
'../input/input-state.js'
;
import
Alert
from
'../modals/alert.react.js'
;
import
{
updateNavInfoActionType
}
from
'../redux/action-types.js'
;
type
Props
=
{
+
userInfoInputArray
:
$ReadOnlyArray
<
AccountUserInfo
>
,
+
otherUserInfos
:
{
[
id
:
string
]
:
AccountUserInfo
},
+
threadID
:
string
,
+
inputState
:
InputState
,
};
type
ActiveThreadBehavior
=
|
'reset-active-thread-if-pending'
|
'keep-active-thread'
;
function
ChatThreadComposer
(
props
:
Props
)
:
React
.
Node
{
const
{
userInfoInputArray
,
otherUserInfos
,
threadID
,
inputState
}
=
props
;
const
[
usernameInputText
,
setUsernameInputText
]
=
React
.
useState
(
''
);
const
dispatch
=
useDispatch
();
const
userInfoInputIDs
=
React
.
useMemo
(
()
=>
userInfoInputArray
.
map
(
userInfo
=>
userInfo
.
id
),
[
userInfoInputArray
],
);
const
searchResults
=
useSearchUsers
(
usernameInputText
);
const
userListItems
=
usePotentialMemberItems
({
text
:
usernameInputText
,
userInfos
:
otherUserInfos
,
excludeUserIDs
:
userInfoInputIDs
,
includeServerSearchUsers
:
searchResults
,
});
const
userListItemsWithENSNames
=
useENSNames
(
userListItems
);
const
{
pushModal
}
=
useModalContext
();
const
loggedInUserInfo
=
useLoggedInUserInfo
();
invariant
(
loggedInUserInfo
,
'loggedInUserInfo should be set'
);
const
pendingPrivateThread
=
React
.
useRef
(
createPendingThread
({
viewerID
:
loggedInUserInfo
.
id
,
threadType
:
threadTypes
.
GENESIS_PRIVATE
,
members
:
[
loggedInUserInfo
],
}),
);
const
existingThreadInfoFinderForCreatingThread
=
useExistingThreadInfoFinder
(
pendingPrivateThread
.
current
,
);
const
onSelectUserFromSearch
=
React
.
useCallback
(
(
userListItem
:
UserListItem
)
=>
{
const
{
alert
,
notice
,
disabled
,
...
user
}
=
userListItem
;
setUsernameInputText
(
''
);
if
(
!
alert
)
{
dispatch
({
type
:
updateNavInfoActionType
,
payload
:
{
selectedUserList
:
[...
userInfoInputArray
,
user
],
},
});
}
else
if
(
notice
===
notFriendNotice
&&
userInfoInputArray
.
length
===
0
)
{
const
newUserInfoInputArray
=
[
{
id
:
userListItem
.
id
,
username
:
userListItem
.
username
},
];
const
threadInfo
=
existingThreadInfoFinderForCreatingThread
({
searching
:
true
,
userInfoInputArray
:
newUserInfoInputArray
,
});
dispatch
({
type
:
updateNavInfoActionType
,
payload
:
{
chatMode
:
'view'
,
activeChatThreadID
:
threadInfo
?
.
id
,
pendingThread
:
threadInfo
,
},
});
}
else
{
pushModal
(
<
Alert
title
=
{
alert
.
title
}
>
{
alert
.
text
}
<
/Alert>);
}
},
[
dispatch
,
existingThreadInfoFinderForCreatingThread
,
pushModal
,
userInfoInputArray
,
],
);
const
onRemoveUserFromSelected
=
React
.
useCallback
(
(
userID
:
string
)
=>
{
const
newSelectedUserList
=
userInfoInputArray
.
filter
(
({
id
})
=>
userID
!==
id
,
);
if
(
_isEqual
(
userInfoInputArray
)(
newSelectedUserList
))
{
return
;
}
dispatch
({
type
:
updateNavInfoActionType
,
payload
:
{
selectedUserList
:
newSelectedUserList
,
},
});
},
[
dispatch
,
userInfoInputArray
],
);
const
userSearchResultList
=
React
.
useMemo
(()
=>
{
if
(
!
userListItemsWithENSNames
.
length
||
(
!
usernameInputText
&&
userInfoInputArray
.
length
)
)
{
return
null
;
}
const
userItems
=
userListItemsWithENSNames
.
map
(
(
userSearchResult
:
UserListItem
)
=>
{
return
(
<
li
key
=
{
userSearchResult
.
id
}
className
=
{
css
.
searchResultsItem
}
>
<
Button
variant
=
"text"
onClick
=
{()
=>
onSelectUserFromSearch
(
userSearchResult
)}
className
=
{
css
.
searchResultsButton
}
>
<
div
className
=
{
css
.
userContainer
}
>
<
UserAvatar
size
=
"S"
userID
=
{
userSearchResult
.
id
}
/>
<
div
className
=
{
css
.
userName
}
>
{
userSearchResult
.
username
}
<
/div>
<
/div>
<
div
className
=
{
css
.
userInfo
}
>
{
userSearchResult
.
notice
}
<
/div>
<
/Button>
<
/li>
);
},
);
return
<
ul
className
=
{
css
.
searchResultsContainer
}
>
{
userItems
}
<
/ul>;
},
[
onSelectUserFromSearch
,
userInfoInputArray
.
length
,
userListItemsWithENSNames
,
usernameInputText
,
]);
const
hideSearch
=
React
.
useCallback
(
(
threadBehavior
:
ActiveThreadBehavior
=
'keep-active-thread'
)
=>
{
dispatch
({
type
:
updateNavInfoActionType
,
payload
:
{
chatMode
:
'view'
,
activeChatThreadID
:
threadBehavior
===
'keep-active-thread'
||
!
threadIsPending
(
threadID
)
?
threadID
:
null
,
},
});
},
[
dispatch
,
threadID
],
);
const
onCloseSearch
=
React
.
useCallback
(()
=>
{
hideSearch
(
'reset-active-thread-if-pending'
);
},
[
hideSearch
]);
const
userInfoInputArrayWithENSNames
=
useENSNames
(
userInfoInputArray
);
const
tagsList
=
React
.
useMemo
(()
=>
{
if
(
!
userInfoInputArrayWithENSNames
?
.
length
)
{
return
null
;
}
const
labels
=
userInfoInputArrayWithENSNames
.
map
(
user
=>
{
return
(
<
Label
key
=
{
user
.
id
}
onClose
=
{()
=>
onRemoveUserFromSelected
(
user
.
id
)}
>
{
user
.
username
}
<
/Label>
);
});
return
<
div
className
=
{
css
.
userSelectedTags
}
>
{
labels
}
<
/div>;
},
[
userInfoInputArrayWithENSNames
,
onRemoveUserFromSelected
]);
React
.
useEffect
(()
=>
{
if
(
!
inputState
)
{
return
undefined
;
}
inputState
.
registerSendCallback
(
hideSearch
);
return
()
=>
inputState
.
unregisterSendCallback
(
hideSearch
);
},
[
hideSearch
,
inputState
]);
const
threadSearchContainerStyles
=
classNames
(
css
.
threadSearchContainer
,
{
[
css
.
fullHeight
]
:
!
userInfoInputArray
.
length
,
});
return
(
<
div
className
=
{
threadSearchContainerStyles
}
>
<
div
className
=
{
css
.
searchRow
}
>
<
div
className
=
{
css
.
searchField
}
>
<
Search
onChangeText
=
{
setUsernameInputText
}
searchText
=
{
usernameInputText
}
placeholder
=
"Select users for chat"
autoFocus
=
{
true
}
/>
<
/div>
<
Button
className
=
{
css
.
closeSearch
}
onClick
=
{
onCloseSearch
}
>
<
SWMansionIcon
size
=
{
25
}
icon
=
"cross"
/>
<
/Button>
<
/div>
{
tagsList
}
{
userSearchResultList
}
<
/div>
);
}
export
default
ChatThreadComposer
;
File Metadata
Details
Attached
Mime Type
text/x-java
Expires
Sun, Dec 7, 7:55 AM (1 h, 14 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5630268
Default Alt Text
chat-thread-composer.react.js (7 KB)
Attached To
Mode
rCOMM Comm
Attached
Detach File
Event Timeline
Log In to Comment