From ae84dac46382258e9a59b194f8aed7184d283e6f Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 31 Oct 2018 16:46:24 -0700 Subject: WIP of new timedprogressbar using CSS animations --- packages/instant/src/components/time_counter.tsx | 64 +++++++++++++ .../instant/src/components/timed_progress_bar.tsx | 101 +++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 packages/instant/src/components/time_counter.tsx create mode 100644 packages/instant/src/components/timed_progress_bar.tsx (limited to 'packages/instant/src/components') diff --git a/packages/instant/src/components/time_counter.tsx b/packages/instant/src/components/time_counter.tsx new file mode 100644 index 000000000..26deb82bd --- /dev/null +++ b/packages/instant/src/components/time_counter.tsx @@ -0,0 +1,64 @@ +import * as React from 'react'; + +import { timeUtil } from '../util/time'; + +import { Flex } from './ui/flex'; +import { Text } from './ui/text'; + +export interface TimeCounterProps { + estimatedTimeMs: number; + ended: boolean; +} +interface TimeCounterState { + elapsedSeconds: number; +} + +export class TimeCounter extends React.Component { + public state = { + elapsedSeconds: 0, + }; + private _timerId?: number; + + public componentDidMount(): void { + this._setupTimerBasedOnProps(); + } + + public componentWillUnmount(): void { + this._clearTimer(); + } + + public componentDidUpdate(prevProps: TimeCounterProps): void { + if (prevProps.ended !== this.props.ended) { + this._setupTimerBasedOnProps(); + } + } + + public render(): React.ReactNode { + const estimatedTimeSeconds = this.props.estimatedTimeMs / 1000; + return ( + + Est. Time ({timeUtil.secondsToHumanDescription(estimatedTimeSeconds)}) + Time: {timeUtil.secondsToStopwatchTime(this.state.elapsedSeconds)} + + ); + } + + private _setupTimerBasedOnProps(): void { + this.props.ended ? this._clearTimer() : this._newTimer(); + } + + private _newTimer(): void { + this._clearTimer(); + this._timerId = window.setInterval(() => { + this.setState({ + elapsedSeconds: this.state.elapsedSeconds + 1, + }); + }, 1000); + } + + private _clearTimer(): void { + if (this._timerId) { + window.clearInterval(this._timerId); + } + } +} diff --git a/packages/instant/src/components/timed_progress_bar.tsx b/packages/instant/src/components/timed_progress_bar.tsx new file mode 100644 index 000000000..7fdfe1a25 --- /dev/null +++ b/packages/instant/src/components/timed_progress_bar.tsx @@ -0,0 +1,101 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { Keyframes } from 'styled-components'; + +import { PROGRESS_FINISH_ANIMATION_TIME_MS, PROGRESS_STALL_AT_PERCENTAGE } from '../constants'; +import { ColorOption, keyframes, styled } from '../style/theme'; +import { timeUtil } from '../util/time'; + +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Text } from './ui/text'; + +export interface TimedProgressBarProps { + expectedTimeMs: number; + ended: boolean; +} + +interface TimedProgressBarState { + animationTimeMs: number; + animationStartingWidth: string; + maxWidthPercent: number; +} + +export const beginningState = (props: TimedProgressBarProps): TimedProgressBarState => { + return { + animationTimeMs: props.expectedTimeMs, + animationStartingWidth: '0%', + maxWidthPercent: PROGRESS_STALL_AT_PERCENTAGE, + }; +}; + +export class TimedProgressBar extends React.Component { + private readonly _barRef = React.createRef(); + + public constructor(props: TimedProgressBarProps) { + super(props); + this.state = beginningState(props); + } + + public componentDidUpdate(prevProps: TimedProgressBarProps, prevState: TimedProgressBarState): void { + if (prevProps.ended === false && this.props.ended === true) { + // Show nice animation going to end + // barRef current should always exist, but checking for typesafety + if (this._barRef.current) { + const curProgressWidth = this._barRef.current.offsetWidth; + this.setState({ + animationTimeMs: PROGRESS_FINISH_ANIMATION_TIME_MS, + animationStartingWidth: `${curProgressWidth}px`, + maxWidthPercent: 100, + }); + } + return; + } + + if (prevProps.expectedTimeMs !== this.props.expectedTimeMs || prevProps.ended !== this.props.ended) { + // things changed, get fresh state + this.setState(beginningState(this.props)); + } + } + + public render(): React.ReactNode { + return ( + + + + ); + } +} + +const expandingWidthKeyframes = (fromWidth: string, maxWidthPercent: number) => { + return keyframes` + from { + width: ${fromWidth} + } + to { + width: ${maxWidthPercent}%; + } + `; +}; + +interface TimedProgressProps { + timeMs: number; + fromWidth: string; + maxWidthPercent: number; +} +// TODO use PrimaryColor instead of black +export const TimedProgress = + styled.div < + TimedProgressProps > + ` + background-color: black; + border-radius: 6px; + height: 6px; + animation: ${props => expandingWidthKeyframes(props.fromWidth, props.maxWidthPercent)} + ${props => props.timeMs}ms linear 1 forwards; + `; -- cgit