diff --git a/internal_packages/account-sidebar/lib/sidebar-item.coffee b/internal_packages/account-sidebar/lib/sidebar-item.coffee
index 3a191e94b..e738d204a 100644
--- a/internal_packages/account-sidebar/lib/sidebar-item.coffee
+++ b/internal_packages/account-sidebar/lib/sidebar-item.coffee
@@ -2,7 +2,6 @@ _ = require 'underscore'
{WorkspaceStore,
MailboxPerspective,
FocusedPerspectiveStore,
- DraftCountStore,
DestroyCategoryTask,
Actions} = require 'nylas-exports'
{OutlineViewItem} = require 'nylas-component-kit'
@@ -18,7 +17,7 @@ countForItem = (perspective) ->
return 0
isItemSelected = (perspective) ->
- (WorkspaceStore.rootSheet() is WorkspaceStore.Sheet.Threads and
+ (WorkspaceStore.rootSheet() in [WorkspaceStore.Sheet.Threads, WorkspaceStore.Sheet.Drafts] and
FocusedPerspectiveStore.current().isEqual(perspective))
isItemDeleted = (perspective) ->
@@ -36,31 +35,29 @@ toggleItemCollapsed = (item) ->
class SidebarItem
- @forPerspective: (id, perspective, {children, deletable, name} = {}) ->
- children ?= []
+ @forPerspective: (id, perspective, opts = {}) ->
counterStyle = OutlineViewItem.CounterStyles.Alt if perspective.isInbox()
- dataTransferType = 'nylas-thread-ids'
- if deletable
- onDeleteItem = (item) ->
+ if opts.deletable
+ onDeleteItem = (item) ->
# TODO Delete multiple categories at once
return if item.perspective.categories.length > 1
return if item.deleted is true
category = item.perspective.categories[0]
Actions.queueTask(new DestroyCategoryTask({category: category}))
- return {
+ return _.extend({
id: id
- name: name ? perspective.name
+ name: perspective.name
count: countForItem(perspective)
iconName: perspective.iconName
- children: children
+ children: []
perspective: perspective
selected: isItemSelected(perspective)
collapsed: isItemCollapsed(id) ? true
deleted: isItemDeleted(perspective)
counterStyle: counterStyle
- dataTransferType: dataTransferType
+ dataTransferType: 'nylas-thread-ids'
onDelete: onDeleteItem
onToggleCollapsed: toggleItemCollapsed
onDrop: (item, ids) ->
@@ -75,7 +72,7 @@ class SidebarItem
onSelect: (item) ->
Actions.selectRootSheet(WorkspaceStore.Sheet.Threads)
Actions.focusMailboxPerspective(item.perspective)
- }
+ }, opts)
@forCategories: (categories = [], opts = {}) ->
@@ -89,30 +86,12 @@ class SidebarItem
id += "-#{opts.name}" if opts.name
@forPerspective(id, perspective, opts)
- @forSheet: (id, name, iconName, sheet, count, collapsed, children = []) ->
- return {
- id,
- name,
- iconName,
- count,
- sheet,
- children,
- collapsed: isItemCollapsed(id) ? true
- onToggleCollapsed: toggleItemCollapsed
- onSelect: (item) ->
- Actions.selectRootSheet(item.sheet)
- }
-
- @forDrafts: ({accountId, name, children, collapsed} = {}) ->
- id = 'Drafts'
- id += "-#{name}" if name
- sheet = WorkspaceStore.Sheet.Drafts
- iconName = 'drafts.png'
- count = if accountId?
- DraftCountStore.count(accountId)
- else
- DraftCountStore.totalCount()
- @forSheet(id, name ? id, iconName, sheet, count, collapsed, children)
-
+ @forDrafts: (accountIds, opts = {}) ->
+ perspective = MailboxPerspective.forDrafts(accountIds)
+ id = "Drafts-#{opts.name}"
+ opts.onSelect = ->
+ Actions.focusMailboxPerspective(perspective)
+ Actions.selectRootSheet(WorkspaceStore.Sheet.Drafts)
+ @forPerspective(id, perspective, opts)
module.exports = SidebarItem
diff --git a/internal_packages/account-sidebar/lib/sidebar-section.coffee b/internal_packages/account-sidebar/lib/sidebar-section.coffee
index 1f05b0bb9..e200b6e0f 100644
--- a/internal_packages/account-sidebar/lib/sidebar-section.coffee
+++ b/internal_packages/account-sidebar/lib/sidebar-section.coffee
@@ -74,9 +74,8 @@ class SidebarSection
starredItem = SidebarItem.forStarred(_.pluck(accounts, 'id'),
children: accounts.map (acc) -> SidebarItem.forStarred([acc.id], name: acc.label)
)
- draftsItem = SidebarItem.forDrafts(
- children: accounts.map (acc) ->
- SidebarItem.forDrafts(accountId: acc.id, name: acc.label)
+ draftsItem = SidebarItem.forDrafts(_.pluck(accounts, 'id'),
+ children: accounts.map (acc) -> SidebarItem.forDrafts([acc.id], name: acc.label)
)
# Order correctly: Inbox, Starred, rest... , Drafts
diff --git a/internal_packages/account-sidebar/lib/sidebar-store.coffee b/internal_packages/account-sidebar/lib/sidebar-store.coffee
index 16a8e36d8..e5052c553 100644
--- a/internal_packages/account-sidebar/lib/sidebar-store.coffee
+++ b/internal_packages/account-sidebar/lib/sidebar-store.coffee
@@ -3,7 +3,6 @@ _ = require 'underscore'
{Actions,
AccountStore,
ThreadCountsStore,
- DraftCountStore,
WorkspaceStore,
FocusedPerspectiveStore,
CategoryStore} = require 'nylas-exports'
@@ -44,8 +43,8 @@ class SidebarStore extends NylasStore
@listenTo AccountStore, @_updateSections
@listenTo WorkspaceStore, @_updateSections
@listenTo ThreadCountsStore, @_updateSections
- @listenTo DraftCountStore, @_updateSections
@listenTo CategoryStore, @_updateSections
+
@configSubscription = NylasEnv.config.observe(
'core.workspace.showUnreadForAllCategories',
@_updateSections
diff --git a/internal_packages/thread-list/lib/draft-list-columns.cjsx b/internal_packages/thread-list/lib/draft-list-columns.cjsx
new file mode 100644
index 000000000..6725f8c5b
--- /dev/null
+++ b/internal_packages/thread-list/lib/draft-list-columns.cjsx
@@ -0,0 +1,48 @@
+_ = require 'underscore'
+React = require 'react'
+classNames = require 'classnames'
+
+{ListTabular, InjectedComponent} = require 'nylas-component-kit'
+{timestamp,
+ subject} = require './formatting-utils'
+
+snippet = (html) =>
+ return "" unless html and typeof(html) is 'string'
+ try
+ @draftSanitizer ?= document.createElement('div')
+ @draftSanitizer.innerHTML = html[0..400]
+ text = @draftSanitizer.innerText
+ text[0..200]
+ catch
+ return ""
+
+c1 = new ListTabular.Column
+ name: "Name"
+ width: 200
+ resolver: (draft) =>
+
+
+
+
+c2 = new ListTabular.Column
+ name: "Message"
+ flex: 4
+ resolver: (draft) =>
+ attachments = []
+ if draft.files?.length > 0
+ attachments =
+
+ {subject(draft.subject)}
+ {snippet(draft.body)}
+ {attachments}
+
+
+c3 = new ListTabular.Column
+ name: "Date"
+ flex: 1
+ resolver: (draft) =>
+ {timestamp(draft.date)}
+
+module.exports =
+ Wide: [c1, c2, c3]
diff --git a/internal_packages/thread-list/lib/draft-list-store.coffee b/internal_packages/thread-list/lib/draft-list-store.coffee
index 277325874..1e0c04f29 100644
--- a/internal_packages/thread-list/lib/draft-list-store.coffee
+++ b/internal_packages/thread-list/lib/draft-list-store.coffee
@@ -1,42 +1,44 @@
NylasStore = require 'nylas-store'
-Reflux = require 'reflux'
Rx = require 'rx-lite'
_ = require 'underscore'
{Message,
- Actions,
- AccountStore,
MutableQuerySubscription,
ObservableListDataSource,
FocusedPerspectiveStore,
DatabaseStore} = require 'nylas-exports'
+{ListTabular} = require 'nylas-component-kit'
class DraftListStore extends NylasStore
constructor: ->
- @listenTo AccountStore, @_onAccountChanged
+ @listenTo FocusedPerspectiveStore, @_onPerspectiveChanged
+ @_createListDataSource()
- @subscription = new MutableQuerySubscription(@_queryForCurrentAccount(), {asResultSet: true})
- $resultSet = Rx.Observable.fromPrivateQuerySubscription('draft-list', @subscription)
+ dataSource: =>
+ @_dataSource
- @_view = new ObservableListDataSource $resultSet, ({start, end}) =>
- @subscription.replaceQuery(@_queryForCurrentAccount().page(start, end))
+ # Inbound Events
- view: =>
- @_view
+ _onPerspectiveChanged: =>
+ @_createListDataSource()
- _queryForCurrentAccount: =>
- matchers = [Message.attributes.draft.equal(true)]
- account = FocusedPerspectiveStore.current().account
+ # Internal
- if account?
- matchers.push(Message.attributes.accountId.equal(account.id))
+ _createListDataSource: =>
+ mailboxPerspective = FocusedPerspectiveStore.current()
- query = DatabaseStore.findAll(Message)
- .include(Message.attributes.body)
- .order(Message.attributes.date.descending())
- .where(matchers)
- .page(0, 1)
+ if mailboxPerspective.drafts
+ query = DatabaseStore.findAll(Message)
+ .include(Message.attributes.body)
+ .order(Message.attributes.date.descending())
+ .where(draft: true, accountId: mailboxPerspective.accountIds)
+ .page(0, 1)
- _onAccountChanged: =>
- @subscription.replaceQuery(@_queryForCurrentAccount())
+ subscription = new MutableQuerySubscription(query, {asResultSet: true})
+ $resultSet = Rx.Observable.fromPrivateQuerySubscription('draft-list', subscription)
+ @_dataSource = new ObservableListDataSource($resultSet, subscription.replaceRange)
+ else
+ @_dataSource = new ListTabular.DataSource.Empty()
+
+ @trigger(@)
module.exports = new DraftListStore()
diff --git a/internal_packages/thread-list/lib/draft-list.cjsx b/internal_packages/thread-list/lib/draft-list.cjsx
index 3ca1af021..c600df2f0 100644
--- a/internal_packages/thread-list/lib/draft-list.cjsx
+++ b/internal_packages/thread-list/lib/draft-list.cjsx
@@ -1,13 +1,13 @@
_ = require 'underscore'
React = require 'react'
-{ListTabular,
- MultiselectList,
- InjectedComponent} = require 'nylas-component-kit'
-{timestamp, subject} = require './formatting-utils'
{Actions,
- FocusedContentStore,
- DatabaseStore} = require 'nylas-exports'
+ FocusedContentStore} = require 'nylas-exports'
+{ListTabular,
+ FluxContainer,
+ MultiselectList} = require 'nylas-component-kit'
DraftListStore = require './draft-list-store'
+DraftListColumns = require './draft-list-columns'
+FocusContainer = require './focus-container'
EmptyState = require './empty-state'
class DraftList extends React.Component
@@ -15,60 +15,26 @@ class DraftList extends React.Component
@containerRequired: false
- componentWillMount: =>
- snippet = (html) =>
- return "" unless html and typeof(html) is 'string'
- try
- @draftSanitizer ?= document.createElement('div')
- @draftSanitizer.innerHTML = html[0..400]
- text = @draftSanitizer.innerText
- text[0..200]
- catch
- return ""
-
- c1 = new ListTabular.Column
- name: "Name"
- width: 200
- resolver: (draft) =>
-
-
-
-
- c2 = new ListTabular.Column
- name: "Message"
- flex: 4
- resolver: (draft) =>
- attachments = []
- if draft.files?.length > 0
- attachments =
-
- {subject(draft.subject)}
- {snippet(draft.body)}
- {attachments}
-
-
- c3 = new ListTabular.Column
- name: "Date"
- flex: 1
- resolver: (draft) =>
- {timestamp(draft.date)}
-
- @columns = [c1, c2, c3]
- @commands =
- 'core:remove-from-view': @_onRemoveFromView
-
render: =>
- {} }
- itemHeight={39}
- className="draft-list"
- collection="draft" />
+
+ dataSource: DraftListStore.dataSource()
+ }>
+
+ {} }
+ itemHeight={39}
+ className="draft-list" />
+
+
+
+ _keymapHandlers: ->
+ 'core:remove-from-view': @_onRemoveFromView
_onDoubleClick: (item) =>
Actions.composePopoutDraft(item.clientId)
diff --git a/internal_packages/thread-list/lib/empty-state.cjsx b/internal_packages/thread-list/lib/empty-state.cjsx
index b65cd3687..08f89c5b0 100644
--- a/internal_packages/thread-list/lib/empty-state.cjsx
+++ b/internal_packages/thread-list/lib/empty-state.cjsx
@@ -69,7 +69,6 @@ class EmptyState extends React.Component
@displayName = 'EmptyState'
@propTypes =
visible: React.PropTypes.bool.isRequired
- dataSource: React.PropTypes.object
constructor: (@props) ->
@state =
@@ -85,9 +84,7 @@ class EmptyState extends React.Component
@setState(active:true)
shouldComponentUpdate: (nextProps, nextState) ->
- # Avoid deep comparison of dataSource, which is a very complex object
return true if nextProps.visible isnt @props.visible
- return true if nextProps.dataSource isnt @props.dataSource
return not _.isEqual(nextState, @state)
componentWillUnmount: ->
diff --git a/internal_packages/thread-list/lib/thread-list-store.coffee b/internal_packages/thread-list/lib/thread-list-store.coffee
index ddfc1788f..9f0f50c75 100644
--- a/internal_packages/thread-list/lib/thread-list-store.coffee
+++ b/internal_packages/thread-list/lib/thread-list-store.coffee
@@ -9,11 +9,10 @@ NylasStore = require 'nylas-store'
FocusedContentStore,
TaskQueueStatusStore,
FocusedPerspectiveStore} = require 'nylas-exports'
+{ListTabular} = require 'nylas-component-kit'
ThreadListDataSource = require './thread-list-data-source'
-# Public: A mutable text container with undo/redo support and the ability
-# to annotate logical regions in the text.
class ThreadListStore extends NylasStore
constructor: ->
@listenTo FocusedPerspectiveStore, @_onPerspectiveChanged
@@ -23,18 +22,22 @@ class ThreadListStore extends NylasStore
@_dataSource
createListDataSource: =>
- mailboxPerspective = FocusedPerspectiveStore.current()
- @_dataSource = new ThreadListDataSource(mailboxPerspective.threads())
-
@_dataSourceUnlisten?()
- @_dataSourceUnlisten = @_dataSource.listen(@_onDataChanged, @)
+ @_dataSource = null
- # Set up a one-time listener to focus an item in the new view
- if WorkspaceStore.layoutMode() is 'split'
- unlisten = @_dataSource.listen =>
- if @_dataSource.loaded()
- Actions.setFocus(collection: 'thread', item: @_dataSource.get(0))
- unlisten()
+ threadsSubscription = FocusedPerspectiveStore.current().threads()
+ if threadsSubscription
+ @_dataSource = new ThreadListDataSource(threadsSubscription)
+ @_dataSourceUnlisten = @_dataSource.listen(@_onDataChanged, @)
+
+ # Set up a one-time listener to focus an item in the new view
+ if WorkspaceStore.layoutMode() is 'split'
+ unlisten = @_dataSource.listen =>
+ if @_dataSource.loaded()
+ Actions.setFocus(collection: 'thread', item: @_dataSource.get(0))
+ unlisten()
+ else
+ @_dataSource = new ListTabular.DataSource.Empty()
@trigger(@)
Actions.setFocus(collection: 'thread', item: null)
diff --git a/spec/list-selection-spec.coffee b/spec/list-selection-spec.coffee
index bb959a96c..628e4f377 100644
--- a/spec/list-selection-spec.coffee
+++ b/spec/list-selection-spec.coffee
@@ -1,8 +1,10 @@
_ = require 'underscore'
-Thread = require '../src/flux/models/thread'
-ListDataSource = require '../src/flux/stores/list-data-source'
-ListSelection = require '../src/flux/stores/list-selection'
+{Thread} = require 'nylas-exports'
+{ListTabular} = require 'nylas-component-kit'
+
+ListDataSource = ListTabular.DataSource
+ListSelection = ListTabular.Selection
describe "ListSelection", ->
beforeEach ->
diff --git a/spec/stores/focused-perspective-store-spec.coffee b/spec/stores/focused-perspective-store-spec.coffee
index b8bececc3..fee30ecad 100644
--- a/spec/stores/focused-perspective-store-spec.coffee
+++ b/spec/stores/focused-perspective-store-spec.coffee
@@ -40,7 +40,7 @@ describe "FocusedPerspectiveStore", ->
expect(FocusedPerspectiveStore.current().categories()).toEqual([@inboxCategory])
describe "_onFocusPerspective", ->
- it "should focus the category and trigger when Actions.focusCategory is called", ->
+ it "should focus the category and trigger", ->
FocusedPerspectiveStore._onFocusPerspective(@userFilter)
expect(FocusedPerspectiveStore.trigger).toHaveBeenCalled()
expect(FocusedPerspectiveStore.current().categories()).toEqual([@userCategory])
diff --git a/src/flux/stores/list-data-source.coffee b/src/components/list-data-source.coffee
similarity index 79%
rename from src/flux/stores/list-data-source.coffee
rename to src/components/list-data-source.coffee
index 44d33614a..b12a0c34d 100644
--- a/src/flux/stores/list-data-source.coffee
+++ b/src/components/list-data-source.coffee
@@ -2,9 +2,7 @@ _ = require 'underscore'
EventEmitter = require('events').EventEmitter
ListSelection = require './list-selection'
-module.exports =
class ListDataSource
-
constructor: ->
@_emitter = new EventEmitter()
@_cleanedup = false
@@ -17,6 +15,8 @@ class ListDataSource
@_emitter.emit('trigger', arg)
listen: (callback, bindContext) ->
+ unless callback instanceof Function
+ throw new Error("ListDataSource: You must pass a function to `listen`")
if @_cleanedup is true
throw new Error("ListDataSource: You cannot listen again after removing the last listener. This is an implementation detail.")
@@ -56,3 +56,17 @@ class ListDataSource
cleanup: ->
@selection.cleanup()
+
+class EmptyListDataSource extends ListDataSource
+ loaded: -> true
+ empty: -> true
+ get: (idx) -> null
+ getById: (id) -> null
+ indexOfId: (id) -> -1
+ count: -> 0
+ itemsCurrentlyInViewMatching: (matchFn) -> []
+ setRetainedRange: ({start, end}) ->
+
+ListDataSource.Empty = EmptyListDataSource
+
+module.exports = ListDataSource
diff --git a/src/flux/stores/list-selection.coffee b/src/components/list-selection.coffee
similarity index 92%
rename from src/flux/stores/list-selection.coffee
rename to src/components/list-selection.coffee
index d00049f70..811242ff1 100644
--- a/src/flux/stores/list-selection.coffee
+++ b/src/components/list-selection.coffee
@@ -1,14 +1,19 @@
-Model = require '../models/model'
-DatabaseStore = require './database-store'
_ = require 'underscore'
+Model = require '../flux/models/model'
+DatabaseStore = require '../flux/stores/database-store'
+
module.exports =
class ListSelection
- constructor: (@_view, @trigger) ->
+ constructor: (@_view, callback) ->
throw new Error("new ListSelection(): You must provide a view.") unless @_view
@_unlisten = DatabaseStore.listen(@_applyChangeRecord, @)
+ @_caches = {}
@_items = []
+ @trigger = =>
+ @_caches = {}
+ callback()
cleanup: ->
@_unlisten()
@@ -17,7 +22,9 @@ class ListSelection
@_items.length
ids: ->
- _.pluck(@_items, 'id')
+ # ListTabular asks for ids /a lot/. Cache this value and clear it on trigger.
+ @_caches['ids'] ?= _.pluck(@_items, 'id')
+ @_caches['ids']
items: -> _.clone(@_items)
diff --git a/src/components/list-tabular-item.cjsx b/src/components/list-tabular-item.cjsx
new file mode 100644
index 000000000..0674091eb
--- /dev/null
+++ b/src/components/list-tabular-item.cjsx
@@ -0,0 +1,63 @@
+_ = require 'underscore'
+React = require 'react/addons'
+{Utils} = require 'nylas-exports'
+
+class ListTabularItem extends React.Component
+ @displayName = 'ListTabularItem'
+ @propTypes =
+ metrics: React.PropTypes.object
+ columns: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
+ item: React.PropTypes.object.isRequired
+ itemProps: React.PropTypes.object
+ onSelect: React.PropTypes.func
+ onClick: React.PropTypes.func
+ onDoubleClick: React.PropTypes.func
+
+ # DO NOT DELETE unless you know what you're doing! This method cuts
+ # React.Perf.wasted-time from ~300msec to 20msec by doing a deep
+ # comparison of props before triggering a re-render.
+ shouldComponentUpdate: (nextProps, nextState) =>
+ if not Utils.isEqualReact(@props.item, nextProps.item) or @props.columns isnt nextProps.columns
+ @_columnCache = null
+ return true
+ if not Utils.isEqualReact(_.omit(@props, 'item'), _.omit(nextProps, 'item'))
+ return true
+ false
+
+ render: =>
+ className = "list-item list-tabular-item #{@props.itemProps?.className}"
+ props = _.omit(@props.itemProps ? {}, 'className')
+
+ # It's expensive to compute the contents of columns (format timestamps, etc.)
+ # We only do it if the item prop has changed.
+ @_columnCache ?= @_columns()
+
+
+ {@_columnCache}
+
+
+ _columns: =>
+ names = {}
+ for column in (@props.columns ? [])
+ if names[column.name]
+ console.warn("ListTabular: Columns do not have distinct names, will cause React error! `#{column.name}` twice.")
+ names[column.name] = true
+
+
+ {column.resolver(@props.item, @)}
+
+
+ _onClick: (event) =>
+ @props.onSelect?(@props.item, event)
+
+ @props.onClick?(@props.item, event)
+ if @_lastClickTime? and Date.now() - @_lastClickTime < 350
+ @props.onDoubleClick?(@props.item, event)
+
+ @_lastClickTime = Date.now()
+
+
+module.exports = ListTabularItem
diff --git a/src/components/list-tabular.cjsx b/src/components/list-tabular.cjsx
index 76c23423e..d0d4969fe 100644
--- a/src/components/list-tabular.cjsx
+++ b/src/components/list-tabular.cjsx
@@ -1,69 +1,16 @@
_ = require 'underscore'
React = require 'react/addons'
ScrollRegion = require './scroll-region'
+Spinner = require './spinner'
{Utils} = require 'nylas-exports'
+ListDataSource = require './list-data-source'
+ListSelection = require './list-selection'
+ListTabularItem = require './list-tabular-item'
+
class ListColumn
constructor: ({@name, @resolver, @flex, @width}) ->
-class ListTabularItem extends React.Component
- @displayName = 'ListTabularItem'
- @propTypes =
- metrics: React.PropTypes.object
- columns: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
- item: React.PropTypes.object.isRequired
- itemProps: React.PropTypes.object
- onSelect: React.PropTypes.func
- onClick: React.PropTypes.func
- onDoubleClick: React.PropTypes.func
-
- # DO NOT DELETE unless you know what you're doing! This method cuts
- # React.Perf.wasted-time from ~300msec to 20msec by doing a deep
- # comparison of props before triggering a re-render.
- shouldComponentUpdate: (nextProps, nextState) =>
- if not Utils.isEqualReact(@props.item, nextProps.item) or @props.columns isnt nextProps.columns
- @_columnCache = null
- return true
- if not Utils.isEqualReact(_.omit(@props, 'item'), _.omit(nextProps, 'item'))
- return true
- false
-
- render: =>
- className = "list-item list-tabular-item #{@props.itemProps?.className}"
- props = _.omit(@props.itemProps ? {}, 'className')
-
- # It's expensive to compute the contents of columns (format timestamps, etc.)
- # We only do it if the item prop has changed.
- @_columnCache ?= @_columns()
-
-
- {@_columnCache}
-
-
- _columns: =>
- names = {}
- for column in (@props.columns ? [])
- if names[column.name]
- console.warn("ListTabular: Columns do not have distinct names, will cause React error! `#{column.name}` twice.")
- names[column.name] = true
-
-
- {column.resolver(@props.item, @)}
-
-
- _onClick: (event) =>
- @props.onSelect?(@props.item, event)
-
- @props.onClick?(@props.item, event)
- if @_lastClickTime? and Date.now() - @_lastClickTime < 350
- @props.onDoubleClick?(@props.item, event)
-
- @_lastClickTime = Date.now()
-
-
class ListTabular extends React.Component
@displayName = 'ListTabular'
@propTypes =
@@ -79,16 +26,42 @@ class ListTabular extends React.Component
if not @props.itemHeight
throw new Error("ListTabular: You must provide an itemHeight - raising to avoid divide by zero errors.")
- @state =
- renderedRangeStart: -1
- renderedRangeEnd: -1
+ @state = @buildStateForRange(start: -1, end: -1)
componentDidMount: =>
window.addEventListener('resize', @onWindowResize, true)
+ @setupDataSource(@props.dataSource)
@updateRangeState()
componentWillUnmount: =>
window.removeEventListener('resize', @onWindowResize, true)
+ @_unlisten?()
+
+ componentWillReceiveProps: (nextProps) =>
+ if nextProps.dataSource isnt @props.dataSource
+ @setupDataSource(nextProps.dataSource)
+ @setState(@buildStateForRange(dataSource: nextProps.dataSource))
+
+ setupDataSource: (dataSource) =>
+ @_unlisten?()
+ @_unlisten = dataSource.listen =>
+ @setState(@buildStateForRange())
+
+ buildStateForRange: ({dataSource, start, end} = {}) =>
+ start ?= @state.renderedRangeStart
+ end ?= @state.renderedRangeEnd
+ dataSource ?= @props.dataSource
+
+ items = {}
+ [start..end].forEach (idx) =>
+ items[idx] = dataSource.get(idx)
+
+ renderedRangeStart: start
+ renderedRangeEnd: end
+ count: dataSource.count()
+ loaded: dataSource.loaded()
+ empty: dataSource.empty()
+ items: items
componentDidUpdate: (prevProps, prevState) =>
# If our view has been swapped out for an entirely different one,
@@ -120,7 +93,7 @@ class ListTabular extends React.Component
# Expand the start/end so that you can advance the keyboard cursor fast and
# we have items to move to and then scroll to.
rangeStart = Math.max(0, rangeStart - 2)
- rangeEnd = Math.min(rangeEnd + 2, @props.dataSource.count() + 1)
+ rangeEnd = Math.min(rangeEnd + 2, @state.count + 1)
# Final sanity check to prevent needless work
return if rangeStart is @state.renderedRangeStart and
@@ -132,46 +105,45 @@ class ListTabular extends React.Component
start: rangeStart
end: rangeEnd
- @setState
- renderedRangeStart: rangeStart
- renderedRangeEnd: rangeEnd
+ @setState(@buildStateForRange(start: rangeStart, end: rangeEnd))
render: =>
innerStyles =
- height: @props.dataSource.count() * @props.itemHeight
+ height: @state.count * @props.itemHeight
-
-
- {@_rows()}
-
-
+ emptyElement = false
+ if @props.emptyComponent
+ emptyElement = <@props.emptyComponent visible={@state.loaded and @state.empty} />
+
+
+
+
+ {@_rows()}
+
+
+
+ {emptyElement}
+
_rows: =>
- rows = []
+ [@state.renderedRangeStart..@state.renderedRangeEnd-1].map (idx) =>
+ item = @state.items[idx]
+ return false unless item
- for idx in [@state.renderedRangeStart..@state.renderedRangeEnd-1]
- item = @props.dataSource.get(idx)
- continue unless item
-
- itemProps = {}
- if @props.itemPropsProvider
- itemProps = @props.itemPropsProvider(item)
-
- rows.push
- rows
+
# Public: Scroll to the DOM node provided.
#
@@ -185,5 +157,7 @@ class ListTabular extends React.Component
ListTabular.Item = ListTabularItem
ListTabular.Column = ListColumn
+ListTabular.Selection = ListSelection
+ListTabular.DataSource = ListDataSource
module.exports = ListTabular
diff --git a/src/components/multiselect-list.cjsx b/src/components/multiselect-list.cjsx
index 3083ccb76..f93cfedd7 100644
--- a/src/components/multiselect-list.cjsx
+++ b/src/components/multiselect-list.cjsx
@@ -32,11 +32,7 @@ class MultiselectList extends React.Component
className: React.PropTypes.string.isRequired
columns: React.PropTypes.array.isRequired
itemPropsProvider: React.PropTypes.func.isRequired
- itemHeight: React.PropTypes.number.isRequired
- scrollTooltipComponent: React.PropTypes.func
- emptyComponent: React.PropTypes.func
keymapHandlers: React.PropTypes.object
- onDoubleClick: React.PropTypes.func
constructor: (@props) ->
@state = @_getStateFromStores()
@@ -103,33 +99,21 @@ class MultiselectList extends React.Component
props = @props.itemPropsProvider(item)
props.className ?= ''
props.className += " " + classNames
- 'selected': item.id in @state.selectedIds
+ 'selected': item.id in @props.dataSource.selection.ids()
'focused': @state.handler.shouldShowFocus() and item.id is @props.focusedId
'keyboard-cursor': @state.handler.shouldShowKeyboardCursor() and item.id is @props.keyboardCursorId
props['data-item-id'] = item.id
props
- emptyElement = []
- if @props.emptyComponent
- emptyElement = <@props.emptyComponent
- visible={@state.loaded and @state.empty}
- dataSource={@props.dataSource} />
-
-
-
-
-
- {emptyElement}
-
+
else
@@ -216,9 +200,6 @@ class MultiselectList extends React.Component
columns: props.columns
computedColumns: computedColumns
layoutMode: layoutMode
- selectedIds: props.dataSource.selection.ids()
- loaded: props.dataSource.loaded()
- empty: props.dataSource.empty()
# Public Methods
diff --git a/src/flux/stores/draft-count-store.coffee b/src/flux/stores/draft-count-store.coffee
deleted file mode 100644
index bda74a007..000000000
--- a/src/flux/stores/draft-count-store.coffee
+++ /dev/null
@@ -1,52 +0,0 @@
-_ = require 'underscore'
-Rx = require 'rx-lite'
-NylasStore = require 'nylas-store'
-Actions = require '../actions'
-Message = require '../models/message'
-Account = require '../models/account'
-DatabaseStore = require './database-store'
-AccountStore = require './account-store'
-FocusedPerspectiveStore = require './focused-perspective-store'
-
-###
-Public: The DraftCountStore exposes a simple API for getting the number of
-drafts in the user's account. If you plugin needs the number of drafts,
-it's more efficient to observe the DraftCountStore than retrieve the value
-yourself from the database.
-
-The DraftCountStore is only available in the main window.
-###
-
-if not NylasEnv.isMainWindow() and not NylasEnv.inSpecMode() then return
-
-class DraftCountStore extends NylasStore
-
- constructor: ->
- @_counts = {}
- @_total = 0
- @_disposable = Rx.Observable.fromQuery(
- DatabaseStore.findAll(Message).where([Message.attributes.draft.equal(true)])
- ).subscribe(@_onDraftsChanged)
-
- totalCount: ->
- @_total
-
- # Public: Returns the number of drafts for the given account
- count: (accountOrId)->
- return 0 unless accountOrId
- accountId = if accountOrId instanceof Account
- accountOrId.id
- else
- accountOrId
- @_counts[accountId]
-
- _onDraftsChanged: (drafts) =>
- @_total = 0
- @_counts = {}
- for account in AccountStore.accounts()
- @_counts[account.id] = _.where(drafts, accountId: account.id).length
- @_total += @_counts[account.id]
- @trigger()
-
-
-module.exports = new DraftCountStore()
diff --git a/src/flux/stores/focused-perspective-store.coffee b/src/flux/stores/focused-perspective-store.coffee
index 0f91cbd34..21c008631 100644
--- a/src/flux/stores/focused-perspective-store.coffee
+++ b/src/flux/stores/focused-perspective-store.coffee
@@ -33,8 +33,6 @@ class FocusedPerspectiveStore extends NylasStore
_onFocusPerspective: (perspective) =>
return if perspective.isEqual(@_current)
- if WorkspaceStore.Sheet.Threads
- Actions.selectRootSheet(WorkspaceStore.Sheet.Threads)
@_setPerspective(perspective)
_onFocusAccounts: (accountsOrIds) =>
diff --git a/src/flux/stores/observable-list-data-source.coffee b/src/flux/stores/observable-list-data-source.coffee
index 6fb32988a..c52261ff8 100644
--- a/src/flux/stores/observable-list-data-source.coffee
+++ b/src/flux/stores/observable-list-data-source.coffee
@@ -5,7 +5,8 @@ Message = require '../models/message'
QuerySubscriptionPool = require '../models/query-subscription-pool'
QuerySubscription = require '../models/query-subscription'
MutableQuerySubscription = require '../models/mutable-query-subscription'
-ListDataSource = require './list-data-source'
+
+{ListTabular} = require 'nylas-component-kit'
###
This class takes an observable which vends QueryResultSets and adapts it so that
@@ -14,7 +15,7 @@ you can make it the data source of a MultiselectList.
When the MultiselectList is refactored to take an Observable, this class should
go away!
###
-class ObservableListDataSource extends ListDataSource
+class ObservableListDataSource extends ListTabular.DataSource
constructor: ($resultSetObservable, @_setRetainedRange) ->
super
diff --git a/src/global/nylas-exports.coffee b/src/global/nylas-exports.coffee
index 37937503d..2e2aab7d8 100644
--- a/src/global/nylas-exports.coffee
+++ b/src/global/nylas-exports.coffee
@@ -118,7 +118,6 @@ class NylasExports
@require "ContactStore", 'flux/stores/contact-store'
@require "CategoryStore", 'flux/stores/category-store'
@require "WorkspaceStore", 'flux/stores/workspace-store'
- @require "DraftCountStore", 'flux/stores/draft-count-store'
@require "FileUploadStore", 'flux/stores/file-upload-store'
@require "MailRulesStore", 'flux/stores/mail-rules-store'
@require "ThreadCountsStore", 'flux/stores/thread-counts-store'
diff --git a/src/mailbox-perspective.coffee b/src/mailbox-perspective.coffee
index b0c395840..275b7dcdf 100644
--- a/src/mailbox-perspective.coffee
+++ b/src/mailbox-perspective.coffee
@@ -18,6 +18,9 @@ class MailboxPerspective
@forNothing: ->
new EmptyMailboxPerspective()
+ @forDrafts: (accountsOrIds) ->
+ new DraftsMailboxPerspective(accountsOrIds)
+
@forCategory: (category) ->
return @forNothing() unless category
new CategoryMailboxPerspective([category])
@@ -111,6 +114,20 @@ class SearchMailboxPerspective extends MailboxPerspective
false
+class DraftsMailboxPerspective extends MailboxPerspective
+ constructor: (@accountIds) ->
+ super(@accountIds)
+ @name = "Drafts"
+ @iconName = "drafts.png"
+ @drafts = true # The DraftListStore looks for this
+ @
+
+ threads: =>
+ null
+
+ canApplyToThreads: =>
+ false
+
class StarredMailboxPerspective extends MailboxPerspective
constructor: (@accountIds) ->
super(@accountIds)