From ab9252e06f5cc8325c79ba33b3cd8723f785ea41 Mon Sep 17 00:00:00 2001 From: Rafael Date: Mon, 3 Jan 2022 09:41:00 -0300 Subject: [PATCH] Refactor dateTime component and getTimeAgoStr function to prevent displaying comments as 'in a few seconds' --- ui/component/dateTime/view.jsx | 90 ++++++------------- ui/component/fileSubtitle/view.jsx | 2 +- ui/component/postViewer/view.jsx | 2 +- .../transactionListTableItem/view.jsx | 4 +- ui/component/youtubeBadge/view.jsx | 6 +- ui/util/formatAriaLabel.js | 4 +- ui/util/time.js | 30 ++++++- 7 files changed, 63 insertions(+), 75 deletions(-) diff --git a/ui/component/dateTime/view.jsx b/ui/component/dateTime/view.jsx index b8e25210f..62140862d 100644 --- a/ui/component/dateTime/view.jsx +++ b/ui/component/dateTime/view.jsx @@ -1,71 +1,38 @@ // @flow -import React from 'react'; +import { getTimeAgoStr } from 'util/time'; import moment from 'moment'; +import React from 'react'; const DEFAULT_MIN_UPDATE_DELTA_MS = 60 * 1000; -type Props = { - date?: any, - timeAgo?: boolean, - formatOptions?: {}, - show?: string, - clock24h?: boolean, - minUpdateDeltaMs?: number, -}; - type State = { lastRenderTime: Date, }; +type Props = { + clock24h?: boolean, + date?: any, + minUpdateDeltaMs?: number, + type?: string, + timeAgo?: boolean, +}; + class DateTime extends React.Component { constructor(props: Props) { super(props); + this.state = { 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 { if ( moment(this.props.date).diff(moment(nextProps.date)) !== 0 || this.props.clock24h !== nextProps.clock24h || this.props.timeAgo !== nextProps.timeAgo || this.props.minUpdateDeltaMs !== nextProps.minUpdateDeltaMs || - this.props.show !== nextProps.show + this.props.type !== nextProps.type ) { return true; } @@ -86,32 +53,25 @@ class DateTime extends React.Component { componentDidUpdate() { const { timeAgo } = this.props; - if (timeAgo) { - this.setState({ lastRenderTime: new Date() }); - } + + if (timeAgo) this.setState({ lastRenderTime: new Date() }); } render() { - const { date, timeAgo, show, clock24h } = this.props; + const { clock24h, date, type, timeAgo } = this.props; - let clockFormat = 'hh:mm A'; - if (clock24h) { - clockFormat = 'HH:mm'; - } - - if (timeAgo) { - if (!date) { - return null; - } - - return {DateTime.getTimeAgoStr(date)}; - } + const clockFormat = clock24h ? 'HH:mm' : 'hh:mm A'; return ( - - {date && show === DateTime.SHOW_DATE && moment(date).format('MMMM Do, YYYY')} - {date && show === DateTime.SHOW_TIME && moment(date).format(clockFormat)} - {!date && '...'} + + {date + ? timeAgo + ? getTimeAgoStr(date) + : moment(date).format(type === 'date' ? 'MMMM Do, YYYY' : clockFormat) + : '...'} ); } diff --git a/ui/component/fileSubtitle/view.jsx b/ui/component/fileSubtitle/view.jsx index ab10ac7cd..b853920f4 100644 --- a/ui/component/fileSubtitle/view.jsx +++ b/ui/component/fileSubtitle/view.jsx @@ -19,7 +19,7 @@ function FileSubtitle(props: Props) {
{livestream && isLive && } - {!livestream && } + {!livestream && }
diff --git a/ui/component/postViewer/view.jsx b/ui/component/postViewer/view.jsx index 84eb408b5..35793655e 100644 --- a/ui/component/postViewer/view.jsx +++ b/ui/component/postViewer/view.jsx @@ -54,7 +54,7 @@ function PostViewer(props: Props) {
- + diff --git a/ui/component/transactionListTableItem/view.jsx b/ui/component/transactionListTableItem/view.jsx index c222dee36..0098165c8 100644 --- a/ui/component/transactionListTableItem/view.jsx +++ b/ui/component/transactionListTableItem/view.jsx @@ -123,9 +123,9 @@ class TransactionListTableItem extends React.PureComponent { {timestamp ? (
- +
- +
) : ( diff --git a/ui/component/youtubeBadge/view.jsx b/ui/component/youtubeBadge/view.jsx index bffa60ad0..6aac914f6 100644 --- a/ui/component/youtubeBadge/view.jsx +++ b/ui/component/youtubeBadge/view.jsx @@ -1,6 +1,6 @@ // @flow import * as React from 'react'; -import DateTime from 'component/dateTime'; +import { getTimeAgoStr } from 'util/time'; import { Lbryio } from 'lbryinc'; type Props = { @@ -14,7 +14,7 @@ export default function YoutubeBadge(props: Props) { React.useEffect(() => { 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) { setIsVerified(true); setLastYtSyncDate(response.last_synced); @@ -35,7 +35,7 @@ export default function YoutubeBadge(props: Props) {
- {lastYtSyncDate && __('Last checked %time_ago%', { time_ago: DateTime.getTimeAgoStr(lastYtSyncDate) })} + {lastYtSyncDate && __('Last checked %time_ago%', { time_ago: getTimeAgoStr(lastYtSyncDate) })}
diff --git a/ui/util/formatAriaLabel.js b/ui/util/formatAriaLabel.js index b684d9372..b21dab3df 100644 --- a/ui/util/formatAriaLabel.js +++ b/ui/util/formatAriaLabel.js @@ -1,8 +1,8 @@ -import DateTime from 'component/dateTime'; +import { getTimeAgoStr } from 'util/time'; export function formatClaimPreviewTitle(title, channelTitle, date, mediaDuration) { // Aria-label value for claim preview - let ariaDate = date ? DateTime.getTimeAgoStr(date) : null; + let ariaDate = date ? getTimeAgoStr(date, true) : null; let ariaLabelData = title; if (mediaDuration) { diff --git a/ui/util/time.js b/ui/util/time.js index ca7127d1a..73c0b14eb 100644 --- a/ui/util/time.js +++ b/ui/util/time.js @@ -1,4 +1,5 @@ // @flow +import moment from 'moment'; export function secondsToHms(seconds: number) { seconds = Math.floor(seconds); @@ -7,7 +8,7 @@ export function secondsToHms(seconds: number) { var seconds = seconds % 60; 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) .join(':'); } @@ -32,3 +33,30 @@ export function hmsToSeconds(str: string) { 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 }); +}