From d137f8724a6003490fc54c055e38ba2f1593af26 Mon Sep 17 00:00:00 2001 From: George Lima Date: Wed, 20 Feb 2019 16:35:23 -0300 Subject: [PATCH] feat(status-pill): move status-pill to redux --- app/components/header.js | 8 +-- app/components/status-pill.js | 117 +++++++++++++++------------------ app/containers/app.js | 13 +++- app/containers/status-pill.js | 57 ++++++++++++++++ app/redux/modules/app.js | 33 ++++++++-- public/flow-coverage-badge.svg | 2 +- services/sync-status.js | 63 ------------------ 7 files changed, 154 insertions(+), 139 deletions(-) create mode 100644 app/containers/status-pill.js delete mode 100644 services/sync-status.js diff --git a/app/components/header.js b/app/components/header.js index 97b23ed..bfaa431 100644 --- a/app/components/header.js +++ b/app/components/header.js @@ -7,9 +7,7 @@ import { ZcashLogo } from './zcash-logo'; import { TextComponent } from './text'; import { Divider } from './divider'; import { RowComponent } from './row'; -import { StatusPill } from './status-pill'; - -import { withSyncStatus } from '../../services/sync-status'; +import { StatusPillContainer } from '../containers/status-pill'; const Wrapper = styled.div` height: ${props => props.theme.headerHeight}; @@ -62,8 +60,6 @@ type Props = { title: string, }; -const Status = withSyncStatus(StatusPill); - export const HeaderComponent = ({ title }: Props) => ( @@ -72,7 +68,7 @@ export const HeaderComponent = ({ title }: Props) => ( - <Status type='syncing' progress={0} /> + <StatusPillContainer /> </TitleRow> <Divider opacity={0.2} /> </TitleWrapper> diff --git a/app/components/status-pill.js b/app/components/status-pill.js index 33842cc..25c2d8d 100644 --- a/app/components/status-pill.js +++ b/app/components/status-pill.js @@ -2,11 +2,9 @@ import React, { PureComponent } from 'react'; import styled, { keyframes, withTheme } from 'styled-components'; -import eres from 'eres'; import { TextComponent } from './text'; -import rpc from '../../services/api'; import { DARK } from '../constants/themes'; import readyIconDark from '../assets/images/green_check_dark.png'; @@ -16,6 +14,8 @@ import syncIconLight from '../assets/images/sync_icon_light.png'; import errorIconDark from '../assets/images/error_icon_dark.png'; import errorIconLight from '../assets/images/error_icon_light.png'; +import type { MapDispatchToProps, MapStateToProps } from '../containers/status-pill'; + const rotate = keyframes` from { transform: rotate(0deg); @@ -55,94 +55,85 @@ const StatusPillLabel = styled(TextComponent)` type Props = { theme: AppTheme, -}; +} & MapStateToProps & + MapDispatchToProps; -type State = { - type: string, - icon: string, - progress: number, - isSyncing: boolean, -}; +const MINUTE_IN_MILI = 60000; -class Component extends PureComponent<Props, State> { +class Component extends PureComponent<Props> { timer: ?IntervalID = null; - constructor(props: Props) { - super(props); + componentDidMount() { + const { getBlockchainStatus } = this.props; - const { theme } = props; - - const syncIcon = theme.mode === DARK - ? syncIconDark - : syncIconLight; - - this.state = { - type: 'syncing', - icon: syncIcon, - progress: 0, - isSyncing: true, - }; + this.timer = setInterval(() => getBlockchainStatus(), 2000); } - componentDidMount() { - this.timer = setInterval(() => { - this.getBlockchainStatus(); - }, 2000); + componentDidUpdate(prevProps: Props) { + const { getBlockchainStatus, nodeSyncType } = this.props; + if (prevProps.nodeSyncType === 'syncing' && nodeSyncType === 'ready') { + // if the status is "ready", we can increase the interval to avoid useless rpc calls + this.cleanUpdateInterval(); + this.timer = setInterval(() => getBlockchainStatus(), MINUTE_IN_MILI); + } } componentWillUnmount() { + this.cleanUpdateInterval(); + } + + cleanUpdateInterval = () => { if (this.timer) { clearInterval(this.timer); this.timer = null; } - } + }; - getBlockchainStatus = async () => { + isSyncing = () => { + const { nodeSyncType } = this.props; + return nodeSyncType === 'syncing'; + }; + + getReadyIcon = () => { const { theme } = this.props; + return theme.mode === DARK ? readyIconDark : readyIconLight; + }; - const readyIcon = theme.mode === DARK - ? readyIconDark - : readyIconLight; - const errorIcon = theme.mode === DARK - ? errorIconDark - : errorIconLight; + getErrorIcon = () => { + const { theme } = this.props; + return theme.mode === DARK ? errorIconDark : errorIconLight; + }; - const [blockchainErr, blockchaininfo] = await eres(rpc.getblockchaininfo()); + getSyncingIcon = () => { + const { theme } = this.props; + return theme.mode === DARK ? syncIconDark : syncIconLight; + }; - if (blockchainErr || !blockchaininfo) return; + getIcon = () => { + const { nodeSyncType } = this.props; - const newProgress = blockchaininfo.verificationprogress * 100; - - this.setState({ - progress: newProgress, - ...(newProgress > 99.99 - ? { - type: 'ready', - icon: readyIcon, - isSyncing: false, - } - : {}), - }); - - if (blockchainErr) { - this.setState(() => ({ - type: 'error', - icon: errorIcon, - })); + switch (nodeSyncType) { + case 'syncing': + return this.getSyncingIcon(); + case 'ready': + return this.getReadyIcon(); + case 'error': + return this.getErrorIcon(); + default: + return null; } }; render() { - const { - type, icon, progress, isSyncing, - } = this.state; - const showPercent = isSyncing ? `(${progress.toFixed(2)}%)` : ''; - const typeText = type === 'ready' ? 'Synced' : type; + const icon = this.getIcon(); + const { nodeSyncType, nodeSyncProgress } = this.props; + const percent = nodeSyncType === 'syncing' ? `(${nodeSyncProgress.toFixed(2)}%)` : ''; + const typeText = nodeSyncType === 'ready' ? 'Synced' : nodeSyncType; return ( <Wrapper id='status-pill'> - <Icon src={icon} animated={isSyncing} /> - <StatusPillLabel value={`${typeText} ${showPercent}`} /> + {icon && <Icon src={icon} animated={this.isSyncing()} />} + <StatusPillLabel value={`${typeText} ${percent}`} /> </Wrapper> ); } diff --git a/app/containers/app.js b/app/containers/app.js index dd6f147..28381d2 100644 --- a/app/containers/app.js +++ b/app/containers/app.js @@ -8,12 +8,21 @@ import { LayoutComponent } from '../components/layout'; import type { Dispatch } from '../types/redux'; import type { AppState } from '../types/app-state'; -const mapStateToProps = ({ app }: AppState) => ({ +export type MapStateToProps = {| + isErrorModalVisible: boolean, + error: string | null, +|}; + +const mapStateToProps = ({ app }: AppState): MapStateToProps => ({ isErrorModalVisible: app.isErrorModalVisible, error: app.error, }); -const mapDispatchToProps = (dispatch: Dispatch) => ({ +export type MapDispatchToProps = {| + closeErrorModal: () => void, +|}; + +const mapDispatchToProps = (dispatch: Dispatch): MapDispatchToProps => ({ closeErrorModal: () => dispatch(closeErrorModal()), }); diff --git a/app/containers/status-pill.js b/app/containers/status-pill.js new file mode 100644 index 0000000..e98d50c --- /dev/null +++ b/app/containers/status-pill.js @@ -0,0 +1,57 @@ +// @flow + +import { connect } from 'react-redux'; +import eres from 'eres'; +import { BigNumber } from 'bignumber.js'; +import { updateNodeSyncStatus } from '../redux/modules/app'; + +import { StatusPill } from '../components/status-pill'; + +import rpc from '../../services/api'; + +import type { Dispatch } from '../types/redux'; +import type { AppState } from '../types/app-state'; + +export type MapStateToProps = {| + nodeSyncProgress: number, + nodeSyncType: 'ready' | 'syncing' | 'error', +|}; + +const mapStateToProps = ({ app }: AppState): MapStateToProps => ({ + nodeSyncProgress: app.nodeSyncProgress, + nodeSyncType: app.nodeSyncType, +}); + +export type MapDispatchToProps = {| + getBlockchainStatus: () => Promise<void>, +|}; + +const mapDispatchToProps = (dispatch: Dispatch): MapDispatchToProps => ({ + getBlockchainStatus: async () => { + const [blockchainErr, blockchaininfo] = await eres(rpc.getblockchaininfo()); + + if (blockchainErr || !blockchaininfo) { + return dispatch( + updateNodeSyncStatus({ + nodeSyncProgress: 0, + nodeSyncType: 'error', + }), + ); + } + + const newProgress = blockchaininfo.verificationprogress * 100; + + dispatch( + updateNodeSyncStatus({ + nodeSyncProgress: newProgress, + nodeSyncType: new BigNumber(newProgress).gt(99.99) ? 'ready' : 'syncing', + }), + ); + }, +}); + +// $FlowFixMe +export const StatusPillContainer = connect( + mapStateToProps, + mapDispatchToProps, +)(StatusPill); diff --git a/app/redux/modules/app.js b/app/redux/modules/app.js index f8d8495..f704ef1 100644 --- a/app/redux/modules/app.js +++ b/app/redux/modules/app.js @@ -2,9 +2,17 @@ import type { Action } from '../../types/redux'; +export type State = {| + isErrorModalVisible: boolean, + error: string | null, + nodeSyncProgress: number, + nodeSyncType: 'ready' | 'syncing' | 'error', +|}; + // Actions export const SHOW_ERROR_MODAL = 'SHOW_ERROR_MODAL'; export const HIDE_ERROR_MODAL = 'HIDE_ERROR_MODAL'; +export const UPDATE_NODE_SYNC_STATUS = 'UPDATE_NODE_SYNC_STATUS'; export const showErrorModal = ({ error }: { error: string }) => ({ type: SHOW_ERROR_MODAL, @@ -18,14 +26,25 @@ export const closeErrorModal = () => ({ payload: {}, }); -export type State = { - isErrorModalVisible: boolean, - error: string | null, -}; +export const updateNodeSyncStatus = ({ + nodeSyncProgress, + nodeSyncType, +}: { + nodeSyncProgress: number, + nodeSyncType: $PropertyType<State, 'nodeSyncType'>, +}) => ({ + type: UPDATE_NODE_SYNC_STATUS, + payload: { + nodeSyncProgress, + nodeSyncType, + }, +}); const initialState: State = { isErrorModalVisible: false, error: null, + nodeSyncProgress: 0, + nodeSyncType: 'syncing', }; // eslint-disable-next-line @@ -35,6 +54,12 @@ export default (state: State = initialState, action: Action) => { return { isErrorModalVisible: true, error: action.payload.error }; case HIDE_ERROR_MODAL: return { isErrorModalVisible: false, error: null }; + case UPDATE_NODE_SYNC_STATUS: + return { + ...state, + nodeSyncProgress: action.payload.nodeSyncProgress, + nodeSyncType: action.payload.nodeSyncType, + }; default: return state; } diff --git a/public/flow-coverage-badge.svg b/public/flow-coverage-badge.svg index f5dabfe..b36be62 100644 --- a/public/flow-coverage-badge.svg +++ b/public/flow-coverage-badge.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="125" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="125" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h91v20H0z"/><path fill="#4C1" d="M91 0h34v20H91z"/><path fill="url(#b)" d="M0 0h125v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,Geneva,sans-serif" font-size="11"><text x="45.5" y="15" fill="#010101" fill-opacity=".3">flow-coverage</text><text x="45.5" y="14">flow-coverage</text><text x="107" y="15" fill="#010101" fill-opacity=".3">89%</text><text x="107" y="14">89%</text></g></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" width="125" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><mask id="a"><rect width="125" height="20" rx="3" fill="#fff"/></mask><g mask="url(#a)"><path fill="#555" d="M0 0h91v20H0z"/><path fill="#4C1" d="M91 0h34v20H91z"/><path fill="url(#b)" d="M0 0h125v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,DejaVu Sans,Geneva,sans-serif" font-size="11"><text x="45.5" y="15" fill="#010101" fill-opacity=".3">flow-coverage</text><text x="45.5" y="14">flow-coverage</text><text x="107" y="15" fill="#010101" fill-opacity=".3">88%</text><text x="107" y="14">88%</text></g></svg> \ No newline at end of file diff --git a/services/sync-status.js b/services/sync-status.js deleted file mode 100644 index 435830f..0000000 --- a/services/sync-status.js +++ /dev/null @@ -1,63 +0,0 @@ -// @flow - -import React, { type ComponentType, Component } from 'react'; -import eres from 'eres'; - -import rpc from './api'; - -type Props = {}; - -type State = { - type?: 'syncing' | 'ready' | 'error', - progress: number, -}; - -/* eslint-disable max-len */ -export const withSyncStatus = <PassedProps: {}>( - WrappedComponent: ComponentType<PassedProps>, -): ComponentType<$Diff<PassedProps, Props>> => class extends Component<PassedProps, State> { - timer: ?IntervalID = null; - - state = { - type: 'syncing', - progress: 0, - }; - - componentDidMount() { - this.timer = setInterval(() => { - this.getBlockchainStatus(); - }, 2000); - } - - componentWillUnmount() { - if (this.timer) { - clearInterval(this.timer); - this.timer = null; - } - } - - getBlockchainStatus = async () => { - const [blockchainErr, blockchaininfo] = await eres( - rpc.getblockchaininfo(), - ); - - const newProgress = blockchaininfo.verificationprogress * 100; - - this.setState({ - progress: newProgress, - ...(newProgress > 99.99 ? { - type: 'ready', - } : {}), - }); - - if (blockchainErr) { - this.setState(() => ({ type: 'error' })); - } - } - - render() { - const { type, progress } = this.state; - - return <WrappedComponent {...this.props} {...this.state} type={type} progress={progress} />; - } - };