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,20 +1,38 @@ // @flow +import invariant from 'invariant'; import _sortBy from 'lodash/fp/sortBy.js'; import * as React from 'react'; - +import uuid from 'uuid'; + +import { + type OutboundDMOperationSpecification, + dmOperationSpecificationTypes, +} from './dm-ops/dm-op-types.js'; +import { useProcessAndSendDMOperation } from './dm-ops/process-dm-ops.js'; +import { getNextLocalID } from './message-utils.js'; import { relationshipBlockedInEitherDirection } from './relationship-utils.js'; import { useThreadHasPermission } from './thread-utils.js'; import { stringForUserExplicit } from './user-utils.js'; +import { sendReactionMessageActionTypes } from '../actions/message-actions.js'; import { useENSNames } from '../hooks/ens-cache.js'; +import { useSendReactionMessage } from '../hooks/message-hooks.js'; import type { ReactionInfo } from '../selectors/chat-selectors.js'; +import type { DMSendReactionMessageOperation } from '../types/dm-ops.js'; +import { messageTypes } from '../types/message-types-enum.js'; import type { ComposableMessageInfo, RobotextMessageInfo, } from '../types/message-types.js'; +import type { RawReactionMessageInfo } from '../types/messages/reaction.js'; import type { ThreadInfo } from '../types/minimally-encoded-thread-permissions-types.js'; import { threadPermissions } from '../types/thread-permission-types.js'; -import { threadTypeIsThick } from '../types/thread-types-enum.js'; +import { + thickThreadTypes, + threadTypeIsThick, +} from '../types/thread-types-enum.js'; +import { getMessageForException, SendMessageError } from '../utils/errors.js'; +import { useDispatchActionPromise } from '../utils/redux-promise-utils.js'; import { useSelector } from '../utils/redux-utils.js'; function useViewerAlreadySelectedMessageReactions( @@ -106,8 +124,126 @@ ); } +function useSendReactionBase( + messageID: ?string, + threadInfo: ThreadInfo, + reactions: ReactionInfo, + showErrorAlert: () => mixed, +): (reaction: string) => mixed { + const viewerID = useSelector( + state => state.currentUserInfo && state.currentUserInfo.id, + ); + + const callSendReactionMessage = useSendReactionMessage(); + const dispatchActionPromise = useDispatchActionPromise(); + const processAndSendDMOperation = useProcessAndSendDMOperation(); + + return React.useCallback( + reaction => { + if (!messageID) { + return; + } + + const localID = getNextLocalID(); + + invariant(viewerID, 'viewerID should be set'); + + const viewerReacted = reactions[reaction] + ? reactions[reaction].viewerReacted + : false; + const action = viewerReacted ? 'remove_reaction' : 'add_reaction'; + + const threadID = threadInfo.id; + + if (threadTypeIsThick(threadInfo.type)) { + const op: DMSendReactionMessageOperation = { + type: 'send_reaction_message', + threadID, + creatorID: viewerID, + time: Date.now(), + messageID: uuid.v4(), + targetMessageID: messageID, + reaction, + action, + }; + const opSpecification: OutboundDMOperationSpecification = { + type: dmOperationSpecificationTypes.OUTBOUND, + op, + recipients: { + type: 'all_thread_members', + threadID: + threadInfo.type === thickThreadTypes.THICK_SIDEBAR && + threadInfo.parentThreadID + ? threadInfo.parentThreadID + : threadInfo.id, + }, + }; + void processAndSendDMOperation(opSpecification); + return; + } + + const reactionMessagePromise = (async () => { + try { + const result = await callSendReactionMessage({ + threadID, + localID, + targetMessageID: messageID, + reaction, + action, + }); + return { + localID, + serverID: result.id, + threadID, + time: result.time, + }; + } catch (e) { + showErrorAlert(); + const exceptionMessage = getMessageForException(e) ?? ''; + throw new SendMessageError( + `Exception while sending reaction: ${exceptionMessage}`, + localID, + threadID, + ); + } + })(); + + const startingPayload: RawReactionMessageInfo = { + type: messageTypes.REACTION, + threadID, + localID, + creatorID: viewerID, + time: Date.now(), + targetMessageID: messageID, + reaction, + action, + }; + + void dispatchActionPromise( + sendReactionMessageActionTypes, + reactionMessagePromise, + undefined, + startingPayload, + ); + }, + [ + messageID, + viewerID, + reactions, + threadInfo.id, + threadInfo.type, + threadInfo.parentThreadID, + dispatchActionPromise, + processAndSendDMOperation, + callSendReactionMessage, + showErrorAlert, + ], + ); +} + export { useViewerAlreadySelectedMessageReactions, useMessageReactionsList, useCanCreateReactionFromMessage, + useSendReactionBase, }; 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 @@ -1,28 +1,10 @@ // @flow -import invariant from 'invariant'; import * as React from 'react'; -import uuid from 'uuid'; -import { sendReactionMessageActionTypes } from 'lib/actions/message-actions.js'; -import { useSendReactionMessage } from 'lib/hooks/message-hooks.js'; import type { ReactionInfo } from 'lib/selectors/chat-selectors.js'; -import { - dmOperationSpecificationTypes, - type OutboundDMOperationSpecification, -} from 'lib/shared/dm-ops/dm-op-types.js'; -import { useProcessAndSendDMOperation } from 'lib/shared/dm-ops/process-dm-ops.js'; -import { getNextLocalID } from 'lib/shared/message-utils.js'; -import type { DMSendReactionMessageOperation } from 'lib/types/dm-ops.js'; -import { messageTypes } from 'lib/types/message-types-enum.js'; -import type { RawReactionMessageInfo } from 'lib/types/messages/reaction.js'; +import { useSendReactionBase } from 'lib/shared/reaction-utils.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types'; -import { - thickThreadTypes, - threadTypeIsThick, -} from 'lib/types/thread-types-enum.js'; -import { SendMessageError, getMessageForException } from 'lib/utils/errors.js'; -import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { useSelector } from '../redux/redux-utils.js'; import type { @@ -31,125 +13,27 @@ } from '../types/layout-types.js'; import Alert from '../utils/alert.js'; +function showReactionErrorAlert() { + Alert.alert( + 'Couldn’t send the reaction', + 'Please try again later', + [{ text: 'OK' }], + { + cancelable: true, + }, + ); +} + function useSendReaction( messageID: ?string, threadInfo: ThreadInfo, reactions: ReactionInfo, ): (reaction: string) => mixed { - const viewerID = useSelector( - state => state.currentUserInfo && state.currentUserInfo.id, - ); - - const callSendReactionMessage = useSendReactionMessage(); - const dispatchActionPromise = useDispatchActionPromise(); - const processAndSendDMOperation = useProcessAndSendDMOperation(); - - return React.useCallback( - reaction => { - if (!messageID) { - return; - } - - const localID = getNextLocalID(); - - invariant(viewerID, 'viewerID should be set'); - - const viewerReacted = reactions[reaction] - ? reactions[reaction].viewerReacted - : false; - const action = viewerReacted ? 'remove_reaction' : 'add_reaction'; - - const threadID = threadInfo.id; - - if (threadTypeIsThick(threadInfo.type)) { - const op: DMSendReactionMessageOperation = { - type: 'send_reaction_message', - threadID, - creatorID: viewerID, - time: Date.now(), - messageID: uuid.v4(), - targetMessageID: messageID, - reaction, - action, - }; - const opSpecification: OutboundDMOperationSpecification = { - type: dmOperationSpecificationTypes.OUTBOUND, - op, - recipients: { - type: 'all_thread_members', - threadID: - threadInfo.type === thickThreadTypes.THICK_SIDEBAR && - threadInfo.parentThreadID - ? threadInfo.parentThreadID - : threadInfo.id, - }, - }; - void processAndSendDMOperation(opSpecification); - return; - } - - const reactionMessagePromise = (async () => { - try { - const result = await callSendReactionMessage({ - threadID, - localID, - targetMessageID: messageID, - reaction, - action, - }); - return { - localID, - serverID: result.id, - threadID, - time: result.time, - }; - } catch (e) { - Alert.alert( - 'Couldn’t send the reaction', - 'Please try again later', - [{ text: 'OK' }], - { - cancelable: true, - }, - ); - const exceptionMessage = getMessageForException(e) ?? ''; - throw new SendMessageError( - `Exception while sending reaction: ${exceptionMessage}`, - localID, - threadID, - ); - } - })(); - - const startingPayload: RawReactionMessageInfo = { - type: messageTypes.REACTION, - threadID, - localID, - creatorID: viewerID, - time: Date.now(), - targetMessageID: messageID, - reaction, - action, - }; - - void dispatchActionPromise( - sendReactionMessageActionTypes, - reactionMessagePromise, - undefined, - startingPayload, - ); - }, - [ - messageID, - viewerID, - reactions, - threadInfo.id, - threadInfo.type, - threadInfo.parentThreadID, - dispatchActionPromise, - processAndSendDMOperation, - callSendReactionMessage, - ], + return useSendReactionBase( + messageID, + threadInfo, + reactions, + showReactionErrorAlert, ); } diff --git a/web/chat/reaction-message-utils.js b/web/chat/reaction-message-utils.js --- a/web/chat/reaction-message-utils.js +++ b/web/chat/reaction-message-utils.js @@ -1,32 +1,13 @@ // @flow -import invariant from 'invariant'; import * as React from 'react'; -import uuid from 'uuid'; -import { sendReactionMessageActionTypes } from 'lib/actions/message-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; -import { useSendReactionMessage } from 'lib/hooks/message-hooks.js'; -import type { ReactionInfo } from 'lib/selectors/chat-selectors'; -import { - dmOperationSpecificationTypes, - type OutboundDMOperationSpecification, -} from 'lib/shared/dm-ops/dm-op-types.js'; -import { useProcessAndSendDMOperation } from 'lib/shared/dm-ops/process-dm-ops.js'; -import { getNextLocalID } from 'lib/shared/message-utils.js'; -import { type DMSendReactionMessageOperation } from 'lib/types/dm-ops.js'; -import { messageTypes } from 'lib/types/message-types-enum.js'; -import type { RawReactionMessageInfo } from 'lib/types/messages/reaction.js'; +import type { ReactionInfo } from 'lib/selectors/chat-selectors.js'; +import { useSendReactionBase } from 'lib/shared/reaction-utils.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; -import { - thickThreadTypes, - threadTypeIsThick, -} from 'lib/types/thread-types-enum.js'; -import { SendMessageError, getMessageForException } from 'lib/utils/errors.js'; -import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import Alert from '../modals/alert.react.js'; -import { useSelector } from '../redux/redux-utils.js'; import { type TooltipSize, type TooltipPositionStyle, @@ -40,121 +21,16 @@ ): (reaction: string) => mixed { const { pushModal } = useModalContext(); - const viewerID = useSelector( - state => state.currentUserInfo && state.currentUserInfo.id, - ); - - const callSendReactionMessage = useSendReactionMessage(); - const dispatchActionPromise = useDispatchActionPromise(); - const processAndSendDMOperation = useProcessAndSendDMOperation(); - - return React.useCallback( - reaction => { - if (!messageID) { - return; - } - - const localID = getNextLocalID(); - - invariant(viewerID, 'viewerID should be set'); - - const viewerReacted = reactions[reaction] - ? reactions[reaction].viewerReacted - : false; - const action = viewerReacted ? 'remove_reaction' : 'add_reaction'; - - const threadID = threadInfo.id; - - if (threadTypeIsThick(threadInfo.type)) { - const op: DMSendReactionMessageOperation = { - type: 'send_reaction_message', - threadID, - creatorID: viewerID, - time: Date.now(), - messageID: uuid.v4(), - targetMessageID: messageID, - reaction, - action, - }; - const opSpecification: OutboundDMOperationSpecification = { - type: dmOperationSpecificationTypes.OUTBOUND, - op, - recipients: { - type: 'all_thread_members', - threadID: - threadInfo.type === thickThreadTypes.THICK_SIDEBAR && - threadInfo.parentThreadID - ? threadInfo.parentThreadID - : threadInfo.id, - }, - }; - void processAndSendDMOperation(opSpecification); - return; - } - - const reactionMessagePromise = (async () => { - try { - const result = await callSendReactionMessage({ - threadID, - localID, - targetMessageID: messageID, - reaction, - action, - }); - const serverID: string = result.id; - const time: number = result.time; - return { - localID, - serverID, - threadID, - time, - }; - } catch (e) { - pushModal( - - Please try again later - , - ); - const exceptionMessage = getMessageForException(e) ?? ''; - throw new SendMessageError( - `Exception while sending reaction: ${exceptionMessage}`, - localID, - threadID, - ); - } - })(); - - const startingPayload: RawReactionMessageInfo = { - type: messageTypes.REACTION, - threadID, - localID, - creatorID: viewerID, - time: Date.now(), - targetMessageID: messageID, - reaction, - action, - }; - - void dispatchActionPromise( - sendReactionMessageActionTypes, - reactionMessagePromise, - undefined, - startingPayload, - ); - }, - [ - messageID, - viewerID, - reactions, - threadInfo.id, - threadInfo.type, - threadInfo.parentThreadID, - dispatchActionPromise, - processAndSendDMOperation, - callSendReactionMessage, - pushModal, - ], + const showErrorAlert = React.useCallback( + () => + pushModal( + + Please try again later + , + ), + [pushModal], ); + return useSendReactionBase(messageID, threadInfo, reactions, showErrorAlert); } type EmojiKeyboardPosition = {