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 +++++++++++++++++++++ packages/instant/src/constants.ts | 3 +- .../selected_asset_simulated_progress_bar.tsx | 18 +++- packages/instant/src/types.ts | 1 - 5 files changed, 180 insertions(+), 7 deletions(-) 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') 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; + `; diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index 3b320ed36..9fdbf2830 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -8,5 +8,6 @@ export const DEFAULT_GAS_PRICE = GWEI_IN_WEI.mul(6); export const DEFAULT_ESTIMATED_TRANSACTION_TIME_MS = 2 * 60 * 1000; // 2 minutes export const ETH_GAS_STATION_API_BASE_URL = 'https://ethgasstation.info'; export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2'; -export const PROGRESS_TICK_INTERVAL_MS = 250; +export const PROGRESS_TICK_INTERVAL_MS = 250; // TODO: remove export const PROGRESS_STALL_AT_PERCENTAGE = 95; +export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200; diff --git a/packages/instant/src/containers/selected_asset_simulated_progress_bar.tsx b/packages/instant/src/containers/selected_asset_simulated_progress_bar.tsx index a7acc4cb7..a989407d5 100644 --- a/packages/instant/src/containers/selected_asset_simulated_progress_bar.tsx +++ b/packages/instant/src/containers/selected_asset_simulated_progress_bar.tsx @@ -3,10 +3,15 @@ import * as React from 'react'; import { connect } from 'react-redux'; import { SimulatedProgressBar } from '../components/simulated_progress_bar'; +import { TimedProgressBar } from '../components/timed_progress_bar'; +import { TimeCounter } from '../components/time_counter'; +import { Container } from '../components/ui'; import { State } from '../redux/reducer'; import { OrderProcessState, OrderState, SimulatedProgress } from '../types'; +// TODO: rename this +// TODO: delete SimulatedProgressBar code and anything else remaining interface SelectedAssetProgressComponentProps { buyOrderState: OrderState; } @@ -21,12 +26,15 @@ export const SelectedAssetSimulatedProgressComponent: React.StatelessComponent< buyOrderState.processState === OrderProcessState.FAILURE ) { const progress = buyOrderState.progress; + const ended = buyOrderState.processState !== OrderProcessState.PROCESSING; + const expectedTimeMs = progress.expectedEndTimeUnix - progress.startTimeUnix; return ( - + + + + + + ); } diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index 34893676d..288a6d111 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -19,7 +19,6 @@ export enum OrderProcessState { export interface SimulatedProgress { startTimeUnix: number; expectedEndTimeUnix: number; - ended: boolean; } interface OrderStatePreTx { -- cgit