Refactor dateTime component and getTimeAgoStr function to prevent displaying comments as 'in a few seconds'
This commit is contained in:
parent
2c8ad2b89a
commit
ab9252e06f
7 changed files with 63 additions and 75 deletions
|
@ -1,71 +1,38 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import { getTimeAgoStr } from 'util/time';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
const DEFAULT_MIN_UPDATE_DELTA_MS = 60 * 1000;
|
const DEFAULT_MIN_UPDATE_DELTA_MS = 60 * 1000;
|
||||||
|
|
||||||
type Props = {
|
|
||||||
date?: any,
|
|
||||||
timeAgo?: boolean,
|
|
||||||
formatOptions?: {},
|
|
||||||
show?: string,
|
|
||||||
clock24h?: boolean,
|
|
||||||
minUpdateDeltaMs?: number,
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
lastRenderTime: Date,
|
lastRenderTime: Date,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
clock24h?: boolean,
|
||||||
|
date?: any,
|
||||||
|
minUpdateDeltaMs?: number,
|
||||||
|
type?: string,
|
||||||
|
timeAgo?: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
class DateTime extends React.Component<Props, State> {
|
class DateTime extends React.Component<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
lastRenderTime: new Date(),
|
lastRenderTime: new Date(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static SHOW_DATE = 'date';
|
|
||||||
static SHOW_TIME = 'time';
|
|
||||||
|
|
||||||
static getTimeAgoStr(date: any) {
|
|
||||||
const suffixList = ['years', 'months', 'days', 'hours', 'minutes', 'seconds', ''];
|
|
||||||
var duration = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < suffixList.length; ++i) {
|
|
||||||
// moment() is very liberal with it's rounding.
|
|
||||||
// Always round down dates for better youtube parity.
|
|
||||||
duration = Math.floor(moment().diff(date, suffixList[i]));
|
|
||||||
if (duration > 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i === suffixList.length) {
|
|
||||||
// This should never happen since we are handling up to 'seconds' now,
|
|
||||||
// but display the English version just in case it does.
|
|
||||||
return moment(date).from(moment());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip off the 's' for the singular suffix, construct the string ID,
|
|
||||||
// then load the localized version.
|
|
||||||
const suffix = duration === 1 ? suffixList[i].substr(0, suffixList[i].length - 1) : suffixList[i];
|
|
||||||
let strId = '%duration% ' + suffix + ' ago';
|
|
||||||
|
|
||||||
if (!suffix) {
|
|
||||||
strId = 'Just now';
|
|
||||||
}
|
|
||||||
|
|
||||||
return __(strId, { duration });
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: Props): boolean {
|
shouldComponentUpdate(nextProps: Props): boolean {
|
||||||
if (
|
if (
|
||||||
moment(this.props.date).diff(moment(nextProps.date)) !== 0 ||
|
moment(this.props.date).diff(moment(nextProps.date)) !== 0 ||
|
||||||
this.props.clock24h !== nextProps.clock24h ||
|
this.props.clock24h !== nextProps.clock24h ||
|
||||||
this.props.timeAgo !== nextProps.timeAgo ||
|
this.props.timeAgo !== nextProps.timeAgo ||
|
||||||
this.props.minUpdateDeltaMs !== nextProps.minUpdateDeltaMs ||
|
this.props.minUpdateDeltaMs !== nextProps.minUpdateDeltaMs ||
|
||||||
this.props.show !== nextProps.show
|
this.props.type !== nextProps.type
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -86,32 +53,25 @@ class DateTime extends React.Component<Props, State> {
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const { timeAgo } = this.props;
|
const { timeAgo } = this.props;
|
||||||
if (timeAgo) {
|
|
||||||
this.setState({ lastRenderTime: new Date() });
|
if (timeAgo) this.setState({ lastRenderTime: new Date() });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { date, timeAgo, show, clock24h } = this.props;
|
const { clock24h, date, type, timeAgo } = this.props;
|
||||||
|
|
||||||
let clockFormat = 'hh:mm A';
|
const clockFormat = clock24h ? 'HH:mm' : 'hh:mm A';
|
||||||
if (clock24h) {
|
|
||||||
clockFormat = 'HH:mm';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeAgo) {
|
|
||||||
if (!date) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <span className="date_time" title={moment(date).format(`MMMM Do, YYYY ${clockFormat}`)}>{DateTime.getTimeAgoStr(date)}</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="date_time">
|
<span
|
||||||
{date && show === DateTime.SHOW_DATE && moment(date).format('MMMM Do, YYYY')}
|
className="date_time"
|
||||||
{date && show === DateTime.SHOW_TIME && moment(date).format(clockFormat)}
|
title={timeAgo && moment(date).format(`MMMM Do, YYYY ${clockFormat}`)}
|
||||||
{!date && '...'}
|
>
|
||||||
|
{date
|
||||||
|
? timeAgo
|
||||||
|
? getTimeAgoStr(date)
|
||||||
|
: moment(date).format(type === 'date' ? 'MMMM Do, YYYY' : clockFormat)
|
||||||
|
: '...'}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ function FileSubtitle(props: Props) {
|
||||||
<div className="media__subtitle--between">
|
<div className="media__subtitle--between">
|
||||||
<div className="file__viewdate">
|
<div className="file__viewdate">
|
||||||
{livestream && isLive && <LivestreamDateTime uri={uri} />}
|
{livestream && isLive && <LivestreamDateTime uri={uri} />}
|
||||||
{!livestream && <DateTime uri={uri} show={DateTime.SHOW_DATE} />}
|
{!livestream && <DateTime uri={uri} type="date" />}
|
||||||
<FileViewCount uri={uri} livestream={livestream} isLive={isLive} />
|
<FileViewCount uri={uri} livestream={livestream} isLive={isLive} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ function PostViewer(props: Props) {
|
||||||
<div className="post">
|
<div className="post">
|
||||||
<FileTitle uri={uri} className="post__title">
|
<FileTitle uri={uri} className="post__title">
|
||||||
<span className="post__date">
|
<span className="post__date">
|
||||||
<DateTime uri={uri} show={DateTime.SHOW_DATE} />
|
<DateTime uri={uri} type="date" />
|
||||||
</span>
|
</span>
|
||||||
</FileTitle>
|
</FileTitle>
|
||||||
|
|
||||||
|
|
|
@ -123,9 +123,9 @@ class TransactionListTableItem extends React.PureComponent<Props, State> {
|
||||||
<td className="table__date">
|
<td className="table__date">
|
||||||
{timestamp ? (
|
{timestamp ? (
|
||||||
<div>
|
<div>
|
||||||
<DateTime date={date} show={DateTime.SHOW_DATE} formatOptions={dateFormat} />
|
<DateTime date={date} type="date" formatOptions={dateFormat} />
|
||||||
<div className="table__item-label">
|
<div className="table__item-label">
|
||||||
<DateTime date={date} show={DateTime.SHOW_TIME} />
|
<DateTime date={date} type="time" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import DateTime from 'component/dateTime';
|
import { getTimeAgoStr } from 'util/time';
|
||||||
import { Lbryio } from 'lbryinc';
|
import { Lbryio } from 'lbryinc';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -14,7 +14,7 @@ export default function YoutubeBadge(props: Props) {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (channelClaimId) {
|
if (channelClaimId) {
|
||||||
Lbryio.call('yt', 'get_youtuber', { channel_claim_id: channelClaimId }).then(response => {
|
Lbryio.call('yt', 'get_youtuber', { channel_claim_id: channelClaimId }).then((response) => {
|
||||||
if (response.is_verified_youtuber) {
|
if (response.is_verified_youtuber) {
|
||||||
setIsVerified(true);
|
setIsVerified(true);
|
||||||
setLastYtSyncDate(response.last_synced);
|
setLastYtSyncDate(response.last_synced);
|
||||||
|
@ -35,7 +35,7 @@ export default function YoutubeBadge(props: Props) {
|
||||||
<label>{__('Official YouTube Creator')}</label>
|
<label>{__('Official YouTube Creator')}</label>
|
||||||
<div className="media__info-text">
|
<div className="media__info-text">
|
||||||
<div className="media__info-text media__info-text--constrained">
|
<div className="media__info-text media__info-text--constrained">
|
||||||
{lastYtSyncDate && __('Last checked %time_ago%', { time_ago: DateTime.getTimeAgoStr(lastYtSyncDate) })}
|
{lastYtSyncDate && __('Last checked %time_ago%', { time_ago: getTimeAgoStr(lastYtSyncDate) })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import DateTime from 'component/dateTime';
|
import { getTimeAgoStr } from 'util/time';
|
||||||
|
|
||||||
export function formatClaimPreviewTitle(title, channelTitle, date, mediaDuration) {
|
export function formatClaimPreviewTitle(title, channelTitle, date, mediaDuration) {
|
||||||
// Aria-label value for claim preview
|
// Aria-label value for claim preview
|
||||||
let ariaDate = date ? DateTime.getTimeAgoStr(date) : null;
|
let ariaDate = date ? getTimeAgoStr(date, true) : null;
|
||||||
let ariaLabelData = title;
|
let ariaLabelData = title;
|
||||||
|
|
||||||
if (mediaDuration) {
|
if (mediaDuration) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
export function secondsToHms(seconds: number) {
|
export function secondsToHms(seconds: number) {
|
||||||
seconds = Math.floor(seconds);
|
seconds = Math.floor(seconds);
|
||||||
|
@ -7,7 +8,7 @@ export function secondsToHms(seconds: number) {
|
||||||
var seconds = seconds % 60;
|
var seconds = seconds % 60;
|
||||||
|
|
||||||
return [hours, minutes, seconds]
|
return [hours, minutes, seconds]
|
||||||
.map(v => (v < 10 ? '0' + v : v))
|
.map((v) => (v < 10 ? '0' + v : v))
|
||||||
.filter((v, i) => v !== '00' || i > 0)
|
.filter((v, i) => v !== '00' || i > 0)
|
||||||
.join(':');
|
.join(':');
|
||||||
}
|
}
|
||||||
|
@ -32,3 +33,30 @@ export function hmsToSeconds(str: string) {
|
||||||
|
|
||||||
return seconds;
|
return seconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only intended use of future dates is for claims, in case of scheduled
|
||||||
|
// publishes or livestreams, used in util/formatAriaLabel
|
||||||
|
export function getTimeAgoStr(date: any, showFutureDate?: boolean) {
|
||||||
|
const suffixList = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];
|
||||||
|
let duration = 0;
|
||||||
|
let suffix = '';
|
||||||
|
|
||||||
|
suffixList.some((s) => {
|
||||||
|
// moment() is very liberal with it's rounding.
|
||||||
|
// Always round down dates for better youtube parity.
|
||||||
|
duration = Math.floor(moment().diff(date, s));
|
||||||
|
suffix = s;
|
||||||
|
|
||||||
|
return duration > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// negative duration === it's a future date from now
|
||||||
|
if (duration < 0 && showFutureDate) return __(moment(date).from(moment()));
|
||||||
|
|
||||||
|
// Strip off the ending 's' for the singular suffix
|
||||||
|
if (duration === 1) suffix = suffix.replace(/s$/g, '');
|
||||||
|
|
||||||
|
const str = duration <= 0 ? 'Just now' : '%duration% ' + suffix + ' ago';
|
||||||
|
|
||||||
|
return __(str, { duration });
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue