feat(sync): expanded details on sync status

Summary: Fixes T3574

Test Plan: manual

Reviewers: dillon, bengotow

Reviewed By: dillon, bengotow

Maniphest Tasks: T3574

Differential Revision: https://phab.nylas.com/D2058
This commit is contained in:
Evan Morikawa 2015-09-24 11:03:11 -07:00
parent a37e567888
commit d73ff30510
4 changed files with 166 additions and 45 deletions

View file

@ -2,12 +2,12 @@ React = require 'react'
_ = require 'underscore'
classNames = require 'classnames'
NotificationStore = require './notifications-store'
InitialSyncActivity = require './initial-sync-activity'
{Actions,
TaskQueue,
AccountStore,
NylasSyncStatusStore,
TaskQueueStatusStore,
NylasAPI} = require 'nylas-exports'
TaskQueueStatusStore} = require 'nylas-exports'
ActivitySidebarLongPollStore = require './activity-sidebar-long-poll-store'
{TimeoutTransitionGroup, RetinaImg} = require 'nylas-component-kit'
@ -25,8 +25,8 @@ class ActivitySidebar extends React.Component
componentDidMount: =>
@_unlisteners = []
@_unlisteners.push TaskQueueStatusStore.listen @_onDataChanged
@_unlisteners.push NylasSyncStatusStore.listen @_onDataChanged
@_unlisteners.push NotificationStore.listen @_onDataChanged
@_unlisteners.push NylasSyncStatusStore.listen @_onDataChanged
@_unlisteners.push ActivitySidebarLongPollStore.listen @_onDeltaReceived
componentWillUnmount: =>
@ -34,10 +34,14 @@ class ActivitySidebar extends React.Component
@_workerUnlisten() if @_workerUnlisten
render: =>
items = [].concat(@_renderSyncActivityItem(), @_renderNotificationActivityItems(), @_renderTaskActivityItems())
items = [@_renderNotificationActivityItems(), @_renderTaskActivityItems()]
if @state.isInitialSyncComplete
if @state.receivingDelta
items.push @_renderDeltaSyncActivityItem()
else
items.push <InitialSyncActivity />
if @state.receivingDelta
items.push @_renderDeltaSyncActivityItem()
names = classNames
"sidebar-activity": true
@ -64,39 +68,6 @@ class ActivitySidebar extends React.Component
{inside}
</TimeoutTransitionGroup>
_renderSyncActivityItem: =>
count = 0
fetched = 0
progress = 0
incomplete = 0
error = null
for acctId, state of @state.sync
for model, modelState of state
incomplete += 1 unless modelState.complete
error ?= modelState.error
if modelState.count
count += modelState.count / 1
fetched += modelState.fetched / 1
progress = (fetched / count) * 100 if count > 0
if incomplete is 0
return []
else if error
<div className="item" key="initial-sync">
<div className="inner">Initial sync encountered an error. Waiting to retry...
<div className="btn btn-emphasis" onClick={@_onTryAgain}>Try Again</div>
</div>
</div>
else
<div className="item" key="initial-sync">
<div className="progress-track">
<div className="progress" style={width: "#{progress}%"}></div>
</div>
<div className="inner">Syncing mail data&hellip;</div>
</div>
_renderTaskActivityItems: =>
summary = {}
@ -131,16 +102,13 @@ class ActivitySidebar extends React.Component
</div>
</div>
_onTryAgain: =>
Actions.retryInitialSync()
_onDataChanged: =>
@setState(@_getStateFromStores())
_getStateFromStores: =>
notifications: NotificationStore.notifications()
tasks: TaskQueueStatusStore.queue()
sync: NylasSyncStatusStore.state()
isInitialSyncComplete: NylasSyncStatusStore.isComplete()
_onDeltaReceived: (countDeltas) =>
tooSmallForNotification = countDeltas <= 10

View file

@ -0,0 +1,109 @@
_ = require 'underscore'
_str = require 'underscore.string'
classNames = require 'classnames'
React = require 'react'
{Actions, AccountStore, NylasSyncStatusStore} = require 'nylas-exports'
class InitialSyncActivity extends React.Component
@displayName: 'InitialSyncActivity'
constructor: (@props) ->
@state = @_getStateFromStores()
componentDidMount: =>
@_usub = NylasSyncStatusStore.listen @_onDataChanged
componentWillUnmount: =>
@_usub?()
_onDataChanged: =>
@setState(@_getStateFromStores())
_getStateFromStores: =>
sync: NylasSyncStatusStore.state()
render: =>
count = 0
fetched = 0
totalProgress = 0
incomplete = 0
error = null
for acctId, state of @state.sync
for model, modelState of state
incomplete += 1 unless modelState.complete
error ?= modelState.error
if modelState.count
count += modelState.count / 1
fetched += modelState.fetched / 1
totalProgress = (fetched / count) * 100 if count > 0
classSet = classNames
'item': true
'expanded-sync': @state.expandedSync
if incomplete is 0
return false
else if error
<div className={classSet} key="initial-sync">
<div className="inner">Initial sync encountered an error. Waiting to retry...
<div className="btn btn-emphasis" onClick={@_onTryAgain}>Try Again</div>
</div>
{@_expandedSyncState()}
</div>
else
<div className={classSet} key="initial-sync" onClick={=> @setState expandedSync: !@state.expandedSync}>
{@_renderProgressBar(totalProgress)}
<div className="inner">Syncing mail data&hellip;</div>
{@_expandedSyncState()}
</div>
_expandedSyncState: ->
accounts = []
for acctId, state of @state.sync
account = _.findWhere(AccountStore.items(), id: acctId)
continue unless account
modelStates = _.map state, (modelState, model) =>
@_renderModelProgress(model, modelState, 100)
accounts.push <div className="account inner" key={acctId}>
<h2>{account.emailAddress}</h2>
{modelStates}
</div>
accounts.push <a className="close-expanded" onClick={@_hideExpandedState}>Hide</a>
<div className="account-detail-area">
{accounts}
</div>
_hideExpandedState: (event) =>
event.stopPropagation() # So it doesn't reach the parent's onClick
event.preventDefault()
@setState expandedSync: false
return
_renderModelProgress: (model, modelState) ->
if modelState.error
status = "error"
else if modelState.complete
status = "complete"
else
status = "busy"
percent = (+modelState.fetched / +modelState.count) * 100
<div className="model-progress #{status}" key={model}>
<h3>{_str.titleize(model)}:</h3>
{@_renderProgressBar(percent)}
<div className="amount">{_str.numberFormat(modelState.fetched)} / {_str.numberFormat(modelState.count)}</div>
<div className="error-text">{modelState.error}</div>
</div>
_renderProgressBar: (percent) ->
<div className="progress-track">
<div className="progress" style={width: "#{percent}%"}></div>
</div>
_onTryAgain: =>
Actions.retryInitialSync()
module.exports = InitialSyncActivity

View file

@ -19,16 +19,17 @@
font-size: @font-size-small;
color: @text-color-subtle;
line-height:@line-height-computed * 0.95;
height:140px;
overflow-y:scroll;
box-shadow:inset 0 1px 0 @border-color-divider;
&:hover { cursor: default }
.item {
border-bottom:1px solid @border-color-divider;
&:hover { cursor: default }
.inner {
padding: @padding-large-vertical @padding-base-horizontal @padding-large-vertical @padding-base-horizontal;
margin-top:3px;
margin: 3px 0;
}
.count {
color: @text-color-very-subtle;
@ -45,6 +46,7 @@
display:block;
height:3px;
font-size:0;
background: rgba(0,0,0,0.1);
.progress {
transition: width 0.4s;
height:3px;
@ -55,6 +57,42 @@
// properly. Removing position relative causes the div to remain visible
position:relative;
opacity: 1;
.account-detail-area {
max-height: 0;
overflow: hidden;
transition: max-height 0.2s;
}
&.expanded-sync {
.account-detail-area {
max-height: 1000px;
}
}
.account.inner {
border-top: 1px solid rgba(0,0,0,0.1)
}
.account h2 {
font-size: 14px;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
}
.account h3 {
font-size: 14px;
margin: 1em 0 4px 0;
}
.acccount .amount {
margin-top: 2px;
}
.complete .progress-track .progress {
background-color: @nylas-green;
}
.close-expanded {
display: block;
margin: 0 @padding-base-horizontal @padding-large-vertical @padding-base-horizontal;
}
}
transition: height 0.4s;

View file

@ -30,6 +30,12 @@ class NylasSyncStatusStore extends NylasStore
state: =>
@_statesByAccount
isComplete: ->
for acctId, state of @_statesByAccount
for model, modelState of state
return false if not modelState.complete
return true
busy: =>
for accountId, states of @_statesByAccount
for key, state of states