diff --git a/lib/shared/reaction-utils.js b/lib/shared/reaction-utils.js index b8c1c6322..10c2120ae 100644 --- a/lib/shared/reaction-utils.js +++ b/lib/shared/reaction-utils.js @@ -1,64 +1,104 @@ // @flow import invariant from 'invariant'; +import _sortBy from 'lodash/fp/sortBy'; import type { MessageReactionInfo } from '../selectors/chat-selectors'; import type { RobotextMessageInfo, ComposableMessageInfo, } from '../types/message-types'; import { threadPermissions, type ThreadInfo } from '../types/thread-types'; import { useSelector } from '../utils/redux-utils'; import { relationshipBlockedInEitherDirection } from './relationship-utils'; import { threadHasPermission } from './thread-utils'; +import { stringForUserExplicit } from './user-utils'; function stringForReactionList( reactions: $ReadOnlyMap, ): string { const reactionText = []; for (const reaction of reactions.keys()) { const reactionInfo = reactions.get(reaction); invariant(reactionInfo, 'reactionInfo should be set'); reactionText.push(reaction); const { length: numberOfReacts } = reactionInfo.users; if (numberOfReacts <= 1) { continue; } reactionText.push(numberOfReacts > 9 ? '9+' : numberOfReacts.toString()); } return reactionText.join(' '); } +type MessageReactionListInfo = { + +id: string, + +isViewer: boolean, + +reaction: string, + +username: string, +}; + +function createMessageReactionsList( + reactions: $ReadOnlyMap, +): $ReadOnlyArray { + const result = []; + + for (const [reaction, reactionInfo] of reactions) { + reactionInfo.users.forEach(user => { + result.push({ + ...user, + username: stringForUserExplicit(user), + reaction, + }); + }); + } + + const sortByNumReactions = (reactionInfo: MessageReactionListInfo) => { + const numOfReactions = reactions.get(reactionInfo.reaction)?.users.length; + return numOfReactions ? -numOfReactions : 0; + }; + + return _sortBy( + ([sortByNumReactions, 'username']: $ReadOnlyArray< + ((reactionInfo: MessageReactionListInfo) => mixed) | string, + >), + )(result); +} + function useCanCreateReactionFromMessage( threadInfo: ThreadInfo, targetMessageInfo: ComposableMessageInfo | RobotextMessageInfo, ): boolean { const targetMessageCreatorRelationship = useSelector( state => state.userStore.userInfos[targetMessageInfo.creator.id] ?.relationshipStatus, ); if ( !targetMessageInfo.id || threadInfo.sourceMessageID === targetMessageInfo.id ) { return false; } const creatorRelationshipHasBlock = targetMessageCreatorRelationship && relationshipBlockedInEitherDirection(targetMessageCreatorRelationship); const hasPermission = threadHasPermission( threadInfo, threadPermissions.VOICED, ); return hasPermission && !creatorRelationshipHasBlock; } -export { stringForReactionList, useCanCreateReactionFromMessage }; +export { + stringForReactionList, + createMessageReactionsList, + useCanCreateReactionFromMessage, +}; diff --git a/web/modals/chat/message-reactions-modal.css b/web/modals/chat/message-reactions-modal.css new file mode 100644 index 000000000..b8e2317bf --- /dev/null +++ b/web/modals/chat/message-reactions-modal.css @@ -0,0 +1,16 @@ +div.modalContentContainer { + padding: 24px 32px 32px 32px; + color: var(--fg); + background-color: var(--modal-bg); + flex: 1; + display: flex; + flex-direction: column; + font-size: var(--l-font-18); + gap: 16px; +} + +div.userRowContainer { + display: flex; + flex-direction: row; + justify-content: space-between; +} diff --git a/web/modals/chat/message-reactions-modal.react.js b/web/modals/chat/message-reactions-modal.react.js new file mode 100644 index 000000000..28c558ddc --- /dev/null +++ b/web/modals/chat/message-reactions-modal.react.js @@ -0,0 +1,37 @@ +// @flow + +import * as React from 'react'; + +import type { MessageReactionInfo } from 'lib/selectors/chat-selectors'; +import { createMessageReactionsList } from 'lib/shared/reaction-utils'; + +import Modal from '../modal.react'; +import css from './message-reactions-modal.css'; + +type Props = { + +onClose: () => void, + +reactions: $ReadOnlyMap, +}; + +function MessageReactionsModal(props: Props): React.Node { + const { onClose, reactions } = props; + + const reactionsList = React.useMemo(() => { + const messageReactionsList = createMessageReactionsList(reactions); + + return messageReactionsList.map(messageReactionUser => ( +
+
{messageReactionUser.username}
+
{messageReactionUser.reaction}
+
+ )); + }, [reactions]); + + return ( + +
{reactionsList}
+
+ ); +} + +export default MessageReactionsModal;