nicolium: migrate relative timestamp component to functional

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-02-26 01:04:50 +01:00
parent 557ffdbda5
commit 5f68d58730

View File

@ -1,5 +1,5 @@
import React from 'react';
import { injectIntl, defineMessages, IntlShape, FormatDateOptions } from 'react-intl';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { defineMessages, useIntl, FormatDateOptions } from 'react-intl';
import Text, { IText } from './ui/text';
@ -78,12 +78,83 @@ const getUnitDelay = (units: string) => {
}
};
const timeAgoString = (intl: IntlShape, date: Date, now: number, year: number) => {
interface IRelativeTimestamp extends IText {
timestamp: string;
year?: number;
futureDate?: boolean;
}
/** Displays a timestamp compared to the current time, eg "1m" for one minute ago. */
const RelativeTimestamp: React.FC<IRelativeTimestamp> = ({
timestamp,
year = new Date().getFullYear(),
futureDate,
theme = 'inherit',
...props
}) => {
const intl = useIntl();
const [now, setNow] = useState(Date.now);
const timerRef = useRef<NodeJS.Timeout>(undefined);
const scheduleNextUpdate = useCallback(() => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
const delta = new Date(timestamp).getTime() - now;
const unitDelay = getUnitDelay(selectUnits(delta));
const unitRemainder = Math.abs(delta % unitDelay);
const updateInterval = 1000 * 10;
const delay =
delta < 0
? Math.max(updateInterval, unitDelay - unitRemainder)
: Math.max(updateInterval, unitRemainder);
timerRef.current = setTimeout(() => {
setNow(Date.now());
}, delay);
}, [timestamp, now]);
useEffect(() => {
setNow(Date.now());
}, [timestamp]);
useEffect(() => {
scheduleNextUpdate();
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
};
}, [scheduleNextUpdate]);
const date = new Date(timestamp);
const delta = now - date.getTime();
let relativeTime;
let relativeTime: string;
if (futureDate) {
const futureDelta = date.getTime() - now;
if (delta < 10 * SECOND) {
if (futureDelta < 10 * SECOND) {
relativeTime = intl.formatMessage(messages.momentsRemaining);
} else if (futureDelta < MINUTE) {
relativeTime = intl.formatMessage(messages.secondsRemaining, {
number: Math.floor(futureDelta / SECOND),
});
} else if (futureDelta < HOUR) {
relativeTime = intl.formatMessage(messages.minutesRemaining, {
number: Math.floor(futureDelta / MINUTE),
});
} else if (futureDelta < DAY) {
relativeTime = intl.formatMessage(messages.hoursRemaining, {
number: Math.floor(futureDelta / HOUR),
});
} else {
relativeTime = intl.formatMessage(messages.daysRemaining, {
number: Math.floor(futureDelta / DAY),
});
}
} else if (delta < 10 * SECOND) {
relativeTime = intl.formatMessage(messages.justNow);
} else if (delta < 7 * DAY) {
if (delta < MINUTE) {
@ -101,130 +172,11 @@ const timeAgoString = (intl: IntlShape, date: Date, now: number, year: number) =
relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' });
}
return relativeTime;
return (
<Text {...props} theme={theme} tag='time' title={intl.formatDate(date, dateFormatOptions)}>
{relativeTime}
</Text>
);
};
const timeRemainingString = (intl: IntlShape, date: Date, now: number) => {
const delta = date.getTime() - now;
let relativeTime;
if (delta < 10 * SECOND) {
relativeTime = intl.formatMessage(messages.momentsRemaining);
} else if (delta < MINUTE) {
relativeTime = intl.formatMessage(messages.secondsRemaining, {
number: Math.floor(delta / SECOND),
});
} else if (delta < HOUR) {
relativeTime = intl.formatMessage(messages.minutesRemaining, {
number: Math.floor(delta / MINUTE),
});
} else if (delta < DAY) {
relativeTime = intl.formatMessage(messages.hoursRemaining, {
number: Math.floor(delta / HOUR),
});
} else {
relativeTime = intl.formatMessage(messages.daysRemaining, { number: Math.floor(delta / DAY) });
}
return relativeTime;
};
interface RelativeTimestampProps extends IText {
intl: IntlShape;
timestamp: string;
year?: number;
futureDate?: boolean;
}
interface RelativeTimestampState {
now: number;
}
/** Displays a timestamp compared to the current time, eg "1m" for one minute ago. */
const RelativeTimestamp = injectIntl(
class RelativeTimestamp extends React.Component<RelativeTimestampProps, RelativeTimestampState> {
_timer: NodeJS.Timeout | undefined;
state = {
now: Date.now(),
};
static defaultProps = {
year: new Date().getFullYear(),
theme: 'inherit' as const,
};
shouldComponentUpdate(nextProps: RelativeTimestampProps, nextState: RelativeTimestampState) {
// As of right now the locale doesn't change without a new page load,
// but we might as well check in case that ever changes.
return (
this.props.timestamp !== nextProps.timestamp ||
this.props.intl.locale !== nextProps.intl.locale ||
this.state.now !== nextState.now
);
}
UNSAFE_componentWillReceiveProps(prevProps: RelativeTimestampProps) {
if (this.props.timestamp !== prevProps.timestamp) {
this.setState({ now: Date.now() });
}
}
componentDidMount() {
this._scheduleNextUpdate();
}
UNSAFE_componentWillUpdate() {
this._scheduleNextUpdate();
}
componentWillUnmount() {
if (this._timer) {
clearTimeout(this._timer);
}
}
_scheduleNextUpdate() {
if (this._timer) {
clearTimeout(this._timer);
}
const { timestamp } = this.props;
const delta = new Date(timestamp).getTime() - this.state.now;
const unitDelay = getUnitDelay(selectUnits(delta));
const unitRemainder = Math.abs(delta % unitDelay);
const updateInterval = 1000 * 10;
const delay =
delta < 0
? Math.max(updateInterval, unitDelay - unitRemainder)
: Math.max(updateInterval, unitRemainder);
this._timer = setTimeout(() => {
this.setState({ now: Date.now() });
}, delay);
}
render() {
const { timestamp, intl, year, futureDate, theme, ...textProps } = this.props;
const date = new Date(timestamp);
const relativeTime = futureDate
? timeRemainingString(intl, date, this.state.now)
: timeAgoString(intl, date, this.state.now, year!);
return (
<Text
{...textProps}
theme={theme}
tag='time'
title={intl.formatDate(date, dateFormatOptions)}
>
{relativeTime}
</Text>
);
}
},
);
export { dateFormatOptions, RelativeTimestamp as default };