Browse Source

Merge pull request #474 from meriadec/design/sync-state-feedbacks

Feedbacks integration on sync state
master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
926792cfd3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 120
      src/components/TopBar/ActivityIndicator.js
  2. 9
      src/components/TopBar/ItemContainer.js
  3. 1
      src/components/TopBar/index.js
  4. 2
      src/config/constants.js

120
src/components/TopBar/ActivityIndicator.js

@ -20,39 +20,48 @@ import IconExclamationCircle from 'icons/ExclamationCircle'
import IconCheckCircle from 'icons/CheckCircle' import IconCheckCircle from 'icons/CheckCircle'
import ItemContainer from './ItemContainer' import ItemContainer from './ItemContainer'
const DISPLAY_SUCCESS_TIME = 2 * 1000
const mapStateToProps = createStructuredSelector({ globalSyncState: globalSyncStateSelector }) const mapStateToProps = createStructuredSelector({ globalSyncState: globalSyncStateSelector })
type Props = { isPending: boolean, isError: boolean, onClick: void => void, t: T } type Props = {
type State = { hasClicked: boolean, displaySuccess: boolean } // FIXME: eslint should see that it is used in static method
isGlobalSyncStatePending: boolean, // eslint-disable-line react/no-unused-prop-types
isPending: boolean,
isError: boolean,
onClick: void => void,
t: T,
}
type State = {
hasClicked: boolean,
isGlobalSyncStatePending: boolean,
isFirstSync: boolean,
}
class ActivityIndicatorInner extends Component<Props, State> { class ActivityIndicatorInner extends Component<Props, State> {
state = { state = {
hasClicked: false, hasClicked: false,
displaySuccess: false, isFirstSync: true,
}
_timeout = null // FIXME: eslint should see that it is used in static method
isGlobalSyncStatePending: false, // eslint-disable-line react/no-unused-state
}
static getDerivedStateFromProps(nextProps: Props, prevState: State) { static getDerivedStateFromProps(nextProps: Props, prevState: State) {
if (prevState.hasClicked && !nextProps.isPending) { const nextState = {
return { hasClicked: false, displaySuccess: !nextProps.isError } ...prevState,
isGlobalSyncStatePending: nextProps.isGlobalSyncStatePending,
} }
return null if (prevState.hasClicked && !nextProps.isGlobalSyncStatePending) {
} nextState.hasClicked = false
}
componentDidUpdate(prevProps: Props, prevState: State) { if (prevState.isGlobalSyncStatePending && !nextProps.isGlobalSyncStatePending) {
if (!prevState.displaySuccess && this.state.displaySuccess) { nextState.isFirstSync = false
if (this._timeout) {
clearTimeout(this._timeout)
}
this._timeout = setTimeout(
() => this.setState({ displaySuccess: false }),
DISPLAY_SUCCESS_TIME,
)
} }
return nextState
} }
handleRefresh = () => { handleRefresh = () => {
@ -63,51 +72,51 @@ class ActivityIndicatorInner extends Component<Props, State> {
render() { render() {
const { isPending, isError, t } = this.props const { isPending, isError, t } = this.props
const { hasClicked, displaySuccess } = this.state const { hasClicked, isFirstSync } = this.state
const isDisabled = hasClicked || displaySuccess || isError const isDisabled = hasClicked || isError
const isRotating = isPending && (hasClicked || isFirstSync)
return ( return (
<ItemContainer isDisabled={isDisabled} onClick={isDisabled ? undefined : this.handleRefresh}> <ItemContainer isDisabled={isDisabled} onClick={isDisabled ? undefined : this.handleRefresh}>
<Rotating <Rotating
size={16} size={16}
isRotating={isPending && hasClicked} isRotating={isRotating}
color={isError ? 'alertRed' : displaySuccess ? 'positiveGreen' : undefined} color={isError ? 'alertRed' : isRotating ? undefined : 'positiveGreen'}
> >
{isError ? ( {isError ? (
<IconExclamationCircle size={16} /> <IconExclamationCircle size={16} />
) : displaySuccess ? ( ) : isRotating ? (
<IconCheckCircle size={16} />
) : (
<IconRefresh size={16} /> <IconRefresh size={16} />
) : (
<IconCheckCircle size={16} />
)} )}
</Rotating> </Rotating>
{(displaySuccess || isError || (isPending && hasClicked)) && ( <Box
<Box ml={2}
ml={2} ff="Open Sans|SemiBold"
ff="Open Sans|SemiBold" color={isError ? 'alertRed' : undefined}
color={isError ? 'alertRed' : undefined} fontSize={4}
fontSize={4} horizontal
horizontal align="center"
align="center" >
> {isRotating ? (
{displaySuccess ? ( t('common:sync.syncing')
t('common:sync.upToDate') ) : isError ? (
) : isError ? ( <Fragment>
<Fragment> <Box>{t('common:sync.error')}</Box>
<Box>{t('common:sync.error')}</Box> <Box
<Box ml={2}
ml={2} cursor="pointer"
cursor="pointer" style={{ textDecoration: 'underline', pointerEvents: 'all' }}
style={{ textDecoration: 'underline', pointerEvents: 'all' }} onClick={this.handleRefresh}
onClick={this.handleRefresh} >
> {t('common:sync.refresh')}
{t('common:sync.refresh')} </Box>
</Box> </Fragment>
</Fragment> ) : (
) : ( t('common:sync.upToDate')
t('common:sync.syncing') )}
)} </Box>
</Box>
)}
</ItemContainer> </ItemContainer>
) )
} }
@ -124,6 +133,7 @@ const ActivityIndicator = ({ globalSyncState, t }: { globalSyncState: AsyncState
<ActivityIndicatorInner <ActivityIndicatorInner
t={t} t={t}
isPending={isPending} isPending={isPending}
isGlobalSyncStatePending={globalSyncState.pending}
isError={!!isError && !isPending} isError={!!isError && !isPending}
onClick={() => { onClick={() => {
cvPolling.poll() cvPolling.poll()

9
src/components/TopBar/ItemContainer.js

@ -12,17 +12,18 @@ export default styled(Tabbable).attrs({
alignItems: 'center', alignItems: 'center',
cursor: p => (p.isDisabled ? 'default' : 'pointer'), cursor: p => (p.isDisabled ? 'default' : 'pointer'),
horizontal: true, horizontal: true,
borderRadius: 1,
})` })`
min-height: 40px; height: 40px;
border: 1px dashed transparent; border: 1px dashed transparent;
&:hover { &:hover {
color: ${p => (p.isDisabled ? '' : p.theme.colors.wallet)}; color: ${p => (p.isDisabled ? '' : p.theme.colors.dark)};
background: ${p => (p.isDisabled ? '' : rgba(p.theme.colors.wallet, 0.05))}; background: ${p => (p.isDisabled ? '' : rgba(p.theme.colors.fog, 0.2))};
} }
&:active { &:active {
background: ${p => (p.isDisabled ? '' : rgba(p.theme.colors.wallet, 0.1))}; background: ${p => (p.isDisabled ? '' : rgba(p.theme.colors.fog, 0.3))};
} }
&:focus { &:focus {

1
src/components/TopBar/index.js

@ -38,6 +38,7 @@ const Inner = styled(Box).attrs({
horizontal: true, horizontal: true,
grow: true, grow: true,
flow: 4, flow: 4,
align: 'center',
})` })`
border-bottom: 1px solid ${p => p.theme.colors.fog}; border-bottom: 1px solid ${p => p.theme.colors.fog};
` `

2
src/config/constants.js

@ -1,6 +1,6 @@
// @flow // @flow
export const SYNC_BOOT_DELAY = 5 * 1000 export const SYNC_BOOT_DELAY = 2 * 1000
export const SYNC_INTERVAL = 30 * 1000 export const SYNC_INTERVAL = 30 * 1000
export const CHECK_APP_INTERVAL_WHEN_INVALID = 600 export const CHECK_APP_INTERVAL_WHEN_INVALID = 600
export const CHECK_APP_INTERVAL_WHEN_VALID = 1200 export const CHECK_APP_INTERVAL_WHEN_VALID = 1200

Loading…
Cancel
Save