mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-02 08:13:37 +01:00
377 lines
14 KiB
TypeScript
377 lines
14 KiB
TypeScript
// Copyright 2019 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import React, { useCallback, useMemo, memo } from 'react';
|
|
import { useSelector } from 'react-redux';
|
|
import { CompositionArea } from '../../components/CompositionArea.dom.js';
|
|
import { useContactNameData } from '../../components/conversation/ContactName.dom.js';
|
|
import type {
|
|
DraftBodyRanges,
|
|
HydratedBodyRangesType,
|
|
} from '../../types/BodyRange.std.js';
|
|
import { hydrateRanges } from '../../util/BodyRange.node.js';
|
|
import { strictAssert } from '../../util/assert.std.js';
|
|
import { getAddedByForOurPendingInvitation } from '../../util/getAddedByForOurPendingInvitation.preload.js';
|
|
import { AutoSubstituteAsciiEmojis } from '../../quill/auto-substitute-ascii-emojis/index.dom.js';
|
|
import { imageToBlurHash } from '../../util/imageToBlurHash.dom.js';
|
|
import { isConversationSMSOnly } from '../../util/isConversationSMSOnly.std.js';
|
|
import { isSignalConversation } from '../../util/isSignalConversation.dom.js';
|
|
import {
|
|
getErrorDialogAudioRecorderType,
|
|
getRecordingState,
|
|
} from '../selectors/audioRecorder.std.js';
|
|
import { getPreferredBadgeSelector } from '../selectors/badges.preload.js';
|
|
import { getComposerStateForConversationIdSelector } from '../selectors/composer.preload.js';
|
|
import {
|
|
getCachedConversationMemberColorsSelector,
|
|
getConversationSelector,
|
|
getGroupAdminsSelector,
|
|
getLastEditableMessageId,
|
|
getMessages,
|
|
getSelectedMessageIds,
|
|
isMissingRequiredProfileSharing,
|
|
} from '../selectors/conversations.dom.js';
|
|
import { getHasPanelOpen } from '../selectors/nav.std.js';
|
|
import { getSharedGroupNames } from '../../util/sharedGroupNames.dom.js';
|
|
import {
|
|
getDefaultConversationColor,
|
|
getEmojiSkinToneDefault,
|
|
getItems,
|
|
getTextFormattingEnabled,
|
|
} from '../selectors/items.dom.js';
|
|
import { canForward, getPropsForQuote } from '../selectors/message.preload.js';
|
|
import {
|
|
getIntl,
|
|
getPlatform,
|
|
getTheme,
|
|
getUserConversationId,
|
|
getVersion,
|
|
} from '../selectors/user.std.js';
|
|
import { SmartCompositionRecording } from './CompositionRecording.preload.js';
|
|
import type { SmartCompositionRecordingDraftProps } from './CompositionRecordingDraft.preload.js';
|
|
import { SmartCompositionRecordingDraft } from './CompositionRecordingDraft.preload.js';
|
|
import { useComposerActions } from '../ducks/composer.preload.js';
|
|
import { useConversationsActions } from '../ducks/conversations.preload.js';
|
|
import { useAudioRecorderActions } from '../ducks/audioRecorder.preload.js';
|
|
import { useEmojisActions } from '../ducks/emojis.preload.js';
|
|
import { useGlobalModalActions } from '../ducks/globalModals.preload.js';
|
|
import { useToastActions } from '../ducks/toast.preload.js';
|
|
import { isShowingAnyModal } from '../selectors/globalModals.std.js';
|
|
import { isConversationEverUnregistered } from '../../util/isConversationUnregistered.dom.js';
|
|
import { isDirectConversation } from '../../util/whatTypeOfConversation.dom.js';
|
|
import { isConversationMuted } from '../../util/isConversationMuted.std.js';
|
|
import { itemStorage } from '../../textsecure/Storage.preload.js';
|
|
import { useNavActions } from '../ducks/nav.std.js';
|
|
import { isFeaturedEnabledSelector } from '../../util/isFeatureEnabled.dom.js';
|
|
|
|
function renderSmartCompositionRecording() {
|
|
return <SmartCompositionRecording />;
|
|
}
|
|
|
|
function renderSmartCompositionRecordingDraft(
|
|
draftProps: SmartCompositionRecordingDraftProps
|
|
) {
|
|
return <SmartCompositionRecordingDraft {...draftProps} />;
|
|
}
|
|
|
|
export const SmartCompositionArea = memo(function SmartCompositionArea({
|
|
id,
|
|
}: {
|
|
id: string;
|
|
}) {
|
|
const conversationSelector = useSelector(getConversationSelector);
|
|
const conversation = conversationSelector(id);
|
|
strictAssert(conversation, `Conversation id ${id} not found!`);
|
|
|
|
const i18n = useSelector(getIntl);
|
|
const theme = useSelector(getTheme);
|
|
const emojiSkinToneDefault = useSelector(getEmojiSkinToneDefault);
|
|
const selectedMessageIds = useSelector(getSelectedMessageIds);
|
|
const messageLookup = useSelector(getMessages);
|
|
const isFormattingEnabled = useSelector(getTextFormattingEnabled);
|
|
const items = useSelector(getItems);
|
|
const version = useSelector(getVersion);
|
|
const lastEditableMessageId = useSelector(getLastEditableMessageId);
|
|
const platform = useSelector(getPlatform);
|
|
const shouldHidePopovers = useSelector(getHasPanelOpen);
|
|
const recordingState = useSelector(getRecordingState);
|
|
const errorDialogAudioRecorderType = useSelector(
|
|
getErrorDialogAudioRecorderType
|
|
);
|
|
const hasGlobalModalOpen = useSelector(isShowingAnyModal);
|
|
const hasPanelOpen = useSelector(getHasPanelOpen);
|
|
const getGroupAdmins = useSelector(getGroupAdminsSelector);
|
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
|
const composerStateForConversationIdSelector = useSelector(
|
|
getComposerStateForConversationIdSelector
|
|
);
|
|
const composerState = composerStateForConversationIdSelector(id);
|
|
const { announcementsOnly, areWeAdmin, draftEditMessage, draftBodyRanges } =
|
|
conversation;
|
|
const {
|
|
attachments: draftAttachments,
|
|
focusCounter,
|
|
disabledCounter,
|
|
isViewOnce,
|
|
linkPreviewLoading,
|
|
linkPreviewResult,
|
|
messageCompositionId,
|
|
sendCounter,
|
|
shouldSendHighQualityAttachments,
|
|
} = composerState;
|
|
|
|
const isDisabled = disabledCounter > 0;
|
|
|
|
const areSelectedMessagesForwardable = useMemo(() => {
|
|
return selectedMessageIds?.every(messageId => {
|
|
const message = messageLookup[messageId];
|
|
if (!message) {
|
|
return false;
|
|
}
|
|
return canForward(message);
|
|
});
|
|
}, [messageLookup, selectedMessageIds]);
|
|
|
|
const isActive = useMemo(() => {
|
|
return !hasGlobalModalOpen && !hasPanelOpen;
|
|
}, [hasGlobalModalOpen, hasPanelOpen]);
|
|
|
|
const groupAdmins = useMemo(() => {
|
|
return getGroupAdmins(id);
|
|
}, [getGroupAdmins, id]);
|
|
const getMemberColors = useSelector(
|
|
getCachedConversationMemberColorsSelector
|
|
);
|
|
const memberColors = getMemberColors(id);
|
|
|
|
const addedBy = useMemo(() => {
|
|
if (conversation.type === 'group') {
|
|
return getAddedByForOurPendingInvitation(conversation);
|
|
}
|
|
return null;
|
|
}, [conversation]);
|
|
|
|
const conversationName = useContactNameData(conversation);
|
|
strictAssert(conversationName, 'conversationName is required');
|
|
const addedByName = useContactNameData(addedBy);
|
|
|
|
const hydratedDraftBodyRanges = useMemo(() => {
|
|
return hydrateRanges(draftBodyRanges, conversationSelector);
|
|
}, [conversationSelector, draftBodyRanges]);
|
|
|
|
const convertDraftBodyRangesIntoHydrated = useCallback(
|
|
(
|
|
bodyRanges: DraftBodyRanges | undefined
|
|
): HydratedBodyRangesType | undefined => {
|
|
return hydrateRanges(bodyRanges, conversationSelector);
|
|
},
|
|
[conversationSelector]
|
|
);
|
|
|
|
let { quotedMessage } = composerState;
|
|
if (!quotedMessage && draftEditMessage?.quote) {
|
|
quotedMessage = {
|
|
conversationId: id,
|
|
quote: draftEditMessage.quote,
|
|
};
|
|
}
|
|
|
|
const ourConversationId = useSelector(getUserConversationId);
|
|
const defaultConversationColor = useSelector(getDefaultConversationColor);
|
|
|
|
const quotedMessageProps = useMemo(() => {
|
|
return quotedMessage
|
|
? getPropsForQuote(quotedMessage, {
|
|
conversationSelector,
|
|
ourConversationId,
|
|
defaultConversationColor,
|
|
})
|
|
: undefined;
|
|
}, [
|
|
quotedMessage,
|
|
conversationSelector,
|
|
ourConversationId,
|
|
defaultConversationColor,
|
|
]);
|
|
|
|
const {
|
|
onTextTooLong,
|
|
onCloseLinkPreview,
|
|
addAttachment,
|
|
removeAttachment,
|
|
onClearAttachments,
|
|
processAttachments,
|
|
setMediaQualitySetting,
|
|
setQuoteByMessageId,
|
|
setViewOnce,
|
|
cancelJoinRequest,
|
|
sendStickerMessage,
|
|
sendEditedMessage,
|
|
sendMultiMediaMessage,
|
|
sendPoll,
|
|
setComposerFocus,
|
|
} = useComposerActions();
|
|
const {
|
|
discardEditMessage,
|
|
acceptConversation,
|
|
blockAndReportSpam,
|
|
blockConversation,
|
|
reportSpam,
|
|
deleteConversation,
|
|
toggleSelectMode,
|
|
scrollToMessage,
|
|
setMessageToEdit,
|
|
setMuteExpiration,
|
|
showConversation,
|
|
} = useConversationsActions();
|
|
const { pushPanelForConversation } = useNavActions();
|
|
const { cancelRecording, completeRecording, startRecording, errorRecording } =
|
|
useAudioRecorderActions();
|
|
const { onUseEmoji } = useEmojisActions();
|
|
const {
|
|
showGV2MigrationDialog,
|
|
toggleForwardMessagesModal,
|
|
toggleDraftGifMessageSendModal,
|
|
} = useGlobalModalActions();
|
|
const { showToast } = useToastActions();
|
|
const { onEditorStateChange } = useComposerActions();
|
|
|
|
AutoSubstituteAsciiEmojis.enable(itemStorage.get('autoConvertEmoji', true));
|
|
|
|
return (
|
|
<CompositionArea
|
|
// Base
|
|
conversationId={id}
|
|
draftBodyRanges={hydratedDraftBodyRanges ?? null}
|
|
draftEditMessage={draftEditMessage ?? null}
|
|
draftText={conversation.draftText ?? null}
|
|
focusCounter={focusCounter}
|
|
getPreferredBadge={getPreferredBadge}
|
|
i18n={i18n}
|
|
isDisabled={isDisabled}
|
|
isFormattingEnabled={isFormattingEnabled}
|
|
isPollSend1to1Enabled={isFeaturedEnabledSelector({
|
|
betaKey: 'desktop.pollSend1to1.beta',
|
|
prodKey: 'desktop.pollSend1to1.prod',
|
|
currentVersion: version,
|
|
remoteConfig: items.remoteConfig,
|
|
})}
|
|
isActive={isActive}
|
|
lastEditableMessageId={lastEditableMessageId ?? null}
|
|
messageCompositionId={messageCompositionId}
|
|
platform={platform}
|
|
ourConversationId={ourConversationId}
|
|
sendCounter={sendCounter}
|
|
shouldHidePopovers={shouldHidePopovers}
|
|
theme={theme}
|
|
convertDraftBodyRangesIntoHydrated={convertDraftBodyRangesIntoHydrated}
|
|
onTextTooLong={onTextTooLong}
|
|
pushPanelForConversation={pushPanelForConversation}
|
|
discardEditMessage={discardEditMessage}
|
|
onCloseLinkPreview={onCloseLinkPreview}
|
|
onEditorStateChange={onEditorStateChange}
|
|
// MediaEditor
|
|
conversationSelector={conversationSelector}
|
|
// AudioCapture
|
|
errorDialogAudioRecorderType={errorDialogAudioRecorderType ?? null}
|
|
recordingState={recordingState}
|
|
cancelRecording={cancelRecording}
|
|
completeRecording={completeRecording}
|
|
startRecording={startRecording}
|
|
errorRecording={errorRecording}
|
|
// AttachmentsList
|
|
draftAttachments={draftAttachments}
|
|
addAttachment={addAttachment}
|
|
removeAttachment={removeAttachment}
|
|
onClearAttachments={onClearAttachments}
|
|
processAttachments={processAttachments}
|
|
// MediaEditor
|
|
imageToBlurHash={imageToBlurHash}
|
|
// MediaQualitySelector
|
|
shouldSendHighQualityAttachments={
|
|
shouldSendHighQualityAttachments !== undefined
|
|
? shouldSendHighQualityAttachments
|
|
: itemStorage.get('sent-media-quality') === 'high'
|
|
}
|
|
setMediaQualitySetting={setMediaQualitySetting}
|
|
// StagedLinkPreview
|
|
linkPreviewLoading={linkPreviewLoading}
|
|
linkPreviewResult={linkPreviewResult ?? null}
|
|
// Quote
|
|
quotedMessageId={quotedMessage?.quote?.messageId ?? null}
|
|
quotedMessageProps={quotedMessageProps ?? null}
|
|
quotedMessageAuthorAci={quotedMessage?.quote?.authorAci ?? null}
|
|
quotedMessageSentAt={quotedMessage?.quote?.id ?? null}
|
|
setQuoteByMessageId={setQuoteByMessageId}
|
|
// View Once
|
|
isViewOnce={isViewOnce}
|
|
setViewOnce={setViewOnce}
|
|
// Fun Picker
|
|
emojiSkinToneDefault={emojiSkinToneDefault}
|
|
onSelectEmoji={onUseEmoji}
|
|
// Message Requests
|
|
acceptedMessageRequest={conversation.acceptedMessageRequest ?? null}
|
|
removalStage={conversation.removalStage ?? null}
|
|
addedByName={addedByName}
|
|
conversationName={conversationName}
|
|
conversationType={conversation.type}
|
|
isBlocked={conversation.isBlocked ?? false}
|
|
isReported={conversation.isReported ?? false}
|
|
isHidden={conversation.removalStage != null}
|
|
isSmsOnlyOrUnregistered={
|
|
isDirectConversation(conversation) &&
|
|
(isConversationSMSOnly(conversation) ||
|
|
isConversationEverUnregistered(conversation))
|
|
}
|
|
isFetchingUUID={conversation.isFetchingUUID ?? null}
|
|
isMissingMandatoryProfileSharing={isMissingRequiredProfileSharing(
|
|
conversation
|
|
)}
|
|
acceptConversation={acceptConversation}
|
|
blockAndReportSpam={blockAndReportSpam}
|
|
blockConversation={blockConversation}
|
|
reportSpam={reportSpam}
|
|
deleteConversation={deleteConversation}
|
|
getSharedGroupNames={getSharedGroupNames}
|
|
// Signal Conversation
|
|
isSignalConversation={isSignalConversation(conversation)}
|
|
isMuted={isConversationMuted(conversation)}
|
|
setMuteExpiration={setMuteExpiration}
|
|
// Groups
|
|
groupVersion={conversation.groupVersion ?? null}
|
|
isGroupV1AndDisabled={conversation.isGroupV1AndDisabled ?? null}
|
|
left={conversation.left ?? null}
|
|
announcementsOnly={announcementsOnly ?? null}
|
|
areWeAdmin={areWeAdmin ?? null}
|
|
areWePending={conversation.areWePending ?? null}
|
|
areWePendingApproval={conversation.areWePendingApproval ?? null}
|
|
groupAdmins={groupAdmins}
|
|
memberColors={memberColors}
|
|
renderSmartCompositionRecording={renderSmartCompositionRecording}
|
|
renderSmartCompositionRecordingDraft={
|
|
renderSmartCompositionRecordingDraft
|
|
}
|
|
showGV2MigrationDialog={showGV2MigrationDialog}
|
|
cancelJoinRequest={cancelJoinRequest}
|
|
sortedGroupMembers={conversation.sortedGroupMembers ?? null}
|
|
// Select Mode
|
|
selectedMessageIds={selectedMessageIds}
|
|
areSelectedMessagesForwardable={areSelectedMessagesForwardable}
|
|
toggleSelectMode={toggleSelectMode}
|
|
toggleForwardMessagesModal={toggleForwardMessagesModal}
|
|
// DraftGifMessageSendModal
|
|
toggleDraftGifMessageSendModal={toggleDraftGifMessageSendModal}
|
|
// Dispatch
|
|
showToast={showToast}
|
|
sendStickerMessage={sendStickerMessage}
|
|
sendEditedMessage={sendEditedMessage}
|
|
sendMultiMediaMessage={sendMultiMediaMessage}
|
|
sendPoll={sendPoll}
|
|
scrollToMessage={scrollToMessage}
|
|
setComposerFocus={setComposerFocus}
|
|
setMessageToEdit={setMessageToEdit}
|
|
showConversation={showConversation}
|
|
/>
|
|
);
|
|
});
|