Mailspring/internal_packages/thread-list/lib/thread-store.coffee
Ben Gotow d15b5080fb fix(stores): FocusedThreadStore, FocusedTagStore, speed improvements
Summary:
ThreadStore is now in the thread-list package.

Account sidebar no longer has random stuff dealing with search, no longer maintains selection apart from FocusedTagStore

Thread nav buttons are in the thread package

Account sidebar pulls selection from FocusedTagStore, no longer fires an Action to select Inbox, which was weird

Thread store is in thread-list package. No longer has any selection concept -> moved to FocusedThreadStore. Also looks at database changes to do "shallow" updates when only threads and not messages have changed, or when only messages of a few...

...threads have changed.

WorkspaceStore now handles both pushing AND popping the thread sheet. So all sheet behavior is here.

ThreadStore => FocusedThreadStore, selectThreadId => selectThread

Include all models in inbox-exports

It actually takes a long time to call Promise.reject because Bluebird generates stack traces. Resolve with false instead (100msec faster!)

Cache the model class map. All the requires take ~20msec per call to this method

ThreadList looks at FocusedThreadStore for selection

FocusedThreadStore, FocusedTagStore

Updated specs

Test Plan: Run tests

Reviewers: evan

Reviewed By: evan

Differential Revision: https://review.inboxapp.com/D1384
2015-03-31 17:19:17 -07:00

210 lines
6 KiB
CoffeeScript

Reflux = require 'reflux'
_ = require 'underscore-plus'
{DatabaseStore,
NamespaceStore,
WorkspaceStore,
AddRemoveTagsTask,
FocusedTagStore,
FocusedThreadStore,
Actions,
Utils,
Thread,
Message} = require 'inbox-exports'
ThreadStore = Reflux.createStore
init: ->
@_resetInstanceVars()
@listenTo Actions.searchQueryCommitted, @_onSearchCommitted
@listenTo Actions.selectLayoutMode, @_autoselectForLayoutMode
@listenTo Actions.archiveAndPrevious, @_onArchiveAndPrev
@listenTo Actions.archiveAndNext, @_onArchiveAndNext
@listenTo Actions.archiveCurrentThread, @_onArchive
@listenTo DatabaseStore, @_onDataChanged
@listenTo FocusedTagStore, @_onFocusedTagChanged
@listenTo NamespaceStore, @_onNamespaceChanged
@fetchFromCache()
@_lastQuery = null
_resetInstanceVars: ->
@_items = []
@_namespaceId = null
@_searchQuery = null
@_itemsLoading = false
itemsLoading: -> @_itemsLoading
fetchFromCache: (options = {}) ->
tagId = FocusedTagStore.tagId()
start = Date.now()
# we don't load search results from cache
return unless @_namespaceId
return if @_searchQuery or !tagId
# If we know that only a Thread model has changed in the database,
# or that only messages of a few specific threads have changed,
# populate a hash of message metadata that can save us queries later.
knownMessages = {}
if options.shallow
for item in @_items
continue if options.invalid and item.id in options.invalid
knownMessages[item.id] = item.messageMetadata
matchers = []
matchers.push Thread.attributes.namespaceId.equal(@_namespaceId)
if tagId and tagId isnt "*"
matchers.push Thread.attributes.tags.contains(tagId)
DatabaseStore.findAll(Thread).where(matchers).limit(100).then (items) =>
# Now, fetch the messages for each thread. We could do this with a
# complex join, but then we'd get thread columns repeated over and over.
# This is reasonably fast because we don't store message bodies in messages
# anymore.
itemMessagePromises = {}
for item in items
itemMessagePromises[item.id] ?= knownMessages[item.id]
itemMessagePromises[item.id] ?= DatabaseStore.findAll(Message, [Message.attributes.threadId.equal(item.id)])
Promise.props(itemMessagePromises).then (results) =>
for item in items
item.messageMetadata = results[item.id]
# Prevent anything from mutating these objects or their nested objects.
# Accidentally modifying items somewhere downstream (in a component)
# can trigger awful re-renders
Utils.modelFreeze(item)
@_items = items
@_autoselectForLayoutMode()
console.log("Thread list refresh took #{Date.now() - start} msec.\
Shallow: #{options?.shallow}. Invalid: #{options?.invalid?.length}")
# If we've loaded threads, remove the loading indicator.
# If there are no results, wait for the API query to finish
if @_items.length > 0
@_itemsLoading = false
@trigger()
fetchFromAPI: ->
return unless @_namespaceId
@_itemsLoading = true
@trigger()
doneLoading = =>
return unless @_itemsLoading
@_itemsLoading = false
@trigger()
if @_searchQuery
atom.inbox.getThreadsForSearch @_namespaceId, @_searchQuery, (items) =>
@_items = items
doneLoading()
else
params = {}
tagId = FocusedTagStore.tagId()
if tagId and tagId isnt "*"
params = {tag: tagId}
atom.inbox.getThreads(@_namespaceId, params, {success: doneLoading, error: doneLoading})
# Inbound Events
_onNamespaceChanged: ->
@_namespaceId = NamespaceStore.current()?.id
@_items = []
@trigger(@)
Actions.focusThread(null)
@fetchFromCache()
@fetchFromAPI()
_onDataChanged: (change) ->
if change.objectClass is Thread.name
@fetchFromCache({shallow: true})
if change.objectClass is Message.name
threadIds = _.uniq _.map change.objects, (msg) -> msg.threadId
@fetchFromCache({shallow: true, invalid: threadIds})
_onSearchCommitted: (query) ->
if query.length > 0
@_searchQuery = query
@_items = []
@trigger()
else
return if not @_lastQuery? or @_lastQuery.length == 0
@_searchQuery = null
@fetchFromCache()
@_lastQuery = query
@fetchFromAPI()
_onFocusedTagChanged: (tag) ->
@_items = []
@trigger()
@fetchFromCache()
@fetchFromAPI()
_onArchive: ->
@_archiveAndShiftBy('auto')
_onArchiveAndPrev: ->
@_archiveAndShiftBy(-1)
_onArchiveAndNext: ->
@_archiveAndShiftBy(1)
_archiveAndShiftBy: (offset) ->
layoutMode = WorkspaceStore.selectedLayoutMode()
selected = FocusedThreadStore.thread()
return unless selected
# Determine the index of the current thread
index = -1
for item, idx in @_items
if item.id is selected.id
index = idx
break
return if index is -1
# Archive the current thread
task = new AddRemoveTagsTask(selected.id, ['archive'], ['inbox'])
Actions.postNotification({message: "Archived thread", type: 'success'})
Actions.queueTask(task)
if offset is 'auto'
if layoutMode is 'list'
# If the user is in list mode, return to the thread lit
Actions.focusThread(null)
return
else if layoutMode is 'split'
# If the user is in split mode, automatically select another
# thead when they archive the current one. We move up if the one above
# the current thread is unread. Otherwise move down.
if @_items[index - 1]?.isUnread()
offset = -1
else
offset = 1
index = Math.min(Math.max(index + offset, 0), @_items.length - 1)
Actions.focusThread(@_items[index])
_autoselectForLayoutMode: ->
selectedId = FocusedThreadStore.threadId()
if WorkspaceStore.selectedLayoutMode() is "split" and not selectedId
_.defer => Actions.focusThread(@_items[0])
# Accessing Data
items: ->
@_items
module.exports = ThreadStore