meriadec
7 years ago
7 changed files with 175 additions and 53 deletions
@ -1,61 +1,143 @@ |
|||||
// @flow
|
// @flow
|
||||
import React, { Component } from 'react' |
|
||||
|
import React, { Component, Fragment } from 'react' |
||||
|
import { compose } from 'redux' |
||||
import { connect } from 'react-redux' |
import { connect } from 'react-redux' |
||||
import styled from 'styled-components' |
|
||||
import { createStructuredSelector } from 'reselect' |
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 { globalSyncStateSelector } from 'reducers/bridgeSync' |
||||
import { BridgeSyncConsumer } from 'bridge/BridgeSyncContext' |
import { BridgeSyncConsumer } from 'bridge/BridgeSyncContext' |
||||
import CounterValues from 'helpers/countervalues' |
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' |
import ItemContainer from './ItemContainer' |
||||
|
|
||||
const Activity = styled.div` |
const DISPLAY_SUCCESS_TIME = 2 * 1000 |
||||
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 mapStateToProps = createStructuredSelector({ globalSyncState: globalSyncStateSelector }) |
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() { |
render() { |
||||
const { pending, error, onClick } = this.props |
const { isPending, isError, t } = this.props |
||||
|
const { hasClicked, displaySuccess } = this.state |
||||
|
const isDisabled = hasClicked || displaySuccess || isError |
||||
return ( |
return ( |
||||
<ItemContainer cursor="pointer" relative onClick={onClick}> |
<ItemContainer isDisabled={isDisabled} onClick={isDisabled ? undefined : this.handleRefresh}> |
||||
<IconActivity size={16} /> |
<Rotating |
||||
<Activity pending={pending} error={error} /> |
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> |
</ItemContainer> |
||||
) |
) |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
const ActivityIndicator = ({ globalSyncState }: *) => ( |
const ActivityIndicator = ({ globalSyncState, t }: { globalSyncState: AsyncState, t: T }) => ( |
||||
<BridgeSyncConsumer> |
<BridgeSyncConsumer> |
||||
{bridgeSync => ( |
{bridgeSync => ( |
||||
<CounterValues.PollingConsumer> |
<CounterValues.PollingConsumer> |
||||
{cvPolling => ( |
{cvPolling => { |
||||
<ActivityIndicatorUI |
const isPending = cvPolling.pending || globalSyncState.pending |
||||
onClick={() => { |
const isError = cvPolling.error || globalSyncState.error |
||||
cvPolling.poll() |
return ( |
||||
bridgeSync.syncAll() |
<ActivityIndicatorInner |
||||
}} |
t={t} |
||||
pending={cvPolling.pending || globalSyncState.pending} |
isPending={isPending} |
||||
error={cvPolling.error || globalSyncState.error} |
isError={!!isError && !isPending} |
||||
/> |
onClick={() => { |
||||
)} |
cvPolling.poll() |
||||
|
bridgeSync.syncAll() |
||||
|
}} |
||||
|
/> |
||||
|
) |
||||
|
}} |
||||
</CounterValues.PollingConsumer> |
</CounterValues.PollingConsumer> |
||||
)} |
)} |
||||
</BridgeSyncConsumer> |
</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