zepio/app/components/status-pill.js

270 lines
6.6 KiB
JavaScript

// @flow
import React, { PureComponent } from 'react';
import styled, { keyframes, withTheme } from 'styled-components';
import { TextComponent } from './text';
import { DARK } from '../constants/themes';
import { NODE_SYNC_TYPES } from '../constants/node-sync-types';
import readyIconDark from '../assets/images/green_check_dark.png';
import readyIconLight from '../assets/images/green_check_light.png';
import syncIconDark from '../assets/images/sync_icon_dark.png';
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);
}
to {
transform: rotate(360deg);
}
`;
const Wrapper = styled.div`
display: flex;
flex-direction: row;
align-items: flex-end;
justify-content: center;
height: 100%;
`;
const StatusWrapper = styled.div`
align-items: center;
display: flex;
background: ${props => props.theme.colors.statusPillBg};
border: 1px solid ${props => props.theme.colors.statusPillBorder}
border-radius: 27px;
padding: 8px 16px;
position: relative;
`;
const Icon = styled.img`
width: 12px;
height: 12px;
margin-right: 8px;
animation: 2s linear infinite;
animation-name: ${/** $FlowFixMe */
(props: PropsWithTheme<{ animated: boolean }>) => (props.animated ? rotate : 'none')};
`;
const StatusPillLabel = styled(TextComponent)`
color: ${props => props.theme.colors.statusPillLabel};
font-weight: ${props => String(props.theme.fontWeight.bold)};
text-transform: uppercase;
font-size: 10px;
padding-top: 1px;
user-select: none;
`;
const Tooltip = styled.div`
background: ${props => props.theme.colors.walletAddressTooltipBg};
position: absolute;
bottom: -35px;
right: 5px;
padding: 6px 10px;
border-radius: ${props => props.theme.boxBorderRadius};
z-index: 100;
white-space: nowrap;
&:after {
bottom: 100%;
left: 90%;
border: solid transparent;
content: ' ';
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
&:after {
border-color: transparent;
border-bottom-color: ${props => props.theme.colors.walletAddressTooltipBg};
border-width: 5px;
margin-left: -5px;
}
`;
const TooltipText = styled(TextComponent)`
color: ${props => props.theme.colors.walletAddressTooltip};
font-size: 10px;
font-weight: 700;
`;
const RefetchingLabel = styled.p`
color: ${props => props.theme.colors.sidebarItem};
font-size: 10px;
letter-spacing: 0.5px;
text-transform: uppercase;
font-family: ${props => props.theme.fontFamily};
margin-right: 10px;
`;
type Props = {
theme: AppTheme,
} & MapStateToProps &
MapDispatchToProps;
type State = {
showTooltip: boolean,
};
const INTERVAL_AFTER_READY = 60000;
const INTERVAL_BEFORE_READY = 10000;
class Component extends PureComponent<Props, State> {
timer: ?IntervalID = null;
requestOnTheFly: boolean = false;
constructor(props: Props) {
super(props);
this.state = {
showTooltip: false,
};
}
componentDidMount() {
this.timer = setInterval(() => this.updateStatus(), INTERVAL_BEFORE_READY);
}
componentDidUpdate(prevProps: Props) {
const { nodeSyncType } = this.props;
if (
prevProps.nodeSyncType === NODE_SYNC_TYPES.SYNCING
&& nodeSyncType === NODE_SYNC_TYPES.READY
) {
// if the status is "ready", we can increase the interval to avoid useless rpc calls
this.cleanUpdateInterval();
this.timer = setInterval(() => this.updateStatus(), INTERVAL_AFTER_READY);
}
}
componentWillUnmount() {
this.cleanUpdateInterval();
}
updateStatus = () => {
if (this.requestOnTheFly) return;
this.requestOnTheFly = true;
const { getBlockchainStatus } = this.props;
getBlockchainStatus()
.then(() => {
this.requestOnTheFly = false;
})
.catch(() => {
this.requestOnTheFly = false;
});
};
cleanUpdateInterval = () => {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
};
isSyncing = () => {
const { nodeSyncType } = this.props;
return nodeSyncType === NODE_SYNC_TYPES.SYNCING;
};
getReadyIcon = () => {
const { theme } = this.props;
return theme.mode === DARK ? readyIconDark : readyIconLight;
};
getErrorIcon = () => {
const { theme } = this.props;
return theme.mode === DARK ? errorIconDark : errorIconLight;
};
getSyncingIcon = () => {
const { theme } = this.props;
return theme.mode === DARK ? syncIconDark : syncIconLight;
};
getIcon = () => {
const { nodeSyncType } = this.props;
switch (nodeSyncType) {
case NODE_SYNC_TYPES.SYNCING:
return this.getSyncingIcon();
case NODE_SYNC_TYPES.READY:
return this.getReadyIcon();
default:
return this.getErrorIcon();
}
};
getStatusText = () => {
const { nodeSyncType } = this.props;
switch (nodeSyncType) {
case NODE_SYNC_TYPES.SYNCING:
return "Syncing blockchain data. You may not send funds or see latest transactions until it's synced.";
case NODE_SYNC_TYPES.READY:
return 'Your node is synced.';
default:
return 'There was an error. Try restarting Zepio.';
}
};
getLabel = () => {
const { nodeSyncType } = this.props;
switch (nodeSyncType) {
case NODE_SYNC_TYPES.SYNCING:
return 'syncing';
case NODE_SYNC_TYPES.READY:
return 'ready';
default:
return 'error';
}
};
render() {
const icon = this.getIcon();
const { nodeSyncType, nodeSyncProgress, isRefetching } = this.props;
const { showTooltip } = this.state;
const percent = nodeSyncType && nodeSyncType === NODE_SYNC_TYPES.SYNCING
? `(${nodeSyncProgress.toFixed(2)}%)`
: '';
return (
<Wrapper>
{isRefetching && <RefetchingLabel>Refetching...</RefetchingLabel>}
{
// eslint-disable-next-line
<StatusWrapper
onMouseOver={() => this.setState({ showTooltip: true })}
onMouseOut={() => this.setState({ showTooltip: false })}
id='status-pill'
>
{showTooltip && (
<Tooltip>
<TooltipText value={this.getStatusText()} />
</Tooltip>
)}
{icon && <Icon src={icon} animated={this.isSyncing()} />}
<StatusPillLabel value={`${this.getLabel()} ${percent}`} />
</StatusWrapper>
}
</Wrapper>
);
}
}
export const StatusPill = withTheme(Component);