feat(status-pill): move status-pill to redux

This commit is contained in:
George Lima 2019-02-20 16:35:23 -03:00
parent c9f39f4a2d
commit d137f8724a
7 changed files with 154 additions and 139 deletions

View File

@ -7,9 +7,7 @@ import { ZcashLogo } from './zcash-logo';
import { TextComponent } from './text'; import { TextComponent } from './text';
import { Divider } from './divider'; import { Divider } from './divider';
import { RowComponent } from './row'; import { RowComponent } from './row';
import { StatusPill } from './status-pill'; import { StatusPillContainer } from '../containers/status-pill';
import { withSyncStatus } from '../../services/sync-status';
const Wrapper = styled.div` const Wrapper = styled.div`
height: ${props => props.theme.headerHeight}; height: ${props => props.theme.headerHeight};
@ -62,8 +60,6 @@ type Props = {
title: string, title: string,
}; };
const Status = withSyncStatus(StatusPill);
export const HeaderComponent = ({ title }: Props) => ( export const HeaderComponent = ({ title }: Props) => (
<Wrapper id='header'> <Wrapper id='header'>
<LogoWrapper> <LogoWrapper>
@ -72,7 +68,7 @@ export const HeaderComponent = ({ title }: Props) => (
<TitleWrapper> <TitleWrapper>
<TitleRow alignItems='center' justifyContent='space-around'> <TitleRow alignItems='center' justifyContent='space-around'>
<Title value={title} /> <Title value={title} />
<Status type='syncing' progress={0} /> <StatusPillContainer />
</TitleRow> </TitleRow>
<Divider opacity={0.2} /> <Divider opacity={0.2} />
</TitleWrapper> </TitleWrapper>

View File

@ -2,11 +2,9 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import styled, { keyframes, withTheme } from 'styled-components'; import styled, { keyframes, withTheme } from 'styled-components';
import eres from 'eres';
import { TextComponent } from './text'; import { TextComponent } from './text';
import rpc from '../../services/api';
import { DARK } from '../constants/themes'; import { DARK } from '../constants/themes';
import readyIconDark from '../assets/images/green_check_dark.png'; 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 errorIconDark from '../assets/images/error_icon_dark.png';
import errorIconLight from '../assets/images/error_icon_light.png'; import errorIconLight from '../assets/images/error_icon_light.png';
import type { MapDispatchToProps, MapStateToProps } from '../containers/status-pill';
const rotate = keyframes` const rotate = keyframes`
from { from {
transform: rotate(0deg); transform: rotate(0deg);
@ -55,94 +55,85 @@ const StatusPillLabel = styled(TextComponent)`
type Props = { type Props = {
theme: AppTheme, theme: AppTheme,
}; } & MapStateToProps &
MapDispatchToProps;
type State = { const MINUTE_IN_MILI = 60000;
type: string,
icon: string,
progress: number,
isSyncing: boolean,
};
class Component extends PureComponent<Props, State> { class Component extends PureComponent<Props> {
timer: ?IntervalID = null; timer: ?IntervalID = null;
constructor(props: Props) { componentDidMount() {
super(props); const { getBlockchainStatus } = this.props;
const { theme } = props; this.timer = setInterval(() => getBlockchainStatus(), 2000);
const syncIcon = theme.mode === DARK
? syncIconDark
: syncIconLight;
this.state = {
type: 'syncing',
icon: syncIcon,
progress: 0,
isSyncing: true,
};
} }
componentDidMount() { componentDidUpdate(prevProps: Props) {
this.timer = setInterval(() => { const { getBlockchainStatus, nodeSyncType } = this.props;
this.getBlockchainStatus(); if (prevProps.nodeSyncType === 'syncing' && nodeSyncType === 'ready') {
}, 2000); // 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() { componentWillUnmount() {
this.cleanUpdateInterval();
}
cleanUpdateInterval = () => {
if (this.timer) { if (this.timer) {
clearInterval(this.timer); clearInterval(this.timer);
this.timer = null; this.timer = null;
} }
} };
getBlockchainStatus = async () => { isSyncing = () => {
const { nodeSyncType } = this.props;
return nodeSyncType === 'syncing';
};
getReadyIcon = () => {
const { theme } = this.props; const { theme } = this.props;
return theme.mode === DARK ? readyIconDark : readyIconLight;
};
const readyIcon = theme.mode === DARK getErrorIcon = () => {
? readyIconDark const { theme } = this.props;
: readyIconLight; return theme.mode === DARK ? errorIconDark : errorIconLight;
const errorIcon = 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; switch (nodeSyncType) {
case 'syncing':
this.setState({ return this.getSyncingIcon();
progress: newProgress, case 'ready':
...(newProgress > 99.99 return this.getReadyIcon();
? { case 'error':
type: 'ready', return this.getErrorIcon();
icon: readyIcon, default:
isSyncing: false, return null;
}
: {}),
});
if (blockchainErr) {
this.setState(() => ({
type: 'error',
icon: errorIcon,
}));
} }
}; };
render() { render() {
const { const icon = this.getIcon();
type, icon, progress, isSyncing, const { nodeSyncType, nodeSyncProgress } = this.props;
} = this.state; const percent = nodeSyncType === 'syncing' ? `(${nodeSyncProgress.toFixed(2)}%)` : '';
const showPercent = isSyncing ? `(${progress.toFixed(2)}%)` : ''; const typeText = nodeSyncType === 'ready' ? 'Synced' : nodeSyncType;
const typeText = type === 'ready' ? 'Synced' : type;
return ( return (
<Wrapper id='status-pill'> <Wrapper id='status-pill'>
<Icon src={icon} animated={isSyncing} /> {icon && <Icon src={icon} animated={this.isSyncing()} />}
<StatusPillLabel value={`${typeText} ${showPercent}`} /> <StatusPillLabel value={`${typeText} ${percent}`} />
</Wrapper> </Wrapper>
); );
} }

View File

@ -8,12 +8,21 @@ import { LayoutComponent } from '../components/layout';
import type { Dispatch } from '../types/redux'; import type { Dispatch } from '../types/redux';
import type { AppState } from '../types/app-state'; 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, isErrorModalVisible: app.isErrorModalVisible,
error: app.error, error: app.error,
}); });
const mapDispatchToProps = (dispatch: Dispatch) => ({ export type MapDispatchToProps = {|
closeErrorModal: () => void,
|};
const mapDispatchToProps = (dispatch: Dispatch): MapDispatchToProps => ({
closeErrorModal: () => dispatch(closeErrorModal()), closeErrorModal: () => dispatch(closeErrorModal()),
}); });

View File

@ -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);

View File

@ -2,9 +2,17 @@
import type { Action } from '../../types/redux'; import type { Action } from '../../types/redux';
export type State = {|
isErrorModalVisible: boolean,
error: string | null,
nodeSyncProgress: number,
nodeSyncType: 'ready' | 'syncing' | 'error',
|};
// Actions // Actions
export const SHOW_ERROR_MODAL = 'SHOW_ERROR_MODAL'; export const SHOW_ERROR_MODAL = 'SHOW_ERROR_MODAL';
export const HIDE_ERROR_MODAL = 'HIDE_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 }) => ({ export const showErrorModal = ({ error }: { error: string }) => ({
type: SHOW_ERROR_MODAL, type: SHOW_ERROR_MODAL,
@ -18,14 +26,25 @@ export const closeErrorModal = () => ({
payload: {}, payload: {},
}); });
export type State = { export const updateNodeSyncStatus = ({
isErrorModalVisible: boolean, nodeSyncProgress,
error: string | null, nodeSyncType,
}; }: {
nodeSyncProgress: number,
nodeSyncType: $PropertyType<State, 'nodeSyncType'>,
}) => ({
type: UPDATE_NODE_SYNC_STATUS,
payload: {
nodeSyncProgress,
nodeSyncType,
},
});
const initialState: State = { const initialState: State = {
isErrorModalVisible: false, isErrorModalVisible: false,
error: null, error: null,
nodeSyncProgress: 0,
nodeSyncType: 'syncing',
}; };
// eslint-disable-next-line // eslint-disable-next-line
@ -35,6 +54,12 @@ export default (state: State = initialState, action: Action) => {
return { isErrorModalVisible: true, error: action.payload.error }; return { isErrorModalVisible: true, error: action.payload.error };
case HIDE_ERROR_MODAL: case HIDE_ERROR_MODAL:
return { isErrorModalVisible: false, error: null }; return { isErrorModalVisible: false, error: null };
case UPDATE_NODE_SYNC_STATUS:
return {
...state,
nodeSyncProgress: action.payload.nodeSyncProgress,
nodeSyncType: action.payload.nodeSyncType,
};
default: default:
return state; return state;
} }

View File

@ -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> <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>

Before

Width:  |  Height:  |  Size: 745 B

After

Width:  |  Height:  |  Size: 745 B

View File

@ -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} />;
}
};