Use a hook for the ever-updating now

This commit is contained in:
Josh Perez 2022-03-08 14:11:11 -05:00 committed by GitHub
parent f8724e91da
commit 4e48d7792b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 34 additions and 78 deletions

View file

@ -163,7 +163,6 @@ export const StoryListItem = ({
<MessageTimestamp
i18n={i18n}
module="StoryListItem__info--timestamp"
now={Date.now()}
timestamp={timestamp}
/>
</>

View file

@ -233,7 +233,6 @@ export const StoryViewer = ({
<MessageTimestamp
i18n={i18n}
module="StoryViewer__meta--timestamp"
now={Date.now()}
timestamp={visibleStory.timestamp}
/>
<div className="StoryViewer__progress">

View file

@ -249,7 +249,6 @@ export const StoryViewsNRepliesModal = ({
<MessageTimestamp
i18n={i18n}
module="StoryViewsNRepliesModal__reply--timestamp"
now={Date.now()}
timestamp={reply.timestamp}
/>
</div>
@ -285,7 +284,6 @@ export const StoryViewsNRepliesModal = ({
<MessageTimestamp
i18n={i18n}
module="StoryViewsNRepliesModal__reply--timestamp"
now={Date.now()}
timestamp={reply.timestamp}
/>
</div>
@ -324,7 +322,6 @@ export const StoryViewsNRepliesModal = ({
<MessageTimestamp
i18n={i18n}
module="StoryViewsNRepliesModal__view--timestamp"
now={Date.now()}
timestamp={view.timestamp}
/>
</div>

View file

@ -32,13 +32,12 @@ type PropsHousekeeping = {
i18n: LocalizerType;
conversationId: string;
nextItem: undefined | TimelineItemType;
now: number;
};
type PropsType = CallingNotificationType & PropsActionsType & PropsHousekeeping;
export const CallingNotification: React.FC<PropsType> = React.memo(props => {
const { i18n, now } = props;
const { i18n } = props;
let timestamp: number;
let wasMissed = false;
@ -67,7 +66,6 @@ export const CallingNotification: React.FC<PropsType> = React.memo(props => {
<MessageTimestamp
direction="outgoing"
i18n={i18n}
now={now}
timestamp={timestamp}
withImageNoCaption={false}
withSticker={false}

View file

@ -19,7 +19,6 @@ const i18n = setupI18n('en', enMessages);
story.add('Default', () => (
<ChangeNumberNotification
now={Date.now()}
sender={getDefaultConversation()}
timestamp={1618894800000}
i18n={i18n}
@ -28,7 +27,6 @@ story.add('Default', () => (
story.add('Long name', () => (
<ChangeNumberNotification
now={Date.now()}
sender={getDefaultConversation({
firstName: '💅😇🖋'.repeat(50),
})}

View file

@ -18,13 +18,12 @@ export type PropsData = {
export type PropsHousekeeping = {
i18n: LocalizerType;
now: number;
};
export type Props = PropsData & PropsHousekeeping;
export const ChangeNumberNotification: React.FC<Props> = props => {
const { i18n, now, sender, timestamp } = props;
const { i18n, sender, timestamp } = props;
return (
<SystemMessage
@ -38,7 +37,7 @@ export const ChangeNumberNotification: React.FC<Props> = props => {
i18n={i18n}
/>
&nbsp;·&nbsp;
<MessageTimestamp i18n={i18n} now={now} timestamp={timestamp} />
<MessageTimestamp i18n={i18n} timestamp={timestamp} />
</>
}
icon="phone"

View file

@ -84,7 +84,6 @@ const MessageAudioContainer: React.FC<AudioAttachmentProps> = props => {
audio={audio}
computePeaks={computePeaks}
setActiveAudioID={(id, context) => setActive({ id, context })}
now={Date.now()}
onFirstPlayed={action('onFirstPlayed')}
activeAudioID={active.id}
activeAudioContext={active.context}
@ -134,7 +133,6 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
getPreferredBadge: overrideProps.getPreferredBadge || (() => undefined),
i18n,
id: text('id', overrideProps.id || ''),
now: Date.now(),
renderingContext: 'storybook',
interactionMode: overrideProps.interactionMode || 'keyboard',
isSticker: isBoolean(overrideProps.isSticker)

View file

@ -143,7 +143,6 @@ export type AudioAttachmentProps = {
expirationLength?: number;
expirationTimestamp?: number;
id: string;
now: number;
played: boolean;
showMessageDetail: (id: string) => void;
status?: MessageStatusType;
@ -240,7 +239,6 @@ export type PropsHousekeeping = {
disableScroll?: boolean;
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
now: number;
interactionMode: InteractionModeType;
item?: TimelineItemType;
nextItem?: TimelineItemType;
@ -709,7 +707,6 @@ export class Message extends React.PureComponent<Props, State> {
isTapToViewExpired,
status,
i18n,
now,
text,
textPending,
timestamp,
@ -732,7 +729,6 @@ export class Message extends React.PureComponent<Props, State> {
isShowingImage={this.isShowingImage()}
isSticker={isStickerLike}
isTapToViewExpired={isTapToViewExpired}
now={now}
onWidthMeasured={isInline ? this.updateMetadataWidth : undefined}
showMessageDetail={showMessageDetail}
status={status}
@ -786,7 +782,6 @@ export class Message extends React.PureComponent<Props, State> {
kickOffAttachmentDownload,
markAttachmentAsCorrupted,
markViewed,
now,
quote,
readStatus,
reducedMotion,
@ -922,7 +917,6 @@ export class Message extends React.PureComponent<Props, State> {
expirationLength,
expirationTimestamp,
id,
now,
played,
showMessageDetail,
status,

View file

@ -28,7 +28,6 @@ export type Props = {
expirationLength?: number;
expirationTimestamp?: number;
id: string;
now: number;
played: boolean;
showMessageDetail: (id: string) => void;
status?: MessageStatusType;
@ -160,7 +159,6 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
expirationLength,
expirationTimestamp,
id,
now,
played,
showMessageDetail,
status,
@ -543,7 +541,6 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
isShowingImage={false}
isSticker={false}
isTapToViewExpired={false}
now={now}
showMessageDetail={showMessageDetail}
status={status}
textPending={textPending}

View file

@ -23,8 +23,6 @@ import { SendStatus } from '../../messages/MessageSendState';
import { WidthBreakpoint } from '../_util';
import * as log from '../../logging/log';
import { formatDateTimeLong } from '../../util/timestamp';
import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
import { MINUTE } from '../../util/durations';
export type Contact = Pick<
ConversationType,
@ -101,20 +99,15 @@ export type PropsReduxActions = Pick<
export type ExternalProps = PropsData & PropsBackboneActions;
export type Props = PropsData & PropsBackboneActions & PropsReduxActions;
type State = { nowThatUpdatesEveryMinute: number };
const contactSortCollator = new Intl.Collator();
const _keyForError = (error: Error): string => {
return `${error.name}-${error.message}`;
};
export class MessageDetail extends React.Component<Props, State> {
override state = { nowThatUpdatesEveryMinute: Date.now() };
export class MessageDetail extends React.Component<Props> {
private readonly focusRef = React.createRef<HTMLDivElement>();
private readonly messageContainerRef = React.createRef<HTMLDivElement>();
private nowThatUpdatesEveryMinuteInterval?: ReturnType<typeof setInterval>;
public override componentDidMount(): void {
// When this component is created, it's initially not part of the DOM, and then it's
@ -124,14 +117,6 @@ export class MessageDetail extends React.Component<Props, State> {
this.focusRef.current.focus();
}
});
this.nowThatUpdatesEveryMinuteInterval = setInterval(() => {
this.setState({ nowThatUpdatesEveryMinute: Date.now() });
}, MINUTE);
}
public override componentWillUnmount(): void {
clearTimeoutIfNecessary(this.nowThatUpdatesEveryMinuteInterval);
}
public renderAvatar(contact: Contact): JSX.Element {
@ -314,7 +299,6 @@ export class MessageDetail extends React.Component<Props, State> {
showVisualAttachment,
theme,
} = this.props;
const { nowThatUpdatesEveryMinute } = this.state;
return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
@ -352,7 +336,6 @@ export class MessageDetail extends React.Component<Props, State> {
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
markViewed={markViewed}
messageExpanded={noop}
now={nowThatUpdatesEveryMinute}
openConversation={openConversation}
openLink={openLink}
reactToMessage={reactToMessage}

View file

@ -24,7 +24,6 @@ type PropsType = {
isShowingImage: boolean;
isSticker?: boolean;
isTapToViewExpired?: boolean;
now: number;
onWidthMeasured?: (width: number) => unknown;
showMessageDetail: (id: string) => void;
status?: MessageStatusType;
@ -44,7 +43,6 @@ export const MessageMetadata = ({
isShowingImage,
isSticker,
isTapToViewExpired,
now,
onWidthMeasured,
showMessageDetail,
status,
@ -106,7 +104,6 @@ export const MessageMetadata = ({
<MessageTimestamp
i18n={i18n}
timestamp={timestamp}
now={now}
direction={metadataDirection}
withImageNoCaption={withImageNoCaption}
withSticker={isSticker}

View file

@ -42,7 +42,6 @@ const times = (): Array<[string, number]> => [
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
i18n,
timestamp: overrideProps.timestamp || Date.now(),
now: Date.now(),
module: text('module', ''),
withImageNoCaption: boolean('withImageNoCaption', false),
withSticker: boolean('withSticker', false),

View file

@ -9,9 +9,9 @@ import { formatTime } from '../../util/timestamp';
import type { LocalizerType } from '../../types/Util';
import { Time } from '../Time';
import { useNowThatUpdatesEveryMinute } from '../../hooks/useNowThatUpdatesEveryMinute';
export type Props = {
now: number;
timestamp: number;
module?: string;
withImageNoCaption?: boolean;
@ -25,12 +25,12 @@ export function MessageTimestamp({
direction,
i18n,
module,
now,
timestamp,
withImageNoCaption,
withSticker,
withTapToViewExpired,
}: Readonly<Props>): ReactElement {
const now = useNowThatUpdatesEveryMinute();
const moduleName = module || 'module-timestamp';
return (

View file

@ -61,7 +61,6 @@ const defaultMessageProps: MessagesProps = {
getPreferredBadge: () => undefined,
i18n,
id: 'messageId',
now: Date.now(),
renderingContext: 'storybook',
interactionMode: 'keyboard',
isBlocked: false,

View file

@ -414,13 +414,11 @@ const renderItem = ({
containerElementRef,
containerWidthBreakpoint,
isOldestTimelineItem,
now,
}: {
messageId: string;
containerElementRef: React.RefObject<HTMLElement>;
containerWidthBreakpoint: WidthBreakpoint;
isOldestTimelineItem: boolean;
now: number;
}) => (
<TimelineItem
getPreferredBadge={() => undefined}
@ -432,7 +430,6 @@ const renderItem = ({
item={items[messageId]}
previousItem={undefined}
nextItem={undefined}
now={now}
i18n={i18n}
interactionMode="keyboard"
theme={ThemeType.light}

View file

@ -41,7 +41,6 @@ import {
scrollToBottom,
setScrollBottom,
} from '../../util/scrollUtil';
import { MINUTE } from '../../util/durations';
const AT_BOTTOM_THRESHOLD = 15;
const MIN_ROW_HEIGHT = 18;
@ -118,7 +117,6 @@ type PropsHousekeepingType = {
isOldestTimelineItem: boolean;
messageId: string;
nextMessageId: undefined | string;
now: number;
previousMessageId: undefined | string;
unreadIndicatorPlacement: undefined | UnreadIndicatorPlacement;
}) => JSX.Element;
@ -175,7 +173,6 @@ type StateType = {
hasRecentlyScrolled: boolean;
lastMeasuredWarningHeight: number;
newestFullyVisibleMessageId?: string;
nowThatUpdatesEveryMinute: number;
oldestPartiallyVisibleMessageId?: string;
widthBreakpoint: WidthBreakpoint;
};
@ -269,12 +266,10 @@ export class Timeline extends React.Component<
private hasRecentlyScrolledTimeout?: NodeJS.Timeout;
private delayedPeekTimeout?: NodeJS.Timeout;
private nowThatUpdatesEveryMinuteInterval?: NodeJS.Timeout;
override state: StateType = {
hasRecentlyScrolled: true,
hasDismissedDirectContactSpoofingWarning: false,
nowThatUpdatesEveryMinute: Date.now(),
// These may be swiftly overridden.
lastMeasuredWarningHeight: 0,
@ -512,17 +507,10 @@ export class Timeline extends React.Component<
const { id, peekGroupCallForTheFirstTime } = this.props;
peekGroupCallForTheFirstTime(id);
}, 500);
this.nowThatUpdatesEveryMinuteInterval = setInterval(() => {
this.setState({ nowThatUpdatesEveryMinute: Date.now() });
}, MINUTE);
}
public override componentWillUnmount(): void {
const {
delayedPeekTimeout,
nowThatUpdatesEveryMinuteInterval: nowThatUpdatesEveryMinuteTimeout,
} = this;
const { delayedPeekTimeout } = this;
window.unregisterForActive(this.markNewestFullyVisibleMessageRead);
@ -530,7 +518,6 @@ export class Timeline extends React.Component<
this.intersectionObserver?.disconnect();
clearTimeoutIfNecessary(delayedPeekTimeout);
clearTimeoutIfNecessary(nowThatUpdatesEveryMinuteTimeout);
}
public override getSnapshotBeforeUpdate(
@ -774,7 +761,6 @@ export class Timeline extends React.Component<
hasRecentlyScrolled,
lastMeasuredWarningHeight,
newestFullyVisibleMessageId,
nowThatUpdatesEveryMinute,
oldestPartiallyVisibleMessageId,
widthBreakpoint,
} = this.state;
@ -883,7 +869,6 @@ export class Timeline extends React.Component<
isOldestTimelineItem: haveOldest && itemIndex === 0,
messageId,
nextMessageId,
now: nowThatUpdatesEveryMinute,
previousMessageId,
unreadIndicatorPlacement,
})}

View file

@ -155,7 +155,6 @@ type PropsLocalType = {
theme: ThemeType;
previousItem: undefined | TimelineItemType;
nextItem: undefined | TimelineItemType;
now: number;
unreadIndicatorPlacement?: undefined | UnreadIndicatorPlacement;
};
@ -190,7 +189,6 @@ export class TimelineItem extends React.PureComponent<PropsType> {
i18n,
theme,
nextItem,
now,
previousItem,
renderContact,
renderUniversalTimerNotification,
@ -237,7 +235,6 @@ export class TimelineItem extends React.PureComponent<PropsType> {
conversationId={conversationId}
i18n={i18n}
nextItem={nextItem}
now={now}
returnToActiveCall={returnToActiveCall}
startCallingLobby={startCallingLobby}
{...item.data}

View file

@ -0,0 +1,27 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { EventEmitter } from 'events';
import { useEffect, useState } from 'react';
import { MINUTE } from '../util/durations';
const ev = new EventEmitter();
setInterval(() => ev.emit('tick'), MINUTE);
export function useNowThatUpdatesEveryMinute(): number {
const [now, setNow] = useState(Date.now());
useEffect(() => {
const updateNow = () => setNow(Date.now());
updateNow();
ev.on('tick', updateNow);
return () => {
ev.off('tick', updateNow);
};
}, []);
return now;
}

View file

@ -29,7 +29,6 @@ export type Props = {
expirationLength?: number;
expirationTimestamp?: number;
id: string;
now: number;
played: boolean;
showMessageDetail: (id: string) => void;
status?: MessageStatusType;

View file

@ -108,7 +108,6 @@ function renderItem({
isOldestTimelineItem,
messageId,
nextMessageId,
now,
previousMessageId,
unreadIndicatorPlacement,
}: {
@ -119,7 +118,6 @@ function renderItem({
isOldestTimelineItem: boolean;
messageId: string;
nextMessageId: undefined | string;
now: number;
previousMessageId: undefined | string;
unreadIndicatorPlacement: undefined | UnreadIndicatorPlacement;
}): JSX.Element {
@ -133,7 +131,6 @@ function renderItem({
messageId={messageId}
previousMessageId={previousMessageId}
nextMessageId={nextMessageId}
now={now}
renderEmojiPicker={renderEmojiPicker}
renderReactionPicker={renderReactionPicker}
renderAudioAttachment={renderAudioAttachment}

View file

@ -28,7 +28,6 @@ type ExternalProps = {
messageId: string;
nextMessageId: undefined | string;
previousMessageId: undefined | string;
now: number;
unreadIndicatorPlacement: undefined | UnreadIndicatorPlacement;
};
@ -48,7 +47,6 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
messageId,
nextMessageId,
previousMessageId,
now,
unreadIndicatorPlacement,
} = props;
@ -71,7 +69,6 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
item,
previousItem,
nextItem,
now,
id: messageId,
containerElementRef,
conversationId,