You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
5.3 KiB
149 lines
5.3 KiB
/*
|
|
* Copyright 2015, Yahoo Inc.
|
|
* Copyrights licensed under the New BSD License.
|
|
* See the accompanying LICENSE file for terms.
|
|
*/
|
|
import * as React from 'react';
|
|
import { Context } from './injectIntl';
|
|
import { invariantIntlContext } from '../utils';
|
|
import { invariant } from '@formatjs/intl-utils';
|
|
const MINUTE = 60;
|
|
const HOUR = 60 * 60;
|
|
const DAY = 60 * 60 * 24;
|
|
function selectUnit(seconds) {
|
|
const absValue = Math.abs(seconds);
|
|
if (absValue < MINUTE) {
|
|
return 'second';
|
|
}
|
|
if (absValue < HOUR) {
|
|
return 'minute';
|
|
}
|
|
if (absValue < DAY) {
|
|
return 'hour';
|
|
}
|
|
return 'day';
|
|
}
|
|
function getDurationInSeconds(unit) {
|
|
switch (unit) {
|
|
case 'second':
|
|
return 1;
|
|
case 'minute':
|
|
return MINUTE;
|
|
case 'hour':
|
|
return HOUR;
|
|
default:
|
|
return DAY;
|
|
}
|
|
}
|
|
function valueToSeconds(value, unit) {
|
|
if (!value) {
|
|
return 0;
|
|
}
|
|
switch (unit) {
|
|
case 'second':
|
|
return value;
|
|
case 'minute':
|
|
return value * MINUTE;
|
|
default:
|
|
return value * HOUR;
|
|
}
|
|
}
|
|
const INCREMENTABLE_UNITS = ['second', 'minute', 'hour'];
|
|
function canIncrement(unit = 'second') {
|
|
return INCREMENTABLE_UNITS.includes(unit);
|
|
}
|
|
export class FormattedRelativeTime extends React.PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
// Public for testing
|
|
this._updateTimer = null;
|
|
this.state = {
|
|
prevUnit: this.props.unit,
|
|
prevValue: this.props.value,
|
|
currentValueInSeconds: canIncrement(this.props.unit)
|
|
? valueToSeconds(this.props.value, this.props.unit)
|
|
: 0,
|
|
};
|
|
invariant(!props.updateIntervalInSeconds ||
|
|
!!(props.updateIntervalInSeconds && canIncrement(props.unit)), 'Cannot schedule update with unit longer than hour');
|
|
}
|
|
scheduleNextUpdate({ updateIntervalInSeconds, unit }, { currentValueInSeconds }) {
|
|
clearTimeout(this._updateTimer);
|
|
this._updateTimer = null;
|
|
// If there's no interval and we cannot increment this unit, do nothing
|
|
if (!updateIntervalInSeconds || !canIncrement(unit)) {
|
|
return;
|
|
}
|
|
// Figure out the next interesting time
|
|
const nextValueInSeconds = currentValueInSeconds - updateIntervalInSeconds;
|
|
const nextUnit = selectUnit(nextValueInSeconds);
|
|
// We've reached the max auto incrementable unit, don't schedule another update
|
|
if (nextUnit === 'day') {
|
|
return;
|
|
}
|
|
const unitDuration = getDurationInSeconds(nextUnit);
|
|
const remainder = nextValueInSeconds % unitDuration;
|
|
const prevInterestingValueInSeconds = nextValueInSeconds - remainder;
|
|
const nextInterestingValueInSeconds = prevInterestingValueInSeconds >= currentValueInSeconds
|
|
? prevInterestingValueInSeconds - unitDuration
|
|
: prevInterestingValueInSeconds;
|
|
const delayInSeconds = Math.abs(nextInterestingValueInSeconds - currentValueInSeconds);
|
|
this._updateTimer = setTimeout(() => this.setState({
|
|
currentValueInSeconds: nextInterestingValueInSeconds,
|
|
}), delayInSeconds * 1e3);
|
|
}
|
|
componentDidMount() {
|
|
this.scheduleNextUpdate(this.props, this.state);
|
|
}
|
|
componentDidUpdate() {
|
|
this.scheduleNextUpdate(this.props, this.state);
|
|
}
|
|
componentWillUnmount() {
|
|
clearTimeout(this._updateTimer);
|
|
this._updateTimer = null;
|
|
}
|
|
static getDerivedStateFromProps(props, state) {
|
|
if (props.unit !== state.prevUnit || props.value !== state.prevValue) {
|
|
return {
|
|
prevValue: props.value,
|
|
prevUnit: props.unit,
|
|
currentValueInSeconds: canIncrement(props.unit)
|
|
? valueToSeconds(props.value, props.unit)
|
|
: 0,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
render() {
|
|
return (React.createElement(Context.Consumer, null, (intl) => {
|
|
invariantIntlContext(intl);
|
|
const { formatRelativeTime, textComponent: Text } = intl;
|
|
const { children, value, unit, updateIntervalInSeconds } = this.props;
|
|
const { currentValueInSeconds } = this.state;
|
|
let currentValue = value || 0;
|
|
let currentUnit = unit;
|
|
if (canIncrement(unit) &&
|
|
typeof currentValueInSeconds === 'number' &&
|
|
updateIntervalInSeconds) {
|
|
currentUnit = selectUnit(currentValueInSeconds);
|
|
const unitDuration = getDurationInSeconds(currentUnit);
|
|
currentValue = Math.round(currentValueInSeconds / unitDuration);
|
|
}
|
|
const formattedRelativeTime = formatRelativeTime(currentValue, currentUnit, Object.assign({}, this.props));
|
|
if (typeof children === 'function') {
|
|
return children(formattedRelativeTime);
|
|
}
|
|
if (Text) {
|
|
return React.createElement(Text, null, formattedRelativeTime);
|
|
}
|
|
return formattedRelativeTime;
|
|
}));
|
|
}
|
|
}
|
|
FormattedRelativeTime.displayName = 'FormattedRelativeTime';
|
|
FormattedRelativeTime.defaultProps = {
|
|
value: 0,
|
|
unit: 'second',
|
|
};
|
|
export default FormattedRelativeTime;
|