signal-desktop/ts/components/conversation/TimelineItem.tsx

367 lines
12 KiB
TypeScript
Raw Normal View History

2023-01-03 19:55:46 +00:00
// Copyright 2019 Signal Messenger, LLC
2020-10-30 20:34:04 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
2022-01-26 23:05:26 +00:00
import type { ReactChild, RefObject } from 'react';
import React from 'react';
2021-08-11 16:23:21 +00:00
import type { LocalizerType, ThemeType } from '../../types/Util';
import type { InteractionModeType } from '../../state/ducks/conversations';
2022-01-26 23:05:26 +00:00
import { TimelineDateHeader } from './TimelineDateHeader';
import type {
Props as AllMessageProps,
2022-11-04 13:22:07 +00:00
PropsData as TimelineMessageProps,
PropsActions as MessageActionsType,
2022-11-04 13:22:07 +00:00
} from './TimelineMessage';
import type { PropsActionsType as CallingNotificationActionsType } from './CallingNotification';
import { CallingNotification } from './CallingNotification';
import { ChatSessionRefreshedNotification } from './ChatSessionRefreshedNotification';
import type { PropsDataType as DeliveryIssueProps } from './DeliveryIssueNotification';
import { DeliveryIssueNotification } from './DeliveryIssueNotification';
import type { PropsData as ChangeNumberNotificationProps } from './ChangeNumberNotification';
import { ChangeNumberNotification } from './ChangeNumberNotification';
import type { CallingNotificationType } from '../../util/callingNotification';
2019-11-07 21:36:16 +00:00
import { InlineNotificationWrapper } from './InlineNotificationWrapper';
import type { PropsData as UnsupportedMessageProps } from './UnsupportedMessage';
import { UnsupportedMessage } from './UnsupportedMessage';
import type { PropsData as TimerNotificationProps } from './TimerNotification';
import { TimerNotification } from './TimerNotification';
import type {
PropsActions as SafetyNumberActionsType,
PropsData as SafetyNumberNotificationProps,
} from './SafetyNumberNotification';
import { SafetyNumberNotification } from './SafetyNumberNotification';
import type { PropsData as VerificationNotificationProps } from './VerificationNotification';
import { VerificationNotification } from './VerificationNotification';
import type { PropsData as GroupNotificationProps } from './GroupNotification';
import { GroupNotification } from './GroupNotification';
import type {
PropsDataType as GroupV2ChangeProps,
PropsActionsType as GroupV2ChangeActionsType,
} from './GroupV2Change';
import { GroupV2Change } from './GroupV2Change';
import type { PropsDataType as GroupV1MigrationProps } from './GroupV1Migration';
import { GroupV1Migration } from './GroupV1Migration';
import type { SmartContactRendererType } from '../../groupChange';
import { ResetSessionNotification } from './ResetSessionNotification';
import type { PropsType as ProfileChangeNotificationPropsType } from './ProfileChangeNotification';
import { ProfileChangeNotification } from './ProfileChangeNotification';
2022-11-30 21:47:54 +00:00
import type { PropsType as PaymentEventNotificationPropsType } from './PaymentEventNotification';
import { PaymentEventNotification } from './PaymentEventNotification';
import type { PropsDataType as ConversationMergeNotificationPropsType } from './ConversationMergeNotification';
import { ConversationMergeNotification } from './ConversationMergeNotification';
import type { FullJSXType } from '../Intl';
2022-11-04 13:22:07 +00:00
import { TimelineMessage } from './TimelineMessage';
2020-06-04 18:16:19 +00:00
type CallHistoryType = {
type: 'callHistory';
data: CallingNotificationType;
2020-06-04 18:16:19 +00:00
};
2021-02-18 16:40:26 +00:00
type ChatSessionRefreshedType = {
type: 'chatSessionRefreshed';
data: null;
};
2021-05-28 19:11:19 +00:00
type DeliveryIssueType = {
type: 'deliveryIssue';
data: DeliveryIssueProps;
};
type MessageType = {
type: 'message';
2022-11-04 13:22:07 +00:00
data: TimelineMessageProps;
};
type UnsupportedMessageType = {
type: 'unsupportedMessage';
data: UnsupportedMessageProps;
};
type TimerNotificationType = {
type: 'timerNotification';
data: TimerNotificationProps;
};
2021-06-01 20:45:43 +00:00
type UniversalTimerNotificationType = {
type: 'universalTimerNotification';
data: null;
};
2021-08-05 23:34:49 +00:00
type ChangeNumberNotificationType = {
type: 'changeNumberNotification';
data: ChangeNumberNotificationProps;
};
type SafetyNumberNotificationType = {
type: 'safetyNumberNotification';
data: SafetyNumberNotificationProps;
};
type VerificationNotificationType = {
type: 'verificationNotification';
data: VerificationNotificationProps;
};
type GroupNotificationType = {
type: 'groupNotification';
data: GroupNotificationProps;
};
2020-09-09 02:25:05 +00:00
type GroupV2ChangeType = {
type: 'groupV2Change';
data: GroupV2ChangeProps;
};
2020-11-20 17:30:45 +00:00
type GroupV1MigrationType = {
type: 'groupV1Migration';
data: GroupV1MigrationProps;
};
type ResetSessionNotificationType = {
type: 'resetSessionNotification';
data: null;
};
type ProfileChangeNotificationType = {
type: 'profileChange';
data: ProfileChangeNotificationPropsType;
};
type ConversationMergeNotificationType = {
type: 'conversationMerge';
data: ConversationMergeNotificationPropsType;
};
2022-11-30 21:47:54 +00:00
type PaymentEventType = {
type: 'paymentEvent';
data: Omit<PaymentEventNotificationPropsType, 'i18n'>;
};
2022-01-26 23:05:26 +00:00
export type TimelineItemType = (
2020-06-04 18:16:19 +00:00
| CallHistoryType
| ChangeNumberNotificationType
2021-02-18 16:40:26 +00:00
| ChatSessionRefreshedType
| ConversationMergeNotificationType
2021-05-28 19:11:19 +00:00
| DeliveryIssueType
| GroupNotificationType
2020-11-20 17:30:45 +00:00
| GroupV1MigrationType
2020-09-09 02:25:05 +00:00
| GroupV2ChangeType
| MessageType
| ProfileChangeNotificationType
| ResetSessionNotificationType
| SafetyNumberNotificationType
| TimerNotificationType
2021-06-01 20:45:43 +00:00
| UniversalTimerNotificationType
| UnsupportedMessageType
2022-01-26 23:05:26 +00:00
| VerificationNotificationType
2022-11-30 21:47:54 +00:00
| PaymentEventType
2022-01-26 23:05:26 +00:00
) & { timestamp: number };
2019-11-07 21:36:16 +00:00
type PropsLocalType = {
containerElementRef: RefObject<HTMLElement>;
2019-11-07 21:36:16 +00:00
conversationId: string;
item?: TimelineItemType;
2019-11-07 21:36:16 +00:00
id: string;
isNextItemCallingNotification: boolean;
2019-11-07 21:36:16 +00:00
isSelected: boolean;
selectMessage: (messageId: string, conversationId: string) => unknown;
shouldRenderDateHeader: boolean;
renderContact: SmartContactRendererType<FullJSXType>;
2021-06-01 20:45:43 +00:00
renderUniversalTimerNotification: () => JSX.Element;
i18n: LocalizerType;
interactionMode: InteractionModeType;
theme: ThemeType;
};
2019-11-07 21:36:16 +00:00
type PropsActionsType = MessageActionsType &
CallingNotificationActionsType &
GroupV2ChangeActionsType &
SafetyNumberActionsType;
export type PropsType = PropsLocalType &
PropsActionsType &
Pick<
AllMessageProps,
| 'containerWidthBreakpoint'
2021-11-17 21:11:46 +00:00
| 'getPreferredBadge'
| 'renderEmojiPicker'
| 'renderAudioAttachment'
| 'renderReactionPicker'
| 'shouldCollapseAbove'
| 'shouldCollapseBelow'
| 'shouldHideMetadata'
>;
2019-11-07 21:36:16 +00:00
export class TimelineItem extends React.PureComponent<PropsType> {
public override render(): JSX.Element | null {
2019-11-07 21:36:16 +00:00
const {
containerElementRef,
2019-11-07 21:36:16 +00:00
conversationId,
2021-11-17 21:11:46 +00:00
getPreferredBadge,
i18n,
2019-11-07 21:36:16 +00:00
id,
isNextItemCallingNotification,
2019-11-07 21:36:16 +00:00
isSelected,
item,
2021-06-01 20:45:43 +00:00
renderUniversalTimerNotification,
returnToActiveCall,
2019-11-07 21:36:16 +00:00
selectMessage,
shouldCollapseAbove,
shouldCollapseBelow,
shouldHideMetadata,
shouldRenderDateHeader,
startCallingLobby,
theme,
2022-12-19 22:33:55 +00:00
...reducedProps
2019-11-07 21:36:16 +00:00
} = this.props;
if (!item) {
// This can happen under normal conditions.
//
// `<Timeline>` and `<TimelineItem>` are connected to Redux separately. If a
// timeline item is removed from Redux, `<TimelineItem>` might re-render before
// `<Timeline>` does, which means we'll try to render nothing. This should resolve
// itself quickly, as soon as `<Timeline>` re-renders.
return null;
}
2022-01-26 23:05:26 +00:00
let itemContents: ReactChild;
if (item.type === 'message') {
2022-01-26 23:05:26 +00:00
itemContents = (
2022-11-04 13:22:07 +00:00
<TimelineMessage
2022-12-19 22:33:55 +00:00
{...reducedProps}
{...item.data}
2022-12-03 00:40:33 +00:00
isSelected={isSelected}
shouldCollapseAbove={shouldCollapseAbove}
shouldCollapseBelow={shouldCollapseBelow}
shouldHideMetadata={shouldHideMetadata}
containerElementRef={containerElementRef}
2021-11-17 21:11:46 +00:00
getPreferredBadge={getPreferredBadge}
i18n={i18n}
theme={theme}
/>
);
2022-01-26 23:05:26 +00:00
} else {
let notification;
2019-11-07 21:36:16 +00:00
2022-01-26 23:05:26 +00:00
if (item.type === 'unsupportedMessage') {
notification = (
2022-12-19 22:33:55 +00:00
<UnsupportedMessage {...reducedProps} {...item.data} i18n={i18n} />
2022-01-26 23:05:26 +00:00
);
} else if (item.type === 'callHistory') {
notification = (
<CallingNotification
conversationId={conversationId}
i18n={i18n}
isNextItemCallingNotification={isNextItemCallingNotification}
2022-01-26 23:05:26 +00:00
returnToActiveCall={returnToActiveCall}
startCallingLobby={startCallingLobby}
{...item.data}
/>
);
} else if (item.type === 'chatSessionRefreshed') {
notification = (
2022-12-19 22:33:55 +00:00
<ChatSessionRefreshedNotification {...reducedProps} i18n={i18n} />
2022-01-26 23:05:26 +00:00
);
} else if (item.type === 'deliveryIssue') {
notification = (
<DeliveryIssueNotification
{...item.data}
2022-12-19 22:33:55 +00:00
{...reducedProps}
2022-01-26 23:05:26 +00:00
i18n={i18n}
/>
);
} else if (item.type === 'timerNotification') {
notification = (
2022-12-19 22:33:55 +00:00
<TimerNotification {...reducedProps} {...item.data} i18n={i18n} />
2022-01-26 23:05:26 +00:00
);
} else if (item.type === 'universalTimerNotification') {
notification = renderUniversalTimerNotification();
} else if (item.type === 'changeNumberNotification') {
notification = (
<ChangeNumberNotification
2022-12-19 22:33:55 +00:00
{...reducedProps}
2022-01-26 23:05:26 +00:00
{...item.data}
i18n={i18n}
/>
);
} else if (item.type === 'safetyNumberNotification') {
notification = (
<SafetyNumberNotification
2022-12-19 22:33:55 +00:00
{...reducedProps}
2022-01-26 23:05:26 +00:00
{...item.data}
i18n={i18n}
/>
);
} else if (item.type === 'verificationNotification') {
notification = (
<VerificationNotification
2022-12-19 22:33:55 +00:00
{...reducedProps}
2022-01-26 23:05:26 +00:00
{...item.data}
i18n={i18n}
/>
);
} else if (item.type === 'groupNotification') {
notification = (
2022-12-19 22:33:55 +00:00
<GroupNotification {...reducedProps} {...item.data} i18n={i18n} />
2022-01-26 23:05:26 +00:00
);
} else if (item.type === 'groupV2Change') {
notification = (
2022-12-19 22:33:55 +00:00
<GroupV2Change {...reducedProps} {...item.data} i18n={i18n} />
2022-01-26 23:05:26 +00:00
);
} else if (item.type === 'groupV1Migration') {
notification = (
2022-12-19 22:33:55 +00:00
<GroupV1Migration
{...reducedProps}
{...item.data}
i18n={i18n}
getPreferredBadge={getPreferredBadge}
theme={theme}
/>
2022-01-26 23:05:26 +00:00
);
} else if (item.type === 'conversationMerge') {
notification = (
<ConversationMergeNotification
2022-12-19 22:33:55 +00:00
{...reducedProps}
{...item.data}
i18n={i18n}
/>
);
2022-01-26 23:05:26 +00:00
} else if (item.type === 'resetSessionNotification') {
2022-12-19 22:33:55 +00:00
notification = (
<ResetSessionNotification {...reducedProps} i18n={i18n} />
);
2022-01-26 23:05:26 +00:00
} else if (item.type === 'profileChange') {
notification = (
<ProfileChangeNotification
2022-12-19 22:33:55 +00:00
{...reducedProps}
2022-11-30 21:47:54 +00:00
{...item.data}
i18n={i18n}
/>
);
} else if (item.type === 'paymentEvent') {
notification = (
<PaymentEventNotification
2022-12-19 22:33:55 +00:00
{...reducedProps}
2022-01-26 23:05:26 +00:00
{...item.data}
i18n={i18n}
/>
);
} else {
// Weird, yes, but the idea is to get a compile error when we aren't comprehensive
// with our if/else checks above, but also log out the type we don't understand
// if we encounter it at runtime.
const unknownItem: never = item;
const asItem = unknownItem as TimelineItemType;
throw new Error(`TimelineItem: Unknown type: ${asItem.type}`);
}
2019-11-07 21:36:16 +00:00
2022-01-26 23:05:26 +00:00
itemContents = (
<InlineNotificationWrapper
id={id}
conversationId={conversationId}
2022-01-26 23:05:26 +00:00
isSelected={isSelected}
selectMessage={selectMessage}
>
{notification}
</InlineNotificationWrapper>
2021-08-05 23:34:49 +00:00
);
2022-01-26 23:05:26 +00:00
}
if (shouldRenderDateHeader) {
return (
<>
<TimelineDateHeader i18n={i18n} timestamp={item.timestamp} />
{itemContents}
</>
);
}
2022-01-26 23:05:26 +00:00
return itemContents;
}
}