diff options
-rw-r--r-- | packages/instant/src/components/time_counter.tsx | 64 | ||||
-rw-r--r-- | packages/instant/src/components/timed_progress_bar.tsx | 101 | ||||
-rw-r--r-- | packages/instant/src/constants.ts | 3 | ||||
-rw-r--r-- | packages/instant/src/containers/selected_asset_simulated_progress_bar.tsx | 18 | ||||
-rw-r--r-- | packages/instant/src/types.ts | 1 |
5 files changed, 180 insertions, 7 deletions
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<TimeCounterProps, TimeCounterState> { + 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 ( + <Flex justify="space-between"> + <Text>Est. Time ({timeUtil.secondsToHumanDescription(estimatedTimeSeconds)})</Text> + <Text>Time: {timeUtil.secondsToStopwatchTime(this.state.elapsedSeconds)}</Text> + </Flex> + ); + } + + 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<TimedProgressBarProps, TimedProgressBarState> { + private readonly _barRef = React.createRef<HTMLDivElement>(); + + 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 ( + <Container width="100%" backgroundColor={ColorOption.lightGrey} borderRadius="6px"> + <TimedProgress + fromWidth={this.state.animationStartingWidth} + timeMs={this.state.animationTimeMs} + maxWidthPercent={this.state.maxWidthPercent} + ref={this._barRef as any} + /> + </Container> + ); + } +} + +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 ( - <SimulatedProgressBar - startTimeUnix={progress.startTimeUnix} - expectedEndTimeUnix={progress.expectedEndTimeUnix} - ended={progress.ended} - /> + <Container padding="20px 20px 0px 20px" width="100%"> + <Container marginBottom="5px"> + <TimeCounter estimatedTimeMs={expectedTimeMs} ended={ended} key={progress.startTimeUnix} /> + </Container> + <TimedProgressBar expectedTimeMs={expectedTimeMs} ended={ended} key={progress.startTimeUnix} /> + </Container> ); } 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 { |