meriadec
7 years ago
7 changed files with 175 additions and 53 deletions
@ -1,61 +1,143 @@ |
|||
// @flow
|
|||
import React, { Component } from 'react' |
|||
|
|||
import React, { Component, Fragment } from 'react' |
|||
import { compose } from 'redux' |
|||
import { connect } from 'react-redux' |
|||
import styled from 'styled-components' |
|||
import { createStructuredSelector } from 'reselect' |
|||
import { translate } from 'react-i18next' |
|||
|
|||
import type { T } from 'types/common' |
|||
import type { AsyncState } from 'reducers/bridgeSync' |
|||
|
|||
import { globalSyncStateSelector } from 'reducers/bridgeSync' |
|||
import { BridgeSyncConsumer } from 'bridge/BridgeSyncContext' |
|||
import CounterValues from 'helpers/countervalues' |
|||
import IconActivity from 'icons/Activity' |
|||
|
|||
import { Rotating } from 'components/base/Spinner' |
|||
import Box from 'components/base/Box' |
|||
import IconRefresh from 'icons/Refresh' |
|||
import IconExclamationCircle from 'icons/ExclamationCircle' |
|||
import IconCheckCircle from 'icons/CheckCircle' |
|||
import ItemContainer from './ItemContainer' |
|||
|
|||
const Activity = styled.div` |
|||
background: ${p => |
|||
p.pending |
|||
? p.theme.colors.wallet |
|||
: p.error |
|||
? p.theme.colors.alertRed |
|||
: p.theme.colors.positiveGreen}; |
|||
border-radius: 50%; |
|||
bottom: 23px; |
|||
position: absolute; |
|||
left: -5px; |
|||
width: 12px; |
|||
height: 12px; |
|||
` |
|||
const DISPLAY_SUCCESS_TIME = 2 * 1000 |
|||
|
|||
const mapStateToProps = createStructuredSelector({ globalSyncState: globalSyncStateSelector }) |
|||
|
|||
class ActivityIndicatorUI extends Component<*> { |
|||
type Props = { isPending: boolean, isError: boolean, onClick: void => void, t: T } |
|||
type State = { hasClicked: boolean, displaySuccess: boolean } |
|||
|
|||
class ActivityIndicatorInner extends Component<Props, State> { |
|||
state = { |
|||
hasClicked: false, |
|||
displaySuccess: false, |
|||
} |
|||
|
|||
_timeout = null |
|||
|
|||
static getDerivedStateFromProps(nextProps: Props, prevState: State) { |
|||
if (prevState.hasClicked && !nextProps.isPending) { |
|||
return { hasClicked: false, displaySuccess: !nextProps.isError } |
|||
} |
|||
|
|||
return null |
|||
} |
|||
|
|||
componentDidUpdate(prevProps: Props, prevState: State) { |
|||
if (!prevState.displaySuccess && this.state.displaySuccess) { |
|||
if (this._timeout) { |
|||
clearTimeout(this._timeout) |
|||
} |
|||
this._timeout = setTimeout( |
|||
() => this.setState({ displaySuccess: false }), |
|||
DISPLAY_SUCCESS_TIME, |
|||
) |
|||
} |
|||
} |
|||
|
|||
handleRefresh = () => { |
|||
const { onClick } = this.props |
|||
this.setState({ hasClicked: true }) |
|||
onClick() |
|||
} |
|||
|
|||
render() { |
|||
const { pending, error, onClick } = this.props |
|||
const { isPending, isError, t } = this.props |
|||
const { hasClicked, displaySuccess } = this.state |
|||
const isDisabled = hasClicked || displaySuccess || isError |
|||
return ( |
|||
<ItemContainer cursor="pointer" relative onClick={onClick}> |
|||
<IconActivity size={16} /> |
|||
<Activity pending={pending} error={error} /> |
|||
<ItemContainer isDisabled={isDisabled} onClick={isDisabled ? undefined : this.handleRefresh}> |
|||
<Rotating |
|||
size={16} |
|||
isRotating={isPending && hasClicked} |
|||
color={isError ? 'alertRed' : displaySuccess ? 'positiveGreen' : undefined} |
|||
> |
|||
{isError ? ( |
|||
<IconExclamationCircle size={16} /> |
|||
) : displaySuccess ? ( |
|||
<IconCheckCircle size={16} /> |
|||
) : ( |
|||
<IconRefresh size={16} /> |
|||
)} |
|||
</Rotating> |
|||
{(displaySuccess || isError || (isPending && hasClicked)) && ( |
|||
<Box |
|||
ml={2} |
|||
ff="Open Sans|SemiBold" |
|||
color={isError ? 'alertRed' : undefined} |
|||
fontSize={4} |
|||
horizontal |
|||
align="center" |
|||
> |
|||
{displaySuccess ? ( |
|||
t('common:sync.upToDate') |
|||
) : isError ? ( |
|||
<Fragment> |
|||
<Box>{t('common:sync.error')}</Box> |
|||
<Box |
|||
ml={2} |
|||
cursor="pointer" |
|||
style={{ textDecoration: 'underline', pointerEvents: 'all' }} |
|||
onClick={this.handleRefresh} |
|||
> |
|||
{t('common:sync.refresh')} |
|||
</Box> |
|||
</Fragment> |
|||
) : ( |
|||
t('common:sync.syncing') |
|||
)} |
|||
</Box> |
|||
)} |
|||
</ItemContainer> |
|||
) |
|||
} |
|||
} |
|||
|
|||
const ActivityIndicator = ({ globalSyncState }: *) => ( |
|||
const ActivityIndicator = ({ globalSyncState, t }: { globalSyncState: AsyncState, t: T }) => ( |
|||
<BridgeSyncConsumer> |
|||
{bridgeSync => ( |
|||
<CounterValues.PollingConsumer> |
|||
{cvPolling => ( |
|||
<ActivityIndicatorUI |
|||
onClick={() => { |
|||
cvPolling.poll() |
|||
bridgeSync.syncAll() |
|||
}} |
|||
pending={cvPolling.pending || globalSyncState.pending} |
|||
error={cvPolling.error || globalSyncState.error} |
|||
/> |
|||
)} |
|||
{cvPolling => { |
|||
const isPending = cvPolling.pending || globalSyncState.pending |
|||
const isError = cvPolling.error || globalSyncState.error |
|||
return ( |
|||
<ActivityIndicatorInner |
|||
t={t} |
|||
isPending={isPending} |
|||
isError={!!isError && !isPending} |
|||
onClick={() => { |
|||
cvPolling.poll() |
|||
bridgeSync.syncAll() |
|||
}} |
|||
/> |
|||
) |
|||
}} |
|||
</CounterValues.PollingConsumer> |
|||
)} |
|||
</BridgeSyncConsumer> |
|||
) |
|||
|
|||
export default connect(mapStateToProps)(ActivityIndicator) |
|||
export default compose( |
|||
translate(), |
|||
connect(mapStateToProps), |
|||
)(ActivityIndicator) |
|||
|
@ -0,0 +1,19 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
|
|||
const path = ( |
|||
<g transform="translate(374.01 -80.021)"> |
|||
<path |
|||
fill="currentColor" |
|||
d="m-360.1 81.906a0.67329 0.67329 0 0 1 1.3465 0v3.5909a0.67329 0.67329 0 0 1-0.67325 0.67325h-3.5909a0.6733 0.6733 0 0 1 0-1.3466h2.9177zm-11.82 6.6575v2.9176a0.67332 0.67332 0 1 1-1.3466 0v-3.5909a0.67329 0.67329 0 0 1 0.67326-0.67325h3.5908a0.67329 0.67329 0 0 1 0 1.3466zm1.4633-3.441a0.67329 0.67329 0 0 1-1.2696-0.44884 6.0597 6.0597 0 0 1 9.9827-2.2775l2.7775 2.6097a0.67329 0.67329 0 1 1-0.92204 0.98128l-2.792-2.6241a4.7131 4.7131 0 0 0-7.7769 1.7596zm-2.5981 3.2587a0.67329 0.67329 0 1 1 0.92203-0.98116l2.7919 2.624a4.7131 4.7131 0 0 0 7.777-1.7595 0.67329 0.67329 0 1 1 1.2696 0.44884 6.0597 6.0597 0 0 1-9.9826 2.2775l-2.7786-2.6097z" |
|||
strokeWidth=".89773" |
|||
/> |
|||
</g> |
|||
) |
|||
|
|||
export default ({ size, ...p }: { size: number }) => ( |
|||
<svg viewBox="0 0 16 13.344" height={size} width={size} {...p}> |
|||
{path} |
|||
</svg> |
|||
) |
Loading…
Reference in new issue