fix(empty-states): Use quotes sparingly, show generic empty text in three-column mode and during search

Summary: We now show the inspirational quotes only when in list mode and viewing a tag. When you're viewing search results, or when you're in three-pane mode, you now see a more generic empty state.

Test Plan: No tests yet, may want to see if this refactor sticks when we start adding more empty states

Reviewers: evan

Reviewed By: evan

Differential Revision: https://phab.nylas.com/D1642
This commit is contained in:
Ben Gotow 2015-06-17 13:14:45 -07:00
parent 672354260e
commit c3287b7d57
15 changed files with 181 additions and 75 deletions

View file

@ -11,7 +11,6 @@ module.exports =
Popover: require '../src/components/popover'
Flexbox: require '../src/components/flexbox'
RetinaImg: require '../src/components/retina-img'
EmptyState: require '../src/components/empty-state'
ListTabular: require '../src/components/list-tabular'
DraggableImg: require '../src/components/draggable-img'
MultiselectList: require '../src/components/multiselect-list'

View file

@ -293,6 +293,7 @@ class MessageList extends React.Component
_scrollToBottom: =>
messageWrap = React.findDOMNode(@refs.messageWrap)
return unless messageWrap
messageWrap.scrollTop = messageWrap.scrollHeight
_cacheScrollPos: =>

View file

@ -73,7 +73,7 @@
&.clear {
position: absolute;
top: floor(40px - 26px)/2 - 1px;
top: 4px;
color: @input-cancel-color;
right: @padding-base-horizontal;
display: none;

View file

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 286 KiB

View file

Before

Width:  |  Height:  |  Size: 389 KiB

After

Width:  |  Height:  |  Size: 389 KiB

View file

Before

Width:  |  Height:  |  Size: 409 KiB

After

Width:  |  Height:  |  Size: 409 KiB

View file

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 280 KiB

View file

@ -0,0 +1,130 @@
_ = require 'underscore'
React = require 'react'
classNames = require 'classnames'
{RetinaImg} = require 'nylas-component-kit'
{DatabaseView,
NamespaceStore,
NylasAPI,
WorkspaceStore} = require 'nylas-exports'
EmptyMessages = [{
"body":"The pessimist complains about the wind.\nThe optimist expects it to change.\nThe realist adjusts the sails."
"byline": "- William Arthur Ward"
},{
"body":"The best and most beautiful things in the world cannot be seen or even touched - they must be felt with the heart."
"byline": "- Hellen Keller"
},{
"body":"Believe you can and you're halfway there."
"byline": "- Theodore Roosevelt"
},{
"body":"Don't judge each day by the harvest you reap but by the seeds that you plant."
"byline": "- Robert Louis Stevenson"
}]
class ContentGeneric extends React.Component
render: ->
<div className="generic">
<div className="message">
{@props.messageOverride ? "No threads to display."}
</div>
</div>
class ContentQuotes extends React.Component
@displayName = 'Quotes'
constructor: (@props) ->
@state = {}
componentDidMount: ->
# Pick a random quote using the day as a seed. I know not all months have
# 31 days - this is good enough to generate one quote a day at random!
d = new Date()
r = d.getDate() + d.getMonth() * 31
message = EmptyMessages[r % EmptyMessages.length]
@setState(message: message)
render: ->
<div className="quotes">
{@_renderMessage()}
<RetinaImg mode={RetinaImg.Mode.ContentLight} url="nylas://thread-list/assets/blank-bottom-left@2x.png" className="bottom-left"/>
<RetinaImg mode={RetinaImg.Mode.ContentLight} url="nylas://thread-list/assets/blank-top-left@2x.png" className="top-left"/>
<RetinaImg mode={RetinaImg.Mode.ContentLight} url="nylas://thread-list/assets/blank-bottom-right@2x.png" className="bottom-right"/>
<RetinaImg mode={RetinaImg.Mode.ContentLight} url="nylas://thread-list/assets/blank-top-right@2x.png" className="top-right"/>
</div>
_renderMessage: ->
if @props.messageOverride
<div className="message">{@props.messageOverride}</div>
else
<div className="message">
{@state.message?.body}
<div className="byline">
{@state.message?.byline}
</div>
</div>
class EmptyState extends React.Component
@displayName = 'EmptyState'
@propTypes =
visible: React.PropTypes.bool.isRequired
dataView: React.PropTypes.object
constructor: (@props) ->
@state =
layoutMode: WorkspaceStore.layoutMode()
syncing: false
active: false
componentDidMount: ->
@_unlisteners = []
@_unlisteners.push WorkspaceStore.listen(@_onChange, @)
@_unlisteners.push NamespaceStore.listen(@_onNamespacesChanged, @)
@_onNamespacesChanged()
_onNamespacesChanged: ->
namespace = NamespaceStore.current()
@_worker = NylasAPI.workerForNamespace(namespace)
@_workerUnlisten() if @_workerUnlisten
@_workerUnlisten = @_worker.listen(@_onChange, @)
console.log(@_worker)
@setState(syncing: @_worker.busy())
componentWillUnmount: ->
unlisten() for unlisten in @_unlisteners
@_workerUnlisten() if @_workerUnlisten
componentDidUpdate: ->
if @props.visible and not @state.active
@setState(active:true)
componentWillReceiveProps: (newProps) ->
if newProps.visible is false
@setState(active:false)
render: ->
ContentComponent = ContentGeneric
messageOverride = null
if @props.dataView instanceof DatabaseView
if @state.layoutMode is 'list'
ContentComponent = ContentQuotes
if @state.syncing
messageOverride = "Please wait while we prepare your mailbox."
classes = classNames
'empty-state': true
'visible': @props.visible
'active': @state.active
<div className={classes}>
<ContentComponent messageOverride={messageOverride}/>
</div>
_onChange: ->
@setState
layoutMode: WorkspaceStore.layoutMode()
syncing: @_worker.busy()
module.exports = EmptyState

View file

@ -14,6 +14,8 @@ ThreadListQuickActions = require './thread-list-quick-actions'
ThreadListStore = require './thread-list-store'
ThreadListIcon = require './thread-list-icon'
EmptyState = require './empty-state'
class ThreadListScrollTooltip extends React.Component
@displayName: 'ThreadListScrollTooltip'
@propTypes:
@ -128,6 +130,7 @@ class ThreadList extends React.Component
'application:reply': @_onReply
'application:reply-all': @_onReplyAll
'application:forward': @_onForward
@itemPropsProvider = (item) ->
className: classNames
'unread': item.isUnread()
@ -149,6 +152,7 @@ class ThreadList extends React.Component
itemHeight={39}
className="thread-list"
scrollTooltipComponent={ThreadListScrollTooltip}
emptyComponent={EmptyState}
collection="thread" />
else if @state.style is 'narrow'
<MultiselectList
@ -159,6 +163,7 @@ class ThreadList extends React.Component
itemHeight={90}
className="thread-list thread-list-narrow"
scrollTooltipComponent={ThreadListScrollTooltip}
emptyComponent={EmptyState}
collection="thread" />
else
<div></div>

View file

@ -13,10 +13,33 @@
overflow:hidden;
> div {
opacity:0;
opacity: 0;
-webkit-transition: opacity @duration ease-out;
width:100%;
height: 100%;
}
// Generic Mode
.generic {
text-align: center;
.message {
color: @text-color-very-subtle;
font-size: 28px;
font-weight: @font-weight-blond;
text-align: center;
top:45%;
left:50%;
width:80%;
transform: translate(-50%, -50%);
position: absolute;
white-space: pre-line;
}
}
// Quotes Mode
.quotes {
min-width: 840px;
min-height: 650px;
position: absolute;
@ -113,4 +136,4 @@
}
}
}
}
}

View file

@ -1,64 +0,0 @@
_ = require 'underscore'
React = require 'react'
classNames = require 'classnames'
RetinaImg = require './retina-img'
EmptyMessages = [{
"body":"The pessimist complains about the wind.\nThe optimist expects it to change.\nThe realist adjusts the sails."
"byline": "- William Arthur Ward"
},{
"body":"The best and most beautiful things in the world cannot be seen or even touched - they must be felt with the heart."
"byline": "- Hellen Keller"
},{
"body":"Believe you can and you're halfway there."
"byline": "- Theodore Roosevelt"
},{
"body":"Don't judge each day by the harvest you reap but by the seeds that you plant."
"byline": "- Robert Louis Stevenson"
}]
class EmptyState extends React.Component
@displayName = 'EmptyState'
@propTypes =
visible: React.PropTypes.bool.isRequired
constructor: (@props) ->
@state =
active: false
componentDidUpdate: ->
if @props.visible and not @state.active
# Pick a random quote using the day as a seed. I know not all months have
# 31 days - this is good enough to generate one quote a day at random!
d = new Date()
r = d.getDate() + d.getMonth() * 31
message = EmptyMessages[r % EmptyMessages.length]
@setState(active:true, message: message)
componentWillReceiveProps: (newProps) ->
if newProps.visible is false
@setState(active:false)
render: ->
classes = classNames
'empty-state': true
'visible': @props.visible
'active': @state.active
<div className={classes}>
<div>
<div className="message">
{@state.message?.body}
<div className="byline">
{@state.message?.byline}
</div>
</div>
<RetinaImg mode={RetinaImg.Mode.ContentLight} name="blank-bottom-left.png" className="bottom-left"/>
<RetinaImg mode={RetinaImg.Mode.ContentLight} name="blank-top-left.png" className="top-left"/>
<RetinaImg mode={RetinaImg.Mode.ContentLight} name="blank-bottom-right.png" className="bottom-right"/>
<RetinaImg mode={RetinaImg.Mode.ContentLight} name="blank-top-right.png" className="top-right"/>
</div>
</div>
module.exports = EmptyState

View file

@ -2,7 +2,6 @@ _ = require 'underscore'
React = require 'react'
classNames = require 'classnames'
ListTabular = require './list-tabular'
EmptyState = require './empty-state'
Spinner = require './spinner'
{Actions,
Utils,
@ -37,6 +36,7 @@ class MultiselectList extends React.Component
itemPropsProvider: React.PropTypes.func.isRequired
itemHeight: React.PropTypes.number.isRequired
scrollTooltipComponent: React.PropTypes.func
emptyComponent: React.PropTypes.func
constructor: (@props) ->
@state = @_getStateFromStores()
@ -107,6 +107,12 @@ class MultiselectList extends React.Component
'keyboard-cursor': @state.handler.shouldShowKeyboardCursor() and item.id is @state.keyboardCursorId
props
emptyElement = []
if @props.emptyComponent
emptyElement = <@props.emptyComponent
visible={@state.ready && @state.dataView.count() is 0}
dataView={@state.dataView} />
if @state.dataView
<div className={className}>
<ListTabular
@ -119,7 +125,7 @@ class MultiselectList extends React.Component
onSelect={@_onClickItem}
onDoubleClick={@props.onDoubleClick} />
<Spinner visible={!@state.ready} />
<EmptyState visible={@state.ready && @state.dataView.count() is 0} />
{emptyElement}
</div>
else
<div className={className}>

View file

@ -33,11 +33,17 @@ class NylasSyncWorker
state: ->
@_state
busy: ->
for key, state of @_state
if state.busy
return true
false
start: ->
@_resumeTimer = setInterval(@resumeFetches, 20000)
@_connection.start()
@resumeFetches()
cleanup: ->
clearInterval(@_resumeTimer)
@_connection.end()
@ -64,7 +70,7 @@ class NylasSyncWorker
@fetchCollectionCount(model)
@fetchCollectionPage(model, {offset: 0, limit: PAGE_SIZE})
fetchCollectionCount: (model) ->
@_api.makeRequest
path: "/n/#{@_namespaceId}/#{model}"

View file

@ -187,8 +187,9 @@ class WorkspaceStore
# Return to the root sheet. This method triggers, allowing observers
# to update.
popToRootSheet: =>
@_sheetStack.length = 1
@trigger()
if @_sheetStack.length > 1
@_sheetStack.length = 1
@trigger()
triggerDebounced: _.debounce(( -> @trigger(@)), 1)

View file

@ -21,5 +21,4 @@
@import "components/scroll-region";
@import "components/spinner";
@import "components/generated-form";
@import "components/empty-state";
@import "components/unsafe";