diff --git a/lib/selectors/chat-selectors.js b/lib/selectors/chat-selectors.js --- a/lib/selectors/chat-selectors.js +++ b/lib/selectors/chat-selectors.js @@ -277,7 +277,7 @@ endsCluster: boolean, +robotext: EntityText, +threadCreatedFromMessage: ?ThreadInfo, - +reactions: $ReadOnlyMap, + +reactions: ReactionInfo, }; export type ChatMessageInfoItem = | RobotextChatMessageInfoItem @@ -290,10 +290,12 @@ +startsCluster: boolean, endsCluster: boolean, +threadCreatedFromMessage: ?ThreadInfo, - +reactions: $ReadOnlyMap, + +reactions: ReactionInfo, }; export type ChatMessageItem = { itemType: 'loader' } | ChatMessageInfoItem; -export type MessageReactionInfo = { + +export type ReactionInfo = { +[reaction: string]: MessageReactionInfo }; +type MessageReactionInfo = { +viewerReacted: boolean, +users: $ReadOnlyArray, }; @@ -408,41 +410,40 @@ ? threadInfoFromSourceMessageID[messageInfo.id] : undefined; - const renderedReactions: $ReadOnlyMap = - (() => { - const result = new Map(); + const renderedReactions: ReactionInfo = (() => { + const result = {}; - let messageReactsMap; - if (originalMessageInfo.id) { - messageReactsMap = targetMessageReactionsMap.get( - originalMessageInfo.id, - ); - } + let messageReactsMap; + if (originalMessageInfo.id) { + messageReactsMap = targetMessageReactionsMap.get( + originalMessageInfo.id, + ); + } - if (!messageReactsMap) { - return result; - } + if (!messageReactsMap) { + return result; + } - for (const reaction of messageReactsMap.keys()) { - const reactionUsersInfoMap = messageReactsMap.get(reaction); - invariant(reactionUsersInfoMap, 'reactionUsersInfoMap should be set'); + for (const reaction of messageReactsMap.keys()) { + const reactionUsersInfoMap = messageReactsMap.get(reaction); + invariant(reactionUsersInfoMap, 'reactionUsersInfoMap should be set'); - if (reactionUsersInfoMap.size === 0) { - continue; - } + if (reactionUsersInfoMap.size === 0) { + continue; + } - const reactionUserInfos = [...reactionUsersInfoMap.values()]; + const reactionUserInfos = [...reactionUsersInfoMap.values()]; - const messageReactionInfo = { - users: reactionUserInfos, - viewerReacted: reactionUsersInfoMap.has(viewerID), - }; + const messageReactionInfo = { + users: reactionUserInfos, + viewerReacted: reactionUsersInfoMap.has(viewerID), + }; - result.set(reaction, messageReactionInfo); - } + result[reaction] = messageReactionInfo; + } - return result; - })(); + return result; + })(); if (isComposableMessageType(originalMessageInfo.type)) { // We use these invariants instead of just checking the messageInfo.type diff --git a/lib/shared/reaction-utils.js b/lib/shared/reaction-utils.js --- a/lib/shared/reaction-utils.js +++ b/lib/shared/reaction-utils.js @@ -1,6 +1,5 @@ // @flow -import invariant from 'invariant'; import _sortBy from 'lodash/fp/sortBy.js'; import * as React from 'react'; @@ -8,7 +7,7 @@ import { threadHasPermission } from './thread-utils.js'; import { stringForUserExplicit } from './user-utils.js'; import { useENSNames } from '../hooks/ens-cache.js'; -import type { MessageReactionInfo } from '../selectors/chat-selectors.js'; +import type { ReactionInfo } from '../selectors/chat-selectors.js'; import type { RobotextMessageInfo, ComposableMessageInfo, @@ -16,14 +15,11 @@ import { threadPermissions, type ThreadInfo } from '../types/thread-types.js'; import { useSelector } from '../utils/redux-utils.js'; -function stringForReactionList( - reactions: $ReadOnlyMap, -): string { +function stringForReactionList(reactions: ReactionInfo): string { const reactionText = []; - for (const reaction of reactions.keys()) { - const reactionInfo = reactions.get(reaction); - invariant(reactionInfo, 'reactionInfo should be set'); + for (const reaction in reactions) { + const reactionInfo = reactions[reaction]; reactionText.push(reaction); const { length: numberOfReacts } = reactionInfo.users; @@ -44,12 +40,14 @@ }; function useMessageReactionsList( - reactions: $ReadOnlyMap, + reactions: ReactionInfo, ): $ReadOnlyArray { const withoutENSNames = React.useMemo(() => { const result = []; - for (const [reaction, reactionInfo] of reactions) { + for (const reaction in reactions) { + const reactionInfo = reactions[reaction]; + reactionInfo.users.forEach(user => { result.push({ ...user, @@ -60,7 +58,7 @@ } const sortByNumReactions = (reactionInfo: MessageReactionListInfo) => { - const numOfReactions = reactions.get(reactionInfo.reaction)?.users.length; + const numOfReactions = reactions[reactionInfo.reaction].users.length; return numOfReactions ? -numOfReactions : 0; }; diff --git a/lib/shared/reaction-utils.test.js b/lib/shared/reaction-utils.test.js --- a/lib/shared/reaction-utils.test.js +++ b/lib/shared/reaction-utils.test.js @@ -1,156 +1,158 @@ // @flow import { stringForReactionList } from './reaction-utils.js'; -import type { MessageReactionInfo } from '../selectors/chat-selectors.js'; - -describe( - 'stringForReactionList(' + - 'reactions: $ReadOnlyMap)', - () => { - it( - 'should return (👍 3) for a message with three user likes' + - ' including the viewer', - () => { - const messageLikesUsers = [ - { id: '83810', isViewer: true, username: 'ginsu' }, - { id: '86622', isViewer: false, username: 'ashoat' }, - { id: '83889', isViewer: false, username: 'atul' }, - ]; - const messageLikesInfo = { - users: messageLikesUsers, - viewerReacted: true, - }; - - const reactionsMap = new Map(); - reactionsMap.set('👍', messageLikesInfo); - - expect(stringForReactionList(reactionsMap)).toBe('👍 3'); - }, - ); - - it( - 'should return (👍 3) for a message with three user likes' + - ' not including the viewer', - () => { - const messageLikesUsers = [ - { id: '83810', isViewer: false, username: 'ginsu' }, - { id: '86622', isViewer: false, username: 'ashoat' }, - { id: '83889', isViewer: false, username: 'atul' }, - ]; - const messageLikesInfo = { - users: messageLikesUsers, - viewerReacted: false, - }; - - const reactionsMap = new Map(); - reactionsMap.set('👍', messageLikesInfo); - - expect(stringForReactionList(reactionsMap)).toBe('👍 3'); - }, - ); - - it( - 'should return (👍) for a message with one user like' + - ' including the viewer', - () => { - const messageLikesUsers = [ - { id: '83810', isViewer: false, username: 'ginsu' }, - ]; - const messageLikesInfo = { - users: messageLikesUsers, - viewerReacted: true, - }; - - const reactionsMap = new Map(); - reactionsMap.set('👍', messageLikesInfo); - - expect(stringForReactionList(reactionsMap)).toBe('👍'); - }, - ); - - it( - 'should return (👍) for a message with one user like' + - ' not including the viewer', - () => { - const messageLikesUsers = [ - { id: '83810', isViewer: false, username: 'ashoat' }, - ]; - const messageLikesInfo = { - users: messageLikesUsers, - viewerReacted: false, - }; - - const reactionsMap = new Map(); - reactionsMap.set('👍', messageLikesInfo); - - expect(stringForReactionList(reactionsMap)).toBe('👍'); - }, - ); - - it('should return an empty string for a message no reactions', () => { - const reactionsMap = new Map(); - - expect(stringForReactionList(reactionsMap)).toBe(''); - }); - - it( - 'should return (👍 😆 3) for a message with one like not including' + - ' the viewer and three laugh reactions including the viewer', - () => { - const messageLikesUsers = [ - { id: '83810', isViewer: false, username: 'varun' }, - ]; - const messageLikesInfo = { - users: messageLikesUsers, - viewerReacted: false, - }; - - const messageLaughsUsers = [ - { id: '12345', isViewer: true, username: 'ginsu' }, - { id: '67890', isViewer: false, username: 'ashoat' }, - { id: '83889', isViewer: false, username: 'atul' }, - ]; - const messageLaughsInfo = { - users: messageLaughsUsers, - viewerReacted: true, - }; - - const reactionsMap = new Map(); - reactionsMap.set('👍', messageLikesInfo); - reactionsMap.set('😆', messageLaughsInfo); - - expect(stringForReactionList(reactionsMap)).toBe('👍 😆 3'); - }, - ); - - it( - 'should return (👍 9+) for a message with 12 user likes' + - ' not including the viewer', - () => { - const messageLikesUsers = [ - { id: '86622', isViewer: false, username: 'ginsu' }, - { id: '12345', isViewer: false, username: 'ashoat' }, - { id: '67890', isViewer: false, username: 'atul' }, - { id: '83889', isViewer: false, username: 'varun' }, - { id: '49203', isViewer: false, username: 'tomek' }, - { id: '83029', isViewer: false, username: 'max' }, - { id: '72902', isViewer: false, username: 'jon' }, - { id: '49022', isViewer: false, username: 'mark' }, - { id: '48902', isViewer: false, username: 'kamil' }, - { id: '80922', isViewer: false, username: 'marcin' }, - { id: '12890', isViewer: false, username: 'inka' }, - { id: '67891', isViewer: false, username: 'przemek' }, - ]; - const messageLikesInfo = { - users: messageLikesUsers, - viewerReacted: false, - }; - - const reactionsMap = new Map(); - reactionsMap.set('👍', messageLikesInfo); - - expect(stringForReactionList(reactionsMap)).toBe('👍 9+'); - }, - ); - }, -); +import type { ReactionInfo } from '../selectors/chat-selectors.js'; + +describe('stringForReactionList(reactions: ReactionInfo)', () => { + it( + 'should return (👍 3) for a message with three user likes' + + ' including the viewer', + () => { + const messageLikesUsers = [ + { id: '83810', isViewer: true, username: 'ginsu' }, + { id: '86622', isViewer: false, username: 'ashoat' }, + { id: '83889', isViewer: false, username: 'atul' }, + ]; + const messageLikesInfo = { + users: messageLikesUsers, + viewerReacted: true, + }; + + const reactions: ReactionInfo = { + '👍': messageLikesInfo, + }; + + expect(stringForReactionList(reactions)).toBe('👍 3'); + }, + ); + + it( + 'should return (👍 3) for a message with three user likes' + + ' not including the viewer', + () => { + const messageLikesUsers = [ + { id: '83810', isViewer: false, username: 'ginsu' }, + { id: '86622', isViewer: false, username: 'ashoat' }, + { id: '83889', isViewer: false, username: 'atul' }, + ]; + const messageLikesInfo = { + users: messageLikesUsers, + viewerReacted: false, + }; + + const reactions: ReactionInfo = { + '👍': messageLikesInfo, + }; + + expect(stringForReactionList(reactions)).toBe('👍 3'); + }, + ); + + it( + 'should return (👍) for a message with one user like' + + ' including the viewer', + () => { + const messageLikesUsers = [ + { id: '83810', isViewer: false, username: 'ginsu' }, + ]; + const messageLikesInfo = { + users: messageLikesUsers, + viewerReacted: true, + }; + + const reactions: ReactionInfo = { + '👍': messageLikesInfo, + }; + + expect(stringForReactionList(reactions)).toBe('👍'); + }, + ); + + it( + 'should return (👍) for a message with one user like' + + ' not including the viewer', + () => { + const messageLikesUsers = [ + { id: '83810', isViewer: false, username: 'ashoat' }, + ]; + const messageLikesInfo = { + users: messageLikesUsers, + viewerReacted: false, + }; + + const reactions: ReactionInfo = { + '👍': messageLikesInfo, + }; + + expect(stringForReactionList(reactions)).toBe('👍'); + }, + ); + + it('should return an empty string for a message no reactions', () => { + const reactions: ReactionInfo = {}; + + expect(stringForReactionList(reactions)).toBe(''); + }); + + it( + 'should return (👍 😆 3) for a message with one like not including' + + ' the viewer and three laugh reactions including the viewer', + () => { + const messageLikesUsers = [ + { id: '83810', isViewer: false, username: 'varun' }, + ]; + const messageLikesInfo = { + users: messageLikesUsers, + viewerReacted: false, + }; + + const messageLaughsUsers = [ + { id: '12345', isViewer: true, username: 'ginsu' }, + { id: '67890', isViewer: false, username: 'ashoat' }, + { id: '83889', isViewer: false, username: 'atul' }, + ]; + const messageLaughsInfo = { + users: messageLaughsUsers, + viewerReacted: true, + }; + + const reactions: ReactionInfo = { + '👍': messageLikesInfo, + '😆': messageLaughsInfo, + }; + + expect(stringForReactionList(reactions)).toBe('👍 😆 3'); + }, + ); + + it( + 'should return (👍 9+) for a message with 12 user likes' + + ' not including the viewer', + () => { + const messageLikesUsers = [ + { id: '86622', isViewer: false, username: 'ginsu' }, + { id: '12345', isViewer: false, username: 'ashoat' }, + { id: '67890', isViewer: false, username: 'atul' }, + { id: '83889', isViewer: false, username: 'varun' }, + { id: '49203', isViewer: false, username: 'tomek' }, + { id: '83029', isViewer: false, username: 'max' }, + { id: '72902', isViewer: false, username: 'jon' }, + { id: '49022', isViewer: false, username: 'mark' }, + { id: '48902', isViewer: false, username: 'kamil' }, + { id: '80922', isViewer: false, username: 'marcin' }, + { id: '12890', isViewer: false, username: 'inka' }, + { id: '67891', isViewer: false, username: 'przemek' }, + ]; + const messageLikesInfo = { + users: messageLikesUsers, + viewerReacted: false, + }; + + const reactions: ReactionInfo = { + '👍': messageLikesInfo, + }; + + expect(stringForReactionList(reactions)).toBe('👍 9+'); + }, + ); +}); diff --git a/native/chat/composed-message.react.js b/native/chat/composed-message.react.js --- a/native/chat/composed-message.react.js +++ b/native/chat/composed-message.react.js @@ -138,7 +138,10 @@ ); let inlineEngagement = null; - if (item.threadCreatedFromMessage || item.reactions.size > 0) { + if ( + item.threadCreatedFromMessage || + Object.keys(item.reactions).length > 0 + ) { const positioning = isViewer ? 'right' : 'left'; const inlineEngagementPositionStyle = positioning === 'left' diff --git a/native/chat/inline-engagement.react.js b/native/chat/inline-engagement.react.js --- a/native/chat/inline-engagement.react.js +++ b/native/chat/inline-engagement.react.js @@ -9,7 +9,7 @@ } from 'react-native-reanimated'; import useInlineEngagementText from 'lib/hooks/inline-engagement-text.react.js'; -import type { MessageReactionInfo } from 'lib/selectors/chat-selectors.js'; +import type { ReactionInfo } from 'lib/selectors/chat-selectors.js'; import { stringForReactionList } from 'lib/shared/reaction-utils.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; @@ -28,7 +28,7 @@ type Props = { +threadInfo: ?ThreadInfo, - +reactions?: $ReadOnlyMap, + +reactions?: ReactionInfo, +disabled?: boolean, }; function InlineEngagement(props: Props): React.Node { @@ -88,7 +88,7 @@ ); const reactionList = React.useMemo(() => { - if (!reactions || reactions.size === 0) { + if (!reactions || Object.keys(reactions).length === 0) { return null; } diff --git a/native/chat/message-reactions-modal.react.js b/native/chat/message-reactions-modal.react.js --- a/native/chat/message-reactions-modal.react.js +++ b/native/chat/message-reactions-modal.react.js @@ -6,7 +6,7 @@ import { View, Text, FlatList, TouchableHighlight } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import type { MessageReactionInfo } from 'lib/selectors/chat-selectors.js'; +import type { ReactionInfo } from 'lib/selectors/chat-selectors.js'; import { useMessageReactionsList } from 'lib/shared/reaction-utils.js'; import Modal from '../components/modal.react.js'; @@ -15,7 +15,7 @@ import { useColors, useStyles } from '../themes/colors.js'; export type MessageReactionsModalParams = { - +reactions: $ReadOnlyMap, + +reactions: ReactionInfo, }; type Props = { diff --git a/native/chat/multimedia-message-utils.js b/native/chat/multimedia-message-utils.js --- a/native/chat/multimedia-message-utils.js +++ b/native/chat/multimedia-message-utils.js @@ -117,7 +117,7 @@ if (multimediaMessageSendFailed(item)) { height += failedSendHeight; } - if (item.threadCreatedFromMessage || item.reactions.size > 0) { + if (item.threadCreatedFromMessage || Object.keys(item.reactions).length > 0) { height += inlineEngagementStyle.height + inlineEngagementStyle.marginTop + diff --git a/native/chat/reaction-message-utils.js b/native/chat/reaction-message-utils.js --- a/native/chat/reaction-message-utils.js +++ b/native/chat/reaction-message-utils.js @@ -8,7 +8,7 @@ sendReactionMessage, sendReactionMessageActionTypes, } from 'lib/actions/message-actions.js'; -import type { MessageReactionInfo } from 'lib/selectors/chat-selectors.js'; +import type { ReactionInfo } from 'lib/selectors/chat-selectors.js'; import { messageTypes } from 'lib/types/message-types.js'; import type { RawReactionMessageInfo } from 'lib/types/messages/reaction.js'; import { @@ -28,7 +28,7 @@ messageID: ?string, localID: string, threadID: string, - reactions: $ReadOnlyMap, + reactions: ReactionInfo, ): (reaction: string) => mixed { const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, @@ -45,7 +45,9 @@ invariant(viewerID, 'viewerID should be set'); - const viewerReacted = reactions.get(reaction)?.viewerReacted; + const viewerReacted = reactions[reaction] + ? reactions[reaction].viewerReacted + : false; const action = viewerReacted ? 'remove_reaction' : 'add_reaction'; const reactionMessagePromise = (async () => { diff --git a/native/chat/robotext-message.react.js b/native/chat/robotext-message.react.js --- a/native/chat/robotext-message.react.js +++ b/native/chat/robotext-message.react.js @@ -53,7 +53,7 @@ const styles = useStyles(unboundStyles); let inlineEngagement = null; - if (item.threadCreatedFromMessage || item.reactions.size > 0) { + if (item.threadCreatedFromMessage || Object.keys(item.reactions).length > 0) { inlineEngagement = ( 0) { + if (item.threadCreatedFromMessage || Object.keys(item.reactions).length > 0) { height += inlineEngagementStyle.height + inlineEngagementStyle.marginTop + @@ -79,7 +79,7 @@ function robotextMessageItemHeight( item: ChatRobotextMessageInfoItemWithHeight, ): number { - if (item.threadCreatedFromMessage || item.reactions.size > 0) { + if (item.threadCreatedFromMessage || Object.keys(item.reactions).length > 0) { return item.contentHeight + inlineEngagementStyle.height; } return item.contentHeight; diff --git a/native/types/chat-types.js b/native/types/chat-types.js --- a/native/types/chat-types.js +++ b/native/types/chat-types.js @@ -1,6 +1,6 @@ // @flow -import type { MessageReactionInfo } from 'lib/selectors/chat-selectors.js'; +import type { ReactionInfo } from 'lib/selectors/chat-selectors.js'; import type { LocalMessageInfo, MultimediaMessageInfo, @@ -23,7 +23,7 @@ +robotext: EntityText, +threadCreatedFromMessage: ?ThreadInfo, +contentHeight: number, - +reactions: $ReadOnlyMap, + +reactions: ReactionInfo, }; export type ChatTextMessageInfoItemWithHeight = { @@ -37,7 +37,7 @@ +endsCluster: boolean, +contentHeight: number, +threadCreatedFromMessage: ?ThreadInfo, - +reactions: $ReadOnlyMap, + +reactions: ReactionInfo, }; export type MultimediaContentSizes = { @@ -57,7 +57,7 @@ +endsCluster: boolean, +threadCreatedFromMessage: ?ThreadInfo, +pendingUploads: ?MessagePendingUploads, - +reactions: $ReadOnlyMap, + +reactions: ReactionInfo, }; export type ChatMessageInfoItemWithHeight = diff --git a/web/chat/composed-message.react.js b/web/chat/composed-message.react.js --- a/web/chat/composed-message.react.js +++ b/web/chat/composed-message.react.js @@ -123,7 +123,7 @@ let inlineEngagement = null; if ( (this.props.containsInlineEngagement && item.threadCreatedFromMessage) || - item.reactions.size > 0 + Object.keys(item.reactions).length > 0 ) { const positioning = isViewer ? 'right' : 'left'; inlineEngagement = ( diff --git a/web/chat/inline-engagement.react.js b/web/chat/inline-engagement.react.js --- a/web/chat/inline-engagement.react.js +++ b/web/chat/inline-engagement.react.js @@ -5,7 +5,7 @@ import { useModalContext } from 'lib/components/modal-provider.react.js'; import useInlineEngagementText from 'lib/hooks/inline-engagement-text.react.js'; -import type { MessageReactionInfo } from 'lib/selectors/chat-selectors.js'; +import type { ReactionInfo } from 'lib/selectors/chat-selectors.js'; import { stringForReactionList } from 'lib/shared/reaction-utils.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; @@ -16,7 +16,7 @@ type Props = { +threadInfo: ?ThreadInfo, - +reactions?: $ReadOnlyMap, + +reactions?: ReactionInfo, +positioning: 'left' | 'center' | 'right', }; function InlineEngagement(props: Props): React.Node { @@ -33,7 +33,7 @@ }, ]); - const reactionsExist = reactions && reactions.size > 0; + const reactionsExist = reactions && Object.keys(reactions).length > 0; const threadsContainerClasses = classNames({ [css.threadsContainer]: threadInfo && !reactionsExist, @@ -74,7 +74,7 @@ ); const reactionsList = React.useMemo(() => { - if (!reactions || reactions.size === 0) { + if (!reactions || Object.keys(reactions).length === 0) { return null; } diff --git a/web/chat/message-tooltip.react.js b/web/chat/message-tooltip.react.js --- a/web/chat/message-tooltip.react.js +++ b/web/chat/message-tooltip.react.js @@ -121,7 +121,10 @@ const onEmojiSelect = React.useCallback( emoji => { const reactionInput = emoji.native; - const viewerReacted = !!reactions.get(reactionInput)?.viewerReacted; + + const viewerReacted = reactions[reactionInput] + ? reactions[reactionInput].viewerReacted + : false; const action = viewerReacted ? 'remove_reaction' : 'add_reaction'; sendReaction(reactionInput, action); diff --git a/web/chat/robotext-message.react.js b/web/chat/robotext-message.react.js --- a/web/chat/robotext-message.react.js +++ b/web/chat/robotext-message.react.js @@ -38,7 +38,7 @@ let inlineEngagement; const { item } = props; const { threadCreatedFromMessage, reactions } = item; - if (threadCreatedFromMessage || reactions.size > 0) { + if (threadCreatedFromMessage || Object.keys(reactions).length > 0) { inlineEngagement = (
void, - +reactions: $ReadOnlyMap, + +reactions: ReactionInfo, }; function MessageReactionsModal(props: Props): React.Node {