diff --git a/internal_packages/notifications/lib/sidebar/activity-sidebar.cjsx b/internal_packages/notifications/lib/sidebar/activity-sidebar.cjsx index 15622d84e..9705e894f 100644 --- a/internal_packages/notifications/lib/sidebar/activity-sidebar.cjsx +++ b/internal_packages/notifications/lib/sidebar/activity-sidebar.cjsx @@ -4,15 +4,19 @@ ReactCSSTransitionGroup = require 'react-addons-css-transition-group' _ = require 'underscore' classNames = require 'classnames' -StreamingSyncActivity = require './streaming-sync-activity' -InitialSyncActivity = require('./initial-sync-activity').default +SyncActivity = require("./sync-activity").default +SyncbackActivity = require("./syncback-activity").default {Utils, Actions, TaskQueue, AccountStore, NylasSyncStatusStore, - TaskQueueStatusStore} = require 'nylas-exports' + TaskQueueStatusStore + PerformSendActionTask, + SendDraftTask} = require 'nylas-exports' + +SEND_TASK_CLASSES = [PerformSendActionTask, SendDraftTask] class ActivitySidebar extends React.Component @displayName: 'ActivitySidebar' @@ -38,12 +42,14 @@ class ActivitySidebar extends React.Component unlisten() for unlisten in @_unlisteners render: => - items = @_renderTaskActivityItems() + sendTasks = [] + nonSendTasks = [] + @state.tasks.forEach (task) -> + if SEND_TASK_CLASSES.some(((taskClass) -> task instanceof taskClass )) + sendTasks.push(task) + else + nonSendTasks.push(task) - if @state.isInitialSyncComplete - items.push - else - items.push names = classNames "sidebar-activity": true @@ -51,16 +57,17 @@ class ActivitySidebar extends React.Component wrapperClass = "sidebar-activity-transition-wrapper " - if items.length is 0 - wrapperClass += "sidebar-activity-empty" - else - inside = - {items} - + inside = + + + - _renderTaskActivityItems: => - summary = {} - - @state.tasks.map (task) -> - label = task.label?() - return unless label - summary[label] ?= 0 - summary[label] += task.numberOfImpactedItems() - - _.pairs(summary).map ([label, count]) -> -
-
- ({new Number(count).toLocaleString()}) - {label} -
-
- _onDataChanged: => @setState(@_getStateFromStores()) diff --git a/internal_packages/notifications/lib/sidebar/initial-sync-activity.jsx b/internal_packages/notifications/lib/sidebar/initial-sync-activity.jsx index aba59866c..dfe560ebc 100644 --- a/internal_packages/notifications/lib/sidebar/initial-sync-activity.jsx +++ b/internal_packages/notifications/lib/sidebar/initial-sync-activity.jsx @@ -1,7 +1,6 @@ import _ from 'underscore'; import _str from 'underscore.string'; -import classNames from 'classnames'; -import {Utils, Actions, AccountStore, NylasSyncStatusStore, React} from 'nylas-exports'; +import {Utils, AccountStore, NylasSyncStatusStore, React} from 'nylas-exports'; const MONTH_SHORT_FORMATS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; @@ -12,7 +11,6 @@ export default class InitialSyncActivity extends React.Component { constructor(props) { super(props); this.state = { - isExpanded: false, syncState: NylasSyncStatusStore.getSyncState(), syncProgress: NylasSyncStatusStore.getSyncProgress(), } @@ -21,10 +19,7 @@ export default class InitialSyncActivity extends React.Component { componentDidMount() { this.mounted = true; - this.unsubs = [ - NylasSyncStatusStore.listen(this.onDataChanged), - Actions.expandInitialSyncState.listen(this.showExpandedState), - ] + this.unsub = NylasSyncStatusStore.listen(this.onDataChanged) } shouldComponentUpdate(nextProps, nextState) { @@ -33,9 +28,7 @@ export default class InitialSyncActivity extends React.Component { } componentWillUnmount() { - if (this.unsubs) { - this.unsubs.forEach((unsub) => unsub()) - } + this.unsub(); this.mounted = false; } @@ -45,24 +38,42 @@ export default class InitialSyncActivity extends React.Component { this.setState({syncState, syncProgress}); } - hideExpandedState = () => { - this.setState({isExpanded: false}); - } - - showExpandedState = () => { - if (!this.state.isExpanded) { - this.setState({isExpanded: true}); + renderFolderProgress(name, progress, oldestProcessedDate) { + let status = 'busy'; + let progressLabel = 'In Progress' + let syncedThrough = 'Syncing this past month'; + if (progress === 1) { + status = 'complete'; + progressLabel = ''; + syncedThrough = 'Up to date' } else { - this.setState({blink: true}); - setTimeout(() => { - if (this.mounted) { - this.setState({blink: false}); + let month = oldestProcessedDate.getMonth(); + let year = oldestProcessedDate.getFullYear(); + const currentDate = new Date(); + if (month !== currentDate.getMonth() || year !== currentDate.getFullYear()) { + // We're currently syncing in `month`, which mean's we've synced through all + // of the month *after* it. + month++; + if (month === 12) { + month = 0; + year++; } - }, 1000) + syncedThrough = `Synced through ${MONTH_SHORT_FORMATS[month]} ${year}`; + } } + + return ( +
+ {_str.titleize(name)} {progressLabel} +
+ ) } - renderExpandedSyncState() { + render() { + if (!AccountStore.accountsAreSyncing() || this.state.syncProgress.progress === 1) { + return false; + } + let maxHeight = 0; let accounts = _.map(this.state.syncState, (accountSyncState, accountId) => { const account = _.findWhere(AccountStore.accounts(), {id: accountId}); @@ -101,70 +112,9 @@ export default class InitialSyncActivity extends React.Component { key="expanded-sync-state" style={{maxHeight: `${maxHeight + 500}px`}} > - - Hide - {accounts} ) } - renderFolderProgress(name, progress, oldestProcessedDate) { - let status = 'busy'; - let progressLabel = 'In Progress' - let syncedThrough = 'Syncing this past month'; - if (progress === 1) { - status = 'complete'; - progressLabel = ''; - syncedThrough = 'Up to date' - } else { - let month = oldestProcessedDate.getMonth(); - let year = oldestProcessedDate.getFullYear(); - const currentDate = new Date(); - if (month !== currentDate.getMonth() || year !== currentDate.getFullYear()) { - // We're currently syncing in `month`, which mean's we've synced through all - // of the month *after* it. - month++; - if (month === 12) { - month = 0; - year++; - } - syncedThrough = `Synced through ${MONTH_SHORT_FORMATS[month]} ${year}`; - } - } - - return ( -
- {_str.titleize(name)} {progressLabel} -
- ) - } - - render() { - if (!AccountStore.accountsAreSyncing()) { - return false; - } - - const {syncProgress: {progress}} = this.state - if (progress === 1) { - return false; - } - - const classSet = classNames({ - 'item': true, - 'expanded-sync': this.state.isExpanded, - 'blink': this.state.blink, - }); - - return ( -
(this.setState({isExpanded: !this.state.isExpanded}))} - > -
Syncing your mailbox
- {this.state.isExpanded ? this.renderExpandedSyncState() : false} -
- ) - } } diff --git a/internal_packages/notifications/lib/sidebar/sync-activity.jsx b/internal_packages/notifications/lib/sidebar/sync-activity.jsx new file mode 100644 index 000000000..b4a1f4f96 --- /dev/null +++ b/internal_packages/notifications/lib/sidebar/sync-activity.jsx @@ -0,0 +1,97 @@ +import classNames from 'classnames'; +import {Actions, React, Utils} from 'nylas-exports'; + +import InitialSyncActivity from './initial-sync-activity'; +import SyncbackActivity from './syncback-activity'; + +export default class SyncActivity extends React.Component { + + static propTypes = { + initialSync: React.PropTypes.bool, + syncbackTasks: React.PropTypes.array, + } + + constructor() { + super() + this.state = { + expanded: false, + blink: false, + } + this.mounted = false; + } + + componentDidMount() { + this.mounted = true; + this.unsub = Actions.expandInitialSyncState.listen(this.showExpandedState); + } + + shouldComponentUpdate(nextProps, nextState) { + return !Utils.isEqualReact(nextProps, this.props) || + !Utils.isEqualReact(nextState, this.state); + } + + componentWillUnmount() { + this.mounted = false; + this.unsub(); + } + + showExpandedState = () => { + if (!this.state.expanded) { + this.setState({expanded: true}); + } else { + this.setState({blink: true}); + setTimeout(() => { + if (this.mounted) { + this.setState({blink: false}); + } + }, 1000) + } + } + + hideExpandedState = () => { + this.setState({expanded: false}); + } + + _renderInitialSync() { + if (!this.props.initialSync) { return false; } + return + } + + _renderSyncbackTasks() { + return + } + + _renderExpandedDetails() { + return ( +
+ Hide + {this._renderSyncbackTasks()} + {this._renderInitialSync()} +
+ ) + } + + render() { + const {initialSync, syncbackTasks} = this.props; + if (!initialSync && (!syncbackTasks || syncbackTasks.length === 0)) { + return false; + } + + const classSet = classNames({ + 'item': true, + 'expanded-sync': this.state.expanded, + 'blink': this.state.blink, + }); + + return ( +
(this.setState({expanded: !this.state.expanded}))} + > +
Syncing your mailbox
+ {this.state.expanded ? this._renderExpandedDetails() : false} +
+ ) + } +} diff --git a/internal_packages/notifications/lib/sidebar/syncback-activity.jsx b/internal_packages/notifications/lib/sidebar/syncback-activity.jsx new file mode 100644 index 000000000..2f6875fe8 --- /dev/null +++ b/internal_packages/notifications/lib/sidebar/syncback-activity.jsx @@ -0,0 +1,55 @@ +import _ from 'underscore'; +import {React, Utils} from 'nylas-exports'; + +export default class SyncbackActivity extends React.Component { + static propTypes = { + syncbackTasks: React.PropTypes.array, + } + + shouldComponentUpdate(nextProps, nextState) { + return !Utils.isEqualReact(nextProps, this.props) || + !Utils.isEqualReact(nextState, this.state); + } + + render() { + const {syncbackTasks} = this.props; + if (!syncbackTasks || syncbackTasks.length === 0) { return false; } + + const counts = {} + this.props.syncbackTasks.forEach((task) => { + const label = task.label ? task.label() : null; + if (!label) { return; } + if (!counts[label]) { + counts[label] = 0; + } + counts[label] += +task.numberOfImpactedItems() + }); + + const items = _.pairs(counts).map(([label, count]) => { + return ( +
+
+ ({count.toLocaleString()}) + {label} +
+
+ ) + }); + + if (items.length === 0) { + items.push( +
+
+ Applying tasks +
+
+ ) + } + + return ( +
+ {items} +
+ ) + } +} diff --git a/internal_packages/notifications/stylesheets/notifications.less b/internal_packages/notifications/stylesheets/notifications.less index 2aef7b5a5..dc4426b1b 100644 --- a/internal_packages/notifications/stylesheets/notifications.less +++ b/internal_packages/notifications/stylesheets/notifications.less @@ -5,12 +5,6 @@ order: 2; z-index: 2; overflow-y: auto; - - &.sidebar-activity-empty { - position: absolute; - bottom: 0; - width: 100%; - } } .sidebar-activity { @@ -24,12 +18,14 @@ box-shadow:inset 0 1px 0 @border-color-divider; &:hover { cursor: default } + .item { - border-bottom:1px solid @border-color-divider; &:hover { cursor: default } + .clickable { cursor: pointer; } .inner { padding: @padding-large-vertical @padding-base-horizontal @padding-large-vertical @padding-base-horizontal; + border-bottom: 1px solid rgba(0,0,0,0.1); } .inner::after { content: '...'; @@ -115,10 +111,11 @@ color: @text-color-subtle; } .close-expanded { - display: block; - text-align: right; - border-top: 1px solid rgba(0,0,0,0.1); - padding: @padding-large-vertical @padding-base-horizontal 0 @padding-base-horizontal; + padding: @padding-large-vertical @padding-base-horizontal; + position: absolute; + top: 0; + right: 0; + cursor: pointer; } } diff --git a/src/K2 b/src/K2 index ae9d6f75d..32c1caa6c 160000 --- a/src/K2 +++ b/src/K2 @@ -1 +1 @@ -Subproject commit ae9d6f75defbab23fe5859d313132125844f5266 +Subproject commit 32c1caa6c6f38e60e15d1919e71559aec9c19ffc diff --git a/src/flux/tasks/change-unread-task.es6 b/src/flux/tasks/change-unread-task.es6 index 93735b90c..70f800bfa 100644 --- a/src/flux/tasks/change-unread-task.es6 +++ b/src/flux/tasks/change-unread-task.es6 @@ -10,6 +10,10 @@ export default class ChangeUnreadTask extends ChangeMailTask { this.unread = options.unread; } + label() { + return this.unread ? "Marking as unread" : "Marking as read"; + } + description() { const count = this.threads.length; const type = count > 1 ? 'threads' : 'thread'; diff --git a/src/flux/tasks/ensure-message-in-sent-folder-task.es6 b/src/flux/tasks/ensure-message-in-sent-folder-task.es6 index 082adb5f8..269ab8cbd 100644 --- a/src/flux/tasks/ensure-message-in-sent-folder-task.es6 +++ b/src/flux/tasks/ensure-message-in-sent-folder-task.es6 @@ -13,6 +13,10 @@ export default class EnsureMessageInSentFolderTask extends Task { this.sentPerRecipient = opts.sentPerRecipient; } + label() { + return "Saving to sent folder"; + } + isDependentOnTask(other) { return (other instanceof SendDraftTask) && (other.message) && (other.message.clientId === this.message.clientId); }