Use a hook for the ever-updating now
This commit is contained in:
parent
f8724e91da
commit
4e48d7792b
21 changed files with 34 additions and 78 deletions
|
@ -163,7 +163,6 @@ export const StoryListItem = ({
|
|||
<MessageTimestamp
|
||||
i18n={i18n}
|
||||
module="StoryListItem__info--timestamp"
|
||||
now={Date.now()}
|
||||
timestamp={timestamp}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -233,7 +233,6 @@ export const StoryViewer = ({
|
|||
<MessageTimestamp
|
||||
i18n={i18n}
|
||||
module="StoryViewer__meta--timestamp"
|
||||
now={Date.now()}
|
||||
timestamp={visibleStory.timestamp}
|
||||
/>
|
||||
<div className="StoryViewer__progress">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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),
|
||||
})}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
·
|
||||
<MessageTimestamp i18n={i18n} now={now} timestamp={timestamp} />
|
||||
<MessageTimestamp i18n={i18n} timestamp={timestamp} />
|
||||
</>
|
||||
}
|
||||
icon="phone"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -61,7 +61,6 @@ const defaultMessageProps: MessagesProps = {
|
|||
getPreferredBadge: () => undefined,
|
||||
i18n,
|
||||
id: 'messageId',
|
||||
now: Date.now(),
|
||||
renderingContext: 'storybook',
|
||||
interactionMode: 'keyboard',
|
||||
isBlocked: false,
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
})}
|
||||
|
|
|
@ -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}
|
||||
|
|
27
ts/hooks/useNowThatUpdatesEveryMinute.ts
Normal file
27
ts/hooks/useNowThatUpdatesEveryMinute.ts
Normal 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;
|
||||
}
|
|
@ -29,7 +29,6 @@ export type Props = {
|
|||
expirationLength?: number;
|
||||
expirationTimestamp?: number;
|
||||
id: string;
|
||||
now: number;
|
||||
played: boolean;
|
||||
showMessageDetail: (id: string) => void;
|
||||
status?: MessageStatusType;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue