Reintroduce inline metadata with full RTL support

This commit is contained in:
Scott Nonnenberg 2022-03-23 13:23:28 -07:00 committed by GitHub
parent 801c70b298
commit bb066d4a84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 185 additions and 9 deletions

View file

@ -25,6 +25,7 @@ import {
MessageBodyReadMore,
} from './MessageBodyReadMore';
import { MessageMetadata } from './MessageMetadata';
import { MessageTextMetadataSpacer } from './MessageTextMetadataSpacer';
import { ImageGrid } from './ImageGrid';
import { GIF } from './GIF';
import { Image } from './Image';
@ -91,6 +92,19 @@ type Trigger = {
handleContextClick: (event: React.MouseEvent<HTMLDivElement>) => void;
};
const GUESS_METADATA_WIDTH_TIMESTAMP_SIZE = 10;
const GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE = 18;
const GUESS_METADATA_WIDTH_OUTGOING_SIZE: Record<MessageStatusType, number> = {
delivered: 24,
error: 24,
paused: 18,
'partial-sent': 24,
read: 24,
sending: 18,
sent: 24,
viewed: 24,
};
const EXPIRATION_CHECK_MINIMUM = 2000;
const EXPIRED_DELAY = 600;
const GROUP_AVATAR_SIZE = AvatarSize.TWENTY_EIGHT;
@ -109,9 +123,17 @@ const SENT_STATUSES = new Set<MessageStatusType>([
enum MetadataPlacement {
NotRendered,
RenderedByMessageAudioComponent,
InlineWithText,
Bottom,
}
export enum TextDirection {
LeftToRight = 'LeftToRight',
RightToLeft = 'RightToLeft',
Default = 'Default',
None = 'None',
}
export const MessageStatuses = [
'delivered',
'error',
@ -161,6 +183,7 @@ export type PropsData = {
conversationId: string;
displayLimit?: number;
text?: string;
textDirection: TextDirection;
textPending?: boolean;
isSticker?: boolean;
isSelected?: boolean;
@ -318,6 +341,8 @@ export type Props = PropsData &
Pick<ReactionPickerProps, 'renderEmojiPicker'>;
type State = {
metadataWidth: number;
expiring: boolean;
expired: boolean;
imageBroken: boolean;
@ -355,6 +380,8 @@ export class Message extends React.PureComponent<Props, State> {
super(props);
this.state = {
metadataWidth: this.guessMetadataWidth(),
expiring: false,
expired: false,
imageBroken: false,
@ -529,8 +556,11 @@ export class Message extends React.PureComponent<Props, State> {
expirationTimestamp,
status,
text,
textDirection,
}: Readonly<Props> = this.props
): MetadataPlacement {
const isRTL = textDirection === TextDirection.RightToLeft;
if (
!expirationLength &&
!expirationTimestamp &&
@ -550,7 +580,37 @@ export class Message extends React.PureComponent<Props, State> {
return MetadataPlacement.Bottom;
}
return MetadataPlacement.Bottom;
if (isRTL) {
return MetadataPlacement.Bottom;
}
return MetadataPlacement.InlineWithText;
}
/**
* A lot of the time, we add an invisible inline spacer for messages. This spacer is the
* same size as the message metadata. Unfortunately, we don't know how wide it is until
* we render it.
*
* This will probably guess wrong, but it's valuable to get close to the real value
* because it can reduce layout jumpiness.
*/
private guessMetadataWidth(): number {
const { direction, expirationLength, expirationTimestamp, status } =
this.props;
let result = GUESS_METADATA_WIDTH_TIMESTAMP_SIZE;
const hasExpireTimer = Boolean(expirationLength && expirationTimestamp);
if (hasExpireTimer) {
result += GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE;
}
if (direction === 'outgoing' && status) {
result += GUESS_METADATA_WIDTH_OUTGOING_SIZE[status];
}
return result;
}
public startSelectedTimer(): void {
@ -672,13 +732,31 @@ export class Message extends React.PureComponent<Props, State> {
);
}
private updateMetadataWidth = (newMetadataWidth: number): void => {
this.setState(({ metadataWidth }) => ({
// We don't want text to jump around if the metadata shrinks, but we want to make
// sure we have enough room.
metadataWidth: Math.max(metadataWidth, newMetadataWidth),
}));
};
private renderMetadata(): ReactNode {
let isInline: boolean;
const metadataPlacement = this.getMetadataPlacement();
if (
metadataPlacement === MetadataPlacement.NotRendered ||
metadataPlacement === MetadataPlacement.RenderedByMessageAudioComponent
) {
return null;
switch (metadataPlacement) {
case MetadataPlacement.NotRendered:
case MetadataPlacement.RenderedByMessageAudioComponent:
return null;
case MetadataPlacement.InlineWithText:
isInline = true;
break;
case MetadataPlacement.Bottom:
isInline = false;
break;
default:
log.error(missingCaseError(metadataPlacement));
isInline = false;
break;
}
const {
@ -708,9 +786,11 @@ export class Message extends React.PureComponent<Props, State> {
hasText={Boolean(text)}
i18n={i18n}
id={id}
isInline={isInline}
isShowingImage={this.isShowingImage()}
isSticker={isStickerLike}
isTapToViewExpired={isTapToViewExpired}
onWidthMeasured={isInline ? this.updateMetadataWidth : undefined}
showMessageDetail={showMessageDetail}
status={status}
textPending={textPending}
@ -1378,8 +1458,11 @@ export class Message extends React.PureComponent<Props, State> {
openConversation,
status,
text,
textDirection,
textPending,
} = this.props;
const { metadataWidth } = this.state;
const isRTL = textDirection === TextDirection.RightToLeft;
// eslint-disable-next-line no-nested-ternary
const contents = deletedForEveryone
@ -1401,7 +1484,7 @@ export class Message extends React.PureComponent<Props, State> {
? 'module-message__text--error'
: null
)}
dir="auto"
dir={isRTL ? 'rtl' : undefined}
>
<MessageBodyReadMore
bodyRanges={bodyRanges}
@ -1415,6 +1498,10 @@ export class Message extends React.PureComponent<Props, State> {
text={contents || ''}
textPending={textPending}
/>
{!isRTL &&
this.getMetadataPlacement() === MetadataPlacement.InlineWithText && (
<MessageTextMetadataSpacer metadataWidth={metadataWidth} />
)}
</div>
);
}