Refactor smart components
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
parent
05c09ef769
commit
27b55e472d
109 changed files with 3583 additions and 2629 deletions
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
|
@ -13,7 +13,6 @@ import {
|
|||
} from '../test-both/helpers/getDefaultConversation';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import { AddUserToAnotherGroupModal } from './AddUserToAnotherGroupModal';
|
||||
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
|
@ -36,7 +35,6 @@ const Template: StoryFn<Props> = args => {
|
|||
toggleAddUserToAnotherGroupModal={action(
|
||||
'toggleAddUserToAnotherGroupModal'
|
||||
)}
|
||||
theme={useContext(StorybookThemeContext)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ import React, { useCallback } from 'react';
|
|||
import type { ListRowProps } from 'react-virtualized';
|
||||
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import type { LocalizerType, ThemeType } from '../types/Util';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { ToastType } from '../types/Toast';
|
||||
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
|
||||
import { ConfirmationDialog } from './ConfirmationDialog';
|
||||
|
@ -25,7 +25,6 @@ import { SizeObserver } from '../hooks/useSizeObserver';
|
|||
|
||||
type OwnProps = {
|
||||
i18n: LocalizerType;
|
||||
theme: ThemeType;
|
||||
contact: Pick<ConversationType, 'id' | 'title' | 'serviceId' | 'pni'>;
|
||||
candidateConversations: ReadonlyArray<ConversationType>;
|
||||
regionCode: string | undefined;
|
||||
|
|
|
@ -139,9 +139,23 @@ export type PropsType = {
|
|||
pauseVoiceNotePlayer: () => void;
|
||||
} & Pick<ReactionPickerProps, 'renderEmojiPicker'>;
|
||||
|
||||
type ActiveCallManagerPropsType = PropsType & {
|
||||
type ActiveCallManagerPropsType = {
|
||||
activeCall: ActiveCallType;
|
||||
};
|
||||
} & Omit<
|
||||
PropsType,
|
||||
| 'acceptCall'
|
||||
| 'bounceAppIconStart'
|
||||
| 'bounceAppIconStop'
|
||||
| 'declineCall'
|
||||
| 'hasInitialLoadCompleted'
|
||||
| 'incomingCall'
|
||||
| 'isConversationTooBigToRin'
|
||||
| 'notifyForCall'
|
||||
| 'playRingtone'
|
||||
| 'setIsCallActive'
|
||||
| 'stopRingtone'
|
||||
| 'isConversationTooBigToRing'
|
||||
>;
|
||||
|
||||
function ActiveCallManager({
|
||||
activeCall,
|
||||
|
@ -472,28 +486,69 @@ function ActiveCallManager({
|
|||
);
|
||||
}
|
||||
|
||||
export function CallManager(props: PropsType): JSX.Element | null {
|
||||
const {
|
||||
acceptCall,
|
||||
activeCall,
|
||||
bounceAppIconStart,
|
||||
bounceAppIconStop,
|
||||
declineCall,
|
||||
i18n,
|
||||
incomingCall,
|
||||
notifyForCall,
|
||||
playRingtone,
|
||||
stopRingtone,
|
||||
setIsCallActive,
|
||||
setOutgoingRing,
|
||||
} = props;
|
||||
|
||||
export function CallManager({
|
||||
acceptCall,
|
||||
activeCall,
|
||||
availableCameras,
|
||||
bounceAppIconStart,
|
||||
bounceAppIconStop,
|
||||
callLink,
|
||||
cancelCall,
|
||||
changeCallView,
|
||||
closeNeedPermissionScreen,
|
||||
declineCall,
|
||||
getGroupCallVideoFrameSource,
|
||||
getPreferredBadge,
|
||||
getPresentingSources,
|
||||
hangUpActiveCall,
|
||||
hasInitialLoadCompleted,
|
||||
i18n,
|
||||
incomingCall,
|
||||
isConversationTooBigToRing,
|
||||
isGroupCallRaiseHandEnabled,
|
||||
isGroupCallReactionsEnabled,
|
||||
keyChangeOk,
|
||||
me,
|
||||
notifyForCall,
|
||||
openSystemPreferencesAction,
|
||||
pauseVoiceNotePlayer,
|
||||
playRingtone,
|
||||
renderDeviceSelection,
|
||||
renderEmojiPicker,
|
||||
renderReactionPicker,
|
||||
renderSafetyNumberViewer,
|
||||
sendGroupCallRaiseHand,
|
||||
sendGroupCallReaction,
|
||||
setGroupCallVideoRequest,
|
||||
setIsCallActive,
|
||||
setLocalAudio,
|
||||
setLocalPreview,
|
||||
setLocalVideo,
|
||||
setOutgoingRing,
|
||||
setPresenting,
|
||||
setRendererCanvas,
|
||||
showToast,
|
||||
startCall,
|
||||
stopRingtone,
|
||||
switchFromPresentationView,
|
||||
switchToPresentationView,
|
||||
theme,
|
||||
toggleParticipants,
|
||||
togglePip,
|
||||
toggleScreenRecordingPermissionsDialog,
|
||||
toggleSettings,
|
||||
}: PropsType): JSX.Element | null {
|
||||
const isCallActive = Boolean(activeCall);
|
||||
useEffect(() => {
|
||||
setIsCallActive(isCallActive);
|
||||
}, [isCallActive, setIsCallActive]);
|
||||
|
||||
const shouldRing = getShouldRing(props);
|
||||
const shouldRing = getShouldRing({
|
||||
activeCall,
|
||||
incomingCall,
|
||||
isConversationTooBigToRing,
|
||||
hasInitialLoadCompleted,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (shouldRing) {
|
||||
log.info('CallManager: Playing ringtone');
|
||||
|
@ -529,8 +584,50 @@ export function CallManager(props: PropsType): JSX.Element | null {
|
|||
// `props` should logically have an `activeCall` at this point, but TypeScript can't
|
||||
// figure that out, so we pass it in again.
|
||||
return (
|
||||
<CallingToastProvider i18n={props.i18n}>
|
||||
<ActiveCallManager {...props} activeCall={activeCall} />
|
||||
<CallingToastProvider i18n={i18n}>
|
||||
<ActiveCallManager
|
||||
activeCall={activeCall}
|
||||
availableCameras={availableCameras}
|
||||
callLink={callLink}
|
||||
cancelCall={cancelCall}
|
||||
changeCallView={changeCallView}
|
||||
closeNeedPermissionScreen={closeNeedPermissionScreen}
|
||||
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
getPresentingSources={getPresentingSources}
|
||||
hangUpActiveCall={hangUpActiveCall}
|
||||
i18n={i18n}
|
||||
isGroupCallRaiseHandEnabled={isGroupCallRaiseHandEnabled}
|
||||
isGroupCallReactionsEnabled={isGroupCallReactionsEnabled}
|
||||
keyChangeOk={keyChangeOk}
|
||||
me={me}
|
||||
openSystemPreferencesAction={openSystemPreferencesAction}
|
||||
pauseVoiceNotePlayer={pauseVoiceNotePlayer}
|
||||
renderDeviceSelection={renderDeviceSelection}
|
||||
renderEmojiPicker={renderEmojiPicker}
|
||||
renderReactionPicker={renderReactionPicker}
|
||||
renderSafetyNumberViewer={renderSafetyNumberViewer}
|
||||
sendGroupCallRaiseHand={sendGroupCallRaiseHand}
|
||||
sendGroupCallReaction={sendGroupCallReaction}
|
||||
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
||||
setLocalAudio={setLocalAudio}
|
||||
setLocalPreview={setLocalPreview}
|
||||
setLocalVideo={setLocalVideo}
|
||||
setOutgoingRing={setOutgoingRing}
|
||||
setPresenting={setPresenting}
|
||||
setRendererCanvas={setRendererCanvas}
|
||||
showToast={showToast}
|
||||
startCall={startCall}
|
||||
switchFromPresentationView={switchFromPresentationView}
|
||||
switchToPresentationView={switchToPresentationView}
|
||||
theme={theme}
|
||||
toggleParticipants={toggleParticipants}
|
||||
togglePip={togglePip}
|
||||
toggleScreenRecordingPermissionsDialog={
|
||||
toggleScreenRecordingPermissionsDialog
|
||||
}
|
||||
toggleSettings={toggleSettings}
|
||||
/>
|
||||
</CallingToastProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,17 +8,20 @@ import { Button, ButtonVariant } from './Button';
|
|||
import { Modal } from './Modal';
|
||||
import { Spinner } from './Spinner';
|
||||
|
||||
export type PropsType = {
|
||||
export type PropsType = Readonly<{
|
||||
i18n: LocalizerType;
|
||||
isPending: boolean;
|
||||
|
||||
onContinue: () => void;
|
||||
onSkip: () => void;
|
||||
};
|
||||
|
||||
export function CaptchaDialog(props: Readonly<PropsType>): JSX.Element {
|
||||
const { i18n, isPending, onSkip, onContinue } = props;
|
||||
}>;
|
||||
|
||||
export function CaptchaDialog({
|
||||
i18n,
|
||||
isPending,
|
||||
onSkip,
|
||||
onContinue,
|
||||
}: PropsType): JSX.Element {
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
|
|
@ -18,9 +18,12 @@ export type PropsType = {
|
|||
isPending: boolean;
|
||||
} & PropsActionsType;
|
||||
|
||||
export function CrashReportDialog(props: Readonly<PropsType>): JSX.Element {
|
||||
const { i18n, isPending, writeCrashReportsToLog, eraseCrashReports } = props;
|
||||
|
||||
export function CrashReportDialog({
|
||||
i18n,
|
||||
isPending,
|
||||
writeCrashReportsToLog,
|
||||
eraseCrashReports,
|
||||
}: Readonly<PropsType>): JSX.Element {
|
||||
const onEraseClick = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ComponentType } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
|
@ -61,9 +62,7 @@ export type DataPropsType = {
|
|||
caretLocation?: number
|
||||
) => unknown;
|
||||
regionCode: string | undefined;
|
||||
RenderCompositionTextArea: (
|
||||
props: SmartCompositionTextAreaProps
|
||||
) => JSX.Element;
|
||||
RenderCompositionTextArea: ComponentType<SmartCompositionTextAreaProps>;
|
||||
showToast: ShowToastAction;
|
||||
theme: ThemeType;
|
||||
};
|
||||
|
@ -413,9 +412,7 @@ type ForwardMessageEditorProps = Readonly<{
|
|||
draft: MessageForwardDraft;
|
||||
linkPreview: LinkPreviewType | null | void;
|
||||
removeLinkPreview(): void;
|
||||
RenderCompositionTextArea: (
|
||||
props: SmartCompositionTextAreaProps
|
||||
) => JSX.Element;
|
||||
RenderCompositionTextArea: ComponentType<SmartCompositionTextAreaProps>;
|
||||
onChange: (
|
||||
messageText: string,
|
||||
bodyRanges: HydratedBodyRangesType,
|
||||
|
|
|
@ -36,13 +36,12 @@ const contact3: ConversationType = getDefaultConversation({
|
|||
|
||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||
areWeInvited: Boolean(overrideProps.areWeInvited),
|
||||
conversationId: '123',
|
||||
droppedMembers: overrideProps.droppedMembers || [contact3, contact1],
|
||||
getPreferredBadge: () => undefined,
|
||||
hasMigrated: Boolean(overrideProps.hasMigrated),
|
||||
i18n,
|
||||
invitedMembers: overrideProps.invitedMembers || [contact2],
|
||||
migrate: action('migrate'),
|
||||
onMigrate: action('onMigrate'),
|
||||
onClose: action('onClose'),
|
||||
theme: ThemeType.light,
|
||||
});
|
||||
|
|
|
@ -10,7 +10,6 @@ import { sortByTitle } from '../util/sortByTitle';
|
|||
import { missingCaseError } from '../util/missingCaseError';
|
||||
|
||||
export type DataPropsType = {
|
||||
conversationId: string;
|
||||
readonly areWeInvited: boolean;
|
||||
readonly droppedMembers: Array<ConversationType>;
|
||||
readonly hasMigrated: boolean;
|
||||
|
@ -20,51 +19,25 @@ export type DataPropsType = {
|
|||
readonly theme: ThemeType;
|
||||
};
|
||||
|
||||
type ActionsPropsType =
|
||||
| {
|
||||
initiateMigrationToGroupV2: (conversationId: string) => unknown;
|
||||
closeGV2MigrationDialog: () => unknown;
|
||||
}
|
||||
| {
|
||||
readonly migrate: () => unknown;
|
||||
readonly onClose: () => unknown;
|
||||
};
|
||||
type ActionsPropsType = Readonly<{
|
||||
onMigrate: () => unknown;
|
||||
onClose: () => unknown;
|
||||
}>;
|
||||
|
||||
export type PropsType = DataPropsType & ActionsPropsType;
|
||||
|
||||
export const GroupV1MigrationDialog: React.FunctionComponent<PropsType> =
|
||||
React.memo(function GroupV1MigrationDialogInner(props: PropsType) {
|
||||
const {
|
||||
areWeInvited,
|
||||
conversationId,
|
||||
droppedMembers,
|
||||
getPreferredBadge,
|
||||
hasMigrated,
|
||||
i18n,
|
||||
invitedMembers,
|
||||
theme,
|
||||
} = props;
|
||||
|
||||
let migrateHandler;
|
||||
if ('migrate' in props) {
|
||||
migrateHandler = props.migrate;
|
||||
} else if ('initiateMigrationToGroupV2' in props) {
|
||||
migrateHandler = () => props.initiateMigrationToGroupV2(conversationId);
|
||||
} else {
|
||||
throw new Error(
|
||||
'GroupV1MigrationDialog: No conversationId or migration function'
|
||||
);
|
||||
}
|
||||
|
||||
let closeHandler;
|
||||
if ('onClose' in props) {
|
||||
closeHandler = props.onClose;
|
||||
} else if ('closeGV2MigrationDialog' in props) {
|
||||
closeHandler = props.closeGV2MigrationDialog;
|
||||
} else {
|
||||
throw new Error('GroupV1MigrationDialog: No close function provided');
|
||||
}
|
||||
|
||||
React.memo(function GroupV1MigrationDialogInner({
|
||||
areWeInvited,
|
||||
droppedMembers,
|
||||
getPreferredBadge,
|
||||
hasMigrated,
|
||||
i18n,
|
||||
invitedMembers,
|
||||
theme,
|
||||
onClose,
|
||||
onMigrate,
|
||||
}: PropsType) {
|
||||
const title = hasMigrated
|
||||
? i18n('icu:GroupV1--Migration--info--title')
|
||||
: i18n('icu:GroupV1--Migration--migrate--title');
|
||||
|
@ -82,13 +55,13 @@ export const GroupV1MigrationDialog: React.FunctionComponent<PropsType> =
|
|||
};
|
||||
if (hasMigrated) {
|
||||
primaryButtonText = i18n('icu:Confirmation--confirm');
|
||||
onClickPrimaryButton = closeHandler;
|
||||
onClickPrimaryButton = onClose;
|
||||
} else {
|
||||
primaryButtonText = i18n('icu:GroupV1--Migration--migrate');
|
||||
onClickPrimaryButton = migrateHandler;
|
||||
onClickPrimaryButton = onMigrate;
|
||||
secondaryButtonProps = {
|
||||
secondaryButtonText: i18n('icu:cancel'),
|
||||
onClickSecondaryButton: closeHandler,
|
||||
onClickSecondaryButton: onClose,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -96,7 +69,7 @@ export const GroupV1MigrationDialog: React.FunctionComponent<PropsType> =
|
|||
<GroupDialog
|
||||
i18n={i18n}
|
||||
onClickPrimaryButton={onClickPrimaryButton}
|
||||
onClose={closeHandler}
|
||||
onClose={onClose}
|
||||
primaryButtonText={primaryButtonText}
|
||||
title={title}
|
||||
{...secondaryButtonProps}
|
||||
|
|
|
@ -30,21 +30,18 @@ function focusRef(el: HTMLElement | null) {
|
|||
}
|
||||
}
|
||||
|
||||
export const GroupV2JoinDialog = React.memo(function GroupV2JoinDialogInner(
|
||||
props: PropsType
|
||||
) {
|
||||
export const GroupV2JoinDialog = React.memo(function GroupV2JoinDialogInner({
|
||||
approvalRequired,
|
||||
avatar,
|
||||
groupDescription,
|
||||
i18n,
|
||||
join,
|
||||
memberCount,
|
||||
onClose,
|
||||
title,
|
||||
}: PropsType) {
|
||||
const [isWorking, setIsWorking] = React.useState(false);
|
||||
const [isJoining, setIsJoining] = React.useState(false);
|
||||
const {
|
||||
approvalRequired,
|
||||
avatar,
|
||||
groupDescription,
|
||||
i18n,
|
||||
join,
|
||||
memberCount,
|
||||
onClose,
|
||||
title,
|
||||
} = props;
|
||||
|
||||
const joinString = approvalRequired
|
||||
? i18n('icu:GroupV2--join--request-to-join-button')
|
||||
|
|
|
@ -151,7 +151,6 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
|
|||
i18n,
|
||||
isMacOS: false,
|
||||
preferredWidthFromStorage: 320,
|
||||
regionCode: 'US',
|
||||
challengeStatus: 'idle',
|
||||
crashReportCount: 0,
|
||||
|
||||
|
|
|
@ -104,7 +104,6 @@ export type PropsType = {
|
|||
preferredWidthFromStorage: number;
|
||||
selectedConversationId: undefined | string;
|
||||
targetedMessageId: undefined | string;
|
||||
regionCode: string | undefined;
|
||||
challengeStatus: 'idle' | 'required' | 'pending';
|
||||
setChallengeStatus: (status: 'idle') => void;
|
||||
crashReportCount: number;
|
||||
|
|
|
@ -24,13 +24,44 @@ type PropsType = {
|
|||
Omit<ProfileEditorPropsType, 'onEditStateChanged' | 'onProfileChanged'>;
|
||||
|
||||
export function ProfileEditorModal({
|
||||
aboutEmoji,
|
||||
aboutText,
|
||||
color,
|
||||
conversationId,
|
||||
deleteAvatarFromDisk,
|
||||
deleteUsername,
|
||||
familyName,
|
||||
firstName,
|
||||
hasCompletedUsernameLinkOnboarding,
|
||||
hasError,
|
||||
i18n,
|
||||
initialEditState,
|
||||
isUsernameDeletionEnabled,
|
||||
markCompletedUsernameLinkOnboarding,
|
||||
myProfileChanged,
|
||||
onSetSkinTone,
|
||||
openUsernameReservationModal,
|
||||
profileAvatarPath,
|
||||
recentEmojis,
|
||||
renderEditUsernameModalBody,
|
||||
replaceAvatar,
|
||||
resetUsernameLink,
|
||||
saveAttachment,
|
||||
saveAvatarToDisk,
|
||||
setUsernameEditState,
|
||||
setUsernameLinkColor,
|
||||
showToast,
|
||||
skinTone,
|
||||
toggleProfileEditor,
|
||||
toggleProfileEditorHasError,
|
||||
...restProps
|
||||
userAvatarData,
|
||||
username,
|
||||
usernameCorrupted,
|
||||
usernameEditState,
|
||||
usernameLink,
|
||||
usernameLinkColor,
|
||||
usernameLinkCorrupted,
|
||||
usernameLinkState,
|
||||
}: PropsType): JSX.Element {
|
||||
const MODAL_TITLES_BY_EDIT_STATE: Record<EditState, string | undefined> = {
|
||||
[EditState.BetterAvatar]: i18n('icu:ProfileEditorModal--avatar'),
|
||||
|
@ -67,14 +98,47 @@ export function ProfileEditorModal({
|
|||
title={modalTitle}
|
||||
>
|
||||
<ProfileEditor
|
||||
{...restProps}
|
||||
aboutEmoji={aboutEmoji}
|
||||
aboutText={aboutText}
|
||||
color={color}
|
||||
conversationId={conversationId}
|
||||
deleteAvatarFromDisk={deleteAvatarFromDisk}
|
||||
deleteUsername={deleteUsername}
|
||||
familyName={familyName}
|
||||
firstName={firstName}
|
||||
hasCompletedUsernameLinkOnboarding={hasCompletedUsernameLinkOnboarding}
|
||||
i18n={i18n}
|
||||
initialEditState={initialEditState}
|
||||
isUsernameDeletionEnabled={isUsernameDeletionEnabled}
|
||||
markCompletedUsernameLinkOnboarding={
|
||||
markCompletedUsernameLinkOnboarding
|
||||
}
|
||||
onEditStateChanged={editState => {
|
||||
setModalTitle(MODAL_TITLES_BY_EDIT_STATE[editState]);
|
||||
}}
|
||||
onProfileChanged={myProfileChanged}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
openUsernameReservationModal={openUsernameReservationModal}
|
||||
profileAvatarPath={profileAvatarPath}
|
||||
recentEmojis={recentEmojis}
|
||||
renderEditUsernameModalBody={renderEditUsernameModalBody}
|
||||
replaceAvatar={replaceAvatar}
|
||||
resetUsernameLink={resetUsernameLink}
|
||||
saveAttachment={saveAttachment}
|
||||
saveAvatarToDisk={saveAvatarToDisk}
|
||||
setUsernameEditState={setUsernameEditState}
|
||||
setUsernameLinkColor={setUsernameLinkColor}
|
||||
showToast={showToast}
|
||||
skinTone={skinTone}
|
||||
toggleProfileEditor={toggleProfileEditor}
|
||||
userAvatarData={userAvatarData}
|
||||
username={username}
|
||||
usernameCorrupted={usernameCorrupted}
|
||||
usernameEditState={usernameEditState}
|
||||
usernameLink={usernameLink}
|
||||
usernameLinkColor={usernameLinkColor}
|
||||
usernameLinkCorrupted={usernameLinkCorrupted}
|
||||
usernameLinkState={usernameLinkState}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -14,10 +14,10 @@ export type PropsType = {
|
|||
conversationId: string;
|
||||
files: ReadonlyArray<File>;
|
||||
}) => void;
|
||||
renderCompositionArea: () => JSX.Element;
|
||||
renderConversationHeader: () => JSX.Element;
|
||||
renderTimeline: () => JSX.Element;
|
||||
renderPanel: () => JSX.Element | undefined;
|
||||
renderCompositionArea: (conversationId: string) => JSX.Element;
|
||||
renderConversationHeader: (conversationId: string) => JSX.Element;
|
||||
renderTimeline: (conversationId: string) => JSX.Element;
|
||||
renderPanel: (conversationId: string) => JSX.Element | undefined;
|
||||
shouldHideConversationView?: boolean;
|
||||
};
|
||||
|
||||
|
@ -121,20 +121,20 @@ export function ConversationView({
|
|||
})}
|
||||
>
|
||||
<div className="ConversationView__header">
|
||||
{renderConversationHeader()}
|
||||
{renderConversationHeader(conversationId)}
|
||||
</div>
|
||||
<div className="ConversationView__pane">
|
||||
<div className="ConversationView__timeline--container">
|
||||
<div aria-live="polite" className="ConversationView__timeline">
|
||||
{renderTimeline()}
|
||||
{renderTimeline(conversationId)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ConversationView__composition-area">
|
||||
{renderCompositionArea()}
|
||||
{renderCompositionArea(conversationId)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{renderPanel()}
|
||||
{renderPanel(conversationId)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ export type PropsType = PropsDataType & PropsHousekeepingType;
|
|||
export function GroupV1Migration(props: PropsType): React.ReactElement {
|
||||
const {
|
||||
areWeInvited,
|
||||
conversationId,
|
||||
droppedMembers,
|
||||
getPreferredBadge,
|
||||
i18n,
|
||||
|
@ -80,13 +79,12 @@ export function GroupV1Migration(props: PropsType): React.ReactElement {
|
|||
{showingDialog ? (
|
||||
<GroupV1MigrationDialog
|
||||
areWeInvited={areWeInvited}
|
||||
conversationId={conversationId}
|
||||
droppedMembers={droppedMembers}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
hasMigrated
|
||||
i18n={i18n}
|
||||
invitedMembers={invitedMembers}
|
||||
migrate={() => log.warn('GroupV1Migration: Modal called migrate()')}
|
||||
onMigrate={() => log.warn('GroupV1Migration: Modal called migrate()')}
|
||||
onClose={dismissDialog}
|
||||
theme={theme}
|
||||
/>
|
||||
|
|
|
@ -452,7 +452,9 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
|||
isIncomingMessageRequest: overrideProps.isIncomingMessageRequest ?? false,
|
||||
items: overrideProps.items ?? Object.keys(items),
|
||||
messageChangeCounter: 0,
|
||||
scrollToIndex: overrideProps.scrollToIndex,
|
||||
messageLoadingState: null,
|
||||
isNearBottom: null,
|
||||
scrollToIndex: overrideProps.scrollToIndex ?? null,
|
||||
scrollToIndexCounter: 0,
|
||||
shouldShowMiniPlayer: Boolean(overrideProps.shouldShowMiniPlayer),
|
||||
totalUnseen: overrideProps.totalUnseen ?? 0,
|
||||
|
|
|
@ -70,11 +70,11 @@ export type PropsDataType = {
|
|||
haveNewest: boolean;
|
||||
haveOldest: boolean;
|
||||
messageChangeCounter: number;
|
||||
messageLoadingState?: TimelineMessageLoadingState;
|
||||
isNearBottom?: boolean;
|
||||
messageLoadingState: TimelineMessageLoadingState | null;
|
||||
isNearBottom: boolean | null;
|
||||
items: ReadonlyArray<string>;
|
||||
oldestUnseenIndex?: number;
|
||||
scrollToIndex?: number;
|
||||
oldestUnseenIndex: number | null;
|
||||
scrollToIndex: number | null;
|
||||
scrollToIndexCounter: number;
|
||||
totalUnseen: number;
|
||||
};
|
||||
|
@ -563,7 +563,7 @@ export class Timeline extends React.Component<
|
|||
case ScrollAnchor.ScrollToBottom:
|
||||
return { scrollBottom: 0 };
|
||||
case ScrollAnchor.ScrollToIndex:
|
||||
if (scrollToIndex === undefined) {
|
||||
if (scrollToIndex == null) {
|
||||
assertDev(
|
||||
false,
|
||||
'<Timeline> got "scroll to index" scroll anchor, but no index'
|
||||
|
|
|
@ -63,7 +63,6 @@ const createProps = (
|
|||
selectedConversationIds
|
||||
)}
|
||||
regionCode="US"
|
||||
getPreferredBadge={() => undefined}
|
||||
ourE164={undefined}
|
||||
ourUsername={undefined}
|
||||
theme={ThemeType.light}
|
||||
|
|
|
@ -21,7 +21,6 @@ import { parseAndFormatPhoneNumber } from '../../../../util/libphonenumberInstan
|
|||
import type { ParsedE164Type } from '../../../../util/libphonenumberInstance';
|
||||
import { filterAndSortConversationsByRecent } from '../../../../util/filterAndSortConversations';
|
||||
import type { ConversationType } from '../../../../state/ducks/conversations';
|
||||
import type { PreferredBadgeSelectorType } from '../../../../state/selectors/badges';
|
||||
import type {
|
||||
UUIDFetchStateKeyType,
|
||||
UUIDFetchStateType,
|
||||
|
@ -50,7 +49,6 @@ export type StatePropsType = {
|
|||
regionCode: string | undefined;
|
||||
candidateContacts: ReadonlyArray<ConversationType>;
|
||||
conversationIdsAlreadyInGroup: Set<string>;
|
||||
getPreferredBadge: PreferredBadgeSelectorType;
|
||||
i18n: LocalizerType;
|
||||
theme: ThemeType;
|
||||
maxGroupSize: number;
|
||||
|
|
|
@ -119,7 +119,6 @@ const createProps = (
|
|||
candidateContacts={allCandidateContacts}
|
||||
selectedContacts={[]}
|
||||
regionCode="US"
|
||||
getPreferredBadge={() => undefined}
|
||||
theme={ThemeType.light}
|
||||
i18n={i18n}
|
||||
lookupConversationWithoutServiceId={makeFakeLookupConversationWithoutServiceId()}
|
||||
|
|
|
@ -29,7 +29,7 @@ export type OwnProps = {
|
|||
|
||||
export type Props = OwnProps;
|
||||
|
||||
function renderBody({ pack, i18n }: Props) {
|
||||
function renderBody({ pack, i18n }: Pick<Props, 'i18n' | 'pack'>) {
|
||||
if (!pack) {
|
||||
return null;
|
||||
}
|
||||
|
@ -73,10 +73,8 @@ function renderBody({ pack, i18n }: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export const StickerPreviewModal = React.memo(function StickerPreviewModalInner(
|
||||
props: Props
|
||||
) {
|
||||
const {
|
||||
export const StickerPreviewModal = React.memo(
|
||||
function StickerPreviewModalInner({
|
||||
closeStickerPackPreview,
|
||||
downloadStickerPack,
|
||||
i18n,
|
||||
|
@ -84,142 +82,143 @@ export const StickerPreviewModal = React.memo(function StickerPreviewModalInner(
|
|||
onClose,
|
||||
pack,
|
||||
uninstallStickerPack,
|
||||
} = props;
|
||||
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
|
||||
}: Props) {
|
||||
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
|
||||
|
||||
// Restore focus on teardown
|
||||
const [focusRef] = useRestoreFocus();
|
||||
// Restore focus on teardown
|
||||
const [focusRef] = useRestoreFocus();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (pack && pack.status === 'known') {
|
||||
downloadStickerPack(pack.id, pack.key);
|
||||
}
|
||||
if (
|
||||
pack &&
|
||||
pack.status === 'error' &&
|
||||
(pack.attemptedStatus === 'downloaded' ||
|
||||
pack.attemptedStatus === 'installed')
|
||||
) {
|
||||
downloadStickerPack(pack.id, pack.key, {
|
||||
finalStatus: pack.attemptedStatus,
|
||||
});
|
||||
}
|
||||
// We only want to attempt downloads on initial load
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
if (pack && pack.status === 'known') {
|
||||
downloadStickerPack(pack.id, pack.key);
|
||||
}
|
||||
if (
|
||||
pack &&
|
||||
pack.status === 'error' &&
|
||||
(pack.attemptedStatus === 'downloaded' ||
|
||||
pack.attemptedStatus === 'installed')
|
||||
) {
|
||||
downloadStickerPack(pack.id, pack.key, {
|
||||
finalStatus: pack.attemptedStatus,
|
||||
});
|
||||
}
|
||||
// We only want to attempt downloads on initial load
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (pack) {
|
||||
return;
|
||||
}
|
||||
React.useEffect(() => {
|
||||
if (pack) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pack fully uninstalled, don't keep the modal open
|
||||
closeStickerPackPreview();
|
||||
}, [pack, closeStickerPackPreview]);
|
||||
|
||||
const handleClose = React.useCallback(() => {
|
||||
if (pack) {
|
||||
// Pack fully uninstalled, don't keep the modal open
|
||||
closeStickerPackPreview();
|
||||
}
|
||||
onClose?.();
|
||||
}, [closeStickerPackPreview, onClose, pack]);
|
||||
}, [pack, closeStickerPackPreview]);
|
||||
|
||||
const isInstalled = Boolean(pack && pack.status === 'installed');
|
||||
const handleToggleInstall = React.useCallback(() => {
|
||||
if (!pack) {
|
||||
return;
|
||||
}
|
||||
if (isInstalled) {
|
||||
setConfirmingUninstall(true);
|
||||
} else if (pack.status === 'ephemeral') {
|
||||
downloadStickerPack(pack.id, pack.key, { finalStatus: 'installed' });
|
||||
handleClose();
|
||||
} else {
|
||||
installStickerPack(pack.id, pack.key);
|
||||
handleClose();
|
||||
}
|
||||
}, [
|
||||
downloadStickerPack,
|
||||
installStickerPack,
|
||||
isInstalled,
|
||||
handleClose,
|
||||
pack,
|
||||
setConfirmingUninstall,
|
||||
]);
|
||||
const handleClose = React.useCallback(() => {
|
||||
if (pack) {
|
||||
closeStickerPackPreview();
|
||||
}
|
||||
onClose?.();
|
||||
}, [closeStickerPackPreview, onClose, pack]);
|
||||
|
||||
const handleUninstall = React.useCallback(() => {
|
||||
if (!pack) {
|
||||
return;
|
||||
}
|
||||
uninstallStickerPack(pack.id, pack.key);
|
||||
setConfirmingUninstall(false);
|
||||
// closeStickerPackPreview is called by <ConfirmationDialog />'s onClose
|
||||
}, [uninstallStickerPack, setConfirmingUninstall, pack]);
|
||||
const isInstalled = Boolean(pack && pack.status === 'installed');
|
||||
const handleToggleInstall = React.useCallback(() => {
|
||||
if (!pack) {
|
||||
return;
|
||||
}
|
||||
if (isInstalled) {
|
||||
setConfirmingUninstall(true);
|
||||
} else if (pack.status === 'ephemeral') {
|
||||
downloadStickerPack(pack.id, pack.key, { finalStatus: 'installed' });
|
||||
handleClose();
|
||||
} else {
|
||||
installStickerPack(pack.id, pack.key);
|
||||
handleClose();
|
||||
}
|
||||
}, [
|
||||
downloadStickerPack,
|
||||
installStickerPack,
|
||||
isInstalled,
|
||||
handleClose,
|
||||
pack,
|
||||
setConfirmingUninstall,
|
||||
]);
|
||||
|
||||
const buttonLabel = isInstalled
|
||||
? i18n('icu:stickers--StickerManager--Uninstall')
|
||||
: i18n('icu:stickers--StickerManager--Install');
|
||||
const handleUninstall = React.useCallback(() => {
|
||||
if (!pack) {
|
||||
return;
|
||||
}
|
||||
uninstallStickerPack(pack.id, pack.key);
|
||||
setConfirmingUninstall(false);
|
||||
// closeStickerPackPreview is called by <ConfirmationDialog />'s onClose
|
||||
}, [uninstallStickerPack, setConfirmingUninstall, pack]);
|
||||
|
||||
const modalFooter =
|
||||
pack && pack.status !== 'error' ? (
|
||||
<div className="module-sticker-manager__preview-modal__footer">
|
||||
<div className="module-sticker-manager__preview-modal__footer--info">
|
||||
<h3 className="module-sticker-manager__preview-modal__footer--title">
|
||||
<UserText text={pack.title} />
|
||||
{pack.isBlessed ? (
|
||||
<span className="module-sticker-manager__preview-modal__footer--blessed-icon" />
|
||||
) : null}
|
||||
</h3>
|
||||
<h4 className="module-sticker-manager__preview-modal__footer--author">
|
||||
{pack.author}
|
||||
</h4>
|
||||
const buttonLabel = isInstalled
|
||||
? i18n('icu:stickers--StickerManager--Uninstall')
|
||||
: i18n('icu:stickers--StickerManager--Install');
|
||||
|
||||
const modalFooter =
|
||||
pack && pack.status !== 'error' ? (
|
||||
<div className="module-sticker-manager__preview-modal__footer">
|
||||
<div className="module-sticker-manager__preview-modal__footer--info">
|
||||
<h3 className="module-sticker-manager__preview-modal__footer--title">
|
||||
<UserText text={pack.title} />
|
||||
{pack.isBlessed ? (
|
||||
<span className="module-sticker-manager__preview-modal__footer--blessed-icon" />
|
||||
) : null}
|
||||
</h3>
|
||||
<h4 className="module-sticker-manager__preview-modal__footer--author">
|
||||
{pack.author}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="module-sticker-manager__preview-modal__footer--install">
|
||||
{pack.status === 'pending' ? (
|
||||
<Spinner svgSize="small" size="14px" />
|
||||
) : (
|
||||
<Button
|
||||
aria-label={buttonLabel}
|
||||
ref={focusRef}
|
||||
onClick={handleToggleInstall}
|
||||
variant={ButtonVariant.Primary}
|
||||
>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="module-sticker-manager__preview-modal__footer--install">
|
||||
{pack.status === 'pending' ? (
|
||||
<Spinner svgSize="small" size="14px" />
|
||||
) : (
|
||||
<Button
|
||||
aria-label={buttonLabel}
|
||||
ref={focusRef}
|
||||
onClick={handleToggleInstall}
|
||||
variant={ButtonVariant.Primary}
|
||||
>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : undefined;
|
||||
) : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
{confirmingUninstall && (
|
||||
<ConfirmationDialog
|
||||
dialogName="StickerPreviewModal.confirmUninstall"
|
||||
actions={[
|
||||
{
|
||||
style: 'negative',
|
||||
text: i18n('icu:stickers--StickerManager--Uninstall'),
|
||||
action: handleUninstall,
|
||||
},
|
||||
]}
|
||||
return (
|
||||
<>
|
||||
{confirmingUninstall && (
|
||||
<ConfirmationDialog
|
||||
dialogName="StickerPreviewModal.confirmUninstall"
|
||||
actions={[
|
||||
{
|
||||
style: 'negative',
|
||||
text: i18n('icu:stickers--StickerManager--Uninstall'),
|
||||
action: handleUninstall,
|
||||
},
|
||||
]}
|
||||
i18n={i18n}
|
||||
onClose={() => setConfirmingUninstall(false)}
|
||||
>
|
||||
{i18n('icu:stickers--StickerManager--UninstallWarning')}
|
||||
</ConfirmationDialog>
|
||||
)}
|
||||
<Modal
|
||||
hasXButton
|
||||
i18n={i18n}
|
||||
onClose={() => setConfirmingUninstall(false)}
|
||||
modalFooter={modalFooter}
|
||||
modalName="StickerPreviewModal"
|
||||
moduleClassName="module-sticker-manager__preview-modal__modal"
|
||||
onClose={handleClose}
|
||||
title={i18n('icu:stickers--StickerPreview--Title')}
|
||||
>
|
||||
{i18n('icu:stickers--StickerManager--UninstallWarning')}
|
||||
</ConfirmationDialog>
|
||||
)}
|
||||
<Modal
|
||||
hasXButton
|
||||
i18n={i18n}
|
||||
modalFooter={modalFooter}
|
||||
modalName="StickerPreviewModal"
|
||||
moduleClassName="module-sticker-manager__preview-modal__modal"
|
||||
onClose={handleClose}
|
||||
title={i18n('icu:stickers--StickerPreview--Title')}
|
||||
>
|
||||
{renderBody(props)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
{renderBody({ pack, i18n })}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
import { useCallback, useEffect } from 'react';
|
||||
import { get } from 'lodash';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type { StateType } from '../state/reducer';
|
||||
import * as KeyboardLayout from '../services/keyboardLayout';
|
||||
import { getHasPanelOpen } from '../state/selectors/conversations';
|
||||
import { isInFullScreenCall } from '../state/selectors/calling';
|
||||
|
@ -33,11 +31,11 @@ function useHasPanels(): boolean {
|
|||
}
|
||||
|
||||
function useHasGlobalModal(): boolean {
|
||||
return useSelector<StateType, boolean>(isShowingAnyModal);
|
||||
return useSelector(isShowingAnyModal);
|
||||
}
|
||||
|
||||
function useHasCalling(): boolean {
|
||||
return useSelector<StateType, boolean>(isInFullScreenCall);
|
||||
return useSelector(isInFullScreenCall);
|
||||
}
|
||||
|
||||
function useHasAnyOverlay(): boolean {
|
||||
|
|
|
@ -11,6 +11,8 @@ import type { StateType as RootStateType } from '../reducer';
|
|||
import { showToast } from './toast';
|
||||
import type { ShowToastActionType } from './toast';
|
||||
import type { PromiseAction } from '../util';
|
||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||
|
||||
// State
|
||||
|
||||
|
@ -44,6 +46,10 @@ export const actions = {
|
|||
eraseCrashReports,
|
||||
};
|
||||
|
||||
export const useCrashReportsActions = (): BoundActionCreatorsMapObject<
|
||||
typeof actions
|
||||
> => useBoundActions(actions);
|
||||
|
||||
function setCrashReportCount(count: number): SetCrashReportCountActionType {
|
||||
return { type: SET_COUNT, payload: count };
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import type { ReadonlyDeep } from 'type-fest';
|
|||
import { SocketStatus } from '../../types/SocketStatus';
|
||||
import { trigger } from '../../shims/events';
|
||||
import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation';
|
||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||
|
||||
// State
|
||||
|
||||
|
@ -113,6 +115,10 @@ export const actions = {
|
|||
setOutage,
|
||||
};
|
||||
|
||||
export const useNetworkActions = (): BoundActionCreatorsMapObject<
|
||||
typeof actions
|
||||
> => useBoundActions(actions);
|
||||
|
||||
// Reducer
|
||||
|
||||
export function getEmptyState(): NetworkStateType {
|
||||
|
|
|
@ -15,6 +15,8 @@ import {
|
|||
import * as log from '../../logging/log';
|
||||
import * as Errors from '../../types/errors';
|
||||
import type { StateType as RootStateType } from '../reducer';
|
||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||
|
||||
export type SafetyNumberContactType = ReadonlyDeep<{
|
||||
safetyNumber: SafetyNumberType;
|
||||
|
@ -174,6 +176,10 @@ export const actions = {
|
|||
toggleVerified,
|
||||
};
|
||||
|
||||
export const useSafetyNumberActions = (): BoundActionCreatorsMapObject<
|
||||
typeof actions
|
||||
> => useBoundActions(actions);
|
||||
|
||||
export function getEmptyState(): SafetyNumberStateType {
|
||||
return {
|
||||
contacts: {},
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
import { trigger } from '../../shims/events';
|
||||
|
||||
import type { LocaleMessagesType } from '../../types/I18N';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { MenuOptionsType } from '../../types/menu';
|
||||
|
@ -11,6 +10,8 @@ import type { NoopActionType } from './noop';
|
|||
import type { AciString, PniString } from '../../types/ServiceId';
|
||||
import OS from '../../util/os/osMain';
|
||||
import { ThemeType } from '../../types/Util';
|
||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||
|
||||
// State
|
||||
|
||||
|
@ -73,6 +74,10 @@ export const actions = {
|
|||
manualReconnect,
|
||||
};
|
||||
|
||||
export const useUserActions = (): BoundActionCreatorsMapObject<
|
||||
typeof actions
|
||||
> => useBoundActions(actions);
|
||||
|
||||
function eraseStorageServiceState(): EraseStorageServiceStateAction {
|
||||
return {
|
||||
type: ERASE_STORAGE_SERVICE,
|
||||
|
|
|
@ -9,12 +9,12 @@ import { Provider } from 'react-redux';
|
|||
import type { Store } from 'redux';
|
||||
|
||||
import { ModalHost } from '../../components/ModalHost';
|
||||
import type { PropsType } from '../smart/GroupV2JoinDialog';
|
||||
import type { SmartGroupV2JoinDialogProps } from '../smart/GroupV2JoinDialog';
|
||||
import { SmartGroupV2JoinDialog } from '../smart/GroupV2JoinDialog';
|
||||
|
||||
export const createGroupV2JoinModal = (
|
||||
store: Store,
|
||||
props: PropsType
|
||||
props: SmartGroupV2JoinDialogProps
|
||||
): React.ReactElement => {
|
||||
const { onClose } = props;
|
||||
|
||||
|
|
12
ts/state/selectors/app.ts
Normal file
12
ts/state/selectors/app.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { createSelector } from 'reselect';
|
||||
import type { StateType } from '../reducer';
|
||||
import type { AppStateType } from '../ducks/app';
|
||||
|
||||
export const getApp = (state: StateType): AppStateType => state.app;
|
||||
|
||||
export const getHasInitialLoadCompleted = createSelector(
|
||||
getApp,
|
||||
({ hasInitialLoadCompleted }) => hasInitialLoadCompleted
|
||||
);
|
|
@ -23,6 +23,36 @@ export type CallStateType = DirectCallStateType | GroupCallStateType;
|
|||
|
||||
const getCalling = (state: StateType): CallingStateType => state.calling;
|
||||
|
||||
export const getAvailableMicrophones = createSelector(
|
||||
getCalling,
|
||||
({ availableMicrophones }) => availableMicrophones
|
||||
);
|
||||
|
||||
export const getSelectedMicrophone = createSelector(
|
||||
getCalling,
|
||||
({ selectedMicrophone }) => selectedMicrophone
|
||||
);
|
||||
|
||||
export const getAvailableSpeakers = createSelector(
|
||||
getCalling,
|
||||
({ availableSpeakers }) => availableSpeakers
|
||||
);
|
||||
|
||||
export const getSelectedSpeaker = createSelector(
|
||||
getCalling,
|
||||
({ selectedSpeaker }) => selectedSpeaker
|
||||
);
|
||||
|
||||
export const getAvailableCameras = createSelector(
|
||||
getCalling,
|
||||
({ availableCameras }) => availableCameras
|
||||
);
|
||||
|
||||
export const getSelectedCamera = createSelector(
|
||||
getCalling,
|
||||
({ selectedCamera }) => selectedCamera
|
||||
);
|
||||
|
||||
export const getActiveCallState = createSelector(
|
||||
getCalling,
|
||||
(state: CallingStateType) => state.activeCallState
|
||||
|
|
|
@ -513,6 +513,13 @@ export const getComposerUUIDFetchState = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
export const getHasContactSpoofingReview = createSelector(
|
||||
getConversations,
|
||||
(state: ConversationsStateType): boolean => {
|
||||
return state.hasContactSpoofingReview;
|
||||
}
|
||||
);
|
||||
|
||||
function isTrusted(conversation: ConversationType): boolean {
|
||||
if (conversation.type === 'group') {
|
||||
return true;
|
||||
|
@ -986,10 +993,10 @@ export function _conversationMessagesSelector(
|
|||
conversation: ConversationMessageType
|
||||
): TimelinePropsType {
|
||||
const {
|
||||
isNearBottom,
|
||||
isNearBottom = null,
|
||||
messageChangeCounter,
|
||||
messageIds,
|
||||
messageLoadingState,
|
||||
messageLoadingState = null,
|
||||
metrics,
|
||||
scrollToMessageCounter,
|
||||
scrollToMessageId,
|
||||
|
@ -1009,10 +1016,10 @@ export function _conversationMessagesSelector(
|
|||
|
||||
const oldestUnseenIndex = oldestUnseen
|
||||
? messageIds.findIndex(id => id === oldestUnseen.id)
|
||||
: undefined;
|
||||
: null;
|
||||
const scrollToIndex = scrollToMessageId
|
||||
? messageIds.findIndex(id => id === scrollToMessageId)
|
||||
: undefined;
|
||||
: null;
|
||||
const { totalUnseen } = metrics;
|
||||
|
||||
return {
|
||||
|
@ -1025,9 +1032,9 @@ export function _conversationMessagesSelector(
|
|||
oldestUnseenIndex:
|
||||
isNumber(oldestUnseenIndex) && oldestUnseenIndex >= 0
|
||||
? oldestUnseenIndex
|
||||
: undefined,
|
||||
: null,
|
||||
scrollToIndex:
|
||||
isNumber(scrollToIndex) && scrollToIndex >= 0 ? scrollToIndex : undefined,
|
||||
isNumber(scrollToIndex) && scrollToIndex >= 0 ? scrollToIndex : null,
|
||||
scrollToIndexCounter: scrollToMessageCounter,
|
||||
totalUnseen,
|
||||
};
|
||||
|
@ -1065,6 +1072,9 @@ export const getConversationMessagesSelector = createSelector(
|
|||
scrollToIndexCounter: 0,
|
||||
totalUnseen: 0,
|
||||
items: [],
|
||||
isNearBottom: null,
|
||||
oldestUnseenIndex: null,
|
||||
scrollToIndex: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
18
ts/state/selectors/crashReports.ts
Normal file
18
ts/state/selectors/crashReports.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { createSelector } from 'reselect';
|
||||
import type { StateType } from '../reducer';
|
||||
import type { CrashReportsStateType } from '../ducks/crashReports';
|
||||
|
||||
const getCrashReports = (state: StateType): CrashReportsStateType =>
|
||||
state.crashReports;
|
||||
|
||||
export const getCrashReportsIsPending = createSelector(
|
||||
getCrashReports,
|
||||
({ isPending }) => isPending
|
||||
);
|
||||
|
||||
export const getCrashReportCount = createSelector(
|
||||
getCrashReports,
|
||||
({ count }) => count
|
||||
);
|
|
@ -21,3 +21,43 @@ export const isShowingAnyModal = createSelector(
|
|||
return Boolean(value);
|
||||
})
|
||||
);
|
||||
|
||||
export const getContactModalState = createSelector(
|
||||
getGlobalModalsState,
|
||||
({ contactModalState }) => contactModalState
|
||||
);
|
||||
|
||||
export const getIsStoriesSettingsVisible = createSelector(
|
||||
getGlobalModalsState,
|
||||
({ isStoriesSettingsVisible }) => isStoriesSettingsVisible
|
||||
);
|
||||
|
||||
export const getSafetyNumberChangedBlockingData = createSelector(
|
||||
getGlobalModalsState,
|
||||
({ safetyNumberChangedBlockingData }) => safetyNumberChangedBlockingData
|
||||
);
|
||||
|
||||
export const getDeleteMessagesProps = createSelector(
|
||||
getGlobalModalsState,
|
||||
({ deleteMessagesProps }) => deleteMessagesProps
|
||||
);
|
||||
|
||||
export const getEditHistoryMessages = createSelector(
|
||||
getGlobalModalsState,
|
||||
({ editHistoryMessages }) => editHistoryMessages
|
||||
);
|
||||
|
||||
export const getForwardMessagesProps = createSelector(
|
||||
getGlobalModalsState,
|
||||
({ forwardMessagesProps }) => forwardMessagesProps
|
||||
);
|
||||
|
||||
export const getProfileEditorHasError = createSelector(
|
||||
getGlobalModalsState,
|
||||
({ profileEditorHasError }) => profileEditorHasError
|
||||
);
|
||||
|
||||
export const getProfileEditorInitialEditState = createSelector(
|
||||
getGlobalModalsState,
|
||||
({ profileEditorInitialEditState }) => profileEditorInitialEditState
|
||||
);
|
||||
|
|
|
@ -10,6 +10,21 @@ import { SocketStatus } from '../../types/SocketStatus';
|
|||
|
||||
const getNetwork = (state: StateType): NetworkStateType => state.network;
|
||||
|
||||
export const getNetworkIsOnline = createSelector(
|
||||
getNetwork,
|
||||
({ isOnline }) => isOnline
|
||||
);
|
||||
|
||||
export const getNetworkIsOutage = createSelector(
|
||||
getNetwork,
|
||||
({ isOutage }) => isOutage
|
||||
);
|
||||
|
||||
export const getNetworkSocketStatus = createSelector(
|
||||
getNetwork,
|
||||
({ socketStatus }) => socketStatus
|
||||
);
|
||||
|
||||
export const hasNetworkDialog = createSelector(
|
||||
getNetwork,
|
||||
isDone,
|
||||
|
@ -31,6 +46,11 @@ export const hasNetworkDialog = createSelector(
|
|||
socketStatus === SocketStatus.CLOSING)
|
||||
);
|
||||
|
||||
export const getChallengeStatus = createSelector(
|
||||
getNetwork,
|
||||
({ challengeStatus }) => challengeStatus
|
||||
);
|
||||
|
||||
export const isChallengePending = createSelector(
|
||||
getNetwork,
|
||||
({ challengeStatus }) => challengeStatus === 'pending'
|
||||
|
|
11
ts/state/selectors/toast.ts
Normal file
11
ts/state/selectors/toast.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { createSelector } from 'reselect';
|
||||
import type { StateType } from '../reducer';
|
||||
import type { ToastStateType } from '../ducks/toast';
|
||||
|
||||
export function getToastState(state: StateType): ToastStateType {
|
||||
return state.toast;
|
||||
}
|
||||
|
||||
export const getToast = createSelector(getToastState, ({ toast }) => toast);
|
|
@ -11,6 +11,26 @@ import type { UpdatesStateType } from '../ducks/updates';
|
|||
export const getUpdatesState = (state: Readonly<StateType>): UpdatesStateType =>
|
||||
state.updates;
|
||||
|
||||
export const getUpdateDialogType = createSelector(
|
||||
getUpdatesState,
|
||||
({ dialogType }) => dialogType
|
||||
);
|
||||
|
||||
export const getUpdateVersion = createSelector(
|
||||
getUpdatesState,
|
||||
({ version }) => version
|
||||
);
|
||||
|
||||
export const getUpdateDownloadSize = createSelector(
|
||||
getUpdatesState,
|
||||
({ downloadSize }) => downloadSize
|
||||
);
|
||||
|
||||
export const getUpdateDownloadedSize = createSelector(
|
||||
getUpdatesState,
|
||||
({ downloadedSize }) => downloadedSize
|
||||
);
|
||||
|
||||
export const isUpdateDialogVisible = createSelector(
|
||||
getUpdatesState,
|
||||
({ dialogType, didSnooze }) => {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { AboutContactModal } from '../../components/conversation/AboutContactModal';
|
||||
import { isSignalConnection } from '../../util/getSignalConnections';
|
||||
import { getIntl } from '../selectors/user';
|
||||
|
@ -12,7 +10,7 @@ import { getConversationSelector } from '../selectors/conversations';
|
|||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
|
||||
export function SmartAboutContactModal(): JSX.Element | null {
|
||||
export const SmartAboutContactModal = memo(function SmartAboutContactModal() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const globalModals = useSelector(getGlobalModalsState);
|
||||
const { aboutContactModalContactId: contactId } = globalModals;
|
||||
|
@ -44,4 +42,4 @@ export function SmartAboutContactModal(): JSX.Element | null {
|
|||
onClose={toggleAboutContactModal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,37 +1,47 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { useSelector } from 'react-redux';
|
||||
import React, { memo } from 'react';
|
||||
import { AddUserToAnotherGroupModal } from '../../components/AddUserToAnotherGroupModal';
|
||||
import type { StateType } from '../reducer';
|
||||
import {
|
||||
getAllGroupsWithInviteAccess,
|
||||
getContactSelector,
|
||||
} from '../selectors/conversations';
|
||||
import { getIntl, getRegionCode, getTheme } from '../selectors/user';
|
||||
import { getIntl, getRegionCode } from '../selectors/user';
|
||||
import { useToastActions } from '../ducks/toast';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
|
||||
export type Props = {
|
||||
export type SmartAddUserToAnotherGroupModalProps = Readonly<{
|
||||
contactID: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
const mapStateToProps = (state: StateType, props: Props) => {
|
||||
const candidateConversations = getAllGroupsWithInviteAccess(state);
|
||||
const getContact = getContactSelector(state);
|
||||
export const SmartAddUserToAnotherGroupModal = memo(
|
||||
function SmartAddUserToAnotherGroupModal({
|
||||
contactID,
|
||||
}: SmartAddUserToAnotherGroupModalProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const candidateConversations = useSelector(getAllGroupsWithInviteAccess);
|
||||
const getContact = useSelector(getContactSelector);
|
||||
const regionCode = useSelector(getRegionCode);
|
||||
|
||||
const regionCode = getRegionCode(state);
|
||||
const { toggleAddUserToAnotherGroupModal } = useGlobalModalActions();
|
||||
const { addMembersToGroup } = useConversationsActions();
|
||||
const { showToast } = useToastActions();
|
||||
|
||||
return {
|
||||
contact: getContact(props.contactID),
|
||||
i18n: getIntl(state),
|
||||
theme: getTheme(state),
|
||||
candidateConversations,
|
||||
regionCode,
|
||||
};
|
||||
};
|
||||
const contact = getContact(contactID);
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartAddUserToAnotherGroupModal = smart(
|
||||
AddUserToAnotherGroupModal
|
||||
return (
|
||||
<AddUserToAnotherGroupModal
|
||||
contact={contact}
|
||||
i18n={i18n}
|
||||
candidateConversations={candidateConversations}
|
||||
regionCode={regionCode}
|
||||
toggleAddUserToAnotherGroupModal={toggleAddUserToAnotherGroupModal}
|
||||
addMembersToGroup={addMembersToGroup}
|
||||
showToast={showToast}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { MediaGallery } from '../../components/conversation/media-gallery/MediaGallery';
|
||||
import { getMediaGalleryState } from '../selectors/mediaGallery';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useLightboxActions } from '../ducks/lightbox';
|
||||
|
||||
import { useMediaGalleryActions } from '../ducks/mediaGallery';
|
||||
|
||||
export type PropsType = {
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
export function SmartAllMedia({ conversationId }: PropsType): JSX.Element {
|
||||
export const SmartAllMedia = memo(function SmartAllMedia({
|
||||
conversationId,
|
||||
}: PropsType) {
|
||||
const { media, documents } = useSelector(getMediaGalleryState);
|
||||
const { loadMediaItems } = useMediaGalleryActions();
|
||||
const { saveAttachment } = useConversationsActions();
|
||||
|
@ -32,4 +31,4 @@ export function SmartAllMedia({ conversationId }: PropsType): JSX.Element {
|
|||
saveAttachment={saveAttachment}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type { VerificationTransport } from '../../types/VerificationTransport';
|
||||
import { App } from '../../components/App';
|
||||
import OS from '../../util/os/osMain';
|
||||
|
@ -30,13 +28,11 @@ function renderInbox(): JSX.Element {
|
|||
return <SmartInbox />;
|
||||
}
|
||||
|
||||
export function SmartApp(): JSX.Element {
|
||||
export const SmartApp = memo(function SmartApp() {
|
||||
const app = useSelector((state: StateType) => state.app);
|
||||
|
||||
const { openInbox } = useAppActions();
|
||||
|
||||
const { scrollToMessage } = useConversationsActions();
|
||||
|
||||
const { viewStory } = useStoriesActions();
|
||||
|
||||
return (
|
||||
|
@ -84,4 +80,4 @@ export function SmartApp(): JSX.Element {
|
|||
viewStory={viewStory}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { memoize } from 'lodash';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type {
|
||||
DirectIncomingCall,
|
||||
GroupIncomingCall,
|
||||
} from '../../components/CallManager';
|
||||
import { CallManager } from '../../components/CallManager';
|
||||
import type { SafetyNumberProps } from '../../components/SafetyNumberChangeDialog';
|
||||
import { isConversationTooBigToRing as getIsConversationTooBigToRing } from '../../conversations/isConversationTooBigToRing';
|
||||
import * as log from '../../logging/log';
|
||||
import { calling as callingService } from '../../services/calling';
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import { getMe, getConversationSelector } from '../selectors/conversations';
|
||||
import { getActiveCall } from '../ducks/calling';
|
||||
import type { ConversationType } from '../ducks/conversations';
|
||||
import { getCallLinkSelector, getIncomingCall } from '../selectors/calling';
|
||||
import { isGroupCallRaiseHandEnabled } from '../../util/isGroupCallRaiseHandEnabled';
|
||||
import { isGroupCallReactionsEnabled } from '../../util/isGroupCallReactionsEnabled';
|
||||
import {
|
||||
FALLBACK_NOTIFICATION_TITLE,
|
||||
NotificationSetting,
|
||||
NotificationType,
|
||||
notificationService,
|
||||
} from '../../services/notifications';
|
||||
import {
|
||||
bounceAppIconStart,
|
||||
bounceAppIconStop,
|
||||
} from '../../shims/bounceAppIcon';
|
||||
import type { CallLinkType } from '../../types/CallLink';
|
||||
import type {
|
||||
ActiveCallBaseType,
|
||||
ActiveCallType,
|
||||
|
@ -27,32 +33,32 @@ import type {
|
|||
ConversationsByDemuxIdType,
|
||||
GroupCallRemoteParticipantType,
|
||||
} from '../../types/Calling';
|
||||
import type { AciString } from '../../types/ServiceId';
|
||||
import { CallMode, CallState } from '../../types/Calling';
|
||||
import type { CallLinkType } from '../../types/CallLink';
|
||||
import type { StateType } from '../reducer';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { SmartCallingDeviceSelection } from './CallingDeviceSelection';
|
||||
import type { SafetyNumberProps } from '../../components/SafetyNumberChangeDialog';
|
||||
import { SmartSafetyNumberViewer } from './SafetyNumberViewer';
|
||||
import { callingTones } from '../../util/callingTones';
|
||||
import {
|
||||
bounceAppIconStart,
|
||||
bounceAppIconStop,
|
||||
} from '../../shims/bounceAppIcon';
|
||||
import {
|
||||
FALLBACK_NOTIFICATION_TITLE,
|
||||
NotificationSetting,
|
||||
NotificationType,
|
||||
notificationService,
|
||||
} from '../../services/notifications';
|
||||
import * as log from '../../logging/log';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import { isConversationTooBigToRing } from '../../conversations/isConversationTooBigToRing';
|
||||
import type { AciString } from '../../types/ServiceId';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { callLinkToConversation } from '../../util/callLinks';
|
||||
import { callingTones } from '../../util/callingTones';
|
||||
import { isGroupCallRaiseHandEnabled } from '../../util/isGroupCallRaiseHandEnabled';
|
||||
import { isGroupCallReactionsEnabled } from '../../util/isGroupCallReactionsEnabled';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { useAudioPlayerActions } from '../ducks/audioPlayer';
|
||||
import { getActiveCall, useCallingActions } from '../ducks/calling';
|
||||
import type { ConversationType } from '../ducks/conversations';
|
||||
import { useToastActions } from '../ducks/toast';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getHasInitialLoadCompleted } from '../selectors/app';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import {
|
||||
getAvailableCameras,
|
||||
getCallLinkSelector,
|
||||
getIncomingCall,
|
||||
} from '../selectors/calling';
|
||||
import { getConversationSelector, getMe } from '../selectors/conversations';
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import { SmartCallingDeviceSelection } from './CallingDeviceSelection';
|
||||
import { SmartSafetyNumberViewer } from './SafetyNumberViewer';
|
||||
import { renderEmojiPicker } from './renderEmojiPicker';
|
||||
import { renderReactionPicker } from './renderReactionPicker';
|
||||
import { callLinkToConversation } from '../../util/callLinks';
|
||||
|
||||
function renderDeviceSelection(): JSX.Element {
|
||||
return <SmartCallingDeviceSelection />;
|
||||
|
@ -321,7 +327,7 @@ const mapStateToActiveCallProp = (
|
|||
conversationsByDemuxId,
|
||||
deviceCount: peekInfo.deviceCount,
|
||||
groupMembers,
|
||||
isConversationTooBigToRing: isConversationTooBigToRing(conversation),
|
||||
isConversationTooBigToRing: getIsConversationTooBigToRing(conversation),
|
||||
joinState: call.joinState,
|
||||
localDemuxId,
|
||||
maxDevices: peekInfo.maxDevices,
|
||||
|
@ -414,37 +420,105 @@ const mapStateToIncomingCallProp = (
|
|||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
const incomingCall = mapStateToIncomingCallProp(state);
|
||||
export const SmartCallManager = memo(function SmartCallManager() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
const activeCall = useSelector(mapStateToActiveCallProp);
|
||||
const callLink = useSelector(mapStateToCallLinkProp);
|
||||
const incomingCall = useSelector(mapStateToIncomingCallProp);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const availableCameras = useSelector(getAvailableCameras);
|
||||
const hasInitialLoadCompleted = useSelector(getHasInitialLoadCompleted);
|
||||
const me = useSelector(getMe);
|
||||
const isConversationTooBigToRing = incomingCall
|
||||
? getIsConversationTooBigToRing(incomingCall.conversation)
|
||||
: false;
|
||||
|
||||
return {
|
||||
activeCall: mapStateToActiveCallProp(state),
|
||||
callLink: mapStateToCallLinkProp(state),
|
||||
bounceAppIconStart,
|
||||
bounceAppIconStop,
|
||||
availableCameras: state.calling.availableCameras,
|
||||
getGroupCallVideoFrameSource,
|
||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||
hasInitialLoadCompleted: state.app.hasInitialLoadCompleted,
|
||||
i18n: getIntl(state),
|
||||
isGroupCallRaiseHandEnabled: isGroupCallRaiseHandEnabled(),
|
||||
isGroupCallReactionsEnabled: isGroupCallReactionsEnabled(),
|
||||
incomingCall,
|
||||
me: getMe(state),
|
||||
notifyForCall,
|
||||
playRingtone,
|
||||
stopRingtone,
|
||||
renderEmojiPicker,
|
||||
renderReactionPicker,
|
||||
renderDeviceSelection,
|
||||
renderSafetyNumberViewer,
|
||||
theme: getTheme(state),
|
||||
isConversationTooBigToRing: incomingCall
|
||||
? isConversationTooBigToRing(incomingCall.conversation)
|
||||
: false,
|
||||
};
|
||||
};
|
||||
const {
|
||||
changeCallView,
|
||||
closeNeedPermissionScreen,
|
||||
getPresentingSources,
|
||||
cancelCall,
|
||||
keyChangeOk,
|
||||
startCall,
|
||||
toggleParticipants,
|
||||
acceptCall,
|
||||
declineCall,
|
||||
openSystemPreferencesAction,
|
||||
sendGroupCallRaiseHand,
|
||||
sendGroupCallReaction,
|
||||
setGroupCallVideoRequest,
|
||||
setIsCallActive,
|
||||
setLocalAudio,
|
||||
setLocalVideo,
|
||||
setLocalPreview,
|
||||
setOutgoingRing,
|
||||
setPresenting,
|
||||
setRendererCanvas,
|
||||
switchToPresentationView,
|
||||
switchFromPresentationView,
|
||||
hangUpActiveCall,
|
||||
togglePip,
|
||||
toggleScreenRecordingPermissionsDialog,
|
||||
toggleSettings,
|
||||
} = useCallingActions();
|
||||
const { showToast } = useToastActions();
|
||||
const { pauseVoiceNotePlayer } = useAudioPlayerActions();
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartCallManager = smart(CallManager);
|
||||
return (
|
||||
<CallManager
|
||||
acceptCall={acceptCall}
|
||||
activeCall={activeCall}
|
||||
availableCameras={availableCameras}
|
||||
bounceAppIconStart={bounceAppIconStart}
|
||||
bounceAppIconStop={bounceAppIconStop}
|
||||
callLink={callLink}
|
||||
cancelCall={cancelCall}
|
||||
changeCallView={changeCallView}
|
||||
closeNeedPermissionScreen={closeNeedPermissionScreen}
|
||||
declineCall={declineCall}
|
||||
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
getPresentingSources={getPresentingSources}
|
||||
hangUpActiveCall={hangUpActiveCall}
|
||||
hasInitialLoadCompleted={hasInitialLoadCompleted}
|
||||
i18n={i18n}
|
||||
incomingCall={incomingCall}
|
||||
isConversationTooBigToRing={isConversationTooBigToRing}
|
||||
isGroupCallRaiseHandEnabled={isGroupCallRaiseHandEnabled()}
|
||||
isGroupCallReactionsEnabled={isGroupCallReactionsEnabled()}
|
||||
keyChangeOk={keyChangeOk}
|
||||
me={me}
|
||||
notifyForCall={notifyForCall}
|
||||
openSystemPreferencesAction={openSystemPreferencesAction}
|
||||
pauseVoiceNotePlayer={pauseVoiceNotePlayer}
|
||||
playRingtone={playRingtone}
|
||||
renderDeviceSelection={renderDeviceSelection}
|
||||
renderEmojiPicker={renderEmojiPicker}
|
||||
renderReactionPicker={renderReactionPicker}
|
||||
renderSafetyNumberViewer={renderSafetyNumberViewer}
|
||||
sendGroupCallRaiseHand={sendGroupCallRaiseHand}
|
||||
sendGroupCallReaction={sendGroupCallReaction}
|
||||
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
||||
setIsCallActive={setIsCallActive}
|
||||
setLocalAudio={setLocalAudio}
|
||||
setLocalPreview={setLocalPreview}
|
||||
setLocalVideo={setLocalVideo}
|
||||
setOutgoingRing={setOutgoingRing}
|
||||
setPresenting={setPresenting}
|
||||
setRendererCanvas={setRendererCanvas}
|
||||
showToast={showToast}
|
||||
startCall={startCall}
|
||||
stopRingtone={stopRingtone}
|
||||
switchFromPresentationView={switchFromPresentationView}
|
||||
switchToPresentationView={switchToPresentationView}
|
||||
theme={theme}
|
||||
toggleParticipants={toggleParticipants}
|
||||
togglePip={togglePip}
|
||||
toggleScreenRecordingPermissionsDialog={
|
||||
toggleScreenRecordingPermissionsDialog
|
||||
}
|
||||
toggleSettings={toggleSettings}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,34 +1,42 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CallingDeviceSelection } from '../../components/CallingDeviceSelection';
|
||||
import type { StateType } from '../reducer';
|
||||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import {
|
||||
getAvailableCameras,
|
||||
getAvailableMicrophones,
|
||||
getAvailableSpeakers,
|
||||
getSelectedCamera,
|
||||
getSelectedMicrophone,
|
||||
getSelectedSpeaker,
|
||||
} from '../selectors/calling';
|
||||
import { useCallingActions } from '../ducks/calling';
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
const {
|
||||
availableMicrophones,
|
||||
availableSpeakers,
|
||||
selectedMicrophone,
|
||||
selectedSpeaker,
|
||||
availableCameras,
|
||||
selectedCamera,
|
||||
} = state.calling;
|
||||
|
||||
return {
|
||||
availableCameras,
|
||||
availableMicrophones,
|
||||
availableSpeakers,
|
||||
i18n: getIntl(state),
|
||||
selectedCamera,
|
||||
selectedMicrophone,
|
||||
selectedSpeaker,
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartCallingDeviceSelection = smart(CallingDeviceSelection);
|
||||
export const SmartCallingDeviceSelection = memo(
|
||||
function SmartCallingDeviceSelection() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const availableMicrophones = useSelector(getAvailableMicrophones);
|
||||
const selectedMicrophone = useSelector(getSelectedMicrophone);
|
||||
const availableSpeakers = useSelector(getAvailableSpeakers);
|
||||
const selectedSpeaker = useSelector(getSelectedSpeaker);
|
||||
const availableCameras = useSelector(getAvailableCameras);
|
||||
const selectedCamera = useSelector(getSelectedCamera);
|
||||
const { changeIODevice, toggleSettings } = useCallingActions();
|
||||
return (
|
||||
<CallingDeviceSelection
|
||||
availableCameras={availableCameras}
|
||||
availableMicrophones={availableMicrophones}
|
||||
availableSpeakers={availableSpeakers}
|
||||
changeIODevice={changeIODevice}
|
||||
i18n={i18n}
|
||||
selectedCamera={selectedCamera}
|
||||
selectedMicrophone={selectedMicrophone}
|
||||
selectedSpeaker={selectedSpeaker}
|
||||
toggleSettings={toggleSettings}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, { memo, useCallback, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useItemsActions } from '../ducks/items';
|
||||
import {
|
||||
|
@ -88,7 +87,7 @@ function renderToastManager(props: {
|
|||
return <SmartToastManager disableMegaphone {...props} />;
|
||||
}
|
||||
|
||||
export function SmartCallsTab(): JSX.Element {
|
||||
export const SmartCallsTab = memo(function SmartCallsTab() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
|
||||
const preferredLeftPaneWidth = useSelector(getPreferredLeftPaneWidth);
|
||||
|
@ -185,4 +184,4 @@ export function SmartCallsTab(): JSX.Element {
|
|||
savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,29 +1,33 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CaptchaDialog } from '../../components/CaptchaDialog';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { isChallengePending } from '../selectors/network';
|
||||
import { getChallengeURL } from '../../challenge';
|
||||
import * as log from '../../logging/log';
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
return {
|
||||
...state.updates,
|
||||
isPending: isChallengePending(state),
|
||||
i18n: getIntl(state),
|
||||
export type SmartCaptchaDialogProps = Readonly<{
|
||||
onSkip: () => void;
|
||||
}>;
|
||||
|
||||
onContinue() {
|
||||
const url = getChallengeURL('chat');
|
||||
log.info(`CaptchaDialog: navigating to ${url}`);
|
||||
document.location.href = url;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartCaptchaDialog = smart(CaptchaDialog);
|
||||
export const SmartCaptchaDialog = memo(function SmartCaptchaDialog({
|
||||
onSkip,
|
||||
}: SmartCaptchaDialogProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const isPending = useSelector(isChallengePending);
|
||||
const handleContinue = useCallback(() => {
|
||||
const url = getChallengeURL('chat');
|
||||
log.info(`CaptchaDialog: navigating to ${url}`);
|
||||
document.location.href = url;
|
||||
}, []);
|
||||
return (
|
||||
<CaptchaDialog
|
||||
i18n={i18n}
|
||||
isPending={isPending}
|
||||
onSkip={onSkip}
|
||||
onContinue={handleContinue}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,53 +1,91 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import type { PropsDataType } from '../../components/ChatColorPicker';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { ChatColorPicker } from '../../components/ChatColorPicker';
|
||||
import type { StateType } from '../reducer';
|
||||
import {
|
||||
getConversationSelector,
|
||||
getConversationsWithCustomColorSelector,
|
||||
} from '../selectors/conversations';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getDefaultConversationColor } from '../selectors/items';
|
||||
import {
|
||||
getCustomColors,
|
||||
getDefaultConversationColor,
|
||||
} from '../selectors/items';
|
||||
import { getConversationColorAttributes } from '../../util/getConversationColorAttributes';
|
||||
import {
|
||||
useConversationsActions,
|
||||
type ConversationType,
|
||||
} from '../ducks/conversations';
|
||||
import { useItemsActions } from '../ducks/items';
|
||||
|
||||
export type SmartChatColorPickerProps = {
|
||||
export type SmartChatColorPickerProps = Readonly<{
|
||||
conversationId?: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: SmartChatColorPickerProps
|
||||
): PropsDataType => {
|
||||
const conversation = props.conversationId
|
||||
? getConversationSelector(state)(props.conversationId)
|
||||
export const SmartChatColorPicker = memo(function SmartChatColorPicker({
|
||||
conversationId,
|
||||
}: SmartChatColorPickerProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const customColors = useSelector(getCustomColors) ?? {};
|
||||
const defaultConversationColor = useSelector(getDefaultConversationColor);
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const conversationWithCustomColorSelector = useSelector(
|
||||
getConversationsWithCustomColorSelector
|
||||
);
|
||||
|
||||
const {
|
||||
addCustomColor,
|
||||
removeCustomColor,
|
||||
setGlobalDefaultConversationColor,
|
||||
resetDefaultChatColor,
|
||||
editCustomColor,
|
||||
} = useItemsActions();
|
||||
const {
|
||||
colorSelected,
|
||||
resetAllChatColors,
|
||||
removeCustomColorOnConversations,
|
||||
} = useConversationsActions();
|
||||
|
||||
const conversation = conversationId
|
||||
? conversationSelector(conversationId)
|
||||
: {};
|
||||
const defaultConversationColor = getDefaultConversationColor(state);
|
||||
|
||||
const colorValues = getConversationColorAttributes(
|
||||
conversation,
|
||||
defaultConversationColor
|
||||
);
|
||||
|
||||
const { customColors } = state.items;
|
||||
|
||||
return {
|
||||
...props,
|
||||
customColors: customColors ? customColors.colors : {},
|
||||
getConversationsWithCustomColor: (colorId: string) =>
|
||||
Promise.resolve(getConversationsWithCustomColorSelector(state)(colorId)),
|
||||
i18n: getIntl(state),
|
||||
selectedColor: colorValues.conversationColor,
|
||||
selectedCustomColor: {
|
||||
id: colorValues.customColorId,
|
||||
value: colorValues.customColor,
|
||||
},
|
||||
const selectedColor = colorValues.conversationColor;
|
||||
const selectedCustomColor = {
|
||||
id: colorValues.customColorId,
|
||||
value: colorValues.customColor,
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
const getConversationsWithCustomColor = useCallback(
|
||||
async (colorId: string): Promise<Array<ConversationType>> => {
|
||||
return conversationWithCustomColorSelector(colorId);
|
||||
},
|
||||
[conversationWithCustomColorSelector]
|
||||
);
|
||||
|
||||
export const SmartChatColorPicker = smart(ChatColorPicker);
|
||||
return (
|
||||
<ChatColorPicker
|
||||
addCustomColor={addCustomColor}
|
||||
colorSelected={colorSelected}
|
||||
conversationId={conversationId}
|
||||
customColors={customColors}
|
||||
editCustomColor={editCustomColor}
|
||||
getConversationsWithCustomColor={getConversationsWithCustomColor}
|
||||
i18n={i18n}
|
||||
isGlobal={false}
|
||||
removeCustomColor={removeCustomColor}
|
||||
removeCustomColorOnConversations={removeCustomColorOnConversations}
|
||||
resetAllChatColors={resetAllChatColors}
|
||||
resetDefaultChatColor={resetDefaultChatColor}
|
||||
selectedColor={selectedColor}
|
||||
selectedCustomColor={selectedCustomColor}
|
||||
setGlobalDefaultConversationColor={setGlobalDefaultConversationColor}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { memo, useEffect, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { ChatsTab } from '../../components/ChatsTab';
|
||||
import { SmartConversationView } from './ConversationView';
|
||||
|
@ -12,7 +11,6 @@ import { useGlobalModalActions } from '../ducks/globalModals';
|
|||
import { getIntl } from '../selectors/user';
|
||||
import { usePrevious } from '../../hooks/usePrevious';
|
||||
import { TargetedMessageSource } from '../ducks/conversationsEnums';
|
||||
import type { ConversationsStateType } from '../ducks/conversations';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useToastActions } from '../ducks/toast';
|
||||
import type { StateType } from '../reducer';
|
||||
|
@ -36,7 +34,7 @@ function renderMiniPlayer(options: { shouldFlow: boolean }) {
|
|||
return <SmartMiniPlayer {...options} />;
|
||||
}
|
||||
|
||||
export function SmartChatsTab(): JSX.Element {
|
||||
export const SmartChatsTab = memo(function SmartChatsTab() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
|
||||
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
|
||||
|
@ -44,9 +42,7 @@ export function SmartChatsTab(): JSX.Element {
|
|||
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
|
||||
|
||||
const { selectedConversationId, targetedMessage, targetedMessageSource } =
|
||||
useSelector<StateType, ConversationsStateType>(
|
||||
state => state.conversations
|
||||
);
|
||||
useSelector((state: StateType) => state.conversations);
|
||||
|
||||
const {
|
||||
onConversationClosed,
|
||||
|
@ -73,13 +69,7 @@ export function SmartChatsTab(): JSX.Element {
|
|||
) {
|
||||
scrollToMessage(selectedConversationId, targetedMessage);
|
||||
}
|
||||
}, [
|
||||
onConversationOpened,
|
||||
selectedConversationId,
|
||||
scrollToMessage,
|
||||
targetedMessage,
|
||||
targetedMessageSource,
|
||||
]);
|
||||
}, [onConversationOpened, selectedConversationId, scrollToMessage, targetedMessage, targetedMessageSource]);
|
||||
|
||||
const prevConversationId = usePrevious(
|
||||
selectedConversationId,
|
||||
|
@ -157,4 +147,4 @@ export function SmartChatsTab(): JSX.Element {
|
|||
showWhatsNewModal={showWhatsNewModal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,26 +1,20 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import type { StateType } from '../reducer';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { lookupConversationWithoutServiceId } from '../../util/lookupConversationWithoutServiceId';
|
||||
import { getUsernameFromSearch } from '../../util/Username';
|
||||
|
||||
import type { StatePropsType } from '../../components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal';
|
||||
import { ChooseGroupMembersModal } from '../../components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal';
|
||||
|
||||
import { getIntl, getTheme, getRegionCode } from '../selectors/user';
|
||||
import {
|
||||
getCandidateContactsForNewGroup,
|
||||
getConversationByIdSelector,
|
||||
getMe,
|
||||
} from '../selectors/conversations';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
|
||||
export type SmartChooseGroupMembersModalPropsType = {
|
||||
export type SmartChooseGroupMembersModalPropsType = Readonly<{
|
||||
conversationIdsAlreadyInGroup: Set<string>;
|
||||
maxGroupSize: number;
|
||||
confirmAdds: () => void;
|
||||
|
@ -30,41 +24,63 @@ export type SmartChooseGroupMembersModalPropsType = {
|
|||
selectedConversationIds: ReadonlyArray<string>;
|
||||
setSearchTerm: (_: string) => void;
|
||||
toggleSelectedContact: (conversationId: string) => void;
|
||||
};
|
||||
}>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: SmartChooseGroupMembersModalPropsType
|
||||
): StatePropsType => {
|
||||
const conversationSelector = getConversationByIdSelector(state);
|
||||
export const SmartChooseGroupMembersModal = memo(
|
||||
function SmartChooseGroupMembersModal({
|
||||
conversationIdsAlreadyInGroup,
|
||||
maxGroupSize,
|
||||
confirmAdds,
|
||||
onClose,
|
||||
removeSelectedContact,
|
||||
searchTerm,
|
||||
selectedConversationIds,
|
||||
setSearchTerm,
|
||||
toggleSelectedContact,
|
||||
}: SmartChooseGroupMembersModalPropsType) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
const regionCode = useSelector(getRegionCode);
|
||||
const me = useSelector(getMe);
|
||||
const conversationSelector = useSelector(getConversationByIdSelector);
|
||||
|
||||
const candidateContacts = getCandidateContactsForNewGroup(state);
|
||||
const selectedContacts = props.selectedConversationIds.map(conversationId => {
|
||||
const convo = conversationSelector(conversationId);
|
||||
strictAssert(
|
||||
convo,
|
||||
'<SmartChooseGroupMemberModal> selected conversation not found'
|
||||
const candidateContacts = useSelector(getCandidateContactsForNewGroup);
|
||||
const selectedContacts = selectedConversationIds.map(conversationId => {
|
||||
const convo = conversationSelector(conversationId);
|
||||
strictAssert(
|
||||
convo,
|
||||
'<SmartChooseGroupMemberModal> selected conversation not found'
|
||||
);
|
||||
return convo;
|
||||
});
|
||||
|
||||
const { showUserNotFoundModal } = useGlobalModalActions();
|
||||
|
||||
const username = useMemo(() => {
|
||||
return getUsernameFromSearch(searchTerm);
|
||||
}, [searchTerm]);
|
||||
|
||||
return (
|
||||
<ChooseGroupMembersModal
|
||||
regionCode={regionCode}
|
||||
candidateContacts={candidateContacts}
|
||||
confirmAdds={confirmAdds}
|
||||
conversationIdsAlreadyInGroup={conversationIdsAlreadyInGroup}
|
||||
i18n={i18n}
|
||||
maxGroupSize={maxGroupSize}
|
||||
onClose={onClose}
|
||||
ourE164={me.e164}
|
||||
ourUsername={me.username}
|
||||
removeSelectedContact={removeSelectedContact}
|
||||
searchTerm={searchTerm}
|
||||
selectedContacts={selectedContacts}
|
||||
setSearchTerm={setSearchTerm}
|
||||
theme={theme}
|
||||
toggleSelectedContact={toggleSelectedContact}
|
||||
lookupConversationWithoutServiceId={lookupConversationWithoutServiceId}
|
||||
showUserNotFoundModal={showUserNotFoundModal}
|
||||
username={username}
|
||||
/>
|
||||
);
|
||||
return convo;
|
||||
});
|
||||
|
||||
const { searchTerm } = props;
|
||||
|
||||
return {
|
||||
...props,
|
||||
regionCode: getRegionCode(state),
|
||||
candidateContacts,
|
||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||
i18n: getIntl(state),
|
||||
theme: getTheme(state),
|
||||
ourE164: getMe(state).e164,
|
||||
ourUsername: getMe(state).username,
|
||||
selectedContacts,
|
||||
lookupConversationWithoutServiceId,
|
||||
username: getUsernameFromSearch(searchTerm),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartChooseGroupMembersModal = smart(ChooseGroupMembersModal);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { CollidingAvatars } from '../../components/CollidingAvatars';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
|
@ -12,9 +10,9 @@ export type PropsType = Readonly<{
|
|||
conversationIds: ReadonlyArray<string>;
|
||||
}>;
|
||||
|
||||
export function SmartCollidingAvatars({
|
||||
export const SmartCollidingAvatars = memo(function SmartCollidingAvatars({
|
||||
conversationIds,
|
||||
}: PropsType): JSX.Element {
|
||||
}: PropsType) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const getConversation = useSelector(getConversationSelector);
|
||||
|
||||
|
@ -25,4 +23,4 @@ export function SmartCollidingAvatars({
|
|||
}, [conversationIds, getConversation]);
|
||||
|
||||
return <CollidingAvatars i18n={i18n} conversations={conversations} />;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2019 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CompositionArea } from '../../components/CompositionArea';
|
||||
import { useContactNameData } from '../../components/conversation/ContactName';
|
||||
|
@ -78,7 +78,11 @@ function renderSmartCompositionRecordingDraft(
|
|||
return <SmartCompositionRecordingDraft {...draftProps} />;
|
||||
}
|
||||
|
||||
export function SmartCompositionArea({ id }: { id: string }): JSX.Element {
|
||||
export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||
id,
|
||||
}: {
|
||||
id: string;
|
||||
}) {
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const conversation = conversationSelector(id);
|
||||
strictAssert(conversation, `Conversation id ${id} not found!`);
|
||||
|
@ -346,4 +350,4 @@ export function SmartCompositionArea({ id }: { id: string }): JSX.Element {
|
|||
showConversation={showConversation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CompositionRecording } from '../../components/CompositionRecording';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
|
@ -15,49 +15,53 @@ export type SmartCompositionRecordingProps = {
|
|||
onBeforeSend: () => void;
|
||||
};
|
||||
|
||||
export function SmartCompositionRecording({
|
||||
onBeforeSend,
|
||||
}: SmartCompositionRecordingProps): JSX.Element | null {
|
||||
const i18n = useSelector(getIntl);
|
||||
const selectedConversationId = useSelector(getSelectedConversationId);
|
||||
const { cancelRecording, completeRecording } = useAudioRecorderActions();
|
||||
|
||||
const { sendMultiMediaMessage } = useComposerActions();
|
||||
const { hideToast, showToast } = useToastActions();
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
cancelRecording();
|
||||
}, [cancelRecording]);
|
||||
|
||||
const handleSend = useCallback(() => {
|
||||
if (selectedConversationId) {
|
||||
completeRecording(selectedConversationId, voiceNoteAttachment => {
|
||||
onBeforeSend();
|
||||
sendMultiMediaMessage(selectedConversationId, { voiceNoteAttachment });
|
||||
});
|
||||
}
|
||||
}, [
|
||||
selectedConversationId,
|
||||
completeRecording,
|
||||
export const SmartCompositionRecording = memo(
|
||||
function SmartCompositionRecording({
|
||||
onBeforeSend,
|
||||
sendMultiMediaMessage,
|
||||
]);
|
||||
}: SmartCompositionRecordingProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const selectedConversationId = useSelector(getSelectedConversationId);
|
||||
const { cancelRecording, completeRecording } = useAudioRecorderActions();
|
||||
|
||||
if (!selectedConversationId) {
|
||||
return null;
|
||||
const { sendMultiMediaMessage } = useComposerActions();
|
||||
const { hideToast, showToast } = useToastActions();
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
cancelRecording();
|
||||
}, [cancelRecording]);
|
||||
|
||||
const handleSend = useCallback(() => {
|
||||
if (selectedConversationId) {
|
||||
completeRecording(selectedConversationId, voiceNoteAttachment => {
|
||||
onBeforeSend();
|
||||
sendMultiMediaMessage(selectedConversationId, {
|
||||
voiceNoteAttachment,
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [
|
||||
selectedConversationId,
|
||||
completeRecording,
|
||||
onBeforeSend,
|
||||
sendMultiMediaMessage,
|
||||
]);
|
||||
|
||||
if (!selectedConversationId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CompositionRecording
|
||||
i18n={i18n}
|
||||
conversationId={selectedConversationId}
|
||||
onCancel={handleCancel}
|
||||
onSend={handleSend}
|
||||
errorRecording={mapDispatchToProps.errorRecording}
|
||||
addAttachment={mapDispatchToProps.addAttachment}
|
||||
completeRecording={mapDispatchToProps.completeRecording}
|
||||
showToast={showToast}
|
||||
hideToast={hideToast}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CompositionRecording
|
||||
i18n={i18n}
|
||||
conversationId={selectedConversationId}
|
||||
onCancel={handleCancel}
|
||||
onSend={handleSend}
|
||||
errorRecording={mapDispatchToProps.errorRecording}
|
||||
addAttachment={mapDispatchToProps.addAttachment}
|
||||
completeRecording={mapDispatchToProps.completeRecording}
|
||||
showToast={showToast}
|
||||
hideToast={hideToast}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CompositionRecordingDraft } from '../../components/CompositionRecordingDraft';
|
||||
import type { AttachmentDraftType } from '../../types/Attachment';
|
||||
|
@ -21,136 +21,138 @@ export type SmartCompositionRecordingDraftProps = {
|
|||
voiceNoteAttachment: AttachmentDraftType;
|
||||
};
|
||||
|
||||
export function SmartCompositionRecordingDraft({
|
||||
voiceNoteAttachment,
|
||||
}: SmartCompositionRecordingDraftProps): JSX.Element {
|
||||
const i18n = useSelector(getIntl);
|
||||
const active = useSelector(selectAudioPlayerActive);
|
||||
const selectedConversationId = useSelector(getSelectedConversationId);
|
||||
const getConversationById = useSelector(getConversationByIdSelector);
|
||||
const {
|
||||
loadVoiceNoteDraftAudio,
|
||||
unloadMessageAudio,
|
||||
setIsPlaying,
|
||||
setPosition,
|
||||
} = useAudioPlayerActions();
|
||||
const { sendMultiMediaMessage, removeAttachment } = useComposerActions();
|
||||
export const SmartCompositionRecordingDraft = memo(
|
||||
function SmartCompositionRecordingDraft({
|
||||
voiceNoteAttachment,
|
||||
}: SmartCompositionRecordingDraftProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const active = useSelector(selectAudioPlayerActive);
|
||||
const selectedConversationId = useSelector(getSelectedConversationId);
|
||||
const getConversationById = useSelector(getConversationByIdSelector);
|
||||
const {
|
||||
loadVoiceNoteDraftAudio,
|
||||
unloadMessageAudio,
|
||||
setIsPlaying,
|
||||
setPosition,
|
||||
} = useAudioPlayerActions();
|
||||
const { sendMultiMediaMessage, removeAttachment } = useComposerActions();
|
||||
|
||||
if (!selectedConversationId) {
|
||||
throw new Error('No selected conversation');
|
||||
}
|
||||
if (!selectedConversationId) {
|
||||
throw new Error('No selected conversation');
|
||||
}
|
||||
|
||||
const playbackRate =
|
||||
getConversationById(selectedConversationId)?.voiceNotePlaybackRate ?? 1;
|
||||
const playbackRate =
|
||||
getConversationById(selectedConversationId)?.voiceNotePlaybackRate ?? 1;
|
||||
|
||||
const audioUrl = !voiceNoteAttachment.pending
|
||||
? voiceNoteAttachment.url
|
||||
: undefined;
|
||||
|
||||
const content = active?.content;
|
||||
|
||||
const draftActive =
|
||||
content && AudioPlayerContent.isDraft(content) && content.url === audioUrl
|
||||
? active
|
||||
const audioUrl = !voiceNoteAttachment.pending
|
||||
? voiceNoteAttachment.url
|
||||
: undefined;
|
||||
|
||||
const handlePlay = useCallback(
|
||||
(positionAsRatio?: number) => {
|
||||
if (!draftActive && audioUrl) {
|
||||
loadVoiceNoteDraftAudio({
|
||||
conversationId: selectedConversationId,
|
||||
url: audioUrl,
|
||||
startPosition: positionAsRatio ?? 0,
|
||||
playbackRate,
|
||||
const content = active?.content;
|
||||
|
||||
const draftActive =
|
||||
content && AudioPlayerContent.isDraft(content) && content.url === audioUrl
|
||||
? active
|
||||
: undefined;
|
||||
|
||||
const handlePlay = useCallback(
|
||||
(positionAsRatio?: number) => {
|
||||
if (!draftActive && audioUrl) {
|
||||
loadVoiceNoteDraftAudio({
|
||||
conversationId: selectedConversationId,
|
||||
url: audioUrl,
|
||||
startPosition: positionAsRatio ?? 0,
|
||||
playbackRate,
|
||||
});
|
||||
}
|
||||
if (draftActive) {
|
||||
if (positionAsRatio !== undefined) {
|
||||
setPosition(positionAsRatio);
|
||||
}
|
||||
if (!draftActive.playing) {
|
||||
setIsPlaying(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
draftActive,
|
||||
audioUrl,
|
||||
loadVoiceNoteDraftAudio,
|
||||
selectedConversationId,
|
||||
playbackRate,
|
||||
setPosition,
|
||||
setIsPlaying,
|
||||
]
|
||||
);
|
||||
|
||||
const handlePause = useCallback(() => {
|
||||
setIsPlaying(false);
|
||||
}, [setIsPlaying]);
|
||||
|
||||
const handleSend = useCallback(() => {
|
||||
if (selectedConversationId) {
|
||||
sendMultiMediaMessage(selectedConversationId, {
|
||||
draftAttachments: [voiceNoteAttachment],
|
||||
});
|
||||
}
|
||||
if (draftActive) {
|
||||
if (positionAsRatio !== undefined) {
|
||||
}, [selectedConversationId, sendMultiMediaMessage, voiceNoteAttachment]);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
unloadMessageAudio();
|
||||
if (selectedConversationId && voiceNoteAttachment.path) {
|
||||
removeAttachment(selectedConversationId, voiceNoteAttachment.path);
|
||||
}
|
||||
}, [
|
||||
removeAttachment,
|
||||
selectedConversationId,
|
||||
unloadMessageAudio,
|
||||
voiceNoteAttachment.path,
|
||||
]);
|
||||
|
||||
const handleScrub = useCallback(
|
||||
(positionAsRatio: number) => {
|
||||
// if scrubbing when audio not loaded
|
||||
if (!draftActive && audioUrl) {
|
||||
loadVoiceNoteDraftAudio({
|
||||
conversationId: selectedConversationId,
|
||||
url: audioUrl,
|
||||
startPosition: positionAsRatio,
|
||||
playbackRate,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// if scrubbing when audio is loaded
|
||||
if (draftActive) {
|
||||
setPosition(positionAsRatio);
|
||||
|
||||
if (draftActive?.playing) {
|
||||
setIsPlaying(true);
|
||||
}
|
||||
}
|
||||
if (!draftActive.playing) {
|
||||
setIsPlaying(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
draftActive,
|
||||
audioUrl,
|
||||
loadVoiceNoteDraftAudio,
|
||||
selectedConversationId,
|
||||
playbackRate,
|
||||
setPosition,
|
||||
setIsPlaying,
|
||||
]
|
||||
);
|
||||
},
|
||||
[
|
||||
audioUrl,
|
||||
draftActive,
|
||||
loadVoiceNoteDraftAudio,
|
||||
playbackRate,
|
||||
selectedConversationId,
|
||||
setIsPlaying,
|
||||
setPosition,
|
||||
]
|
||||
);
|
||||
|
||||
const handlePause = useCallback(() => {
|
||||
setIsPlaying(false);
|
||||
}, [setIsPlaying]);
|
||||
|
||||
const handleSend = useCallback(() => {
|
||||
if (selectedConversationId) {
|
||||
sendMultiMediaMessage(selectedConversationId, {
|
||||
draftAttachments: [voiceNoteAttachment],
|
||||
});
|
||||
}
|
||||
}, [selectedConversationId, sendMultiMediaMessage, voiceNoteAttachment]);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
unloadMessageAudio();
|
||||
if (selectedConversationId && voiceNoteAttachment.path) {
|
||||
removeAttachment(selectedConversationId, voiceNoteAttachment.path);
|
||||
}
|
||||
}, [
|
||||
removeAttachment,
|
||||
selectedConversationId,
|
||||
unloadMessageAudio,
|
||||
voiceNoteAttachment.path,
|
||||
]);
|
||||
|
||||
const handleScrub = useCallback(
|
||||
(positionAsRatio: number) => {
|
||||
// if scrubbing when audio not loaded
|
||||
if (!draftActive && audioUrl) {
|
||||
loadVoiceNoteDraftAudio({
|
||||
conversationId: selectedConversationId,
|
||||
url: audioUrl,
|
||||
startPosition: positionAsRatio,
|
||||
playbackRate,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// if scrubbing when audio is loaded
|
||||
if (draftActive) {
|
||||
setPosition(positionAsRatio);
|
||||
|
||||
if (draftActive?.playing) {
|
||||
setIsPlaying(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
audioUrl,
|
||||
draftActive,
|
||||
loadVoiceNoteDraftAudio,
|
||||
playbackRate,
|
||||
selectedConversationId,
|
||||
setIsPlaying,
|
||||
setPosition,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<CompositionRecordingDraft
|
||||
i18n={i18n}
|
||||
audioUrl={audioUrl}
|
||||
active={draftActive}
|
||||
onCancel={handleCancel}
|
||||
onSend={handleSend}
|
||||
onPlay={handlePlay}
|
||||
onPause={handlePause}
|
||||
onScrub={handleScrub}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<CompositionRecordingDraft
|
||||
i18n={i18n}
|
||||
audioUrl={audioUrl}
|
||||
active={draftActive}
|
||||
onCancel={handleCancel}
|
||||
onSend={handleSend}
|
||||
onPlay={handlePlay}
|
||||
onPause={handlePause}
|
||||
onScrub={handleScrub}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { CompositionTextAreaProps } from '../../components/CompositionTextArea';
|
||||
import { CompositionTextArea } from '../../components/CompositionTextArea';
|
||||
|
@ -26,9 +25,9 @@ export type SmartCompositionTextAreaProps = Pick<
|
|||
| 'scrollerRef'
|
||||
>;
|
||||
|
||||
export function SmartCompositionTextArea(
|
||||
export const SmartCompositionTextArea = memo(function SmartCompositionTextArea(
|
||||
props: SmartCompositionTextAreaProps
|
||||
): JSX.Element {
|
||||
) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const platform = useSelector(getPlatform);
|
||||
|
||||
|
@ -51,4 +50,4 @@ export function SmartCompositionTextArea(
|
|||
platform={platform}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import type { StateType } from '../reducer';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
|
||||
import type { StatePropsType } from '../../components/conversation/conversation-details/AddGroupMembersModal/ConfirmAdditionsModal';
|
||||
import { ConfirmAdditionsModal } from '../../components/conversation/conversation-details/AddGroupMembersModal/ConfirmAdditionsModal';
|
||||
import type { RequestState } from '../../components/conversation/conversation-details/util';
|
||||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getConversationByIdSelector } from '../selectors/conversations';
|
||||
|
||||
|
@ -22,28 +16,35 @@ export type SmartConfirmAdditionsModalPropsType = {
|
|||
requestState: RequestState;
|
||||
};
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: SmartConfirmAdditionsModalPropsType
|
||||
): StatePropsType => {
|
||||
const conversationSelector = getConversationByIdSelector(state);
|
||||
export const SmartConfirmAdditionsModal = memo(
|
||||
function SmartConfirmAdditionsModal({
|
||||
selectedConversationIds,
|
||||
groupTitle,
|
||||
makeRequest,
|
||||
onClose,
|
||||
requestState,
|
||||
}: SmartConfirmAdditionsModalPropsType) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const conversationSelector = useSelector(getConversationByIdSelector);
|
||||
|
||||
const selectedContacts = props.selectedConversationIds.map(conversationId => {
|
||||
const convo = conversationSelector(conversationId);
|
||||
strictAssert(
|
||||
convo,
|
||||
'<SmartChooseGroupMemberModal> selected conversation not found'
|
||||
const selectedContacts = selectedConversationIds.map(conversationId => {
|
||||
const convo = conversationSelector(conversationId);
|
||||
strictAssert(
|
||||
convo,
|
||||
'<SmartChooseGroupMemberModal> selected conversation not found'
|
||||
);
|
||||
return convo;
|
||||
});
|
||||
|
||||
return (
|
||||
<ConfirmAdditionsModal
|
||||
i18n={i18n}
|
||||
selectedContacts={selectedContacts}
|
||||
groupTitle={groupTitle}
|
||||
makeRequest={makeRequest}
|
||||
onClose={onClose}
|
||||
requestState={requestState}
|
||||
/>
|
||||
);
|
||||
return convo;
|
||||
});
|
||||
|
||||
return {
|
||||
...props,
|
||||
selectedContacts,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartConfirmAdditionsModal = smart(ConfirmAdditionsModal);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,59 +1,92 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import type { PropsDataType } from '../../components/conversation/ContactModal';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { ContactModal } from '../../components/conversation/ContactModal';
|
||||
import type { StateType } from '../reducer';
|
||||
|
||||
import { getAreWeASubscriber } from '../selectors/items';
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import { getBadgesSelector } from '../selectors/badges';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
import { getHasStoriesSelector } from '../selectors/stories2';
|
||||
import { getActiveCallState } from '../selectors/calling';
|
||||
import { useStoriesActions } from '../ducks/stories';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { useCallingActions } from '../ducks/calling';
|
||||
import { getContactModalState } from '../selectors/globalModals';
|
||||
|
||||
const mapStateToProps = (state: StateType): PropsDataType => {
|
||||
const { contactId, conversationId } =
|
||||
state.globalModals.contactModalState || {};
|
||||
export const SmartContactModal = memo(function SmartContactModal() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
const { conversationId, contactId } = useSelector(getContactModalState) ?? {};
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const hasStoriesSelector = useSelector(getHasStoriesSelector);
|
||||
const activeCallState = useSelector(getActiveCallState);
|
||||
const badgesSelector = useSelector(getBadgesSelector);
|
||||
const areWeASubscriber = useSelector(getAreWeASubscriber);
|
||||
|
||||
const currentConversation = getConversationSelector(state)(conversationId);
|
||||
const contact = getConversationSelector(state)(contactId);
|
||||
const conversation = conversationSelector(conversationId);
|
||||
const contact = conversationSelector(contactId);
|
||||
const hasStories = hasStoriesSelector(contactId);
|
||||
const hasActiveCall = activeCallState != null;
|
||||
const badges = badgesSelector(contact.badges);
|
||||
|
||||
const areWeAdmin =
|
||||
currentConversation && currentConversation.areWeAdmin
|
||||
? currentConversation.areWeAdmin
|
||||
: false;
|
||||
const areWeAdmin = conversation?.areWeAdmin ?? false;
|
||||
|
||||
let isMember = false;
|
||||
let isAdmin = false;
|
||||
if (contact && currentConversation && currentConversation.memberships) {
|
||||
currentConversation.memberships.forEach(membership => {
|
||||
if (membership.aci === contact.serviceId) {
|
||||
isMember = true;
|
||||
isAdmin = membership.isAdmin;
|
||||
}
|
||||
const ourMembership = useMemo(() => {
|
||||
return conversation?.memberships?.find(membership => {
|
||||
return membership.aci === contact.serviceId;
|
||||
});
|
||||
}
|
||||
}, [conversation?.memberships, contact]);
|
||||
|
||||
const hasStories = getHasStoriesSelector(state)(contactId);
|
||||
const isMember = ourMembership != null;
|
||||
const isAdmin = ourMembership?.isAdmin ?? false;
|
||||
|
||||
return {
|
||||
areWeASubscriber: getAreWeASubscriber(state),
|
||||
areWeAdmin,
|
||||
badges: getBadgesSelector(state)(contact.badges),
|
||||
hasActiveCall: Boolean(getActiveCallState(state)),
|
||||
contact,
|
||||
conversation: currentConversation,
|
||||
hasStories,
|
||||
i18n: getIntl(state),
|
||||
isAdmin,
|
||||
isMember,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
};
|
||||
const {
|
||||
removeMemberFromGroup,
|
||||
showConversation,
|
||||
updateConversationModelSharedGroups,
|
||||
toggleAdmin,
|
||||
blockConversation,
|
||||
} = useConversationsActions();
|
||||
const { viewUserStories } = useStoriesActions();
|
||||
const {
|
||||
toggleAboutContactModal,
|
||||
toggleAddUserToAnotherGroupModal,
|
||||
toggleSafetyNumberModal,
|
||||
hideContactModal,
|
||||
} = useGlobalModalActions();
|
||||
const {
|
||||
onOutgoingVideoCallInConversation,
|
||||
onOutgoingAudioCallInConversation,
|
||||
} = useCallingActions();
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartContactModal = smart(ContactModal);
|
||||
return (
|
||||
<ContactModal
|
||||
areWeAdmin={areWeAdmin}
|
||||
areWeASubscriber={areWeASubscriber}
|
||||
badges={badges}
|
||||
blockConversation={blockConversation}
|
||||
contact={contact}
|
||||
conversation={conversation}
|
||||
hasActiveCall={hasActiveCall}
|
||||
hasStories={hasStories}
|
||||
hideContactModal={hideContactModal}
|
||||
i18n={i18n}
|
||||
isAdmin={isAdmin}
|
||||
isMember={isMember}
|
||||
onOutgoingAudioCallInConversation={onOutgoingAudioCallInConversation}
|
||||
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
|
||||
removeMemberFromGroup={removeMemberFromGroup}
|
||||
showConversation={showConversation}
|
||||
theme={theme}
|
||||
toggleAboutContactModal={toggleAboutContactModal}
|
||||
toggleAddUserToAnotherGroupModal={toggleAddUserToAnotherGroupModal}
|
||||
toggleAdmin={toggleAdmin}
|
||||
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
||||
updateConversationModelSharedGroups={updateConversationModelSharedGroups}
|
||||
viewUserStories={viewUserStories}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,32 +1,25 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { StateType } from '../reducer';
|
||||
|
||||
import { ContactName } from '../../components/conversation/ContactName';
|
||||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import type { GetConversationByIdType } from '../selectors/conversations';
|
||||
import {
|
||||
getConversationSelector,
|
||||
getSelectedConversationId,
|
||||
} from '../selectors/conversations';
|
||||
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
|
||||
type ExternalProps = {
|
||||
contactId: string;
|
||||
};
|
||||
|
||||
export function SmartContactName(props: ExternalProps): JSX.Element {
|
||||
export const SmartContactName = memo(function SmartContactName(
|
||||
props: ExternalProps
|
||||
) {
|
||||
const { contactId } = props;
|
||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||
const getConversation = useSelector<StateType, GetConversationByIdType>(
|
||||
getConversationSelector
|
||||
);
|
||||
const i18n = useSelector(getIntl);
|
||||
const getConversation = useSelector(getConversationSelector);
|
||||
|
||||
const contact = getConversation(contactId) || {
|
||||
title: i18n('icu:unknownContact'),
|
||||
|
@ -43,4 +36,4 @@ export function SmartContactName(props: ExternalProps): JSX.Element {
|
|||
onClick={() => showContactModal(contact.id, currentConversation.id)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { mapValues } from 'lodash';
|
||||
import type { StateType } from '../reducer';
|
||||
|
||||
import { ContactSpoofingReviewDialog } from '../../components/conversation/ContactSpoofingReviewDialog';
|
||||
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import type { GetConversationByIdType } from '../selectors/conversations';
|
||||
import {
|
||||
getConversationSelector,
|
||||
getConversationByServiceIdSelector,
|
||||
|
@ -33,116 +30,114 @@ export type PropsType = Readonly<{
|
|||
onClose: () => void;
|
||||
}>;
|
||||
|
||||
export function SmartContactSpoofingReviewDialog(
|
||||
props: PropsType
|
||||
): JSX.Element | null {
|
||||
const { conversationId } = props;
|
||||
export const SmartContactSpoofingReviewDialog = memo(
|
||||
function SmartContactSpoofingReviewDialog(props: PropsType) {
|
||||
const { conversationId } = props;
|
||||
|
||||
const getConversation = useSelector<StateType, GetConversationByIdType>(
|
||||
getConversationSelector
|
||||
);
|
||||
const getConversation = useSelector(getConversationSelector);
|
||||
|
||||
const {
|
||||
acceptConversation,
|
||||
reportSpam,
|
||||
blockAndReportSpam,
|
||||
blockConversation,
|
||||
deleteConversation,
|
||||
removeMember,
|
||||
updateSharedGroups,
|
||||
} = useConversationsActions();
|
||||
const { showContactModal, toggleSignalConnectionsModal } =
|
||||
useGlobalModalActions();
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
const getConversationByServiceId = useSelector(
|
||||
getConversationByServiceIdSelector
|
||||
);
|
||||
const conversation = getConversation(conversationId);
|
||||
|
||||
// Just binding the options argument
|
||||
const safeConversationSelector = useCallback(
|
||||
(state: StateType) => {
|
||||
return getSafeConversationWithSameTitle(state, {
|
||||
possiblyUnsafeConversation: conversation,
|
||||
});
|
||||
},
|
||||
[conversation]
|
||||
);
|
||||
const safeConvo = useSelector(safeConversationSelector);
|
||||
|
||||
const sharedProps = {
|
||||
...props,
|
||||
acceptConversation,
|
||||
reportSpam,
|
||||
blockAndReportSpam,
|
||||
blockConversation,
|
||||
deleteConversation,
|
||||
getPreferredBadge,
|
||||
i18n,
|
||||
removeMember,
|
||||
updateSharedGroups,
|
||||
showContactModal,
|
||||
toggleSignalConnectionsModal,
|
||||
theme,
|
||||
};
|
||||
|
||||
if (conversation.type === 'group') {
|
||||
const { memberships } = getGroupMemberships(
|
||||
conversation,
|
||||
getConversationByServiceId
|
||||
const {
|
||||
acceptConversation,
|
||||
reportSpam,
|
||||
blockAndReportSpam,
|
||||
blockConversation,
|
||||
deleteConversation,
|
||||
removeMember,
|
||||
updateSharedGroups,
|
||||
} = useConversationsActions();
|
||||
const { showContactModal, toggleSignalConnectionsModal } =
|
||||
useGlobalModalActions();
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
const getConversationByServiceId = useSelector(
|
||||
getConversationByServiceIdSelector
|
||||
);
|
||||
const groupNameCollisions = getCollisionsFromMemberships(memberships);
|
||||
const conversation = getConversation(conversationId);
|
||||
|
||||
const previouslyAcknowledgedTitlesById = invertIdsByTitle(
|
||||
conversation.acknowledgedGroupNameCollisions
|
||||
// Just binding the options argument
|
||||
const safeConversationSelector = useCallback(
|
||||
(state: StateType) => {
|
||||
return getSafeConversationWithSameTitle(state, {
|
||||
possiblyUnsafeConversation: conversation,
|
||||
});
|
||||
},
|
||||
[conversation]
|
||||
);
|
||||
const safeConvo = useSelector(safeConversationSelector);
|
||||
|
||||
const sharedProps = {
|
||||
...props,
|
||||
acceptConversation,
|
||||
reportSpam,
|
||||
blockAndReportSpam,
|
||||
blockConversation,
|
||||
deleteConversation,
|
||||
getPreferredBadge,
|
||||
i18n,
|
||||
removeMember,
|
||||
updateSharedGroups,
|
||||
showContactModal,
|
||||
toggleSignalConnectionsModal,
|
||||
theme,
|
||||
};
|
||||
|
||||
if (conversation.type === 'group') {
|
||||
const { memberships } = getGroupMemberships(
|
||||
conversation,
|
||||
getConversationByServiceId
|
||||
);
|
||||
const groupNameCollisions = getCollisionsFromMemberships(memberships);
|
||||
|
||||
const previouslyAcknowledgedTitlesById = invertIdsByTitle(
|
||||
conversation.acknowledgedGroupNameCollisions
|
||||
);
|
||||
|
||||
const collisionInfoByTitle = mapValues(groupNameCollisions, collisions =>
|
||||
collisions.map(collision => ({
|
||||
conversation: collision,
|
||||
isSignalConnection: isSignalConnection(collision),
|
||||
oldName: getOwn(previouslyAcknowledgedTitlesById, collision.id),
|
||||
}))
|
||||
);
|
||||
|
||||
return (
|
||||
<ContactSpoofingReviewDialog
|
||||
{...sharedProps}
|
||||
type={ContactSpoofingType.MultipleGroupMembersWithSameTitle}
|
||||
group={conversation}
|
||||
collisionInfoByTitle={collisionInfoByTitle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const possiblyUnsafeConvo = conversation;
|
||||
assertDev(
|
||||
possiblyUnsafeConvo.type === 'direct',
|
||||
'DirectConversationWithSameTitle: expects possibly unsafe direct ' +
|
||||
'conversation'
|
||||
);
|
||||
|
||||
const collisionInfoByTitle = mapValues(groupNameCollisions, collisions =>
|
||||
collisions.map(collision => ({
|
||||
conversation: collision,
|
||||
isSignalConnection: isSignalConnection(collision),
|
||||
oldName: getOwn(previouslyAcknowledgedTitlesById, collision.id),
|
||||
}))
|
||||
);
|
||||
if (!safeConvo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const possiblyUnsafe = {
|
||||
conversation: possiblyUnsafeConvo,
|
||||
isSignalConnection: isSignalConnection(possiblyUnsafeConvo),
|
||||
};
|
||||
const safe = {
|
||||
conversation: safeConvo,
|
||||
isSignalConnection: isSignalConnection(safeConvo),
|
||||
};
|
||||
|
||||
return (
|
||||
<ContactSpoofingReviewDialog
|
||||
{...sharedProps}
|
||||
type={ContactSpoofingType.MultipleGroupMembersWithSameTitle}
|
||||
group={conversation}
|
||||
collisionInfoByTitle={collisionInfoByTitle}
|
||||
type={ContactSpoofingType.DirectConversationWithSameTitle}
|
||||
possiblyUnsafe={possiblyUnsafe}
|
||||
safe={safe}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const possiblyUnsafeConvo = conversation;
|
||||
assertDev(
|
||||
possiblyUnsafeConvo.type === 'direct',
|
||||
'DirectConversationWithSameTitle: expects possibly unsafe direct ' +
|
||||
'conversation'
|
||||
);
|
||||
|
||||
if (!safeConvo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const possiblyUnsafe = {
|
||||
conversation: possiblyUnsafeConvo,
|
||||
isSignalConnection: isSignalConnection(possiblyUnsafeConvo),
|
||||
};
|
||||
const safe = {
|
||||
conversation: safeConvo,
|
||||
isSignalConnection: isSignalConnection(safeConvo),
|
||||
};
|
||||
|
||||
return (
|
||||
<ContactSpoofingReviewDialog
|
||||
{...sharedProps}
|
||||
type={ContactSpoofingType.DirectConversationWithSameTitle}
|
||||
possiblyUnsafe={possiblyUnsafe}
|
||||
safe={safe}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,43 +1,45 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
import type { StateType } from '../reducer';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import type { StateProps } from '../../components/conversation/conversation-details/ConversationDetails';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { ConversationDetails } from '../../components/conversation/conversation-details/ConversationDetails';
|
||||
import {
|
||||
getConversationByIdSelector,
|
||||
getConversationByServiceIdSelector,
|
||||
getAllComposableConversations,
|
||||
} from '../selectors/conversations';
|
||||
getGroupSizeHardLimit,
|
||||
getGroupSizeRecommendedLimit,
|
||||
} from '../../groups/limits';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
import type { CallHistoryGroup } from '../../types/CallDisposition';
|
||||
import { assertDev } from '../../util/assert';
|
||||
import { getConversationColorAttributes } from '../../util/getConversationColorAttributes';
|
||||
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
||||
import { getActiveCallState } from '../selectors/calling';
|
||||
import {
|
||||
getAreWeASubscriber,
|
||||
getDefaultConversationColor,
|
||||
} from '../selectors/items';
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import {
|
||||
getBadgesSelector,
|
||||
getPreferredBadgeSelector,
|
||||
} from '../selectors/badges';
|
||||
import { assertDev } from '../../util/assert';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
import { getConversationColorAttributes } from '../../util/getConversationColorAttributes';
|
||||
import { getActiveCallState } from '../selectors/calling';
|
||||
import {
|
||||
getAllComposableConversations,
|
||||
getConversationByIdSelector,
|
||||
getConversationByServiceIdSelector,
|
||||
} from '../selectors/conversations';
|
||||
import {
|
||||
getAreWeASubscriber,
|
||||
getDefaultConversationColor,
|
||||
} from '../selectors/items';
|
||||
import { getSelectedNavTab } from '../selectors/nav';
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import type { SmartChooseGroupMembersModalPropsType } from './ChooseGroupMembersModal';
|
||||
import { SmartChooseGroupMembersModal } from './ChooseGroupMembersModal';
|
||||
import type { SmartConfirmAdditionsModalPropsType } from './ConfirmAdditionsModal';
|
||||
import { SmartConfirmAdditionsModal } from './ConfirmAdditionsModal';
|
||||
import {
|
||||
getGroupSizeRecommendedLimit,
|
||||
getGroupSizeHardLimit,
|
||||
} from '../../groups/limits';
|
||||
import type { CallHistoryGroup } from '../../types/CallDisposition';
|
||||
import { getSelectedNavTab } from '../selectors/nav';
|
||||
import type { ConversationType } from '../ducks/conversations';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useCallingActions } from '../ducks/calling';
|
||||
import { useSearchActions } from '../ducks/search';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { useLightboxActions } from '../ducks/lightbox';
|
||||
|
||||
export type SmartConversationDetailsProps = {
|
||||
conversationId: string;
|
||||
|
@ -58,79 +60,155 @@ const renderConfirmAdditionsModal = (
|
|||
return <SmartConfirmAdditionsModal {...props} />;
|
||||
};
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: SmartConversationDetailsProps
|
||||
): StateProps => {
|
||||
const conversationSelector = getConversationByIdSelector(state);
|
||||
const conversation = conversationSelector(props.conversationId);
|
||||
function getGroupsInCommonSorted(
|
||||
conversation: ConversationType,
|
||||
allComposableConversations: ReadonlyArray<ConversationType>
|
||||
) {
|
||||
if (conversation.type === 'direct') {
|
||||
return [];
|
||||
}
|
||||
const groupsInCommonUnsorted = allComposableConversations.filter(
|
||||
otherConversation => {
|
||||
if (otherConversation.type !== 'group') {
|
||||
return false;
|
||||
}
|
||||
return otherConversation.memberships?.some(member => {
|
||||
return member.aci === conversation.serviceId;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return sortBy(groupsInCommonUnsorted, 'title');
|
||||
}
|
||||
|
||||
export const SmartConversationDetails = memo(function SmartConversationDetails({
|
||||
conversationId,
|
||||
callHistoryGroup,
|
||||
}: SmartConversationDetailsProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
const activeCall = useSelector(getActiveCallState);
|
||||
const allComposableConversations = useSelector(getAllComposableConversations);
|
||||
const areWeASubscriber = useSelector(getAreWeASubscriber);
|
||||
const badgesSelector = useSelector(getBadgesSelector);
|
||||
const conversationByServiceIdSelector = useSelector(
|
||||
getConversationByServiceIdSelector
|
||||
);
|
||||
const conversationSelector = useSelector(getConversationByIdSelector);
|
||||
const defaultConversationColor = useSelector(getDefaultConversationColor);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const selectedNavTab = useSelector(getSelectedNavTab);
|
||||
|
||||
const {
|
||||
acceptConversation,
|
||||
addMembersToGroup,
|
||||
blockConversation,
|
||||
deleteAvatarFromDisk,
|
||||
getProfilesForConversation,
|
||||
leaveGroup,
|
||||
loadRecentMediaItems,
|
||||
pushPanelForConversation,
|
||||
replaceAvatar,
|
||||
saveAvatarToDisk,
|
||||
setDisappearingMessages,
|
||||
setMuteExpiration,
|
||||
showConversation,
|
||||
updateGroupAttributes,
|
||||
} = useConversationsActions();
|
||||
const {
|
||||
onOutgoingAudioCallInConversation,
|
||||
onOutgoingVideoCallInConversation,
|
||||
} = useCallingActions();
|
||||
const { searchInConversation } = useSearchActions();
|
||||
const {
|
||||
showContactModal,
|
||||
toggleAboutContactModal,
|
||||
toggleAddUserToAnotherGroupModal,
|
||||
toggleSafetyNumberModal,
|
||||
} = useGlobalModalActions();
|
||||
const { showLightboxWithMedia } = useLightboxActions();
|
||||
|
||||
const conversation = conversationSelector(conversationId);
|
||||
assertDev(
|
||||
conversation,
|
||||
'<SmartConversationDetails> expected a conversation to be found'
|
||||
);
|
||||
const conversationWithColorAttributes = {
|
||||
...conversation,
|
||||
...getConversationColorAttributes(conversation, defaultConversationColor),
|
||||
};
|
||||
|
||||
const canEditGroupInfo = Boolean(conversation.canEditGroupInfo);
|
||||
const canAddNewMembers = Boolean(conversation.canAddNewMembers);
|
||||
const isAdmin = Boolean(conversation.areWeAdmin);
|
||||
|
||||
const hasGroupLink =
|
||||
Boolean(conversation.groupLink) &&
|
||||
conversation.accessControlAddFromInviteLink !== ACCESS_ENUM.UNSATISFIABLE;
|
||||
|
||||
const conversationByServiceIdSelector =
|
||||
getConversationByServiceIdSelector(state);
|
||||
const groupMemberships = getGroupMemberships(
|
||||
conversation,
|
||||
conversationByServiceIdSelector
|
||||
);
|
||||
|
||||
const badges = getBadgesSelector(state)(conversation.badges);
|
||||
const defaultConversationColor = getDefaultConversationColor(state);
|
||||
|
||||
const groupsInCommon =
|
||||
conversation.type === 'direct'
|
||||
? getAllComposableConversations(state).filter(
|
||||
c =>
|
||||
c.type === 'group' &&
|
||||
(c.memberships ?? []).some(
|
||||
member => member.aci === conversation.serviceId
|
||||
)
|
||||
)
|
||||
: [];
|
||||
|
||||
const groupsInCommonSorted = sortBy(groupsInCommon, 'title');
|
||||
|
||||
const { memberships, pendingApprovalMemberships, pendingMemberships } =
|
||||
groupMemberships;
|
||||
const badges = badgesSelector(conversation.badges);
|
||||
const canAddNewMembers = conversation.canAddNewMembers ?? false;
|
||||
const canEditGroupInfo = conversation.canEditGroupInfo ?? false;
|
||||
const groupsInCommon = getGroupsInCommonSorted(
|
||||
conversation,
|
||||
allComposableConversations
|
||||
);
|
||||
const hasActiveCall = activeCall != null;
|
||||
const hasGroupLink =
|
||||
conversation.groupLink != null &&
|
||||
conversation.accessControlAddFromInviteLink !== ACCESS_ENUM.UNSATISFIABLE;
|
||||
const isAdmin = conversation.areWeAdmin ?? false;
|
||||
const isGroup = conversation.type === 'group';
|
||||
const maxGroupSize = getGroupSizeHardLimit(1001);
|
||||
const maxRecommendedGroupSize = getGroupSizeRecommendedLimit(151);
|
||||
return {
|
||||
...props,
|
||||
const userAvatarData = conversation.avatars ?? [];
|
||||
|
||||
areWeASubscriber: getAreWeASubscriber(state),
|
||||
badges,
|
||||
canEditGroupInfo,
|
||||
canAddNewMembers,
|
||||
conversation: {
|
||||
...conversation,
|
||||
...getConversationColorAttributes(conversation, defaultConversationColor),
|
||||
},
|
||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||
hasActiveCall: Boolean(getActiveCallState(state)),
|
||||
i18n: getIntl(state),
|
||||
isAdmin,
|
||||
...groupMemberships,
|
||||
maxGroupSize,
|
||||
maxRecommendedGroupSize,
|
||||
userAvatarData: conversation.avatars || [],
|
||||
hasGroupLink,
|
||||
groupsInCommon: groupsInCommonSorted,
|
||||
isGroup: conversation.type === 'group',
|
||||
selectedNavTab: getSelectedNavTab(state),
|
||||
theme: getTheme(state),
|
||||
renderChooseGroupMembersModal,
|
||||
renderConfirmAdditionsModal,
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartConversationDetails = smart(ConversationDetails);
|
||||
return (
|
||||
<ConversationDetails
|
||||
acceptConversation={acceptConversation}
|
||||
addMembersToGroup={addMembersToGroup}
|
||||
areWeASubscriber={areWeASubscriber}
|
||||
badges={badges}
|
||||
blockConversation={blockConversation}
|
||||
callHistoryGroup={callHistoryGroup}
|
||||
canAddNewMembers={canAddNewMembers}
|
||||
canEditGroupInfo={canEditGroupInfo}
|
||||
conversation={conversationWithColorAttributes}
|
||||
deleteAvatarFromDisk={deleteAvatarFromDisk}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
getProfilesForConversation={getProfilesForConversation}
|
||||
groupsInCommon={groupsInCommon}
|
||||
hasActiveCall={hasActiveCall}
|
||||
hasGroupLink={hasGroupLink}
|
||||
i18n={i18n}
|
||||
isAdmin={isAdmin}
|
||||
isGroup={isGroup}
|
||||
leaveGroup={leaveGroup}
|
||||
loadRecentMediaItems={loadRecentMediaItems}
|
||||
maxGroupSize={maxGroupSize}
|
||||
maxRecommendedGroupSize={maxRecommendedGroupSize}
|
||||
memberships={memberships}
|
||||
onOutgoingAudioCallInConversation={onOutgoingAudioCallInConversation}
|
||||
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
|
||||
pendingApprovalMemberships={pendingApprovalMemberships}
|
||||
pendingMemberships={pendingMemberships}
|
||||
pushPanelForConversation={pushPanelForConversation}
|
||||
renderChooseGroupMembersModal={renderChooseGroupMembersModal}
|
||||
renderConfirmAdditionsModal={renderConfirmAdditionsModal}
|
||||
replaceAvatar={replaceAvatar}
|
||||
saveAvatarToDisk={saveAvatarToDisk}
|
||||
searchInConversation={searchInConversation}
|
||||
selectedNavTab={selectedNavTab}
|
||||
setDisappearingMessages={setDisappearingMessages}
|
||||
setMuteExpiration={setMuteExpiration}
|
||||
showContactModal={showContactModal}
|
||||
showConversation={showConversation}
|
||||
showLightboxWithMedia={showLightboxWithMedia}
|
||||
theme={theme}
|
||||
toggleAboutContactModal={toggleAboutContactModal}
|
||||
toggleAddUserToAnotherGroupModal={toggleAddUserToAnotherGroupModal}
|
||||
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
||||
updateGroupAttributes={updateGroupAttributes}
|
||||
userAvatarData={userAvatarData}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { pick } from 'lodash';
|
||||
import type { ConversationType } from '../ducks/conversations';
|
||||
|
@ -78,7 +78,9 @@ const getOutgoingCallButtonStyle = (
|
|||
}
|
||||
};
|
||||
|
||||
export function SmartConversationHeader({ id }: OwnProps): JSX.Element {
|
||||
export const SmartConversationHeader = memo(function SmartConversationHeader({
|
||||
id,
|
||||
}: OwnProps) {
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const conversation = conversationSelector(id);
|
||||
if (!conversation) {
|
||||
|
@ -91,11 +93,10 @@ export function SmartConversationHeader({ id }: OwnProps): JSX.Element {
|
|||
const badgeSelector = useSelector(getPreferredBadgeSelector);
|
||||
const badge = badgeSelector(conversation.badges);
|
||||
const i18n = useSelector(getIntl);
|
||||
const hasPanelShowing = useSelector<StateType, boolean>(getHasPanelOpen);
|
||||
const outgoingCallButtonStyle = useSelector<
|
||||
StateType,
|
||||
OutgoingCallButtonStyle
|
||||
>(state => getOutgoingCallButtonStyle(conversation, state));
|
||||
const hasPanelShowing = useSelector(getHasPanelOpen);
|
||||
const outgoingCallButtonStyle = useSelector((state: StateType) => {
|
||||
return getOutgoingCallButtonStyle(conversation, state);
|
||||
});
|
||||
const theme = useSelector(getTheme);
|
||||
|
||||
const {
|
||||
|
@ -216,4 +217,4 @@ export function SmartConversationHeader({ id }: OwnProps): JSX.Element {
|
|||
deleteConversation={deleteConversation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,38 +1,43 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import React, { memo } from 'react';
|
||||
import { ConversationNotificationsSettings } from '../../components/conversation/conversation-details/ConversationNotificationsSettings';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getConversationByIdSelector } from '../selectors/conversations';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
|
||||
export type OwnProps = {
|
||||
export type SmartConversationNotificationsSettingsProps = {
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StateType, props: OwnProps) => {
|
||||
const { conversationId } = props;
|
||||
|
||||
const conversationSelector = getConversationByIdSelector(state);
|
||||
const conversation = conversationSelector(conversationId);
|
||||
strictAssert(conversation, 'Expected a conversation to be found');
|
||||
|
||||
return {
|
||||
id: conversationId,
|
||||
conversationType: conversation.type,
|
||||
dontNotifyForMentionsIfMuted: Boolean(
|
||||
conversation.dontNotifyForMentionsIfMuted
|
||||
),
|
||||
i18n: getIntl(state),
|
||||
muteExpiresAt: conversation.muteExpiresAt,
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartConversationNotificationsSettings = smart(
|
||||
ConversationNotificationsSettings
|
||||
export const SmartConversationNotificationsSettings = memo(
|
||||
function SmartConversationNotificationsSettings({
|
||||
conversationId,
|
||||
}: SmartConversationNotificationsSettingsProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const conversationSelector = useSelector(getConversationByIdSelector);
|
||||
const { setMuteExpiration, setDontNotifyForMentionsIfMuted } =
|
||||
useConversationsActions();
|
||||
const conversation = conversationSelector(conversationId);
|
||||
strictAssert(conversation, 'Expected a conversation to be found');
|
||||
const {
|
||||
type: conversationType,
|
||||
dontNotifyForMentionsIfMuted,
|
||||
muteExpiresAt,
|
||||
} = conversation;
|
||||
return (
|
||||
<ConversationNotificationsSettings
|
||||
id={conversationId}
|
||||
conversationType={conversationType}
|
||||
dontNotifyForMentionsIfMuted={dontNotifyForMentionsIfMuted ?? false}
|
||||
i18n={i18n}
|
||||
muteExpiresAt={muteExpiresAt}
|
||||
setMuteExpiration={setMuteExpiration}
|
||||
setDontNotifyForMentionsIfMuted={setDontNotifyForMentionsIfMuted}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import type { MutableRefObject } from 'react';
|
||||
import React, {
|
||||
forwardRef,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
|
@ -91,11 +92,11 @@ function doAnimate({
|
|||
};
|
||||
}
|
||||
|
||||
export function ConversationPanel({
|
||||
export const ConversationPanel = memo(function ConversationPanel({
|
||||
conversationId,
|
||||
}: {
|
||||
conversationId: string;
|
||||
}): JSX.Element | null {
|
||||
}) {
|
||||
const panelInformation = useSelector(getPanelInformation);
|
||||
const { panelAnimationDone, panelAnimationStarted } =
|
||||
useConversationsActions();
|
||||
|
@ -250,7 +251,7 @@ export function ConversationPanel({
|
|||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
type PanelPropsType = {
|
||||
conversationId: string;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { StateType } from '../reducer';
|
||||
import { ConversationPanel } from './ConversationPanel';
|
||||
|
@ -18,51 +18,67 @@ import {
|
|||
import { useComposerActions } from '../ducks/composer';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
|
||||
export function SmartConversationView(): JSX.Element {
|
||||
const conversationId = useSelector(getSelectedConversationId);
|
||||
|
||||
if (!conversationId) {
|
||||
throw new Error('SmartConversationView: No selected conversation');
|
||||
}
|
||||
|
||||
const { toggleSelectMode } = useConversationsActions();
|
||||
const selectedMessageIds = useSelector(getSelectedMessageIds);
|
||||
const isSelectMode = selectedMessageIds != null;
|
||||
|
||||
const { processAttachments } = useComposerActions();
|
||||
|
||||
const hasOpenModal = useSelector((state: StateType) => {
|
||||
return (
|
||||
state.globalModals.forwardMessagesProps != null ||
|
||||
state.globalModals.deleteMessagesProps != null ||
|
||||
state.globalModals.hasConfirmationModal
|
||||
);
|
||||
});
|
||||
|
||||
const shouldHideConversationView = useSelector((state: StateType) => {
|
||||
const activePanel = getActivePanel(state);
|
||||
const isAnimating = getIsPanelAnimating(state);
|
||||
return activePanel && !isAnimating;
|
||||
});
|
||||
|
||||
return (
|
||||
<ConversationView
|
||||
conversationId={conversationId}
|
||||
hasOpenModal={hasOpenModal}
|
||||
isSelectMode={isSelectMode}
|
||||
onExitSelectMode={() => {
|
||||
toggleSelectMode(false);
|
||||
}}
|
||||
processAttachments={processAttachments}
|
||||
renderCompositionArea={() => <SmartCompositionArea id={conversationId} />}
|
||||
renderConversationHeader={() => (
|
||||
<SmartConversationHeader id={conversationId} />
|
||||
)}
|
||||
renderTimeline={() => (
|
||||
<SmartTimeline key={conversationId} id={conversationId} />
|
||||
)}
|
||||
renderPanel={() => <ConversationPanel conversationId={conversationId} />}
|
||||
shouldHideConversationView={shouldHideConversationView}
|
||||
/>
|
||||
);
|
||||
function renderCompositionArea(conversationId: string) {
|
||||
return <SmartCompositionArea id={conversationId} />;
|
||||
}
|
||||
|
||||
function renderConversationHeader(conversationId: string) {
|
||||
return <SmartConversationHeader id={conversationId} />;
|
||||
}
|
||||
|
||||
function renderTimeline(conversationId: string) {
|
||||
return <SmartTimeline key={conversationId} id={conversationId} />;
|
||||
}
|
||||
|
||||
function renderPanel(conversationId: string) {
|
||||
return <ConversationPanel conversationId={conversationId} />;
|
||||
}
|
||||
|
||||
export const SmartConversationView = memo(
|
||||
function SmartConversationView(): JSX.Element {
|
||||
const conversationId = useSelector(getSelectedConversationId);
|
||||
|
||||
if (!conversationId) {
|
||||
throw new Error('SmartConversationView: No selected conversation');
|
||||
}
|
||||
|
||||
const { toggleSelectMode } = useConversationsActions();
|
||||
const selectedMessageIds = useSelector(getSelectedMessageIds);
|
||||
const isSelectMode = selectedMessageIds != null;
|
||||
|
||||
const { processAttachments } = useComposerActions();
|
||||
|
||||
const hasOpenModal = useSelector((state: StateType) => {
|
||||
return (
|
||||
state.globalModals.forwardMessagesProps != null ||
|
||||
state.globalModals.deleteMessagesProps != null ||
|
||||
state.globalModals.hasConfirmationModal
|
||||
);
|
||||
});
|
||||
|
||||
const shouldHideConversationView = useSelector((state: StateType) => {
|
||||
const activePanel = getActivePanel(state);
|
||||
const isAnimating = getIsPanelAnimating(state);
|
||||
return activePanel && !isAnimating;
|
||||
});
|
||||
|
||||
const onExitSelectMode = useCallback(() => {
|
||||
toggleSelectMode(false);
|
||||
}, [toggleSelectMode]);
|
||||
|
||||
return (
|
||||
<ConversationView
|
||||
conversationId={conversationId}
|
||||
hasOpenModal={hasOpenModal}
|
||||
isSelectMode={isSelectMode}
|
||||
onExitSelectMode={onExitSelectMode}
|
||||
processAttachments={processAttachments}
|
||||
renderCompositionArea={renderCompositionArea}
|
||||
renderConversationHeader={renderConversationHeader}
|
||||
renderTimeline={renderTimeline}
|
||||
renderPanel={renderPanel}
|
||||
shouldHideConversationView={shouldHideConversationView}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { useSelector } from 'react-redux';
|
||||
import React, { memo } from 'react';
|
||||
import { CrashReportDialog } from '../../components/CrashReportDialog';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { useCrashReportsActions } from '../ducks/crashReports';
|
||||
import { getCrashReportsIsPending } from '../selectors/crashReports';
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
return {
|
||||
...state.crashReports,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartCrashReportDialog = smart(CrashReportDialog);
|
||||
export const SmartCrashReportDialog = memo(function SmartCrashReportDialog() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const isPending = useSelector(getCrashReportsIsPending);
|
||||
const { writeCrashReportsToLog, eraseCrashReports } =
|
||||
useCrashReportsActions();
|
||||
return (
|
||||
<CrashReportDialog
|
||||
i18n={i18n}
|
||||
isPending={isPending}
|
||||
writeCrashReportsToLog={writeCrashReportsToLog}
|
||||
eraseCrashReports={eraseCrashReports}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,51 +1,66 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type { StateType } from '../reducer';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import { usePreferredReactionsActions } from '../ducks/preferredReactions';
|
||||
import { useItemsActions } from '../ducks/items';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getEmojiSkinTone } from '../selectors/items';
|
||||
import { useRecentEmojis } from '../selectors/emojis';
|
||||
import { getCustomizeModalState } from '../selectors/preferredReactions';
|
||||
|
||||
import { CustomizingPreferredReactionsModal } from '../../components/CustomizingPreferredReactionsModal';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
|
||||
export function SmartCustomizingPreferredReactionsModal(): JSX.Element {
|
||||
const preferredReactionsActions = usePreferredReactionsActions();
|
||||
const { onSetSkinTone } = useItemsActions();
|
||||
export const SmartCustomizingPreferredReactionsModal = memo(
|
||||
function SmartCustomizingPreferredReactionsModal(): JSX.Element {
|
||||
const i18n = useSelector(getIntl);
|
||||
const customizeModalState = useSelector(getCustomizeModalState);
|
||||
const skinTone = useSelector(getEmojiSkinTone);
|
||||
const recentEmojis = useRecentEmojis();
|
||||
|
||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||
const {
|
||||
cancelCustomizePreferredReactionsModal,
|
||||
deselectDraftEmoji,
|
||||
replaceSelectedDraftEmoji,
|
||||
resetDraftEmoji,
|
||||
savePreferredReactions,
|
||||
selectDraftEmojiToBeReplaced,
|
||||
} = usePreferredReactionsActions();
|
||||
const { onSetSkinTone } = useItemsActions();
|
||||
|
||||
const customizeModalState = useSelector<
|
||||
StateType,
|
||||
ReturnType<typeof getCustomizeModalState>
|
||||
>(state => getCustomizeModalState(state));
|
||||
|
||||
const recentEmojis = useRecentEmojis();
|
||||
|
||||
const skinTone = useSelector<StateType, number>(state =>
|
||||
getEmojiSkinTone(state)
|
||||
);
|
||||
|
||||
if (!customizeModalState) {
|
||||
throw new Error(
|
||||
strictAssert(
|
||||
customizeModalState != null,
|
||||
'<SmartCustomizingPreferredReactionsModal> requires a modal'
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomizingPreferredReactionsModal
|
||||
i18n={i18n}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
recentEmojis={recentEmojis}
|
||||
skinTone={skinTone}
|
||||
{...preferredReactionsActions}
|
||||
{...customizeModalState}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const {
|
||||
hadSaveError,
|
||||
isSaving,
|
||||
draftPreferredReactions,
|
||||
originalPreferredReactions,
|
||||
selectedDraftEmojiIndex,
|
||||
} = customizeModalState;
|
||||
|
||||
return (
|
||||
<CustomizingPreferredReactionsModal
|
||||
cancelCustomizePreferredReactionsModal={
|
||||
cancelCustomizePreferredReactionsModal
|
||||
}
|
||||
deselectDraftEmoji={deselectDraftEmoji}
|
||||
draftPreferredReactions={draftPreferredReactions}
|
||||
hadSaveError={hadSaveError}
|
||||
i18n={i18n}
|
||||
isSaving={isSaving}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
originalPreferredReactions={originalPreferredReactions}
|
||||
recentEmojis={recentEmojis}
|
||||
replaceSelectedDraftEmoji={replaceSelectedDraftEmoji}
|
||||
resetDraftEmoji={resetDraftEmoji}
|
||||
savePreferredReactions={savePreferredReactions}
|
||||
selectDraftEmojiToBeReplaced={selectDraftEmojiToBeReplaced}
|
||||
selectedDraftEmojiIndex={selectedDraftEmojiIndex}
|
||||
skinTone={skinTone}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { DeleteMessagesPropsType } from '../ducks/globalModals';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
|
@ -12,56 +11,57 @@ import { strictAssert } from '../../util/assert';
|
|||
import { canDeleteMessagesForEveryone } from '../selectors/message';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useToastActions } from '../ducks/toast';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
import {
|
||||
getConversationSelector,
|
||||
getLastSelectedMessage,
|
||||
} from '../selectors/conversations';
|
||||
import { getDeleteMessagesProps } from '../selectors/globalModals';
|
||||
|
||||
export function SmartDeleteMessagesModal(): JSX.Element | null {
|
||||
const deleteMessagesProps = useSelector<
|
||||
StateType,
|
||||
DeleteMessagesPropsType | undefined
|
||||
>(state => state.globalModals.deleteMessagesProps);
|
||||
strictAssert(
|
||||
deleteMessagesProps != null,
|
||||
'Cannot render delete messages modal without messages'
|
||||
);
|
||||
const { conversationId, messageIds, onDelete } = deleteMessagesProps;
|
||||
const isMe = useSelector((state: StateType) => {
|
||||
return getConversationSelector(state)(conversationId).isMe;
|
||||
});
|
||||
export const SmartDeleteMessagesModal = memo(
|
||||
function SmartDeleteMessagesModal() {
|
||||
const deleteMessagesProps = useSelector(getDeleteMessagesProps);
|
||||
strictAssert(
|
||||
deleteMessagesProps != null,
|
||||
'Cannot render delete messages modal without messages'
|
||||
);
|
||||
const { conversationId, messageIds, onDelete } = deleteMessagesProps;
|
||||
const isMe = useSelector((state: StateType) => {
|
||||
return getConversationSelector(state)(conversationId).isMe;
|
||||
});
|
||||
|
||||
const canDeleteForEveryone = useSelector((state: StateType) => {
|
||||
return canDeleteMessagesForEveryone(state, { messageIds, isMe });
|
||||
});
|
||||
const lastSelectedMessage = useSelector((state: StateType) => {
|
||||
return state.conversations.lastSelectedMessage;
|
||||
});
|
||||
const i18n = useSelector(getIntl);
|
||||
const { toggleDeleteMessagesModal } = useGlobalModalActions();
|
||||
const { deleteMessages, deleteMessagesForEveryone } =
|
||||
useConversationsActions();
|
||||
const { showToast } = useToastActions();
|
||||
const canDeleteForEveryone = useSelector((state: StateType) => {
|
||||
return canDeleteMessagesForEveryone(state, { messageIds, isMe });
|
||||
});
|
||||
const lastSelectedMessage = useSelector(getLastSelectedMessage);
|
||||
const i18n = useSelector(getIntl);
|
||||
const { toggleDeleteMessagesModal } = useGlobalModalActions();
|
||||
const { deleteMessages, deleteMessagesForEveryone } =
|
||||
useConversationsActions();
|
||||
const { showToast } = useToastActions();
|
||||
|
||||
return (
|
||||
<DeleteMessagesModal
|
||||
isMe={isMe}
|
||||
canDeleteForEveryone={canDeleteForEveryone}
|
||||
i18n={i18n}
|
||||
messageCount={deleteMessagesProps.messageIds.length}
|
||||
onClose={() => {
|
||||
toggleDeleteMessagesModal(undefined);
|
||||
}}
|
||||
onDeleteForMe={() => {
|
||||
deleteMessages({
|
||||
conversationId,
|
||||
messageIds,
|
||||
lastSelectedMessage,
|
||||
});
|
||||
onDelete?.();
|
||||
}}
|
||||
onDeleteForEveryone={() => {
|
||||
deleteMessagesForEveryone(messageIds);
|
||||
onDelete?.();
|
||||
}}
|
||||
showToast={showToast}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<DeleteMessagesModal
|
||||
isMe={isMe}
|
||||
canDeleteForEveryone={canDeleteForEveryone}
|
||||
i18n={i18n}
|
||||
messageCount={deleteMessagesProps.messageIds.length}
|
||||
onClose={() => {
|
||||
toggleDeleteMessagesModal(undefined);
|
||||
}}
|
||||
onDeleteForMe={() => {
|
||||
deleteMessages({
|
||||
conversationId,
|
||||
messageIds,
|
||||
lastSelectedMessage,
|
||||
});
|
||||
onDelete?.();
|
||||
}}
|
||||
onDeleteForEveryone={() => {
|
||||
deleteMessagesForEveryone(messageIds);
|
||||
onDelete?.();
|
||||
}}
|
||||
showToast={showToast}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { GlobalModalsStateType } from '../ducks/globalModals';
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
import type { StateType } from '../reducer';
|
||||
import { EditHistoryMessagesModal } from '../../components/EditHistoryMessagesModal';
|
||||
import { getIntl, getPlatform } from '../selectors/user';
|
||||
import { getMessagePropsSelector } from '../selectors/message';
|
||||
|
@ -14,49 +12,46 @@ import { useConversationsActions } from '../ducks/conversations';
|
|||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { useLightboxActions } from '../ducks/lightbox';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { getEditHistoryMessages } from '../selectors/globalModals';
|
||||
|
||||
export function SmartEditHistoryMessagesModal(): JSX.Element {
|
||||
const i18n = useSelector(getIntl);
|
||||
const platform = useSelector(getPlatform);
|
||||
export const SmartEditHistoryMessagesModal = memo(
|
||||
function SmartEditHistoryMessagesModal(): JSX.Element {
|
||||
const i18n = useSelector(getIntl);
|
||||
const platform = useSelector(getPlatform);
|
||||
|
||||
const { closeEditHistoryModal } = useGlobalModalActions();
|
||||
const { closeEditHistoryModal } = useGlobalModalActions();
|
||||
const { kickOffAttachmentDownload } = useConversationsActions();
|
||||
const { showLightbox } = useLightboxActions();
|
||||
|
||||
const { kickOffAttachmentDownload } = useConversationsActions();
|
||||
const { showLightbox } = useLightboxActions();
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const messagesAttributes = useSelector(getEditHistoryMessages);
|
||||
const messagePropsSelector = useSelector(getMessagePropsSelector);
|
||||
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
strictAssert(messagesAttributes, 'messages not provided');
|
||||
|
||||
const { editHistoryMessages: messagesAttributes } = useSelector<
|
||||
StateType,
|
||||
GlobalModalsStateType
|
||||
>(state => state.globalModals);
|
||||
const editHistoryMessages = useMemo(() => {
|
||||
return messagesAttributes.map(messageAttributes => ({
|
||||
...messagePropsSelector(messageAttributes as MessageAttributesType),
|
||||
// Make sure the messages don't get an "edited" badge
|
||||
isEditedMessage: false,
|
||||
// Do not show the same reactions in the message history UI
|
||||
reactions: undefined,
|
||||
// Make sure that the timestamp is the correct timestamp from attributes
|
||||
// not the one that the selector derives.
|
||||
timestamp: messageAttributes.timestamp,
|
||||
}));
|
||||
}, [messagesAttributes, messagePropsSelector]);
|
||||
|
||||
const messagePropsSelector = useSelector(getMessagePropsSelector);
|
||||
|
||||
strictAssert(messagesAttributes, 'messages not provided');
|
||||
|
||||
const editHistoryMessages = useMemo(() => {
|
||||
return messagesAttributes.map(messageAttributes => ({
|
||||
...messagePropsSelector(messageAttributes as MessageAttributesType),
|
||||
// Make sure the messages don't get an "edited" badge
|
||||
isEditedMessage: false,
|
||||
// Do not show the same reactions in the message history UI
|
||||
reactions: undefined,
|
||||
// Make sure that the timestamp is the correct timestamp from attributes
|
||||
// not the one that the selector derives.
|
||||
timestamp: messageAttributes.timestamp,
|
||||
}));
|
||||
}, [messagesAttributes, messagePropsSelector]);
|
||||
|
||||
return (
|
||||
<EditHistoryMessagesModal
|
||||
closeEditHistoryModal={closeEditHistoryModal}
|
||||
editHistoryMessages={editHistoryMessages}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
platform={platform}
|
||||
kickOffAttachmentDownload={kickOffAttachmentDownload}
|
||||
showLightbox={showLightbox}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<EditHistoryMessagesModal
|
||||
closeEditHistoryModal={closeEditHistoryModal}
|
||||
editHistoryMessages={editHistoryMessages}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
platform={platform}
|
||||
kickOffAttachmentDownload={kickOffAttachmentDownload}
|
||||
showLightbox={showLightbox}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
|
||||
import type { PropsDataType } from '../../components/EditUsernameModalBody';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { EditUsernameModalBody } from '../../components/EditUsernameModalBody';
|
||||
import { getMinNickname, getMaxNickname } from '../../util/Username';
|
||||
|
||||
import type { StateType } from '../reducer';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import {
|
||||
getUsernameReservationState,
|
||||
|
@ -18,25 +13,55 @@ import {
|
|||
} from '../selectors/username';
|
||||
import { getUsernameCorrupted } from '../selectors/items';
|
||||
import { getMe } from '../selectors/conversations';
|
||||
import { useUsernameActions } from '../ducks/username';
|
||||
import { useToastActions } from '../ducks/toast';
|
||||
|
||||
function mapStateToProps(state: StateType): PropsDataType {
|
||||
const i18n = getIntl(state);
|
||||
const { username } = getMe(state);
|
||||
const usernameCorrupted = getUsernameCorrupted(state);
|
||||
export type SmartEditUsernameModalBodyProps = Readonly<{
|
||||
isRootModal: boolean;
|
||||
onClose(): void;
|
||||
}>;
|
||||
|
||||
return {
|
||||
i18n,
|
||||
usernameCorrupted,
|
||||
currentUsername: usernameCorrupted ? undefined : username,
|
||||
minNickname: getMinNickname(),
|
||||
maxNickname: getMaxNickname(),
|
||||
state: getUsernameReservationState(state),
|
||||
recoveredUsername: getRecoveredUsername(state),
|
||||
reservation: getUsernameReservationObject(state),
|
||||
error: getUsernameReservationError(state),
|
||||
};
|
||||
}
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartEditUsernameModalBody = smart(EditUsernameModalBody);
|
||||
export const SmartEditUsernameModalBody = memo(
|
||||
function SmartEditUsernameModalBody({
|
||||
isRootModal,
|
||||
onClose,
|
||||
}: SmartEditUsernameModalBodyProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const { username } = useSelector(getMe);
|
||||
const usernameCorrupted = useSelector(getUsernameCorrupted);
|
||||
const currentUsername = usernameCorrupted ? undefined : username;
|
||||
const minNickname = getMinNickname();
|
||||
const maxNickname = getMaxNickname();
|
||||
const state = useSelector(getUsernameReservationState);
|
||||
const recoveredUsername = useSelector(getRecoveredUsername);
|
||||
const reservation = useSelector(getUsernameReservationObject);
|
||||
const error = useSelector(getUsernameReservationError);
|
||||
const {
|
||||
setUsernameReservationError,
|
||||
clearUsernameReservation,
|
||||
reserveUsername,
|
||||
confirmUsername,
|
||||
} = useUsernameActions();
|
||||
const { showToast } = useToastActions();
|
||||
return (
|
||||
<EditUsernameModalBody
|
||||
i18n={i18n}
|
||||
usernameCorrupted={usernameCorrupted}
|
||||
currentUsername={currentUsername}
|
||||
minNickname={minNickname}
|
||||
maxNickname={maxNickname}
|
||||
state={state}
|
||||
recoveredUsername={recoveredUsername}
|
||||
reservation={reservation}
|
||||
error={error}
|
||||
setUsernameReservationError={setUsernameReservationError}
|
||||
clearUsernameReservation={clearUsernameReservation}
|
||||
reserveUsername={reserveUsername}
|
||||
confirmUsername={confirmUsername}
|
||||
showToast={showToast}
|
||||
isRootModal={isRootModal}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,56 +1,53 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import React, { forwardRef, memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { StateType } from '../reducer';
|
||||
import { useRecentEmojis } from '../selectors/emojis';
|
||||
import { useEmojisActions as useEmojiActions } from '../ducks/emojis';
|
||||
|
||||
import type { Props as EmojiPickerProps } from '../../components/emoji/EmojiPicker';
|
||||
import { EmojiPicker } from '../../components/emoji/EmojiPicker';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getEmojiSkinTone } from '../selectors/items';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
|
||||
export const SmartEmojiPicker = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
Pick<
|
||||
EmojiPickerProps,
|
||||
'onClickSettings' | 'onPickEmoji' | 'onSetSkinTone' | 'onClose' | 'style'
|
||||
>
|
||||
>(function SmartEmojiPickerInner(
|
||||
{ onClickSettings, onPickEmoji, onSetSkinTone, onClose, style },
|
||||
ref
|
||||
) {
|
||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||
const skinTone = useSelector<StateType, number>(state =>
|
||||
getEmojiSkinTone(state)
|
||||
);
|
||||
export const SmartEmojiPicker = memo(
|
||||
forwardRef<
|
||||
HTMLDivElement,
|
||||
Pick<
|
||||
EmojiPickerProps,
|
||||
'onClickSettings' | 'onPickEmoji' | 'onSetSkinTone' | 'onClose' | 'style'
|
||||
>
|
||||
>(function SmartEmojiPickerInner(
|
||||
{ onClickSettings, onPickEmoji, onSetSkinTone, onClose, style },
|
||||
ref
|
||||
) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const skinTone = useSelector(getEmojiSkinTone);
|
||||
|
||||
const recentEmojis = useRecentEmojis();
|
||||
const recentEmojis = useRecentEmojis();
|
||||
|
||||
const { onUseEmoji } = useEmojiActions();
|
||||
const { onUseEmoji } = useEmojiActions();
|
||||
|
||||
const handlePickEmoji = React.useCallback(
|
||||
data => {
|
||||
onUseEmoji({ shortName: data.shortName });
|
||||
onPickEmoji(data);
|
||||
},
|
||||
[onUseEmoji, onPickEmoji]
|
||||
);
|
||||
const handlePickEmoji = React.useCallback(
|
||||
data => {
|
||||
onUseEmoji({ shortName: data.shortName });
|
||||
onPickEmoji(data);
|
||||
},
|
||||
[onUseEmoji, onPickEmoji]
|
||||
);
|
||||
|
||||
return (
|
||||
<EmojiPicker
|
||||
ref={ref}
|
||||
i18n={i18n}
|
||||
skinTone={skinTone}
|
||||
onClickSettings={onClickSettings}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
onPickEmoji={handlePickEmoji}
|
||||
recentEmojis={recentEmojis}
|
||||
onClose={onClose}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<EmojiPicker
|
||||
ref={ref}
|
||||
i18n={i18n}
|
||||
skinTone={skinTone}
|
||||
onClickSettings={onClickSettings}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
onPickEmoji={handlePickEmoji}
|
||||
recentEmojis={recentEmojis}
|
||||
onClose={onClose}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
})
|
||||
);
|
||||
|
|
|
@ -7,7 +7,6 @@ import type {
|
|||
ForwardMessagePropsType,
|
||||
ForwardMessagesPropsType,
|
||||
} from '../ducks/globalModals';
|
||||
import type { StateType } from '../reducer';
|
||||
import * as log from '../../logging/log';
|
||||
import { ForwardMessagesModal } from '../../components/ForwardMessagesModal';
|
||||
import { LinkPreviewSourceType } from '../../types/LinkPreview';
|
||||
|
@ -37,6 +36,7 @@ import type {
|
|||
ForwardMessageData,
|
||||
MessageForwardDraft,
|
||||
} from '../../types/ForwardDraft';
|
||||
import { getForwardMessagesProps } from '../selectors/globalModals';
|
||||
|
||||
function toMessageForwardDraft(
|
||||
props: ForwardMessagePropsType,
|
||||
|
@ -54,10 +54,7 @@ function toMessageForwardDraft(
|
|||
}
|
||||
|
||||
export function SmartForwardMessagesModal(): JSX.Element | null {
|
||||
const forwardMessagesProps = useSelector<
|
||||
StateType,
|
||||
ForwardMessagesPropsType | undefined
|
||||
>(state => state.globalModals.forwardMessagesProps);
|
||||
const forwardMessagesProps = useSelector(getForwardMessagesProps);
|
||||
|
||||
if (forwardMessagesProps == null) {
|
||||
return null;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { ConversationDetailsMembershipList } from '../../components/conversation/conversation-details/ConversationDetailsMembershipList';
|
||||
|
@ -19,7 +19,9 @@ export type PropsType = {
|
|||
conversationId: string;
|
||||
};
|
||||
|
||||
export function SmartGV1Members({ conversationId }: PropsType): JSX.Element {
|
||||
export const SmartGV1Members = memo(function SmartGV1Members({
|
||||
conversationId,
|
||||
}: PropsType): JSX.Element {
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
|
@ -53,4 +55,4 @@ export function SmartGV1Members({ conversationId }: PropsType): JSX.Element {
|
|||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type { GlobalModalsStateType } from '../ducks/globalModals';
|
||||
import type { StateType } from '../reducer';
|
||||
import type { ButtonVariant } from '../../components/Button';
|
||||
import { ErrorModal } from '../../components/ErrorModal';
|
||||
import { GlobalModalContainer } from '../../components/GlobalModalContainer';
|
||||
|
@ -26,6 +23,7 @@ import { getIntl, getTheme } from '../selectors/user';
|
|||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { SmartDeleteMessagesModal } from './DeleteMessagesModal';
|
||||
import { SmartMessageRequestActionsConfirmation } from './MessageRequestActionsConfirmation';
|
||||
import { getGlobalModalsState } from '../selectors/globalModals';
|
||||
|
||||
function renderEditHistoryMessagesModal(): JSX.Element {
|
||||
return <SmartEditHistoryMessagesModal />;
|
||||
|
@ -71,148 +69,152 @@ function renderAboutContactModal(): JSX.Element {
|
|||
return <SmartAboutContactModal />;
|
||||
}
|
||||
|
||||
export function SmartGlobalModalContainer(): JSX.Element {
|
||||
const conversationsStoppingSend = useSelector(getConversationsStoppingSend);
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
export const SmartGlobalModalContainer = memo(
|
||||
function SmartGlobalModalContainer() {
|
||||
const conversationsStoppingSend = useSelector(getConversationsStoppingSend);
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
|
||||
const hasSafetyNumberChangeModal = conversationsStoppingSend.length > 0;
|
||||
const hasSafetyNumberChangeModal = conversationsStoppingSend.length > 0;
|
||||
|
||||
const {
|
||||
aboutContactModalContactId,
|
||||
addUserToAnotherGroupModalContactId,
|
||||
authArtCreatorData,
|
||||
contactModalState,
|
||||
deleteMessagesProps,
|
||||
editHistoryMessages,
|
||||
errorModalProps,
|
||||
formattingWarningData,
|
||||
forwardMessagesProps,
|
||||
messageRequestActionsConfirmationProps,
|
||||
isAuthorizingArtCreator,
|
||||
isProfileEditorVisible,
|
||||
isShortcutGuideModalVisible,
|
||||
isSignalConnectionsVisible,
|
||||
isStoriesSettingsVisible,
|
||||
isWhatsNewVisible,
|
||||
usernameOnboardingState,
|
||||
safetyNumberChangedBlockingData,
|
||||
safetyNumberModalContactId,
|
||||
sendEditWarningData,
|
||||
stickerPackPreviewId,
|
||||
userNotFoundModalState,
|
||||
} = useSelector<StateType, GlobalModalsStateType>(
|
||||
state => state.globalModals
|
||||
);
|
||||
const {
|
||||
aboutContactModalContactId,
|
||||
addUserToAnotherGroupModalContactId,
|
||||
authArtCreatorData,
|
||||
contactModalState,
|
||||
deleteMessagesProps,
|
||||
editHistoryMessages,
|
||||
errorModalProps,
|
||||
formattingWarningData,
|
||||
forwardMessagesProps,
|
||||
messageRequestActionsConfirmationProps,
|
||||
isAuthorizingArtCreator,
|
||||
isProfileEditorVisible,
|
||||
isShortcutGuideModalVisible,
|
||||
isSignalConnectionsVisible,
|
||||
isStoriesSettingsVisible,
|
||||
isWhatsNewVisible,
|
||||
usernameOnboardingState,
|
||||
safetyNumberChangedBlockingData,
|
||||
safetyNumberModalContactId,
|
||||
sendEditWarningData,
|
||||
stickerPackPreviewId,
|
||||
userNotFoundModalState,
|
||||
} = useSelector(getGlobalModalsState);
|
||||
|
||||
const {
|
||||
cancelAuthorizeArtCreator,
|
||||
closeErrorModal,
|
||||
confirmAuthorizeArtCreator,
|
||||
hideUserNotFoundModal,
|
||||
hideWhatsNewModal,
|
||||
showFormattingWarningModal,
|
||||
showSendEditWarningModal,
|
||||
toggleSignalConnectionsModal,
|
||||
} = useGlobalModalActions();
|
||||
const {
|
||||
cancelAuthorizeArtCreator,
|
||||
closeErrorModal,
|
||||
confirmAuthorizeArtCreator,
|
||||
hideUserNotFoundModal,
|
||||
hideWhatsNewModal,
|
||||
showFormattingWarningModal,
|
||||
showSendEditWarningModal,
|
||||
toggleSignalConnectionsModal,
|
||||
} = useGlobalModalActions();
|
||||
|
||||
const renderAddUserToAnotherGroup = useCallback(() => {
|
||||
return (
|
||||
<SmartAddUserToAnotherGroupModal
|
||||
contactID={String(addUserToAnotherGroupModalContactId)}
|
||||
/>
|
||||
);
|
||||
}, [addUserToAnotherGroupModalContactId]);
|
||||
|
||||
const renderSafetyNumber = useCallback(
|
||||
() => (
|
||||
<SmartSafetyNumberModal
|
||||
contactID={String(safetyNumberModalContactId)}
|
||||
/>
|
||||
),
|
||||
[safetyNumberModalContactId]
|
||||
);
|
||||
|
||||
const renderStickerPreviewModal = useCallback(
|
||||
() =>
|
||||
stickerPackPreviewId ? (
|
||||
<SmartStickerPreviewModal packId={stickerPackPreviewId} />
|
||||
) : null,
|
||||
[stickerPackPreviewId]
|
||||
);
|
||||
|
||||
const renderErrorModal = useCallback(
|
||||
({
|
||||
buttonVariant,
|
||||
description,
|
||||
title,
|
||||
}: {
|
||||
buttonVariant?: ButtonVariant;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}) => (
|
||||
<ErrorModal
|
||||
buttonVariant={buttonVariant}
|
||||
description={description}
|
||||
title={title}
|
||||
i18n={i18n}
|
||||
onClose={closeErrorModal}
|
||||
/>
|
||||
),
|
||||
[closeErrorModal, i18n]
|
||||
);
|
||||
|
||||
const renderAddUserToAnotherGroup = useCallback(() => {
|
||||
return (
|
||||
<SmartAddUserToAnotherGroupModal
|
||||
contactID={String(addUserToAnotherGroupModalContactId)}
|
||||
<GlobalModalContainer
|
||||
addUserToAnotherGroupModalContactId={
|
||||
addUserToAnotherGroupModalContactId
|
||||
}
|
||||
contactModalState={contactModalState}
|
||||
editHistoryMessages={editHistoryMessages}
|
||||
errorModalProps={errorModalProps}
|
||||
deleteMessagesProps={deleteMessagesProps}
|
||||
formattingWarningData={formattingWarningData}
|
||||
forwardMessagesProps={forwardMessagesProps}
|
||||
messageRequestActionsConfirmationProps={
|
||||
messageRequestActionsConfirmationProps
|
||||
}
|
||||
hasSafetyNumberChangeModal={hasSafetyNumberChangeModal}
|
||||
hideUserNotFoundModal={hideUserNotFoundModal}
|
||||
hideWhatsNewModal={hideWhatsNewModal}
|
||||
i18n={i18n}
|
||||
isAboutContactModalVisible={aboutContactModalContactId != null}
|
||||
isProfileEditorVisible={isProfileEditorVisible}
|
||||
isShortcutGuideModalVisible={isShortcutGuideModalVisible}
|
||||
isSignalConnectionsVisible={isSignalConnectionsVisible}
|
||||
isStoriesSettingsVisible={isStoriesSettingsVisible}
|
||||
isWhatsNewVisible={isWhatsNewVisible}
|
||||
renderAboutContactModal={renderAboutContactModal}
|
||||
renderAddUserToAnotherGroup={renderAddUserToAnotherGroup}
|
||||
renderContactModal={renderContactModal}
|
||||
renderEditHistoryMessagesModal={renderEditHistoryMessagesModal}
|
||||
renderErrorModal={renderErrorModal}
|
||||
renderDeleteMessagesModal={renderDeleteMessagesModal}
|
||||
renderForwardMessagesModal={renderForwardMessagesModal}
|
||||
renderMessageRequestActionsConfirmation={
|
||||
renderMessageRequestActionsConfirmation
|
||||
}
|
||||
renderProfileEditor={renderProfileEditor}
|
||||
renderUsernameOnboarding={renderUsernameOnboarding}
|
||||
renderSafetyNumber={renderSafetyNumber}
|
||||
renderSendAnywayDialog={renderSendAnywayDialog}
|
||||
renderShortcutGuideModal={renderShortcutGuideModal}
|
||||
renderStickerPreviewModal={renderStickerPreviewModal}
|
||||
renderStoriesSettings={renderStoriesSettings}
|
||||
safetyNumberChangedBlockingData={safetyNumberChangedBlockingData}
|
||||
safetyNumberModalContactId={safetyNumberModalContactId}
|
||||
sendEditWarningData={sendEditWarningData}
|
||||
showFormattingWarningModal={showFormattingWarningModal}
|
||||
showSendEditWarningModal={showSendEditWarningModal}
|
||||
stickerPackPreviewId={stickerPackPreviewId}
|
||||
theme={theme}
|
||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||
userNotFoundModalState={userNotFoundModalState}
|
||||
usernameOnboardingState={usernameOnboardingState}
|
||||
isAuthorizingArtCreator={isAuthorizingArtCreator}
|
||||
authArtCreatorData={authArtCreatorData}
|
||||
cancelAuthorizeArtCreator={cancelAuthorizeArtCreator}
|
||||
confirmAuthorizeArtCreator={confirmAuthorizeArtCreator}
|
||||
/>
|
||||
);
|
||||
}, [addUserToAnotherGroupModalContactId]);
|
||||
|
||||
const renderSafetyNumber = useCallback(
|
||||
() => (
|
||||
<SmartSafetyNumberModal contactID={String(safetyNumberModalContactId)} />
|
||||
),
|
||||
[safetyNumberModalContactId]
|
||||
);
|
||||
|
||||
const renderStickerPreviewModal = useCallback(
|
||||
() =>
|
||||
stickerPackPreviewId ? (
|
||||
<SmartStickerPreviewModal packId={stickerPackPreviewId} />
|
||||
) : null,
|
||||
[stickerPackPreviewId]
|
||||
);
|
||||
|
||||
const renderErrorModal = useCallback(
|
||||
({
|
||||
buttonVariant,
|
||||
description,
|
||||
title,
|
||||
}: {
|
||||
buttonVariant?: ButtonVariant;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}) => (
|
||||
<ErrorModal
|
||||
buttonVariant={buttonVariant}
|
||||
description={description}
|
||||
title={title}
|
||||
i18n={i18n}
|
||||
onClose={closeErrorModal}
|
||||
/>
|
||||
),
|
||||
[closeErrorModal, i18n]
|
||||
);
|
||||
|
||||
return (
|
||||
<GlobalModalContainer
|
||||
addUserToAnotherGroupModalContactId={addUserToAnotherGroupModalContactId}
|
||||
contactModalState={contactModalState}
|
||||
editHistoryMessages={editHistoryMessages}
|
||||
errorModalProps={errorModalProps}
|
||||
deleteMessagesProps={deleteMessagesProps}
|
||||
formattingWarningData={formattingWarningData}
|
||||
forwardMessagesProps={forwardMessagesProps}
|
||||
messageRequestActionsConfirmationProps={
|
||||
messageRequestActionsConfirmationProps
|
||||
}
|
||||
hasSafetyNumberChangeModal={hasSafetyNumberChangeModal}
|
||||
hideUserNotFoundModal={hideUserNotFoundModal}
|
||||
hideWhatsNewModal={hideWhatsNewModal}
|
||||
i18n={i18n}
|
||||
isAboutContactModalVisible={aboutContactModalContactId != null}
|
||||
isProfileEditorVisible={isProfileEditorVisible}
|
||||
isShortcutGuideModalVisible={isShortcutGuideModalVisible}
|
||||
isSignalConnectionsVisible={isSignalConnectionsVisible}
|
||||
isStoriesSettingsVisible={isStoriesSettingsVisible}
|
||||
isWhatsNewVisible={isWhatsNewVisible}
|
||||
renderAboutContactModal={renderAboutContactModal}
|
||||
renderAddUserToAnotherGroup={renderAddUserToAnotherGroup}
|
||||
renderContactModal={renderContactModal}
|
||||
renderEditHistoryMessagesModal={renderEditHistoryMessagesModal}
|
||||
renderErrorModal={renderErrorModal}
|
||||
renderDeleteMessagesModal={renderDeleteMessagesModal}
|
||||
renderForwardMessagesModal={renderForwardMessagesModal}
|
||||
renderMessageRequestActionsConfirmation={
|
||||
renderMessageRequestActionsConfirmation
|
||||
}
|
||||
renderProfileEditor={renderProfileEditor}
|
||||
renderUsernameOnboarding={renderUsernameOnboarding}
|
||||
renderSafetyNumber={renderSafetyNumber}
|
||||
renderSendAnywayDialog={renderSendAnywayDialog}
|
||||
renderShortcutGuideModal={renderShortcutGuideModal}
|
||||
renderStickerPreviewModal={renderStickerPreviewModal}
|
||||
renderStoriesSettings={renderStoriesSettings}
|
||||
safetyNumberChangedBlockingData={safetyNumberChangedBlockingData}
|
||||
safetyNumberModalContactId={safetyNumberModalContactId}
|
||||
sendEditWarningData={sendEditWarningData}
|
||||
showFormattingWarningModal={showFormattingWarningModal}
|
||||
showSendEditWarningModal={showSendEditWarningModal}
|
||||
stickerPackPreviewId={stickerPackPreviewId}
|
||||
theme={theme}
|
||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||
userNotFoundModalState={userNotFoundModalState}
|
||||
usernameOnboardingState={usernameOnboardingState}
|
||||
isAuthorizingArtCreator={isAuthorizingArtCreator}
|
||||
authArtCreatorData={authArtCreatorData}
|
||||
cancelAuthorizeArtCreator={cancelAuthorizeArtCreator}
|
||||
confirmAuthorizeArtCreator={confirmAuthorizeArtCreator}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,34 +1,38 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import type { PropsDataType } from '../../components/conversation/conversation-details/GroupLinkManagement';
|
||||
import type { StateType } from '../reducer';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { GroupLinkManagement } from '../../components/conversation/conversation-details/GroupLinkManagement';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
|
||||
export type SmartGroupLinkManagementProps = {
|
||||
export type SmartGroupLinkManagementProps = Readonly<{
|
||||
conversationId: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: SmartGroupLinkManagementProps
|
||||
): PropsDataType => {
|
||||
const conversation = getConversationSelector(state)(props.conversationId);
|
||||
const isAdmin = Boolean(conversation?.areWeAdmin);
|
||||
|
||||
return {
|
||||
...props,
|
||||
conversation,
|
||||
i18n: getIntl(state),
|
||||
isAdmin,
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartGroupLinkManagement = smart(GroupLinkManagement);
|
||||
export const SmartGroupLinkManagement = memo(function SmartGroupLinkManagement({
|
||||
conversationId,
|
||||
}: SmartGroupLinkManagementProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const conversation = conversationSelector(conversationId);
|
||||
const isAdmin = conversation?.areWeAdmin ?? false;
|
||||
const {
|
||||
changeHasGroupLink,
|
||||
generateNewGroupLink,
|
||||
setAccessControlAddFromInviteLinkSetting,
|
||||
} = useConversationsActions();
|
||||
return (
|
||||
<GroupLinkManagement
|
||||
i18n={i18n}
|
||||
changeHasGroupLink={changeHasGroupLink}
|
||||
conversation={conversation}
|
||||
generateNewGroupLink={generateNewGroupLink}
|
||||
isAdmin={isAdmin}
|
||||
setAccessControlAddFromInviteLinkSetting={
|
||||
setAccessControlAddFromInviteLinkSetting
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { DataPropsType as GroupV1MigrationDialogPropsType } from '../../components/GroupV1MigrationDialog';
|
||||
import { GroupV1MigrationDialog } from '../../components/GroupV1MigrationDialog';
|
||||
import type { ConversationType } from '../ducks/conversations';
|
||||
import type { StateType } from '../reducer';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import * as log from '../../logging/log';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
|
||||
export type PropsType = {
|
||||
readonly conversationId: string;
|
||||
readonly droppedMemberIds: Array<string>;
|
||||
readonly invitedMemberIds: Array<string>;
|
||||
} & Omit<
|
||||
|
@ -21,37 +20,62 @@ export type PropsType = {
|
|||
'i18n' | 'droppedMembers' | 'invitedMembers' | 'theme' | 'getPreferredBadge'
|
||||
>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: PropsType
|
||||
): GroupV1MigrationDialogPropsType => {
|
||||
const getConversation = getConversationSelector(state);
|
||||
const { droppedMemberIds, invitedMemberIds } = props;
|
||||
function isNonNullable<T>(value: T | null | undefined): value is T {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
const droppedMembers = droppedMemberIds
|
||||
.map(getConversation)
|
||||
.filter(Boolean) as Array<ConversationType>;
|
||||
if (droppedMembers.length !== droppedMemberIds.length) {
|
||||
log.warn('smart/GroupV1MigrationDialog: droppedMembers length changed');
|
||||
export const SmartGroupV1MigrationDialog = memo(
|
||||
function SmartGroupV1MigrationDialog({
|
||||
conversationId,
|
||||
areWeInvited,
|
||||
hasMigrated,
|
||||
droppedMemberIds,
|
||||
invitedMemberIds,
|
||||
}: PropsType) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
const getConversation = useSelector(getConversationSelector);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
|
||||
const { initiateMigrationToGroupV2 } = useConversationsActions();
|
||||
const { closeGV2MigrationDialog } = useGlobalModalActions();
|
||||
|
||||
const droppedMembers = useMemo(() => {
|
||||
const result = droppedMemberIds
|
||||
.map(getConversation)
|
||||
.filter(isNonNullable);
|
||||
if (result.length !== droppedMemberIds.length) {
|
||||
log.warn('smart/GroupV1MigrationDialog: droppedMembers length changed');
|
||||
}
|
||||
return result;
|
||||
}, [droppedMemberIds, getConversation]);
|
||||
|
||||
const invitedMembers = useMemo(() => {
|
||||
const result = invitedMemberIds
|
||||
.map(getConversation)
|
||||
.filter(isNonNullable);
|
||||
if (result.length !== invitedMemberIds.length) {
|
||||
log.warn('smart/GroupV1MigrationDialog: invitedMembers length changed');
|
||||
}
|
||||
return result;
|
||||
}, [invitedMemberIds, getConversation]);
|
||||
|
||||
const handleMigrate = useCallback(() => {
|
||||
initiateMigrationToGroupV2(conversationId);
|
||||
}, [initiateMigrationToGroupV2, conversationId]);
|
||||
|
||||
return (
|
||||
<GroupV1MigrationDialog
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
areWeInvited={areWeInvited}
|
||||
hasMigrated={hasMigrated}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
droppedMembers={droppedMembers}
|
||||
invitedMembers={invitedMembers}
|
||||
onMigrate={handleMigrate}
|
||||
onClose={closeGV2MigrationDialog}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const invitedMembers = invitedMemberIds
|
||||
.map(getConversation)
|
||||
.filter(Boolean) as Array<ConversationType>;
|
||||
if (invitedMembers.length !== invitedMemberIds.length) {
|
||||
log.warn('smart/GroupV1MigrationDialog: invitedMembers length changed');
|
||||
}
|
||||
|
||||
return {
|
||||
...props,
|
||||
droppedMembers,
|
||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||
invitedMembers,
|
||||
i18n: getIntl(state),
|
||||
theme: getTheme(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartGroupV1MigrationDialog = smart(GroupV1MigrationDialog);
|
||||
);
|
||||
|
|
|
@ -1,34 +1,38 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { PropsType as GroupV2JoinDialogPropsType } from '../../components/GroupV2JoinDialog';
|
||||
import { GroupV2JoinDialog } from '../../components/GroupV2JoinDialog';
|
||||
import type { StateType } from '../reducer';
|
||||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getPreJoinConversation } from '../selectors/conversations';
|
||||
|
||||
export type PropsType = Pick<GroupV2JoinDialogPropsType, 'join' | 'onClose'>;
|
||||
export type SmartGroupV2JoinDialogProps = Pick<
|
||||
GroupV2JoinDialogPropsType,
|
||||
'join' | 'onClose'
|
||||
>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: PropsType
|
||||
): GroupV2JoinDialogPropsType => {
|
||||
const preJoinConversation = getPreJoinConversation(state);
|
||||
|
||||
if (!preJoinConversation) {
|
||||
export const SmartGroupV2JoinDialog = memo(function SmartGroupV2JoinDialog({
|
||||
join,
|
||||
onClose,
|
||||
}: SmartGroupV2JoinDialogProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const preJoinConversation = useSelector(getPreJoinConversation);
|
||||
if (preJoinConversation == null) {
|
||||
throw new Error('smart/GroupV2JoinDialog: No pre-join conversation!');
|
||||
}
|
||||
|
||||
return {
|
||||
...props,
|
||||
...preJoinConversation,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartGroupV2JoinDialog = smart(GroupV2JoinDialog);
|
||||
const { memberCount, title, groupDescription, approvalRequired, avatar } =
|
||||
preJoinConversation;
|
||||
return (
|
||||
<GroupV2JoinDialog
|
||||
approvalRequired={approvalRequired}
|
||||
avatar={avatar}
|
||||
groupDescription={groupDescription}
|
||||
i18n={i18n}
|
||||
join={join}
|
||||
memberCount={memberCount}
|
||||
onClose={onClose}
|
||||
title={title}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,32 +1,35 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import type { StateType } from '../reducer';
|
||||
import type { PropsDataType } from '../../components/conversation/conversation-details/GroupV2Permissions';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { useSelector } from 'react-redux';
|
||||
import React, { memo } from 'react';
|
||||
import { GroupV2Permissions } from '../../components/conversation/conversation-details/GroupV2Permissions';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
|
||||
export type SmartGroupV2PermissionsProps = {
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: SmartGroupV2PermissionsProps
|
||||
): PropsDataType => {
|
||||
const conversation = getConversationSelector(state)(props.conversationId);
|
||||
|
||||
return {
|
||||
...props,
|
||||
conversation,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartGroupV2Permissions = smart(GroupV2Permissions);
|
||||
export const SmartGroupV2Permissions = memo(function SmartGroupV2Permissions({
|
||||
conversationId,
|
||||
}: SmartGroupV2PermissionsProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const conversation = conversationSelector(conversationId);
|
||||
const {
|
||||
setAccessControlAttributesSetting,
|
||||
setAccessControlMembersSetting,
|
||||
setAnnouncementsOnly,
|
||||
} = useConversationsActions();
|
||||
return (
|
||||
<GroupV2Permissions
|
||||
i18n={i18n}
|
||||
conversation={conversation}
|
||||
setAccessControlAttributesSetting={setAccessControlAttributesSetting}
|
||||
setAccessControlMembersSetting={setAccessControlMembersSetting}
|
||||
setAnnouncementsOnly={setAnnouncementsOnly}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,41 +1,77 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { ConversationHero } from '../../components/conversation/ConversationHero';
|
||||
|
||||
import type { StateType } from '../reducer';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import { getHasStoriesSelector } from '../selectors/stories2';
|
||||
import { isSignalConversation } from '../../util/isSignalConversation';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { useStoriesActions } from '../ducks/stories';
|
||||
|
||||
type ExternalProps = {
|
||||
type SmartHeroRowProps = Readonly<{
|
||||
id: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||
const { id } = props;
|
||||
|
||||
const conversation = state.conversations.conversationLookup[id];
|
||||
|
||||
if (!conversation) {
|
||||
export const SmartHeroRow = memo(function SmartHeroRow({
|
||||
id,
|
||||
}: SmartHeroRowProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const hasStoriesSelector = useSelector(getHasStoriesSelector);
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const conversation = conversationSelector(id);
|
||||
if (conversation == null) {
|
||||
throw new Error(`Did not find conversation ${id} in state!`);
|
||||
}
|
||||
|
||||
return {
|
||||
i18n: getIntl(state),
|
||||
...conversation,
|
||||
conversationType: conversation.type,
|
||||
hasStories: getHasStoriesSelector(state)(id),
|
||||
badge: getPreferredBadgeSelector(state)(conversation.badges),
|
||||
isSignalConversation: isSignalConversation(conversation),
|
||||
theme: getTheme(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartHeroRow = smart(ConversationHero);
|
||||
const badge = getPreferredBadge(conversation.badges);
|
||||
const hasStories = hasStoriesSelector(id);
|
||||
const isSignalConversationValue = isSignalConversation(conversation);
|
||||
const { unblurAvatar, updateSharedGroups } = useConversationsActions();
|
||||
const { toggleAboutContactModal } = useGlobalModalActions();
|
||||
const { viewUserStories } = useStoriesActions();
|
||||
const {
|
||||
about,
|
||||
acceptedMessageRequest,
|
||||
avatarPath,
|
||||
groupDescription,
|
||||
isMe,
|
||||
membersCount,
|
||||
phoneNumber,
|
||||
profileName,
|
||||
sharedGroupNames,
|
||||
title,
|
||||
type,
|
||||
unblurredAvatarPath,
|
||||
} = conversation;
|
||||
return (
|
||||
<ConversationHero
|
||||
about={about}
|
||||
acceptedMessageRequest={acceptedMessageRequest}
|
||||
avatarPath={avatarPath}
|
||||
badge={badge}
|
||||
conversationType={type}
|
||||
groupDescription={groupDescription}
|
||||
hasStories={hasStories}
|
||||
i18n={i18n}
|
||||
id={id}
|
||||
isMe={isMe}
|
||||
isSignalConversation={isSignalConversationValue}
|
||||
membersCount={membersCount}
|
||||
phoneNumber={phoneNumber}
|
||||
profileName={profileName}
|
||||
sharedGroupNames={sharedGroupNames}
|
||||
theme={theme}
|
||||
title={title}
|
||||
toggleAboutContactModal={toggleAboutContactModal}
|
||||
unblurAvatar={unblurAvatar}
|
||||
unblurredAvatarPath={unblurredAvatarPath}
|
||||
updateSharedGroups={updateSharedGroups}
|
||||
viewUserStories={viewUserStories}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { AppStateType } from '../ducks/app';
|
||||
import type { StateType } from '../reducer';
|
||||
import { Inbox } from '../../components/Inbox';
|
||||
import { getIntl } from '../selectors/user';
|
||||
|
@ -37,19 +36,19 @@ function renderStoriesTab() {
|
|||
return <SmartStoriesTab />;
|
||||
}
|
||||
|
||||
export function SmartInbox(): JSX.Element {
|
||||
export const SmartInbox = memo(function SmartInbox(): JSX.Element {
|
||||
const i18n = useSelector(getIntl);
|
||||
const isCustomizingPreferredReactions = useSelector(
|
||||
getIsCustomizingPreferredReactions
|
||||
);
|
||||
const envelopeTimestamp = useSelector<StateType, number | undefined>(
|
||||
state => state.inbox.envelopeTimestamp
|
||||
const envelopeTimestamp = useSelector(
|
||||
(state: StateType) => state.inbox.envelopeTimestamp
|
||||
);
|
||||
const firstEnvelopeTimestamp = useSelector<StateType, number | undefined>(
|
||||
state => state.inbox.firstEnvelopeTimestamp
|
||||
const firstEnvelopeTimestamp = useSelector(
|
||||
(state: StateType) => state.inbox.firstEnvelopeTimestamp
|
||||
);
|
||||
const { hasInitialLoadCompleted } = useSelector<StateType, AppStateType>(
|
||||
state => state.app
|
||||
const { hasInitialLoadCompleted } = useSelector(
|
||||
(state: StateType) => state.app
|
||||
);
|
||||
|
||||
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
|
||||
|
@ -73,4 +72,4 @@ export function SmartInbox(): JSX.Element {
|
|||
renderStoriesTab={renderStoriesTab}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ComponentProps, ReactElement } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { ComponentProps } from 'react';
|
||||
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import pTimeout, { TimeoutError } from 'p-timeout';
|
||||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getUpdatesState } from '../selectors/updates';
|
||||
import { useUpdatesActions } from '../ducks/updates';
|
||||
import { hasExpired as hasExpiredSelector } from '../selectors/expiration';
|
||||
|
||||
import * as log from '../../logging/log';
|
||||
import type { Loadable } from '../../util/loadable';
|
||||
import { LoadingState } from '../../util/loadable';
|
||||
|
@ -87,7 +85,7 @@ function getInstallError(err: unknown): InstallError {
|
|||
return InstallError.UnknownError;
|
||||
}
|
||||
|
||||
export function SmartInstallScreen(): ReactElement {
|
||||
export const SmartInstallScreen = memo(function SmartInstallScreen() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const updates = useSelector(getUpdatesState);
|
||||
const { startUpdate } = useUpdatesActions();
|
||||
|
@ -339,4 +337,4 @@ export function SmartInstallScreen(): ReactElement {
|
|||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,23 +1,71 @@
|
|||
// Copyright 2019 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { get } from 'lodash';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { PropsType as DialogExpiredBuildPropsType } from '../../components/DialogExpiredBuild';
|
||||
import { DialogExpiredBuild } from '../../components/DialogExpiredBuild';
|
||||
import type { PropsType as LeftPanePropsType } from '../../components/LeftPane';
|
||||
import { LeftPane } from '../../components/LeftPane';
|
||||
import { DialogExpiredBuild } from '../../components/DialogExpiredBuild';
|
||||
import type { PropsType as DialogExpiredBuildPropsType } from '../../components/DialogExpiredBuild';
|
||||
import type { StateType } from '../reducer';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { lookupConversationWithoutServiceId } from '../../util/lookupConversationWithoutServiceId';
|
||||
import { isDone as isRegistrationDone } from '../../util/registration';
|
||||
import { getCountryDataForLocale } from '../../util/getCountryData';
|
||||
import { getUsernameFromSearch } from '../../util/Username';
|
||||
import type { NavTabPanelProps } from '../../components/NavTabs';
|
||||
import type { WidthBreakpoint } from '../../components/_util';
|
||||
import {
|
||||
getGroupSizeHardLimit,
|
||||
getGroupSizeRecommendedLimit,
|
||||
} from '../../groups/limits';
|
||||
import { LeftPaneMode } from '../../types/leftPane';
|
||||
|
||||
import { getUsernameFromSearch } from '../../util/Username';
|
||||
import { getCountryDataForLocale } from '../../util/getCountryData';
|
||||
import { lookupConversationWithoutServiceId } from '../../util/lookupConversationWithoutServiceId';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { isDone as isRegistrationDone } from '../../util/registration';
|
||||
import { useCallingActions } from '../ducks/calling';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { ComposerStep, OneTimeModalState } from '../ducks/conversationsEnums';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { useItemsActions } from '../ducks/items';
|
||||
import { useNetworkActions } from '../ducks/network';
|
||||
import { useSearchActions } from '../ducks/search';
|
||||
import { useUsernameActions } from '../ducks/username';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import {
|
||||
getComposeAvatarData,
|
||||
getComposeGroupAvatar,
|
||||
getComposeGroupExpireTimer,
|
||||
getComposeGroupName,
|
||||
getComposeSelectedContacts,
|
||||
getComposerConversationSearchTerm,
|
||||
getComposerSelectedRegion,
|
||||
getComposerStep,
|
||||
getComposerUUIDFetchState,
|
||||
getFilteredCandidateContactsForNewGroup,
|
||||
getFilteredComposeContacts,
|
||||
getFilteredComposeGroups,
|
||||
getLeftPaneLists,
|
||||
getMaximumGroupSizeModalState,
|
||||
getMe,
|
||||
getRecommendedGroupSizeModalState,
|
||||
getSelectedConversationId,
|
||||
getShowArchived,
|
||||
getTargetedMessage,
|
||||
hasGroupCreationError,
|
||||
isCreatingGroup,
|
||||
isEditingAvatar,
|
||||
} from '../selectors/conversations';
|
||||
import { getCrashReportCount } from '../selectors/crashReports';
|
||||
import { hasExpired } from '../selectors/expiration';
|
||||
import {
|
||||
getNavTabsCollapsed,
|
||||
getPreferredLeftPaneWidth,
|
||||
getUsernameCorrupted,
|
||||
getUsernameLinkCorrupted,
|
||||
} from '../selectors/items';
|
||||
import {
|
||||
getChallengeStatus,
|
||||
hasNetworkDialog as getHasNetworkDialog,
|
||||
} from '../selectors/network';
|
||||
import {
|
||||
getIsSearching,
|
||||
getQuery,
|
||||
|
@ -26,65 +74,26 @@ import {
|
|||
getStartSearchCounter,
|
||||
isSearching,
|
||||
} from '../selectors/search';
|
||||
import {
|
||||
isUpdateDownloaded as getIsUpdateDownloaded,
|
||||
isOSUnsupported,
|
||||
isUpdateDialogVisible,
|
||||
} from '../selectors/updates';
|
||||
import {
|
||||
getIntl,
|
||||
getIsMacOS,
|
||||
getRegionCode,
|
||||
getTheme,
|
||||
getIsMacOS,
|
||||
} from '../selectors/user';
|
||||
import { hasExpired } from '../selectors/expiration';
|
||||
import {
|
||||
isUpdateDialogVisible,
|
||||
isUpdateDownloaded,
|
||||
isOSUnsupported,
|
||||
} from '../selectors/updates';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import { hasNetworkDialog } from '../selectors/network';
|
||||
import {
|
||||
getPreferredLeftPaneWidth,
|
||||
getUsernameCorrupted,
|
||||
getUsernameLinkCorrupted,
|
||||
getNavTabsCollapsed,
|
||||
} from '../selectors/items';
|
||||
import {
|
||||
getComposeAvatarData,
|
||||
getComposeGroupAvatar,
|
||||
getComposeGroupExpireTimer,
|
||||
getComposeGroupName,
|
||||
getComposerConversationSearchTerm,
|
||||
getComposerSelectedRegion,
|
||||
getComposerStep,
|
||||
getComposerUUIDFetchState,
|
||||
getComposeSelectedContacts,
|
||||
getFilteredCandidateContactsForNewGroup,
|
||||
getFilteredComposeContacts,
|
||||
getFilteredComposeGroups,
|
||||
getLeftPaneLists,
|
||||
getMaximumGroupSizeModalState,
|
||||
getMe,
|
||||
getRecommendedGroupSizeModalState,
|
||||
getSelectedConversationId,
|
||||
getTargetedMessage,
|
||||
getShowArchived,
|
||||
hasGroupCreationError,
|
||||
isCreatingGroup,
|
||||
isEditingAvatar,
|
||||
} from '../selectors/conversations';
|
||||
import type { WidthBreakpoint } from '../../components/_util';
|
||||
import {
|
||||
getGroupSizeRecommendedLimit,
|
||||
getGroupSizeHardLimit,
|
||||
} from '../../groups/limits';
|
||||
|
||||
import { SmartCaptchaDialog } from './CaptchaDialog';
|
||||
import { SmartCrashReportDialog } from './CrashReportDialog';
|
||||
import { SmartMessageSearchResult } from './MessageSearchResult';
|
||||
import { SmartNetworkStatus } from './NetworkStatus';
|
||||
import { SmartRelinkDialog } from './RelinkDialog';
|
||||
import { SmartUnsupportedOSDialog } from './UnsupportedOSDialog';
|
||||
import { SmartToastManager } from './ToastManager';
|
||||
import type { PropsType as SmartUnsupportedOSDialogPropsType } from './UnsupportedOSDialog';
|
||||
import { SmartUnsupportedOSDialog } from './UnsupportedOSDialog';
|
||||
import { SmartUpdateDialog } from './UpdateDialog';
|
||||
import { SmartCaptchaDialog } from './CaptchaDialog';
|
||||
import { SmartCrashReportDialog } from './CrashReportDialog';
|
||||
|
||||
function renderMessageSearchResult(id: string): JSX.Element {
|
||||
return <SmartMessageSearchResult id={id} />;
|
||||
|
@ -120,7 +129,7 @@ function renderUnsupportedOSDialog(
|
|||
): JSX.Element {
|
||||
return <SmartUnsupportedOSDialog {...props} />;
|
||||
}
|
||||
function renderToastManager(props: {
|
||||
function renderToastManagerWithMegaphone(props: {
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
}): JSX.Element {
|
||||
return <SmartToastManager {...props} />;
|
||||
|
@ -243,15 +252,81 @@ const getModeSpecificProps = (
|
|||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
const hasUpdateDialog = isUpdateDialogVisible(state);
|
||||
const hasUnsupportedOS = isOSUnsupported(state);
|
||||
const usernameCorrupted = getUsernameCorrupted(state);
|
||||
const usernameLinkCorrupted = getUsernameLinkCorrupted(state);
|
||||
export const SmartLeftPane = memo(function SmartLeftPane({
|
||||
hasFailedStorySends,
|
||||
hasPendingUpdate,
|
||||
otherTabsUnreadStats,
|
||||
}: NavTabPanelProps) {
|
||||
const challengeStatus = useSelector(getChallengeStatus);
|
||||
const composerStep = useSelector(getComposerStep);
|
||||
const crashReportCount = useSelector(getCrashReportCount);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const hasAppExpired = useSelector(hasExpired);
|
||||
const hasNetworkDialog = useSelector(getHasNetworkDialog);
|
||||
const hasSearchQuery = useSelector(isSearching);
|
||||
const hasUnsupportedOS = useSelector(isOSUnsupported);
|
||||
const hasUpdateDialog = useSelector(isUpdateDialogVisible);
|
||||
const i18n = useSelector(getIntl);
|
||||
const isMacOS = useSelector(getIsMacOS);
|
||||
const isUpdateDownloaded = useSelector(getIsUpdateDownloaded);
|
||||
const modeSpecificProps = useSelector(getModeSpecificProps);
|
||||
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
|
||||
const preferredWidthFromStorage = useSelector(getPreferredLeftPaneWidth);
|
||||
const selectedConversationId = useSelector(getSelectedConversationId);
|
||||
const showArchived = useSelector(getShowArchived);
|
||||
const targetedMessage = useSelector(getTargetedMessage);
|
||||
const theme = useSelector(getTheme);
|
||||
const usernameCorrupted = useSelector(getUsernameCorrupted);
|
||||
const usernameLinkCorrupted = useSelector(getUsernameLinkCorrupted);
|
||||
|
||||
const {
|
||||
blockConversation,
|
||||
clearGroupCreationError,
|
||||
closeMaximumGroupSizeModal,
|
||||
closeRecommendedGroupSizeModal,
|
||||
composeDeleteAvatarFromDisk,
|
||||
composeReplaceAvatar,
|
||||
composeSaveAvatarToDisk,
|
||||
createGroup,
|
||||
removeConversation,
|
||||
setComposeGroupAvatar,
|
||||
setComposeGroupExpireTimer,
|
||||
setComposeGroupName,
|
||||
setComposeSearchTerm,
|
||||
setComposeSelectedRegion,
|
||||
setIsFetchingUUID,
|
||||
showArchivedConversations,
|
||||
showChooseGroupMembers,
|
||||
showConversation,
|
||||
showFindByPhoneNumber,
|
||||
showFindByUsername,
|
||||
showInbox,
|
||||
startComposing,
|
||||
startSettingGroupMetadata,
|
||||
toggleComposeEditingAvatar,
|
||||
toggleConversationInChooseMembers,
|
||||
} = useConversationsActions();
|
||||
const {
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
searchInConversation,
|
||||
startSearch,
|
||||
updateSearchTerm,
|
||||
} = useSearchActions();
|
||||
const {
|
||||
onOutgoingAudioCallInConversation,
|
||||
onOutgoingVideoCallInConversation,
|
||||
} = useCallingActions();
|
||||
const { openUsernameReservationModal } = useUsernameActions();
|
||||
const { savePreferredLeftPaneWidth, toggleNavTabsCollapse } =
|
||||
useItemsActions();
|
||||
const { setChallengeStatus } = useNetworkActions();
|
||||
const { showUserNotFoundModal, toggleProfileEditor } =
|
||||
useGlobalModalActions();
|
||||
|
||||
let hasExpiredDialog = false;
|
||||
let unsupportedOSDialogType: 'error' | 'warning' | undefined;
|
||||
if (hasExpired(state)) {
|
||||
if (hasAppExpired) {
|
||||
if (hasUnsupportedOS) {
|
||||
unsupportedOSDialogType = 'error';
|
||||
} else {
|
||||
|
@ -261,49 +336,87 @@ const mapStateToProps = (state: StateType) => {
|
|||
unsupportedOSDialogType = 'warning';
|
||||
}
|
||||
|
||||
const composerStep = getComposerStep(state);
|
||||
const showArchived = getShowArchived(state);
|
||||
const hasSearchQuery = isSearching(state);
|
||||
const hasRelinkDialog = !isRegistrationDone();
|
||||
|
||||
return {
|
||||
hasNetworkDialog: hasNetworkDialog(state),
|
||||
hasExpiredDialog,
|
||||
hasRelinkDialog: !isRegistrationDone(),
|
||||
hasUpdateDialog,
|
||||
isUpdateDownloaded: isUpdateDownloaded(state),
|
||||
unsupportedOSDialogType,
|
||||
usernameCorrupted,
|
||||
usernameLinkCorrupted,
|
||||
const renderToastManager =
|
||||
composerStep == null && !showArchived && !hasSearchQuery
|
||||
? renderToastManagerWithMegaphone
|
||||
: renderToastManagerWithoutMegaphone;
|
||||
|
||||
modeSpecificProps: getModeSpecificProps(state),
|
||||
navTabsCollapsed: getNavTabsCollapsed(state),
|
||||
preferredWidthFromStorage: getPreferredLeftPaneWidth(state),
|
||||
selectedConversationId: getSelectedConversationId(state),
|
||||
targetedMessageId: getTargetedMessage(state)?.id,
|
||||
showArchived,
|
||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||
i18n: getIntl(state),
|
||||
isMacOS: getIsMacOS(state),
|
||||
regionCode: getRegionCode(state),
|
||||
challengeStatus: state.network.challengeStatus,
|
||||
crashReportCount: state.crashReports.count,
|
||||
renderMessageSearchResult,
|
||||
renderNetworkStatus,
|
||||
renderRelinkDialog,
|
||||
renderUpdateDialog,
|
||||
renderCaptchaDialog,
|
||||
renderCrashReportDialog,
|
||||
renderExpiredBuildDialog,
|
||||
renderUnsupportedOSDialog,
|
||||
renderToastManager:
|
||||
composerStep == null && !showArchived && !hasSearchQuery
|
||||
? renderToastManager
|
||||
: renderToastManagerWithoutMegaphone,
|
||||
lookupConversationWithoutServiceId,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
};
|
||||
const targetedMessageId = targetedMessage?.id;
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartLeftPane = smart(LeftPane);
|
||||
return (
|
||||
<LeftPane
|
||||
blockConversation={blockConversation}
|
||||
challengeStatus={challengeStatus}
|
||||
clearConversationSearch={clearConversationSearch}
|
||||
clearGroupCreationError={clearGroupCreationError}
|
||||
clearSearch={clearSearch}
|
||||
closeMaximumGroupSizeModal={closeMaximumGroupSizeModal}
|
||||
closeRecommendedGroupSizeModal={closeRecommendedGroupSizeModal}
|
||||
composeDeleteAvatarFromDisk={composeDeleteAvatarFromDisk}
|
||||
composeReplaceAvatar={composeReplaceAvatar}
|
||||
composeSaveAvatarToDisk={composeSaveAvatarToDisk}
|
||||
crashReportCount={crashReportCount}
|
||||
createGroup={createGroup}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
hasExpiredDialog={hasExpiredDialog}
|
||||
hasFailedStorySends={hasFailedStorySends}
|
||||
hasNetworkDialog={hasNetworkDialog}
|
||||
hasPendingUpdate={hasPendingUpdate}
|
||||
hasRelinkDialog={hasRelinkDialog}
|
||||
hasUpdateDialog={hasUpdateDialog}
|
||||
i18n={i18n}
|
||||
isMacOS={isMacOS}
|
||||
isUpdateDownloaded={isUpdateDownloaded}
|
||||
lookupConversationWithoutServiceId={lookupConversationWithoutServiceId}
|
||||
modeSpecificProps={modeSpecificProps}
|
||||
navTabsCollapsed={navTabsCollapsed}
|
||||
onOutgoingAudioCallInConversation={onOutgoingAudioCallInConversation}
|
||||
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
|
||||
openUsernameReservationModal={openUsernameReservationModal}
|
||||
otherTabsUnreadStats={otherTabsUnreadStats}
|
||||
preferredWidthFromStorage={preferredWidthFromStorage}
|
||||
removeConversation={removeConversation}
|
||||
renderCaptchaDialog={renderCaptchaDialog}
|
||||
renderCrashReportDialog={renderCrashReportDialog}
|
||||
renderExpiredBuildDialog={renderExpiredBuildDialog}
|
||||
renderMessageSearchResult={renderMessageSearchResult}
|
||||
renderNetworkStatus={renderNetworkStatus}
|
||||
renderRelinkDialog={renderRelinkDialog}
|
||||
renderToastManager={renderToastManager}
|
||||
renderUnsupportedOSDialog={renderUnsupportedOSDialog}
|
||||
renderUpdateDialog={renderUpdateDialog}
|
||||
savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
|
||||
searchInConversation={searchInConversation}
|
||||
selectedConversationId={selectedConversationId}
|
||||
setChallengeStatus={setChallengeStatus}
|
||||
setComposeGroupAvatar={setComposeGroupAvatar}
|
||||
setComposeGroupExpireTimer={setComposeGroupExpireTimer}
|
||||
setComposeGroupName={setComposeGroupName}
|
||||
setComposeSearchTerm={setComposeSearchTerm}
|
||||
setComposeSelectedRegion={setComposeSelectedRegion}
|
||||
setIsFetchingUUID={setIsFetchingUUID}
|
||||
showArchivedConversations={showArchivedConversations}
|
||||
showChooseGroupMembers={showChooseGroupMembers}
|
||||
showConversation={showConversation}
|
||||
showFindByPhoneNumber={showFindByPhoneNumber}
|
||||
showFindByUsername={showFindByUsername}
|
||||
showInbox={showInbox}
|
||||
showUserNotFoundModal={showUserNotFoundModal}
|
||||
startComposing={startComposing}
|
||||
startSearch={startSearch}
|
||||
startSettingGroupMetadata={startSettingGroupMetadata}
|
||||
targetedMessageId={targetedMessageId}
|
||||
theme={theme}
|
||||
toggleComposeEditingAvatar={toggleComposeEditingAvatar}
|
||||
toggleConversationInChooseMembers={toggleConversationInChooseMembers}
|
||||
toggleNavTabsCollapse={toggleNavTabsCollapse}
|
||||
toggleProfileEditor={toggleProfileEditor}
|
||||
unsupportedOSDialogType={unsupportedOSDialogType}
|
||||
updateSearchTerm={updateSearchTerm}
|
||||
usernameCorrupted={usernameCorrupted}
|
||||
usernameLinkCorrupted={usernameLinkCorrupted}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
import type { GetConversationByIdType } from '../selectors/conversations';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { MediaItemType } from '../../types/MediaItem';
|
||||
import type { StateType } from '../reducer';
|
||||
import { Lightbox } from '../../components/Lightbox';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
import { getIntl } from '../selectors/user';
|
||||
|
@ -26,8 +20,8 @@ import {
|
|||
shouldShowLightbox,
|
||||
} from '../selectors/lightbox';
|
||||
|
||||
export function SmartLightbox(): JSX.Element | null {
|
||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||
export const SmartLightbox = memo(function SmartLightbox() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const { saveAttachment } = useConversationsActions();
|
||||
const {
|
||||
closeLightbox,
|
||||
|
@ -38,20 +32,15 @@ export function SmartLightbox(): JSX.Element | null {
|
|||
const { toggleForwardMessagesModal } = useGlobalModalActions();
|
||||
const { pauseVoiceNotePlayer } = useAudioPlayerActions();
|
||||
|
||||
const conversationSelector = useSelector<StateType, GetConversationByIdType>(
|
||||
getConversationSelector
|
||||
);
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
|
||||
const isShowingLightbox = useSelector<StateType, boolean>(shouldShowLightbox);
|
||||
const isViewOnce = useSelector<StateType, boolean>(getIsViewOnce);
|
||||
const media = useSelector<
|
||||
StateType,
|
||||
ReadonlyArray<ReadonlyDeep<MediaItemType>>
|
||||
>(getMedia);
|
||||
const hasPrevMessage = useSelector<StateType, boolean>(getHasPrevMessage);
|
||||
const hasNextMessage = useSelector<StateType, boolean>(getHasNextMessage);
|
||||
const selectedIndex = useSelector<StateType, number>(getSelectedIndex);
|
||||
const playbackDisabled = useSelector<StateType, boolean>(getPlaybackDisabled);
|
||||
const isShowingLightbox = useSelector(shouldShowLightbox);
|
||||
const isViewOnce = useSelector(getIsViewOnce);
|
||||
const media = useSelector(getMedia);
|
||||
const hasPrevMessage = useSelector(getHasPrevMessage);
|
||||
const hasNextMessage = useSelector(getHasNextMessage);
|
||||
const selectedIndex = useSelector(getSelectedIndex);
|
||||
const playbackDisabled = useSelector(getPlaybackDisabled);
|
||||
|
||||
const onPrevAttachment = useCallback(() => {
|
||||
if (selectedIndex <= 0) {
|
||||
|
@ -107,4 +96,4 @@ export function SmartLightbox(): JSX.Element | null {
|
|||
hasPrevMessage={hasPrevMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { MessageAudio } from '../../components/conversation/MessageAudio';
|
||||
|
@ -26,10 +26,10 @@ export type Props = Omit<MessageAudioOwnProps, 'active' | 'onPlayMessage'> & {
|
|||
renderingContext: string;
|
||||
};
|
||||
|
||||
export function SmartMessageAudio({
|
||||
export const SmartMessageAudio = memo(function SmartMessageAudio({
|
||||
renderingContext,
|
||||
...props
|
||||
}: Props): JSX.Element | null {
|
||||
}: Props) {
|
||||
const active = useSelector(selectAudioPlayerActive);
|
||||
const { loadVoiceNoteAudio, setIsPlaying, setPlaybackRate, setPosition } =
|
||||
useAudioPlayerActions();
|
||||
|
@ -100,4 +100,4 @@ export function SmartMessageAudio({
|
|||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { memo, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type { Props as MessageDetailProps } from '../../components/conversation/MessageDetail';
|
||||
|
@ -28,89 +28,91 @@ export type OwnProps = Pick<
|
|||
'contacts' | 'errors' | 'message' | 'receivedAt'
|
||||
>;
|
||||
|
||||
export function SmartMessageDetail(): JSX.Element | null {
|
||||
const getContactNameColor = useSelector(getContactNameColorSelector);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const i18n = useSelector(getIntl);
|
||||
const platform = useSelector(getPlatform);
|
||||
const interactionMode = useSelector(getInteractionMode);
|
||||
const messageDetails = useSelector(getMessageDetails);
|
||||
const theme = useSelector(getTheme);
|
||||
const { checkForAccount } = useAccountsActions();
|
||||
const {
|
||||
clearTargetedMessage: clearSelectedMessage,
|
||||
doubleCheckMissingQuoteReference,
|
||||
kickOffAttachmentDownload,
|
||||
markAttachmentAsCorrupted,
|
||||
messageExpanded,
|
||||
openGiftBadge,
|
||||
retryMessageSend,
|
||||
popPanelForConversation,
|
||||
pushPanelForConversation,
|
||||
saveAttachment,
|
||||
showConversation,
|
||||
showExpiredIncomingTapToViewToast,
|
||||
showExpiredOutgoingTapToViewToast,
|
||||
showSpoiler,
|
||||
startConversation,
|
||||
} = useConversationsActions();
|
||||
const { showContactModal, showEditHistoryModal, toggleSafetyNumberModal } =
|
||||
useGlobalModalActions();
|
||||
const { showLightbox, showLightboxForViewOnceMedia } = useLightboxActions();
|
||||
const { viewStory } = useStoriesActions();
|
||||
export const SmartMessageDetail = memo(
|
||||
function SmartMessageDetail(): JSX.Element | null {
|
||||
const getContactNameColor = useSelector(getContactNameColorSelector);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const i18n = useSelector(getIntl);
|
||||
const platform = useSelector(getPlatform);
|
||||
const interactionMode = useSelector(getInteractionMode);
|
||||
const messageDetails = useSelector(getMessageDetails);
|
||||
const theme = useSelector(getTheme);
|
||||
const { checkForAccount } = useAccountsActions();
|
||||
const {
|
||||
clearTargetedMessage: clearSelectedMessage,
|
||||
doubleCheckMissingQuoteReference,
|
||||
kickOffAttachmentDownload,
|
||||
markAttachmentAsCorrupted,
|
||||
messageExpanded,
|
||||
openGiftBadge,
|
||||
retryMessageSend,
|
||||
popPanelForConversation,
|
||||
pushPanelForConversation,
|
||||
saveAttachment,
|
||||
showConversation,
|
||||
showExpiredIncomingTapToViewToast,
|
||||
showExpiredOutgoingTapToViewToast,
|
||||
showSpoiler,
|
||||
startConversation,
|
||||
} = useConversationsActions();
|
||||
const { showContactModal, showEditHistoryModal, toggleSafetyNumberModal } =
|
||||
useGlobalModalActions();
|
||||
const { showLightbox, showLightboxForViewOnceMedia } = useLightboxActions();
|
||||
const { viewStory } = useStoriesActions();
|
||||
|
||||
useEffect(() => {
|
||||
if (!messageDetails) {
|
||||
popPanelForConversation();
|
||||
}
|
||||
}, [messageDetails, popPanelForConversation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!messageDetails) {
|
||||
popPanelForConversation();
|
||||
return null;
|
||||
}
|
||||
}, [messageDetails, popPanelForConversation]);
|
||||
|
||||
if (!messageDetails) {
|
||||
return null;
|
||||
const { contacts, errors, message, receivedAt } = messageDetails;
|
||||
|
||||
const contactNameColor =
|
||||
message.conversationType === 'group'
|
||||
? getContactNameColor(message.conversationId, message.author.id)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<MessageDetail
|
||||
checkForAccount={checkForAccount}
|
||||
clearTargetedMessage={clearSelectedMessage}
|
||||
contactNameColor={contactNameColor}
|
||||
contacts={contacts}
|
||||
doubleCheckMissingQuoteReference={doubleCheckMissingQuoteReference}
|
||||
errors={errors}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
platform={platform}
|
||||
interactionMode={interactionMode}
|
||||
kickOffAttachmentDownload={kickOffAttachmentDownload}
|
||||
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
|
||||
message={message}
|
||||
messageExpanded={messageExpanded}
|
||||
openGiftBadge={openGiftBadge}
|
||||
retryMessageSend={retryMessageSend}
|
||||
pushPanelForConversation={pushPanelForConversation}
|
||||
receivedAt={receivedAt}
|
||||
renderAudioAttachment={renderAudioAttachment}
|
||||
saveAttachment={saveAttachment}
|
||||
sentAt={message.timestamp}
|
||||
showContactModal={showContactModal}
|
||||
showConversation={showConversation}
|
||||
showEditHistoryModal={showEditHistoryModal}
|
||||
showExpiredIncomingTapToViewToast={showExpiredIncomingTapToViewToast}
|
||||
showExpiredOutgoingTapToViewToast={showExpiredOutgoingTapToViewToast}
|
||||
showLightbox={showLightbox}
|
||||
showLightboxForViewOnceMedia={showLightboxForViewOnceMedia}
|
||||
showSpoiler={showSpoiler}
|
||||
startConversation={startConversation}
|
||||
theme={theme}
|
||||
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
||||
viewStory={viewStory}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const { contacts, errors, message, receivedAt } = messageDetails;
|
||||
|
||||
const contactNameColor =
|
||||
message.conversationType === 'group'
|
||||
? getContactNameColor(message.conversationId, message.author.id)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<MessageDetail
|
||||
checkForAccount={checkForAccount}
|
||||
clearTargetedMessage={clearSelectedMessage}
|
||||
contactNameColor={contactNameColor}
|
||||
contacts={contacts}
|
||||
doubleCheckMissingQuoteReference={doubleCheckMissingQuoteReference}
|
||||
errors={errors}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
platform={platform}
|
||||
interactionMode={interactionMode}
|
||||
kickOffAttachmentDownload={kickOffAttachmentDownload}
|
||||
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
|
||||
message={message}
|
||||
messageExpanded={messageExpanded}
|
||||
openGiftBadge={openGiftBadge}
|
||||
retryMessageSend={retryMessageSend}
|
||||
pushPanelForConversation={pushPanelForConversation}
|
||||
receivedAt={receivedAt}
|
||||
renderAudioAttachment={renderAudioAttachment}
|
||||
saveAttachment={saveAttachment}
|
||||
sentAt={message.timestamp}
|
||||
showContactModal={showContactModal}
|
||||
showConversation={showConversation}
|
||||
showEditHistoryModal={showEditHistoryModal}
|
||||
showExpiredIncomingTapToViewToast={showExpiredIncomingTapToViewToast}
|
||||
showExpiredOutgoingTapToViewToast={showExpiredOutgoingTapToViewToast}
|
||||
showLightbox={showLightbox}
|
||||
showLightboxForViewOnceMedia={showLightboxForViewOnceMedia}
|
||||
showSpoiler={showSpoiler}
|
||||
startConversation={startConversation}
|
||||
theme={theme}
|
||||
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
||||
viewStory={viewStory}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getGlobalModalsState } from '../selectors/globalModals';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
|
@ -17,68 +16,70 @@ import { getAddedByForOurPendingInvitation } from '../../util/getAddedByForOurPe
|
|||
import { strictAssert } from '../../util/assert';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
|
||||
export function SmartMessageRequestActionsConfirmation(): JSX.Element | null {
|
||||
const i18n = useSelector(getIntl);
|
||||
const globalModals = useSelector(getGlobalModalsState);
|
||||
const { messageRequestActionsConfirmationProps } = globalModals;
|
||||
strictAssert(
|
||||
messageRequestActionsConfirmationProps,
|
||||
'messageRequestActionsConfirmationProps are required'
|
||||
);
|
||||
const { conversationId, state } = messageRequestActionsConfirmationProps;
|
||||
strictAssert(state !== MessageRequestState.default, 'state is required');
|
||||
const getConversation = useSelector(getConversationSelector);
|
||||
const conversation = getConversation(conversationId);
|
||||
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 {
|
||||
acceptConversation,
|
||||
blockConversation,
|
||||
reportSpam,
|
||||
blockAndReportSpam,
|
||||
deleteConversation,
|
||||
} = useConversationsActions();
|
||||
const { toggleMessageRequestActionsConfirmation } = useGlobalModalActions();
|
||||
|
||||
const handleChangeState = useCallback(
|
||||
(nextState: MessageRequestState) => {
|
||||
if (nextState === MessageRequestState.default) {
|
||||
toggleMessageRequestActionsConfirmation(null);
|
||||
} else {
|
||||
toggleMessageRequestActionsConfirmation({
|
||||
conversationId,
|
||||
state: nextState,
|
||||
});
|
||||
export const SmartMessageRequestActionsConfirmation = memo(
|
||||
function SmartMessageRequestActionsConfirmation() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const globalModals = useSelector(getGlobalModalsState);
|
||||
const { messageRequestActionsConfirmationProps } = globalModals;
|
||||
strictAssert(
|
||||
messageRequestActionsConfirmationProps,
|
||||
'messageRequestActionsConfirmationProps are required'
|
||||
);
|
||||
const { conversationId, state } = messageRequestActionsConfirmationProps;
|
||||
strictAssert(state !== MessageRequestState.default, 'state is required');
|
||||
const getConversation = useSelector(getConversationSelector);
|
||||
const conversation = getConversation(conversationId);
|
||||
const addedBy = useMemo(() => {
|
||||
if (conversation.type === 'group') {
|
||||
return getAddedByForOurPendingInvitation(conversation);
|
||||
}
|
||||
},
|
||||
[conversationId, toggleMessageRequestActionsConfirmation]
|
||||
);
|
||||
return null;
|
||||
}, [conversation]);
|
||||
|
||||
return (
|
||||
<MessageRequestActionsConfirmation
|
||||
i18n={i18n}
|
||||
conversationId={conversation.id}
|
||||
conversationType={conversation.type}
|
||||
conversationName={conversationName}
|
||||
addedByName={addedByName}
|
||||
isBlocked={conversation.isBlocked ?? false}
|
||||
isReported={conversation.isReported ?? false}
|
||||
acceptConversation={acceptConversation}
|
||||
blockConversation={blockConversation}
|
||||
reportSpam={reportSpam}
|
||||
blockAndReportSpam={blockAndReportSpam}
|
||||
deleteConversation={deleteConversation}
|
||||
state={state}
|
||||
onChangeState={handleChangeState}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const conversationName = useContactNameData(conversation);
|
||||
strictAssert(conversationName, 'conversationName is required');
|
||||
const addedByName = useContactNameData(addedBy);
|
||||
|
||||
const {
|
||||
acceptConversation,
|
||||
blockConversation,
|
||||
reportSpam,
|
||||
blockAndReportSpam,
|
||||
deleteConversation,
|
||||
} = useConversationsActions();
|
||||
const { toggleMessageRequestActionsConfirmation } = useGlobalModalActions();
|
||||
|
||||
const handleChangeState = useCallback(
|
||||
(nextState: MessageRequestState) => {
|
||||
if (nextState === MessageRequestState.default) {
|
||||
toggleMessageRequestActionsConfirmation(null);
|
||||
} else {
|
||||
toggleMessageRequestActionsConfirmation({
|
||||
conversationId,
|
||||
state: nextState,
|
||||
});
|
||||
}
|
||||
},
|
||||
[conversationId, toggleMessageRequestActionsConfirmation]
|
||||
);
|
||||
|
||||
return (
|
||||
<MessageRequestActionsConfirmation
|
||||
i18n={i18n}
|
||||
conversationId={conversation.id}
|
||||
conversationType={conversation.type}
|
||||
conversationName={conversationName}
|
||||
addedByName={addedByName}
|
||||
isBlocked={conversation.isBlocked ?? false}
|
||||
isReported={conversation.isReported ?? false}
|
||||
acceptConversation={acceptConversation}
|
||||
blockConversation={blockConversation}
|
||||
reportSpam={reportSpam}
|
||||
blockAndReportSpam={blockAndReportSpam}
|
||||
deleteConversation={deleteConversation}
|
||||
state={state}
|
||||
onChangeState={handleChangeState}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,40 +1,51 @@
|
|||
// Copyright 2019 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { CSSProperties } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import type { StateType } from '../reducer';
|
||||
|
||||
// SPDX-License-Identifier: AGPL-3.0-onlyå
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { MessageSearchResult } from '../../components/conversationList/MessageSearchResult';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import { getMessageSearchResultSelector } from '../selectors/search';
|
||||
import * as log from '../../logging/log';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
|
||||
type SmartProps = {
|
||||
type SmartMessageSearchResultProps = {
|
||||
id: string;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
function mapStateToProps(state: StateType, ourProps: SmartProps) {
|
||||
const { id, style } = ourProps;
|
||||
export const SmartMessageSearchResult = memo(function SmartMessageSearchResult({
|
||||
id,
|
||||
}: SmartMessageSearchResultProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
const messageSearchResultSelector = useSelector(
|
||||
getMessageSearchResultSelector
|
||||
);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const { showConversation } = useConversationsActions();
|
||||
|
||||
const props = getMessageSearchResultSelector(state)(id);
|
||||
if (!props) {
|
||||
const messageResult = messageSearchResultSelector(id);
|
||||
if (messageResult == null) {
|
||||
log.error('SmartMessageSearchResult: no message was found');
|
||||
return null;
|
||||
}
|
||||
const { conversationId, snippet, body, bodyRanges, from, to, sentAt } =
|
||||
messageResult;
|
||||
|
||||
return {
|
||||
...props,
|
||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||
i18n: getIntl(state),
|
||||
style,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartMessageSearchResult = smart(MessageSearchResult);
|
||||
return (
|
||||
<MessageSearchResult
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
id={id}
|
||||
conversationId={conversationId}
|
||||
snippet={snippet}
|
||||
body={body}
|
||||
bodyRanges={bodyRanges}
|
||||
from={from}
|
||||
to={to}
|
||||
showConversation={showConversation}
|
||||
sentAt={sentAt}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { MiniPlayer, PlayerState } from '../../components/MiniPlayer';
|
||||
import type { Props as DumbProps } from '../../components/MiniPlayer';
|
||||
|
@ -23,7 +23,9 @@ type Props = Pick<DumbProps, 'shouldFlow'>;
|
|||
* It also triggers side-effecting actions (actual playback) in response to changes in
|
||||
* the state
|
||||
*/
|
||||
export function SmartMiniPlayer({ shouldFlow }: Props): JSX.Element | null {
|
||||
export const SmartMiniPlayer = memo(function SmartMiniPlayer({
|
||||
shouldFlow,
|
||||
}: Props): JSX.Element | null {
|
||||
const i18n = useSelector(getIntl);
|
||||
const active = useSelector(selectAudioPlayerActive);
|
||||
const getVoiceNoteTitle = useSelector(selectVoiceNoteTitle);
|
||||
|
@ -66,4 +68,4 @@ export function SmartMiniPlayer({ shouldFlow }: Props): JSX.Element | null {
|
|||
playbackRate={active.playbackRate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { NavTabPanelProps } from '../../components/NavTabs';
|
||||
import { NavTabs } from '../../components/NavTabs';
|
||||
|
@ -33,7 +33,7 @@ export type SmartNavTabsProps = Readonly<{
|
|||
renderStoriesTab(props: NavTabPanelProps): JSX.Element;
|
||||
}>;
|
||||
|
||||
export function SmartNavTabs({
|
||||
export const SmartNavTabs = memo(function SmartNavTabs({
|
||||
navTabsCollapsed,
|
||||
onToggleNavTabsCollapse,
|
||||
renderCallsTab,
|
||||
|
@ -91,4 +91,4 @@ export function SmartNavTabs({
|
|||
unreadStoriesCount={unreadStoriesCount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,23 +1,37 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { DialogNetworkStatus } from '../../components/DialogNetworkStatus';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import type { WidthBreakpoint } from '../../components/_util';
|
||||
import {
|
||||
getNetworkIsOnline,
|
||||
getNetworkIsOutage,
|
||||
getNetworkSocketStatus,
|
||||
} from '../selectors/network';
|
||||
import { useUserActions } from '../ducks/user';
|
||||
|
||||
type PropsType = Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>;
|
||||
type SmartNetworkStatusProps = Readonly<{
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
}>;
|
||||
|
||||
const mapStateToProps = (state: StateType, ownProps: PropsType) => {
|
||||
return {
|
||||
...state.network,
|
||||
i18n: getIntl(state),
|
||||
...ownProps,
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartNetworkStatus = smart(DialogNetworkStatus);
|
||||
export const SmartNetworkStatus = memo(function SmartNetworkStatus({
|
||||
containerWidthBreakpoint,
|
||||
}: SmartNetworkStatusProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const isOnline = useSelector(getNetworkIsOnline);
|
||||
const isOutage = useSelector(getNetworkIsOutage);
|
||||
const socketStatus = useSelector(getNetworkSocketStatus);
|
||||
const { manualReconnect } = useUserActions();
|
||||
return (
|
||||
<DialogNetworkStatus
|
||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||
i18n={i18n}
|
||||
isOnline={isOnline}
|
||||
isOutage={isOutage}
|
||||
socketStatus={socketStatus}
|
||||
manualReconnect={manualReconnect}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import type { PropsDataType } from '../../components/conversation/conversation-details/PendingInvites';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { PendingInvites } from '../../components/conversation/conversation-details/PendingInvites';
|
||||
import type { StateType } from '../reducer';
|
||||
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import {
|
||||
|
@ -16,36 +12,48 @@ import {
|
|||
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
||||
import { assertDev } from '../../util/assert';
|
||||
import type { AciString } from '../../types/ServiceId';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
|
||||
export type SmartPendingInvitesProps = {
|
||||
conversationId: string;
|
||||
ourAci: AciString;
|
||||
};
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: SmartPendingInvitesProps
|
||||
): PropsDataType => {
|
||||
const conversationSelector = getConversationByIdSelector(state);
|
||||
const conversationByServiceIdSelector =
|
||||
getConversationByServiceIdSelector(state);
|
||||
|
||||
const conversation = conversationSelector(props.conversationId);
|
||||
export const SmartPendingInvites = memo(function SmartPendingInvites({
|
||||
conversationId,
|
||||
ourAci,
|
||||
}: SmartPendingInvitesProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const conversationSelector = useSelector(getConversationByIdSelector);
|
||||
const conversationByServiceIdSelector = useSelector(
|
||||
getConversationByServiceIdSelector
|
||||
);
|
||||
const conversation = conversationSelector(conversationId);
|
||||
assertDev(
|
||||
conversation,
|
||||
'<SmartPendingInvites> expected a conversation to be found'
|
||||
);
|
||||
|
||||
return {
|
||||
...props,
|
||||
...getGroupMemberships(conversation, conversationByServiceIdSelector),
|
||||
const groupMemberships = getGroupMemberships(
|
||||
conversation,
|
||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||
i18n: getIntl(state),
|
||||
theme: getTheme(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartPendingInvites = smart(PendingInvites);
|
||||
conversationByServiceIdSelector
|
||||
);
|
||||
const {
|
||||
approvePendingMembershipFromGroupV2,
|
||||
revokePendingMembershipsFromGroupV2,
|
||||
} = useConversationsActions();
|
||||
return (
|
||||
<PendingInvites
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
conversation={conversation}
|
||||
ourAci={ourAci}
|
||||
pendingMemberships={groupMemberships.pendingMemberships}
|
||||
pendingApprovalMemberships={groupMemberships.pendingApprovalMemberships}
|
||||
approvePendingMembershipFromGroupV2={approvePendingMembershipFromGroupV2}
|
||||
revokePendingMembershipsFromGroupV2={revokePendingMembershipsFromGroupV2}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,93 +1,130 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import type { PropsDataType as ProfileEditorModalPropsType } from '../../components/ProfileEditorModal';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { ProfileEditorModal } from '../../components/ProfileEditorModal';
|
||||
import type { PropsDataType } from '../../components/ProfileEditor';
|
||||
import { SmartEditUsernameModalBody } from './EditUsernameModalBody';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { useItemsActions } from '../ducks/items';
|
||||
import { useToastActions } from '../ducks/toast';
|
||||
import { useUsernameActions } from '../ducks/username';
|
||||
import { getMe } from '../selectors/conversations';
|
||||
import { selectRecentEmojis } from '../selectors/emojis';
|
||||
import {
|
||||
getProfileEditorHasError,
|
||||
getProfileEditorInitialEditState,
|
||||
} from '../selectors/globalModals';
|
||||
import {
|
||||
getEmojiSkinTone,
|
||||
getHasCompletedUsernameLinkOnboarding,
|
||||
getUsernameCorrupted,
|
||||
getUsernameLinkColor,
|
||||
getUsernameLink,
|
||||
getUsernameLinkColor,
|
||||
getUsernameLinkCorrupted,
|
||||
isInternalUser,
|
||||
} from '../selectors/items';
|
||||
import { getMe } from '../selectors/conversations';
|
||||
import { selectRecentEmojis } from '../selectors/emojis';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import {
|
||||
getUsernameEditState,
|
||||
getUsernameLinkState,
|
||||
} from '../selectors/username';
|
||||
import type { SmartEditUsernameModalBodyProps } from './EditUsernameModalBody';
|
||||
import { SmartEditUsernameModalBody } from './EditUsernameModalBody';
|
||||
|
||||
function renderEditUsernameModalBody(props: {
|
||||
isRootModal: boolean;
|
||||
onClose: () => void;
|
||||
}): JSX.Element {
|
||||
function renderEditUsernameModalBody(
|
||||
props: SmartEditUsernameModalBodyProps
|
||||
): JSX.Element {
|
||||
return <SmartEditUsernameModalBody {...props} />;
|
||||
}
|
||||
|
||||
function mapStateToProps(
|
||||
state: StateType
|
||||
): Omit<PropsDataType, 'onEditStateChange' | 'onProfileChanged'> &
|
||||
ProfileEditorModalPropsType {
|
||||
export const SmartProfileEditorModal = memo(function SmartProfileEditorModal() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const {
|
||||
profileAvatarPath,
|
||||
aboutEmoji,
|
||||
aboutText,
|
||||
avatars: userAvatarData = [],
|
||||
aboutText,
|
||||
aboutEmoji,
|
||||
color,
|
||||
familyName,
|
||||
firstName,
|
||||
familyName,
|
||||
id: conversationId,
|
||||
username,
|
||||
} = getMe(state);
|
||||
const recentEmojis = selectRecentEmojis(state);
|
||||
const skinTone = getEmojiSkinTone(state);
|
||||
const hasCompletedUsernameLinkOnboarding =
|
||||
getHasCompletedUsernameLinkOnboarding(state);
|
||||
const usernameEditState = getUsernameEditState(state);
|
||||
const usernameLinkState = getUsernameLinkState(state);
|
||||
const usernameLinkColor = getUsernameLinkColor(state);
|
||||
const usernameLink = getUsernameLink(state);
|
||||
const usernameCorrupted = getUsernameCorrupted(state);
|
||||
const usernameLinkCorrupted = getUsernameLinkCorrupted(state);
|
||||
|
||||
return {
|
||||
aboutEmoji,
|
||||
aboutText,
|
||||
profileAvatarPath,
|
||||
color,
|
||||
conversationId,
|
||||
familyName,
|
||||
firstName: String(firstName),
|
||||
hasCompletedUsernameLinkOnboarding,
|
||||
hasError: state.globalModals.profileEditorHasError,
|
||||
initialEditState: state.globalModals.profileEditorInitialEditState,
|
||||
i18n: getIntl(state),
|
||||
recentEmojis,
|
||||
skinTone,
|
||||
userAvatarData,
|
||||
username,
|
||||
usernameCorrupted,
|
||||
usernameEditState,
|
||||
usernameLinkState,
|
||||
usernameLinkColor,
|
||||
usernameLinkCorrupted,
|
||||
usernameLink,
|
||||
isUsernameDeletionEnabled: isInternalUser(state),
|
||||
} = useSelector(getMe);
|
||||
const hasCompletedUsernameLinkOnboarding = useSelector(
|
||||
getHasCompletedUsernameLinkOnboarding
|
||||
);
|
||||
const hasError = useSelector(getProfileEditorHasError);
|
||||
const initialEditState = useSelector(getProfileEditorInitialEditState);
|
||||
const isUsernameDeletionEnabled = useSelector(isInternalUser);
|
||||
const recentEmojis = useSelector(selectRecentEmojis);
|
||||
const skinTone = useSelector(getEmojiSkinTone);
|
||||
const usernameCorrupted = useSelector(getUsernameCorrupted);
|
||||
const usernameEditState = useSelector(getUsernameEditState);
|
||||
const usernameLink = useSelector(getUsernameLink);
|
||||
const usernameLinkColor = useSelector(getUsernameLinkColor);
|
||||
const usernameLinkCorrupted = useSelector(getUsernameLinkCorrupted);
|
||||
const usernameLinkState = useSelector(getUsernameLinkState);
|
||||
|
||||
renderEditUsernameModalBody,
|
||||
};
|
||||
}
|
||||
const {
|
||||
replaceAvatar,
|
||||
saveAvatarToDisk,
|
||||
saveAttachment,
|
||||
deleteAvatarFromDisk,
|
||||
myProfileChanged,
|
||||
} = useConversationsActions();
|
||||
const {
|
||||
resetUsernameLink,
|
||||
setUsernameLinkColor,
|
||||
setUsernameEditState,
|
||||
openUsernameReservationModal,
|
||||
markCompletedUsernameLinkOnboarding,
|
||||
deleteUsername,
|
||||
} = useUsernameActions();
|
||||
const { toggleProfileEditor, toggleProfileEditorHasError } =
|
||||
useGlobalModalActions();
|
||||
const { showToast } = useToastActions();
|
||||
const { onSetSkinTone } = useItemsActions();
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartProfileEditorModal = smart(ProfileEditorModal);
|
||||
return (
|
||||
<ProfileEditorModal
|
||||
aboutEmoji={aboutEmoji}
|
||||
aboutText={aboutText}
|
||||
color={color}
|
||||
conversationId={conversationId}
|
||||
deleteAvatarFromDisk={deleteAvatarFromDisk}
|
||||
deleteUsername={deleteUsername}
|
||||
familyName={familyName}
|
||||
firstName={firstName ?? ''}
|
||||
hasCompletedUsernameLinkOnboarding={hasCompletedUsernameLinkOnboarding}
|
||||
hasError={hasError}
|
||||
i18n={i18n}
|
||||
initialEditState={initialEditState}
|
||||
isUsernameDeletionEnabled={isUsernameDeletionEnabled}
|
||||
markCompletedUsernameLinkOnboarding={markCompletedUsernameLinkOnboarding}
|
||||
myProfileChanged={myProfileChanged}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
openUsernameReservationModal={openUsernameReservationModal}
|
||||
profileAvatarPath={profileAvatarPath}
|
||||
recentEmojis={recentEmojis}
|
||||
renderEditUsernameModalBody={renderEditUsernameModalBody}
|
||||
replaceAvatar={replaceAvatar}
|
||||
resetUsernameLink={resetUsernameLink}
|
||||
saveAttachment={saveAttachment}
|
||||
saveAvatarToDisk={saveAvatarToDisk}
|
||||
setUsernameEditState={setUsernameEditState}
|
||||
setUsernameLinkColor={setUsernameLinkColor}
|
||||
showToast={showToast}
|
||||
skinTone={skinTone}
|
||||
toggleProfileEditor={toggleProfileEditor}
|
||||
toggleProfileEditorHasError={toggleProfileEditorHasError}
|
||||
userAvatarData={userAvatarData}
|
||||
username={username}
|
||||
usernameCorrupted={usernameCorrupted}
|
||||
usernameEditState={usernameEditState}
|
||||
usernameLink={usernameLink}
|
||||
usernameLinkColor={usernameLinkColor}
|
||||
usernameLinkCorrupted={usernameLinkCorrupted}
|
||||
usernameLinkState={usernameLinkState}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import type { Ref } from 'react';
|
||||
import React, { forwardRef, memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { StateType } from '../reducer';
|
||||
import { usePreferredReactionsActions } from '../ducks/preferredReactions';
|
||||
import { useItemsActions } from '../ducks/items';
|
||||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getPreferredReactionEmoji } from '../selectors/items';
|
||||
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { Props as InternalProps } from '../../components/conversation/ReactionPicker';
|
||||
import { ReactionPicker } from '../../components/conversation/ReactionPicker';
|
||||
|
||||
|
@ -24,31 +21,30 @@ type ExternalProps = Omit<
|
|||
| 'skinTone'
|
||||
>;
|
||||
|
||||
export const SmartReactionPicker = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
ExternalProps
|
||||
>(function SmartReactionPickerInner(props, ref) {
|
||||
const { openCustomizePreferredReactionsModal } =
|
||||
usePreferredReactionsActions();
|
||||
export const SmartReactionPicker = memo(
|
||||
forwardRef(function SmartReactionPickerInner(
|
||||
props: ExternalProps,
|
||||
ref: Ref<HTMLDivElement>
|
||||
) {
|
||||
const { openCustomizePreferredReactionsModal } =
|
||||
usePreferredReactionsActions();
|
||||
|
||||
const { onSetSkinTone } = useItemsActions();
|
||||
const { onSetSkinTone } = useItemsActions();
|
||||
|
||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||
const i18n = useSelector(getIntl);
|
||||
const preferredReactionEmoji = useSelector(getPreferredReactionEmoji);
|
||||
|
||||
const preferredReactionEmoji = useSelector<StateType, ReadonlyArray<string>>(
|
||||
getPreferredReactionEmoji
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactionPicker
|
||||
i18n={i18n}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
openCustomizePreferredReactionsModal={
|
||||
openCustomizePreferredReactionsModal
|
||||
}
|
||||
preferredReactionEmoji={preferredReactionEmoji}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<ReactionPicker
|
||||
i18n={i18n}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
openCustomizePreferredReactionsModal={
|
||||
openCustomizePreferredReactionsModal
|
||||
}
|
||||
preferredReactionEmoji={preferredReactionEmoji}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
})
|
||||
);
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { DialogRelink } from '../../components/DialogRelink';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import type { WidthBreakpoint } from '../../components/_util';
|
||||
import { useNetworkActions } from '../ducks/network';
|
||||
|
||||
type PropsType = Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>;
|
||||
type SmartRelinkDialogProps = Readonly<{
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
}>;
|
||||
|
||||
const mapStateToProps = (state: StateType, ownProps: PropsType) => {
|
||||
return {
|
||||
i18n: getIntl(state),
|
||||
...ownProps,
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartRelinkDialog = smart(DialogRelink);
|
||||
export const SmartRelinkDialog = memo(function SmartRelinkDialog({
|
||||
containerWidthBreakpoint,
|
||||
}: SmartRelinkDialogProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const { relinkDevice } = useNetworkActions();
|
||||
return (
|
||||
<DialogRelink
|
||||
i18n={i18n}
|
||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||
relinkDevice={relinkDevice}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,27 +1,39 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { SafetyNumberModal } from '../../components/SafetyNumberModal';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getContactSafetyNumber } from '../selectors/safetyNumber';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { useSafetyNumberActions } from '../ducks/safetyNumber';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
|
||||
export type Props = {
|
||||
export type SmartSafetyNumberModalProps = {
|
||||
contactID: string;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StateType, props: Props) => {
|
||||
return {
|
||||
...props,
|
||||
...getContactSafetyNumber(state, props),
|
||||
contact: getConversationSelector(state)(props.contactID),
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartSafetyNumberModal = smart(SafetyNumberModal);
|
||||
export const SmartSafetyNumberModal = memo(function SmartSafetyNumberModal({
|
||||
contactID,
|
||||
}: SmartSafetyNumberModalProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const contact = conversationSelector(contactID);
|
||||
const contactSafetyNumber = useSelector((state: StateType) => {
|
||||
return getContactSafetyNumber(state, { contactID });
|
||||
});
|
||||
const { generateSafetyNumber, toggleVerified } = useSafetyNumberActions();
|
||||
const { toggleSafetyNumberModal } = useGlobalModalActions();
|
||||
return (
|
||||
<SafetyNumberModal
|
||||
i18n={i18n}
|
||||
contact={contact}
|
||||
safetyNumber={contactSafetyNumber.safetyNumber}
|
||||
verificationDisabled={contactSafetyNumber.verificationDisabled}
|
||||
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
||||
generateSafetyNumber={generateSafetyNumber}
|
||||
toggleVerified={toggleVerified}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,24 +1,38 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { SafetyNumberViewer } from '../../components/SafetyNumberViewer';
|
||||
import type { StateType } from '../reducer';
|
||||
import type { SafetyNumberProps } from '../../components/SafetyNumberChangeDialog';
|
||||
import { getContactSafetyNumber } from '../selectors/safetyNumber';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { useSafetyNumberActions } from '../ducks/safetyNumber';
|
||||
|
||||
const mapStateToProps = (state: StateType, props: SafetyNumberProps) => {
|
||||
return {
|
||||
...props,
|
||||
...getContactSafetyNumber(state, props),
|
||||
contact: getConversationSelector(state)(props.contactID),
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
export const SmartSafetyNumberViewer = memo(function SmartSafetyNumberViewer({
|
||||
contactID,
|
||||
onClose,
|
||||
}: SafetyNumberProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const safetyNumberContact = useSelector((state: StateType) => {
|
||||
return getContactSafetyNumber(state, { contactID });
|
||||
});
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const contact = conversationSelector(contactID);
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
const { generateSafetyNumber, toggleVerified } = useSafetyNumberActions();
|
||||
|
||||
export const SmartSafetyNumberViewer = smart(SafetyNumberViewer);
|
||||
return (
|
||||
<SafetyNumberViewer
|
||||
contact={contact}
|
||||
generateSafetyNumber={generateSafetyNumber}
|
||||
i18n={i18n}
|
||||
onClose={onClose}
|
||||
safetyNumber={safetyNumberContact.safetyNumber}
|
||||
toggleVerified={toggleVerified}
|
||||
verificationDisabled={safetyNumberContact.verificationDisabled}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { SafetyNumberChangedBlockingDataType } from '../ducks/globalModals';
|
||||
import type { StateType } from '../reducer';
|
||||
import * as SingleServePromise from '../../services/singleServePromise';
|
||||
import {
|
||||
SafetyNumberChangeDialog,
|
||||
|
@ -18,68 +15,72 @@ import { getPreferredBadgeSelector } from '../selectors/badges';
|
|||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { useStoryDistributionListsActions } from '../ducks/storyDistributionLists';
|
||||
import { getSafetyNumberChangedBlockingData } from '../selectors/globalModals';
|
||||
|
||||
export function SmartSendAnywayDialog(): JSX.Element {
|
||||
const { hideBlockingSafetyNumberChangeDialog } = useGlobalModalActions();
|
||||
const { removeMembersFromDistributionList } =
|
||||
useStoryDistributionListsActions();
|
||||
const { cancelConversationVerification, verifyConversationsStoppingSend } =
|
||||
useConversationsActions();
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
export const SmartSendAnywayDialog = memo(
|
||||
function SmartSendAnywayDialog(): JSX.Element {
|
||||
const { hideBlockingSafetyNumberChangeDialog } = useGlobalModalActions();
|
||||
const { removeMembersFromDistributionList } =
|
||||
useStoryDistributionListsActions();
|
||||
const { cancelConversationVerification, verifyConversationsStoppingSend } =
|
||||
useConversationsActions();
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const i18n = useSelector(getIntl);
|
||||
const theme = useSelector(getTheme);
|
||||
|
||||
const contacts = useSelector(getByDistributionListConversationsStoppingSend);
|
||||
const contacts = useSelector(
|
||||
getByDistributionListConversationsStoppingSend
|
||||
);
|
||||
|
||||
const safetyNumberChangedBlockingData = useSelector<
|
||||
StateType,
|
||||
SafetyNumberChangedBlockingDataType | undefined
|
||||
>(state => state.globalModals.safetyNumberChangedBlockingData);
|
||||
const safetyNumberChangedBlockingData = useSelector(
|
||||
getSafetyNumberChangedBlockingData
|
||||
);
|
||||
|
||||
const explodedPromise = safetyNumberChangedBlockingData
|
||||
? SingleServePromise.get<boolean>(
|
||||
safetyNumberChangedBlockingData.promiseUuid
|
||||
)
|
||||
: undefined;
|
||||
const explodedPromise = safetyNumberChangedBlockingData
|
||||
? SingleServePromise.get<boolean>(
|
||||
safetyNumberChangedBlockingData.promiseUuid
|
||||
)
|
||||
: undefined;
|
||||
|
||||
let confirmText: string | undefined = i18n(
|
||||
'icu:safetyNumberChangeDialog__pending-messages'
|
||||
);
|
||||
if (
|
||||
safetyNumberChangedBlockingData?.source ===
|
||||
SafetyNumberChangeSource.InitiateCall
|
||||
) {
|
||||
confirmText = i18n('icu:callAnyway');
|
||||
} else if (
|
||||
safetyNumberChangedBlockingData?.source ===
|
||||
SafetyNumberChangeSource.JoinCall
|
||||
) {
|
||||
confirmText = i18n('icu:joinAnyway');
|
||||
} else {
|
||||
confirmText = undefined;
|
||||
let confirmText: string | undefined = i18n(
|
||||
'icu:safetyNumberChangeDialog__pending-messages'
|
||||
);
|
||||
if (
|
||||
safetyNumberChangedBlockingData?.source ===
|
||||
SafetyNumberChangeSource.InitiateCall
|
||||
) {
|
||||
confirmText = i18n('icu:callAnyway');
|
||||
} else if (
|
||||
safetyNumberChangedBlockingData?.source ===
|
||||
SafetyNumberChangeSource.JoinCall
|
||||
) {
|
||||
confirmText = i18n('icu:joinAnyway');
|
||||
} else {
|
||||
confirmText = undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafetyNumberChangeDialog
|
||||
confirmText={confirmText}
|
||||
contacts={contacts}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
onCancel={() => {
|
||||
cancelConversationVerification();
|
||||
explodedPromise?.resolve(false);
|
||||
hideBlockingSafetyNumberChangeDialog();
|
||||
}}
|
||||
onConfirm={() => {
|
||||
verifyConversationsStoppingSend();
|
||||
explodedPromise?.resolve(true);
|
||||
hideBlockingSafetyNumberChangeDialog();
|
||||
}}
|
||||
removeFromStory={removeMembersFromDistributionList}
|
||||
renderSafetyNumber={({ contactID, onClose }) => (
|
||||
<SmartSafetyNumberViewer contactID={contactID} onClose={onClose} />
|
||||
)}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafetyNumberChangeDialog
|
||||
confirmText={confirmText}
|
||||
contacts={contacts}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
onCancel={() => {
|
||||
cancelConversationVerification();
|
||||
explodedPromise?.resolve(false);
|
||||
hideBlockingSafetyNumberChangeDialog();
|
||||
}}
|
||||
onConfirm={() => {
|
||||
verifyConversationsStoppingSend();
|
||||
explodedPromise?.resolve(true);
|
||||
hideBlockingSafetyNumberChangeDialog();
|
||||
}}
|
||||
removeFromStory={removeMembersFromDistributionList}
|
||||
renderSafetyNumber={({ contactID, onClose }) => (
|
||||
<SmartSafetyNumberViewer contactID={contactID} onClose={onClose} />
|
||||
)}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
// Copyright 2019 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { ShortcutGuideModal } from '../../components/ShortcutGuideModal';
|
||||
import type { StateType } from '../reducer';
|
||||
|
||||
import { countStickers } from '../../components/stickers/lib';
|
||||
import { getIntl, getPlatform } from '../selectors/user';
|
||||
import {
|
||||
|
@ -14,30 +12,35 @@ import {
|
|||
getKnownStickerPacks,
|
||||
getReceivedStickerPacks,
|
||||
} from '../selectors/stickers';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
const blessedPacks = getBlessedStickerPacks(state);
|
||||
const installedPacks = getInstalledStickerPacks(state);
|
||||
const knownPacks = getKnownStickerPacks(state);
|
||||
const receivedPacks = getReceivedStickerPacks(state);
|
||||
export const SmartShortcutGuideModal = memo(function SmartShortcutGuideModal() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const blessedPacks = useSelector(getBlessedStickerPacks);
|
||||
const installedPacks = useSelector(getInstalledStickerPacks);
|
||||
const knownPacks = useSelector(getKnownStickerPacks);
|
||||
const receivedPacks = useSelector(getReceivedStickerPacks);
|
||||
const platform = useSelector(getPlatform);
|
||||
|
||||
const hasInstalledStickers =
|
||||
countStickers({
|
||||
knownPacks,
|
||||
blessedPacks,
|
||||
installedPacks,
|
||||
receivedPacks,
|
||||
}) > 0;
|
||||
const { closeShortcutGuideModal } = useGlobalModalActions();
|
||||
|
||||
const platform = getPlatform(state);
|
||||
const hasInstalledStickers = useMemo(() => {
|
||||
return (
|
||||
countStickers({
|
||||
knownPacks,
|
||||
blessedPacks,
|
||||
installedPacks,
|
||||
receivedPacks,
|
||||
}) > 0
|
||||
);
|
||||
}, [blessedPacks, installedPacks, knownPacks, receivedPacks]);
|
||||
|
||||
return {
|
||||
hasInstalledStickers,
|
||||
platform,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartShortcutGuideModal = smart(ShortcutGuideModal);
|
||||
return (
|
||||
<ShortcutGuideModal
|
||||
hasInstalledStickers={hasInstalledStickers}
|
||||
platform={platform}
|
||||
closeShortcutGuideModal={closeShortcutGuideModal}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
// Copyright 2019 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { StickerManager } from '../../components/stickers/StickerManager';
|
||||
import type { StateType } from '../reducer';
|
||||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import {
|
||||
getBlessedStickerPacks,
|
||||
|
@ -13,22 +11,31 @@ import {
|
|||
getKnownStickerPacks,
|
||||
getReceivedStickerPacks,
|
||||
} from '../selectors/stickers';
|
||||
import { useStickersActions } from '../ducks/stickers';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
const blessedPacks = getBlessedStickerPacks(state);
|
||||
const receivedPacks = getReceivedStickerPacks(state);
|
||||
const installedPacks = getInstalledStickerPacks(state);
|
||||
const knownPacks = getKnownStickerPacks(state);
|
||||
export const SmartStickerManager = memo(function SmartStickerManager() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const blessedPacks = useSelector(getBlessedStickerPacks);
|
||||
const receivedPacks = useSelector(getReceivedStickerPacks);
|
||||
const installedPacks = useSelector(getInstalledStickerPacks);
|
||||
const knownPacks = useSelector(getKnownStickerPacks);
|
||||
|
||||
return {
|
||||
blessedPacks,
|
||||
receivedPacks,
|
||||
installedPacks,
|
||||
knownPacks,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
const { downloadStickerPack, installStickerPack, uninstallStickerPack } =
|
||||
useStickersActions();
|
||||
const { closeStickerPackPreview } = useGlobalModalActions();
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartStickerManager = smart(StickerManager);
|
||||
return (
|
||||
<StickerManager
|
||||
blessedPacks={blessedPacks}
|
||||
closeStickerPackPreview={closeStickerPackPreview}
|
||||
downloadStickerPack={downloadStickerPack}
|
||||
i18n={i18n}
|
||||
installStickerPack={installStickerPack}
|
||||
installedPacks={installedPacks}
|
||||
knownPacks={knownPacks}
|
||||
receivedPacks={receivedPacks}
|
||||
uninstallStickerPack={uninstallStickerPack}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,40 +1,48 @@
|
|||
// Copyright 2019 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { StickerPreviewModal } from '../../components/stickers/StickerPreviewModal';
|
||||
import type { StateType } from '../reducer';
|
||||
|
||||
import { getIntl, getStickersPath, getTempPath } from '../selectors/user';
|
||||
import {
|
||||
getBlessedPacks,
|
||||
getPacks,
|
||||
translatePackFromDB,
|
||||
} from '../selectors/stickers';
|
||||
import { useStickersActions } from '../ducks/stickers';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
|
||||
export type ExternalProps = {
|
||||
packId: string;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||
const { packId } = props;
|
||||
const stickersPath = getStickersPath(state);
|
||||
const tempPath = getTempPath(state);
|
||||
export const SmartStickerPreviewModal = memo(function SmartStickerPreviewModal({
|
||||
packId,
|
||||
}: ExternalProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const packs = useSelector(getPacks);
|
||||
const blessedPacks = useSelector(getBlessedPacks);
|
||||
const stickersPath = useSelector(getStickersPath);
|
||||
const tempPath = useSelector(getTempPath);
|
||||
|
||||
const packs = getPacks(state);
|
||||
const blessedPacks = getBlessedPacks(state);
|
||||
const pack = packs[packId];
|
||||
const { downloadStickerPack, installStickerPack, uninstallStickerPack } =
|
||||
useStickersActions();
|
||||
const { closeStickerPackPreview } = useGlobalModalActions();
|
||||
|
||||
return {
|
||||
...props,
|
||||
pack: pack
|
||||
? translatePackFromDB(pack, packs, blessedPacks, stickersPath, tempPath)
|
||||
: undefined,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
const packDb = packs[packId];
|
||||
const pack = packDb
|
||||
? translatePackFromDB(packDb, packs, blessedPacks, stickersPath, tempPath)
|
||||
: undefined;
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartStickerPreviewModal = smart(StickerPreviewModal);
|
||||
return (
|
||||
<StickerPreviewModal
|
||||
closeStickerPackPreview={closeStickerPackPreview}
|
||||
downloadStickerPack={downloadStickerPack}
|
||||
i18n={i18n}
|
||||
installStickerPack={installStickerPack}
|
||||
pack={pack}
|
||||
uninstallStickerPack={uninstallStickerPack}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { StateType } from '../reducer';
|
||||
import { StoriesSettingsModal } from '../../components/StoriesSettingsModal';
|
||||
import {
|
||||
getAllSignalConnections,
|
||||
|
@ -23,59 +20,60 @@ import { useStoryDistributionListsActions } from '../ducks/storyDistributionList
|
|||
import { useStoriesActions } from '../ducks/stories';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
|
||||
export function SmartStoriesSettingsModal(): JSX.Element | null {
|
||||
const { setStoriesDisabled } = useStoriesActions();
|
||||
const { hideStoriesSettings, toggleSignalConnectionsModal } =
|
||||
useGlobalModalActions();
|
||||
const {
|
||||
allowsRepliesChanged,
|
||||
createDistributionList,
|
||||
deleteDistributionList,
|
||||
hideMyStoriesFrom,
|
||||
removeMembersFromDistributionList,
|
||||
setMyStoriesToAllSignalConnections,
|
||||
updateStoryViewers,
|
||||
} = useStoryDistributionListsActions();
|
||||
const { toggleGroupsForStorySend } = useConversationsActions();
|
||||
const signalConnections = useSelector(getAllSignalConnections);
|
||||
export const SmartStoriesSettingsModal = memo(
|
||||
function SmartStoriesSettingsModal() {
|
||||
const { setStoriesDisabled } = useStoriesActions();
|
||||
const { hideStoriesSettings, toggleSignalConnectionsModal } =
|
||||
useGlobalModalActions();
|
||||
const {
|
||||
allowsRepliesChanged,
|
||||
createDistributionList,
|
||||
deleteDistributionList,
|
||||
hideMyStoriesFrom,
|
||||
removeMembersFromDistributionList,
|
||||
setMyStoriesToAllSignalConnections,
|
||||
updateStoryViewers,
|
||||
} = useStoryDistributionListsActions();
|
||||
const { toggleGroupsForStorySend } = useConversationsActions();
|
||||
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const storyViewReceiptsEnabled = useSelector(getHasStoryViewReceiptSetting);
|
||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||
const me = useSelector(getMe);
|
||||
const signalConnections = useSelector(getAllSignalConnections);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const storyViewReceiptsEnabled = useSelector(getHasStoryViewReceiptSetting);
|
||||
const i18n = useSelector(getIntl);
|
||||
const me = useSelector(getMe);
|
||||
const candidateConversations = useSelector(getCandidateContactsForNewGroup);
|
||||
const distributionLists = useSelector(getDistributionListsWithMembers);
|
||||
const groupStories = useSelector(getGroupStories);
|
||||
|
||||
const candidateConversations = useSelector(getCandidateContactsForNewGroup);
|
||||
const distributionLists = useSelector(getDistributionListsWithMembers);
|
||||
const groupStories = useSelector(getGroupStories);
|
||||
const getConversationByServiceId = useSelector(
|
||||
getConversationByServiceIdSelector
|
||||
);
|
||||
const theme = useSelector(getTheme);
|
||||
|
||||
const getConversationByServiceId = useSelector(
|
||||
getConversationByServiceIdSelector
|
||||
);
|
||||
const theme = useSelector(getTheme);
|
||||
|
||||
return (
|
||||
<StoriesSettingsModal
|
||||
candidateConversations={candidateConversations}
|
||||
distributionLists={distributionLists}
|
||||
groupStories={groupStories}
|
||||
signalConnections={signalConnections}
|
||||
hideStoriesSettings={hideStoriesSettings}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
me={me}
|
||||
getConversationByServiceId={getConversationByServiceId}
|
||||
onDeleteList={deleteDistributionList}
|
||||
toggleGroupsForStorySend={toggleGroupsForStorySend}
|
||||
onDistributionListCreated={createDistributionList}
|
||||
onHideMyStoriesFrom={hideMyStoriesFrom}
|
||||
onRemoveMembers={removeMembersFromDistributionList}
|
||||
onRepliesNReactionsChanged={allowsRepliesChanged}
|
||||
onViewersUpdated={updateStoryViewers}
|
||||
setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections}
|
||||
storyViewReceiptsEnabled={storyViewReceiptsEnabled}
|
||||
theme={theme}
|
||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||
setStoriesDisabled={setStoriesDisabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<StoriesSettingsModal
|
||||
candidateConversations={candidateConversations}
|
||||
distributionLists={distributionLists}
|
||||
groupStories={groupStories}
|
||||
signalConnections={signalConnections}
|
||||
hideStoriesSettings={hideStoriesSettings}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
me={me}
|
||||
getConversationByServiceId={getConversationByServiceId}
|
||||
onDeleteList={deleteDistributionList}
|
||||
toggleGroupsForStorySend={toggleGroupsForStorySend}
|
||||
onDistributionListCreated={createDistributionList}
|
||||
onHideMyStoriesFrom={hideMyStoriesFrom}
|
||||
onRemoveMembers={removeMembersFromDistributionList}
|
||||
onRepliesNReactionsChanged={allowsRepliesChanged}
|
||||
onViewersUpdated={updateStoryViewers}
|
||||
setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections}
|
||||
storyViewReceiptsEnabled={storyViewReceiptsEnabled}
|
||||
theme={theme}
|
||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||
setStoriesDisabled={setStoriesDisabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { memo, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { StateType } from '../reducer';
|
||||
import { SmartStoryCreator } from './StoryCreator';
|
||||
import { SmartToastManager } from './ToastManager';
|
||||
import type { WidthBreakpoint } from '../../components/_util';
|
||||
|
@ -35,6 +32,7 @@ import { useAudioPlayerActions } from '../ducks/audioPlayer';
|
|||
import { useItemsActions } from '../ducks/items';
|
||||
import { getHasPendingUpdate } from '../selectors/updates';
|
||||
import { getOtherTabsUnreadStats } from '../selectors/nav';
|
||||
import { getIsStoriesSettingsVisible } from '../selectors/globalModals';
|
||||
|
||||
function renderStoryCreator(): JSX.Element {
|
||||
return <SmartStoryCreator />;
|
||||
|
@ -46,7 +44,7 @@ function renderToastManager(props: {
|
|||
return <SmartToastManager disableMegaphone {...props} />;
|
||||
}
|
||||
|
||||
export function SmartStoriesTab(): JSX.Element | null {
|
||||
export const SmartStoriesTab = memo(function SmartStoriesTab() {
|
||||
const storiesActions = useStoriesActions();
|
||||
const {
|
||||
retryMessageSend,
|
||||
|
@ -58,30 +56,20 @@ export function SmartStoriesTab(): JSX.Element | null {
|
|||
useGlobalModalActions();
|
||||
const { showToast } = useToastActions();
|
||||
|
||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||
|
||||
const preferredWidthFromStorage = useSelector<StateType, number>(
|
||||
getPreferredLeftPaneWidth
|
||||
);
|
||||
const i18n = useSelector(getIntl);
|
||||
const preferredWidthFromStorage = useSelector(getPreferredLeftPaneWidth);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
|
||||
const addStoryData = useSelector(getAddStoryData);
|
||||
const { hiddenStories, myStories, stories } = useSelector(getStories);
|
||||
|
||||
const me = useSelector(getMe);
|
||||
|
||||
const selectedStoryData = useSelector(getSelectedStoryData);
|
||||
|
||||
const isStoriesSettingsVisible = useSelector(
|
||||
(state: StateType) => state.globalModals.isStoriesSettingsVisible
|
||||
);
|
||||
|
||||
const isStoriesSettingsVisible = useSelector(getIsStoriesSettingsVisible);
|
||||
const hasViewReceiptSetting = useSelector(getHasStoryViewReceiptSetting);
|
||||
const hasPendingUpdate = useSelector(getHasPendingUpdate);
|
||||
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
|
||||
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
|
||||
|
||||
const remoteConfig = useSelector(getRemoteConfig);
|
||||
|
||||
const maxAttachmentSizeInKb = getMaximumOutgoingAttachmentSizeInKb(
|
||||
(name: ConfigKeyType) => {
|
||||
const value = remoteConfig[name]?.value;
|
||||
|
@ -145,4 +133,4 @@ export function SmartStoriesTab(): JSX.Element | null {
|
|||
{...storiesActions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { ThemeType, type LocalizerType } from '../../types/Util';
|
||||
import type { StateType } from '../reducer';
|
||||
import { ThemeType } from '../../types/Util';
|
||||
import { LinkPreviewSourceType } from '../../types/LinkPreview';
|
||||
import { StoryCreator } from '../../components/StoryCreator';
|
||||
import {
|
||||
|
@ -48,7 +46,7 @@ export type PropsType = {
|
|||
onClose: () => unknown;
|
||||
};
|
||||
|
||||
export function SmartStoryCreator(): JSX.Element | null {
|
||||
export const SmartStoryCreator = memo(function SmartStoryCreator() {
|
||||
const { debouncedMaybeGrabLinkPreview } = useLinkPreviewActions();
|
||||
const {
|
||||
sendStoryModalOpenStateChanged,
|
||||
|
@ -75,7 +73,7 @@ export function SmartStoryCreator(): JSX.Element | null {
|
|||
const groupConversations = useSelector(getNonGroupStories);
|
||||
const groupStories = useSelector(getGroupStories);
|
||||
const hasSetMyStoriesPrivacy = useSelector(getHasSetMyStoriesPrivacy);
|
||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||
const i18n = useSelector(getIntl);
|
||||
const installedPacks = useSelector(getInstalledStickerPacks);
|
||||
const linkPreviewForSource = useSelector(getLinkPreview);
|
||||
const me = useSelector(getMe);
|
||||
|
@ -96,7 +94,7 @@ export function SmartStoryCreator(): JSX.Element | null {
|
|||
}
|
||||
|
||||
const recentEmojis = useRecentEmojis();
|
||||
const skinTone = useSelector<StateType, number>(getEmojiSkinTone);
|
||||
const skinTone = useSelector(getEmojiSkinTone);
|
||||
const { onSetSkinTone } = useItemsActions();
|
||||
const { onUseEmoji } = useEmojisActions();
|
||||
const { pauseVoiceNotePlayer } = useAudioPlayerActions();
|
||||
|
@ -155,4 +153,4 @@ export function SmartStoryCreator(): JSX.Element | null {
|
|||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type { GetConversationByIdType } from '../selectors/conversations';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { StateType } from '../reducer';
|
||||
import type { SelectedStoryDataType } from '../ducks/stories';
|
||||
import { StoryViewer } from '../../components/StoryViewer';
|
||||
import { ToastType } from '../../types/Toast';
|
||||
import { useToastActions } from '../ducks/toast';
|
||||
|
@ -41,7 +36,7 @@ import { useGlobalModalActions } from '../ducks/globalModals';
|
|||
import { useStoriesActions } from '../ducks/stories';
|
||||
import { useIsWindowActive } from '../../hooks/useIsWindowActive';
|
||||
|
||||
export function SmartStoryViewer(): JSX.Element | null {
|
||||
export const SmartStoryViewer = memo(function SmartStoryViewer() {
|
||||
const storiesActions = useStoriesActions();
|
||||
const { onUseEmoji } = useEmojisActions();
|
||||
const {
|
||||
|
@ -56,40 +51,24 @@ export function SmartStoryViewer(): JSX.Element | null {
|
|||
|
||||
const isWindowActive = useIsWindowActive();
|
||||
|
||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
||||
const i18n = useSelector(getIntl);
|
||||
const platform = useSelector(getPlatform);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const preferredReactionEmoji = useSelector<StateType, ReadonlyArray<string>>(
|
||||
getPreferredReactionEmoji
|
||||
);
|
||||
|
||||
const selectedStoryData = useSelector<
|
||||
StateType,
|
||||
SelectedStoryDataType | undefined
|
||||
>(getSelectedStoryData);
|
||||
|
||||
const internalUser = useSelector<StateType, boolean>(isInternalUser);
|
||||
const preferredReactionEmoji = useSelector(getPreferredReactionEmoji);
|
||||
const selectedStoryData = useSelector(getSelectedStoryData);
|
||||
const internalUser = useSelector(isInternalUser);
|
||||
|
||||
strictAssert(selectedStoryData, 'StoryViewer: !selectedStoryData');
|
||||
|
||||
const conversationSelector = useSelector<StateType, GetConversationByIdType>(
|
||||
getConversationSelector
|
||||
);
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
|
||||
const getStoryById = useSelector(getStoryByIdSelector);
|
||||
|
||||
const recentEmojis = useRecentEmojis();
|
||||
const skinTone = useSelector<StateType, number>(getEmojiSkinTone);
|
||||
const skinTone = useSelector(getEmojiSkinTone);
|
||||
const replyState = useSelector(getStoryReplies);
|
||||
const hasAllStoriesUnmuted = useSelector<StateType, boolean>(
|
||||
getHasAllStoriesUnmuted
|
||||
);
|
||||
|
||||
const hasAllStoriesUnmuted = useSelector(getHasAllStoriesUnmuted);
|
||||
const hasActiveCall = useSelector(isInFullScreenCall);
|
||||
const hasViewReceiptSetting = useSelector<StateType, boolean>(
|
||||
getHasStoryViewReceiptSetting
|
||||
);
|
||||
|
||||
const hasViewReceiptSetting = useSelector(getHasStoryViewReceiptSetting);
|
||||
const isFormattingEnabled = useSelector(getTextFormattingEnabled);
|
||||
|
||||
const { pauseVoiceNotePlayer } = useAudioPlayerActions();
|
||||
|
@ -161,4 +140,4 @@ export function SmartStoryViewer(): JSX.Element | null {
|
|||
{...storiesActions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,46 +1,47 @@
|
|||
// Copyright 2019 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { isEmpty, pick } from 'lodash';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import type { WarningType as TimelineWarningType } from '../../components/conversation/Timeline';
|
||||
import { Timeline } from '../../components/conversation/Timeline';
|
||||
import type { StateType } from '../reducer';
|
||||
import type { ConversationType } from '../ducks/conversations';
|
||||
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import {
|
||||
getMessages,
|
||||
getConversationByServiceIdSelector,
|
||||
getConversationMessagesSelector,
|
||||
getConversationSelector,
|
||||
getInvitedContactsForNewlyCreatedGroup,
|
||||
getSafeConversationWithSameTitle,
|
||||
getTargetedMessage,
|
||||
} from '../selectors/conversations';
|
||||
import { selectAudioPlayerActive } from '../selectors/audioPlayer';
|
||||
|
||||
import { SmartTimelineItem, type SmartTimelineItemProps } from './TimelineItem';
|
||||
import { SmartCollidingAvatars } from './CollidingAvatars';
|
||||
import type { PropsType as SmartCollidingAvatarsPropsType } from './CollidingAvatars';
|
||||
import { SmartContactSpoofingReviewDialog } from './ContactSpoofingReviewDialog';
|
||||
import type { PropsType as SmartContactSpoofingReviewDialogPropsType } from './ContactSpoofingReviewDialog';
|
||||
import { SmartTypingBubble } from './TypingBubble';
|
||||
import { SmartHeroRow } from './HeroRow';
|
||||
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { ContactSpoofingType } from '../../util/contactSpoofing';
|
||||
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
||||
import {
|
||||
dehydrateCollisionsWithConversations,
|
||||
getCollisionsFromMemberships,
|
||||
} from '../../util/groupMemberNameCollisions';
|
||||
import { ContactSpoofingType } from '../../util/contactSpoofing';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { useCallingActions } from '../ducks/calling';
|
||||
import {
|
||||
useConversationsActions,
|
||||
type ConversationType,
|
||||
} from '../ducks/conversations';
|
||||
import type { StateType } from '../reducer';
|
||||
import { selectAudioPlayerActive } from '../selectors/audioPlayer';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import {
|
||||
getConversationByServiceIdSelector,
|
||||
getConversationMessagesSelector,
|
||||
getConversationSelector,
|
||||
getHasContactSpoofingReview,
|
||||
getInvitedContactsForNewlyCreatedGroup,
|
||||
getMessages,
|
||||
getSafeConversationWithSameTitle,
|
||||
getSelectedConversationId,
|
||||
getTargetedMessage,
|
||||
} from '../selectors/conversations';
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import type { PropsType as SmartCollidingAvatarsPropsType } from './CollidingAvatars';
|
||||
import { SmartCollidingAvatars } from './CollidingAvatars';
|
||||
import type { PropsType as SmartContactSpoofingReviewDialogPropsType } from './ContactSpoofingReviewDialog';
|
||||
import { SmartContactSpoofingReviewDialog } from './ContactSpoofingReviewDialog';
|
||||
import { SmartHeroRow } from './HeroRow';
|
||||
import { SmartMiniPlayer } from './MiniPlayer';
|
||||
import { SmartTimelineItem, type SmartTimelineItemProps } from './TimelineItem';
|
||||
import { SmartTypingBubble } from './TypingBubble';
|
||||
|
||||
type ExternalProps = {
|
||||
id: string;
|
||||
|
@ -144,60 +145,145 @@ const getWarning = (
|
|||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||
const { id } = props;
|
||||
export const SmartTimeline = memo(function SmartTimeline({
|
||||
id,
|
||||
}: ExternalProps) {
|
||||
const activeAudioPlayer = useSelector(selectAudioPlayerActive);
|
||||
const conversationMessagesSelector = useSelector(
|
||||
getConversationMessagesSelector
|
||||
);
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
const hasContactSpoofingReview = useSelector(getHasContactSpoofingReview);
|
||||
const i18n = useSelector(getIntl);
|
||||
const invitedContactsForNewlyCreatedGroup = useSelector(
|
||||
getInvitedContactsForNewlyCreatedGroup
|
||||
);
|
||||
const messages = useSelector(getMessages);
|
||||
const selectedConversationId = useSelector(getSelectedConversationId);
|
||||
const targetedMessage = useSelector(getTargetedMessage);
|
||||
const theme = useSelector(getTheme);
|
||||
|
||||
const conversation = getConversationSelector(state)(id);
|
||||
const conversation = conversationSelector(id);
|
||||
const conversationMessages = conversationMessagesSelector(id);
|
||||
|
||||
const conversationMessages = getConversationMessagesSelector(state)(id);
|
||||
const targetedMessage = getTargetedMessage(state);
|
||||
const warning = useSelector(
|
||||
useCallback(
|
||||
(state: StateType) => {
|
||||
return getWarning(conversation, state);
|
||||
},
|
||||
[conversation]
|
||||
)
|
||||
);
|
||||
|
||||
const getTimestampForMessage = (messageId: string): undefined | number =>
|
||||
getMessages(state)[messageId]?.timestamp;
|
||||
const {
|
||||
acknowledgeGroupMemberNameCollisions,
|
||||
clearInvitedServiceIdsForNewlyCreatedGroup,
|
||||
clearTargetedMessage,
|
||||
closeContactSpoofingReview,
|
||||
discardMessages,
|
||||
loadNewerMessages,
|
||||
loadNewestMessages,
|
||||
loadOlderMessages,
|
||||
markMessageRead,
|
||||
reviewConversationNameCollision,
|
||||
scrollToOldestUnreadMention,
|
||||
setIsNearBottom,
|
||||
targetMessage,
|
||||
} = useConversationsActions();
|
||||
const { peekGroupCallForTheFirstTime, peekGroupCallIfItHasMembers } =
|
||||
useCallingActions();
|
||||
|
||||
const shouldShowMiniPlayer = Boolean(selectAudioPlayerActive(state));
|
||||
const getTimestampForMessage = useCallback(
|
||||
(messageId: string): undefined | number => {
|
||||
return messages[messageId]?.timestamp;
|
||||
},
|
||||
[messages]
|
||||
);
|
||||
|
||||
return {
|
||||
id,
|
||||
...pick(conversation, [
|
||||
'unreadCount',
|
||||
'unreadMentionsCount',
|
||||
'isGroupV1AndDisabled',
|
||||
'typingContactIdTimestamps',
|
||||
]),
|
||||
isBlocked: conversation.isBlocked ?? false,
|
||||
isConversationSelected: state.conversations.selectedConversationId === id,
|
||||
isIncomingMessageRequest: Boolean(
|
||||
!conversation.acceptedMessageRequest &&
|
||||
conversation.removalStage !== 'justNotification'
|
||||
),
|
||||
isSomeoneTyping: Boolean(
|
||||
Object.keys(conversation.typingContactIdTimestamps ?? {}).length > 0
|
||||
),
|
||||
...conversationMessages,
|
||||
const shouldShowMiniPlayer = activeAudioPlayer != null;
|
||||
const {
|
||||
acceptedMessageRequest,
|
||||
isBlocked = false,
|
||||
isGroupV1AndDisabled,
|
||||
removalStage,
|
||||
typingContactIdTimestamps = {},
|
||||
unreadCount,
|
||||
unreadMentionsCount,
|
||||
} = conversation ?? {};
|
||||
const {
|
||||
haveNewest,
|
||||
haveOldest,
|
||||
isNearBottom,
|
||||
items,
|
||||
messageChangeCounter,
|
||||
messageLoadingState,
|
||||
oldestUnseenIndex,
|
||||
scrollToIndex,
|
||||
scrollToIndexCounter,
|
||||
totalUnseen,
|
||||
} = conversationMessages;
|
||||
|
||||
invitedContactsForNewlyCreatedGroup:
|
||||
getInvitedContactsForNewlyCreatedGroup(state),
|
||||
targetedMessageId: targetedMessage ? targetedMessage.id : undefined,
|
||||
shouldShowMiniPlayer,
|
||||
const isConversationSelected = selectedConversationId === id;
|
||||
const isIncomingMessageRequest =
|
||||
!acceptedMessageRequest && removalStage !== 'justNotification';
|
||||
const isSomeoneTyping = Object.keys(typingContactIdTimestamps).length > 0;
|
||||
const targetedMessageId = targetedMessage?.id;
|
||||
|
||||
warning: getWarning(conversation, state),
|
||||
hasContactSpoofingReview: state.conversations.hasContactSpoofingReview,
|
||||
|
||||
getTimestampForMessage,
|
||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||
i18n: getIntl(state),
|
||||
theme: getTheme(state),
|
||||
|
||||
renderCollidingAvatars,
|
||||
renderContactSpoofingReviewDialog,
|
||||
renderHeroRow,
|
||||
renderItem,
|
||||
renderMiniPlayer,
|
||||
renderTypingBubble,
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartTimeline = smart(Timeline);
|
||||
return (
|
||||
<Timeline
|
||||
acknowledgeGroupMemberNameCollisions={
|
||||
acknowledgeGroupMemberNameCollisions
|
||||
}
|
||||
clearInvitedServiceIdsForNewlyCreatedGroup={
|
||||
clearInvitedServiceIdsForNewlyCreatedGroup
|
||||
}
|
||||
clearTargetedMessage={clearTargetedMessage}
|
||||
closeContactSpoofingReview={closeContactSpoofingReview}
|
||||
discardMessages={discardMessages}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
getTimestampForMessage={getTimestampForMessage}
|
||||
hasContactSpoofingReview={hasContactSpoofingReview}
|
||||
haveNewest={haveNewest}
|
||||
haveOldest={haveOldest}
|
||||
i18n={i18n}
|
||||
id={id}
|
||||
invitedContactsForNewlyCreatedGroup={invitedContactsForNewlyCreatedGroup}
|
||||
isBlocked={isBlocked}
|
||||
isConversationSelected={isConversationSelected}
|
||||
isGroupV1AndDisabled={isGroupV1AndDisabled}
|
||||
isIncomingMessageRequest={isIncomingMessageRequest}
|
||||
isNearBottom={isNearBottom}
|
||||
isSomeoneTyping={isSomeoneTyping}
|
||||
items={items}
|
||||
loadNewerMessages={loadNewerMessages}
|
||||
loadNewestMessages={loadNewestMessages}
|
||||
loadOlderMessages={loadOlderMessages}
|
||||
markMessageRead={markMessageRead}
|
||||
messageChangeCounter={messageChangeCounter}
|
||||
messageLoadingState={messageLoadingState}
|
||||
oldestUnseenIndex={oldestUnseenIndex}
|
||||
peekGroupCallForTheFirstTime={peekGroupCallForTheFirstTime}
|
||||
peekGroupCallIfItHasMembers={peekGroupCallIfItHasMembers}
|
||||
renderCollidingAvatars={renderCollidingAvatars}
|
||||
renderContactSpoofingReviewDialog={renderContactSpoofingReviewDialog}
|
||||
renderHeroRow={renderHeroRow}
|
||||
renderItem={renderItem}
|
||||
renderMiniPlayer={renderMiniPlayer}
|
||||
renderTypingBubble={renderTypingBubble}
|
||||
reviewConversationNameCollision={reviewConversationNameCollision}
|
||||
scrollToIndex={scrollToIndex}
|
||||
scrollToIndexCounter={scrollToIndexCounter}
|
||||
scrollToOldestUnreadMention={scrollToOldestUnreadMention}
|
||||
setIsNearBottom={setIsNearBottom}
|
||||
shouldShowMiniPlayer={shouldShowMiniPlayer}
|
||||
targetedMessageId={targetedMessageId}
|
||||
targetMessage={targetMessage}
|
||||
theme={theme}
|
||||
totalUnseen={totalUnseen}
|
||||
unreadCount={unreadCount}
|
||||
unreadMentionsCount={unreadMentionsCount}
|
||||
warning={warning}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { RefObject } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { TimelineItem } from '../../components/conversation/TimelineItem';
|
||||
|
@ -56,7 +56,9 @@ function renderContact(contactId: string): JSX.Element {
|
|||
function renderUniversalTimerNotification(): JSX.Element {
|
||||
return <SmartUniversalTimerNotification />;
|
||||
}
|
||||
export function SmartTimelineItem(props: SmartTimelineItemProps): JSX.Element {
|
||||
export const SmartTimelineItem = memo(function SmartTimelineItem(
|
||||
props: SmartTimelineItemProps
|
||||
): JSX.Element {
|
||||
const {
|
||||
containerElementRef,
|
||||
containerWidthBreakpoint,
|
||||
|
@ -224,4 +226,4 @@ export function SmartTimelineItem(props: SmartTimelineItemProps): JSX.Element {
|
|||
toggleSelectMessage={toggleSelectMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue