TaskFactory now returns N tasks for performing standard actions, undo uses arrays

This commit is contained in:
Ben Gotow 2016-01-21 13:46:04 -08:00
parent 979c29e24f
commit 7458fdd4db
12 changed files with 137 additions and 103 deletions

View file

@ -24,10 +24,10 @@ class ThreadArchiveButton extends React.Component
_onArchive: (e) =>
return unless DOMUtils.nodeIsVisible(e.currentTarget)
task = TaskFactory.taskForArchiving
tasks = TaskFactory.tasksForArchiving
threads: [@props.thread],
fromPerspective: FocusedPerspectiveStore.current()
Actions.queueTask(task)
Actions.queueTasks(tasks)
Actions.popSheet()
e.stopPropagation()

View file

@ -26,10 +26,10 @@ class ThreadTrashButton extends React.Component
_onRemove: (e) =>
return unless DOMUtils.nodeIsVisible(e.currentTarget)
task = TaskFactory.taskForMovingToTrash
tasks = TaskFactory.tasksForMovingToTrash
threads: [@props.thread],
fromPerspective: FocusedPerspectiveStore.current()
Actions.queueTask(task)
Actions.queueTasks(tasks)
Actions.popSheet()
e.stopPropagation()

View file

@ -28,10 +28,10 @@ class ThreadBulkArchiveButton extends React.Component
</button>
_onArchive: =>
task = TaskFactory.taskForArchiving
tasks = TaskFactory.tasksForArchiving
threads: @props.selection.items(),
fromPerspective: FocusedPerspectiveStore.current()
Actions.queueTask(task)
Actions.queueTasks(tasks)
class ThreadBulkTrashButton extends React.Component
@displayName: 'ThreadBulkTrashButton'
@ -52,10 +52,10 @@ class ThreadBulkTrashButton extends React.Component
</button>
_onRemove: =>
task = TaskFactory.taskForMovingToTrash
tasks = TaskFactory.tasksForMovingToTrash
threads: @props.selection.items(),
fromPerspective: FocusedPerspectiveStore.current()
Actions.queueTask(task)
Actions.queueTasks(tasks)
class ThreadBulkStarButton extends React.Component

View file

@ -25,10 +25,10 @@ class ThreadArchiveQuickAction extends React.Component
newProps.thread.id isnt @props?.thread.id
_onArchive: (event) =>
task = TaskFactory.taskForArchiving
tasks = TaskFactory.tasksForArchiving
threads: [@props.thread]
fromPerspective: FocusedPerspectiveStore.current()
Actions.queueTask(task)
Actions.queueTasks(tasks)
# Don't trigger the thread row click
event.stopPropagation()
@ -54,10 +54,10 @@ class ThreadTrashQuickAction extends React.Component
newProps.thread.id isnt @props?.thread.id
_onRemove: (event) =>
task = TaskFactory.taskForMovingToTrash
tasks = TaskFactory.tasksForMovingToTrash
threads: [@props.thread]
fromPerspective: FocusedPerspectiveStore.current()
Actions.queueTask(task)
Actions.queueTasks(tasks)
# Don't trigger the thread row click
event.stopPropagation()

View file

@ -176,19 +176,19 @@ class ThreadList extends React.Component
if threads
if backspaceDelete
if FocusedPerspectiveStore.current().canTrashThreads()
removeMethod = TaskFactory.taskForMovingToTrash
removeMethod = TaskFactory.tasksForMovingToTrash
else
return
else
if FocusedPerspectiveStore.current().canArchiveThreads()
removeMethod = TaskFactory.taskForArchiving
removeMethod = TaskFactory.tasksForArchiving
else
removeMethod = TaskFactory.taskForMovingToTrash
removeMethod = TaskFactory.tasksForMovingToTrash
task = removeMethod
tasks = removeMethod
threads: threads
fromPerspective: FocusedPerspectiveStore.current()
Actions.queueTask(task)
Actions.queueTasks(tasks)
Actions.popSheet()
@ -196,20 +196,20 @@ class ThreadList extends React.Component
return unless FocusedPerspectiveStore.current().canArchiveThreads()
threads = @_threadsForKeyboardAction()
if threads
task = TaskFactory.taskForArchiving
tasks = TaskFactory.tasksForArchiving
threads: threads
fromPerspective: FocusedPerspectiveStore.current()
Actions.queueTask(task)
Actions.queueTasks(tasks)
Actions.popSheet()
_onDeleteItem: =>
return unless FocusedPerspectiveStore.current().canTrashThreads()
threads = @_threadsForKeyboardAction()
if threads
task = TaskFactory.taskForMovingToTrash
tasks = TaskFactory.tasksForMovingToTrash
threads: threads
fromPerspective: FocusedPerspectiveStore.current()
Actions.queueTask(task)
Actions.queueTasks(tasks)
Actions.popSheet()
_onSelectRead: =>

View file

@ -33,12 +33,12 @@ class UndoRedoComponent extends React.Component
), 3000
_getStateFromStores: ->
task = UndoRedoStore.getMostRecentTask()
tasks = UndoRedoStore.getMostRecent()
show = false
if task
if tasks
show = true
return {show, task}
return {show, tasks}
componentWillMount: ->
@_unsubscribe = UndoRedoStore.listen(@_onChange)
@ -65,7 +65,7 @@ class UndoRedoComponent extends React.Component
if @state.show
<div className="undo-redo" onMouseEnter={@_clearTimeout} onMouseLeave={@_setNewTimeout}>
<div className="undo-redo-message-wrapper">
{@state.task.description()}
{@state.tasks.map((t) -> t.description()).join(', ')}
</div>
<div className="undo-redo-action-wrapper" onClick={@_onClick}>
<RetinaImg name="undo-icon@2x.png"
@ -81,6 +81,6 @@ class UndoRedoComponent extends React.Component
@_hide()
_hide: =>
@setState({show: false, task: null})
@setState({show: false, tasks: null})
module.exports = UndoRedoComponent

View file

@ -90,6 +90,14 @@ class Actions
###
@queueTask: ActionScopeWorkWindow
###
Public: Queue multiple {Task} objects to the {TaskQueue}, which should be
undone as a single user action.
*Scope: Work Window*
###
@queueTasks: ActionScopeWorkWindow
@undoTaskId: ActionScopeWorkWindow
###

View file

@ -88,7 +88,9 @@ class QuerySubscription
mustRefetchAllIds = true if @_itemSortOrderHasChanged(oldItem, item)
if impactCount > 0
@_set = null if mustRefetchAllIds
if mustRefetchAllIds
@log("Clearing result set - mustRefetchAllIds")
@_set = null
@update()
_itemSortOrderHasChanged: (old, updated) ->
@ -102,6 +104,9 @@ class QuerySubscription
return false
log: (msg) =>
console.log(msg) if @_query._klass.name is 'Thread'
update: =>
version = @_version += 1
@ -116,18 +121,23 @@ class QuerySubscription
entireModels = not @_set or @_set.modelCacheCount() is 0
Promise.each ranges, (range) =>
return unless version is @_version
@_fetchRange(range, {entireModels})
return @log("Update (#{version}) - Cancelled @ Step 0") unless version is @_version
@log("Update (#{version}) - Fetching range #{range}")
@_fetchRange(range, {entireModels, version})
.then =>
return unless version is @_version
return @log("Update (#{version}) - Cancelled @ Step 1") unless version is @_version
ids = @_set.ids().filter (id) => not @_set.modelWithId(id)
return if ids.length is 0
return DatabaseStore.findAll(@_query._klass, {id: ids}).then(@_appendToModelCache)
return @log("Update (#{version}) - No missing Ids") if ids.length is 0
@log("Update (#{version}) - Fetching missing Ids: #{ids}")
return DatabaseStore.findAll(@_query._klass, {id: ids}).then (models) =>
@log("Update (#{version}) - Fetched missing Ids")
@_set.replaceModel(m) for m in models
.then =>
return unless version is @_version
return @log("Update (#{version}) - Cancelled @ Step 2") unless version is @_version
@log("Update (#{version}) - Triggering...")
@_createResultAndTrigger()
_fetchRange: (range, {entireModels} = {}) ->
_fetchRange: (range, {entireModels, version} = {}) ->
rangeQuery = undefined
unless range.isInfinite()
@ -141,7 +151,12 @@ class QuerySubscription
rangeQuery ?= @_query
DatabaseStore.run(rangeQuery, {format: false}).then (results) =>
@_set = null unless @_set?.range().isContiguousWith(range)
if version and version isnt @_version
return @log("Update (#{version}) - fetchRange Cancelled")
unless @_set?.range().isContiguousWith(range)
@log("Clearing result set - #{range} isnt contiguous with #{@_set?.range()}")
@_set = null
@_set ?= new MutableQueryResultSet()
if entireModels

View file

@ -233,7 +233,7 @@ class ModelQuery
formatResultObjects: (objects) ->
return objects[0] if @_returnOne
return objects
return [].concat(objects)
# Query SQL Building

View file

@ -76,14 +76,14 @@ class TaskQueue
@_restoreQueue()
@listenTo(Actions.queueTask, @enqueue)
@listenTo(Actions.undoTaskId, @enqueueUndoOfTaskId)
@listenTo(Actions.dequeueTask, @dequeue)
@listenTo(Actions.dequeueAllTasks, @dequeueAll)
@listenTo(Actions.dequeueMatchingTask, @dequeueMatching)
@listenTo(Actions.clearDeveloperConsole, @clearCompleted)
@listenTo Actions.queueTask, @enqueue
@listenTo Actions.queueTasks, (tasks) =>
@enqueue(t) for t in tasks
@listenTo Actions.undoTaskId, @enqueueUndoOfTaskId
@listenTo Actions.dequeueTask, @dequeue
@listenTo Actions.dequeueAllTasks, @dequeueAll
@listenTo Actions.dequeueMatchingTask, @dequeueMatching
@listenTo Actions.clearDeveloperConsole, @clearCompleted
@listenTo Actions.longPollConnected, =>
@_processQueue()

View file

@ -16,32 +16,41 @@ class UndoRedoStore
@_undo = []
@_redo = []
@listenTo(Actions.queueTask, @_onTaskQueued)
@listenTo(Actions.queueTask, @_onQueue)
@listenTo(Actions.queueTasks, @_onQueue)
NylasEnv.commands.add('body', {'core:undo': => @undo() })
NylasEnv.commands.add('body', {'core:redo': => @redo() })
NylasEnv.commands.add('body', {'core:undo': @undo })
NylasEnv.commands.add('body', {'core:redo': @redo })
_onTaskQueued: (task) =>
if task.canBeUndone()
_onQueue: (tasks) =>
tasks = [tasks] unless tasks instanceof Array
undoable = _.every tasks, (t) -> t.canBeUndone()
if undoable
@_redo = []
@_undo.push(task)
@trigger() unless task._isReverting
@_undo.push(tasks)
@trigger()
undo: =>
topTask = @_undo.pop()
return unless topTask
topTasks = @_undo.pop()
return unless topTasks
@trigger()
Actions.undoTaskId(topTask.id)
@_redo.push(topTask.createIdenticalTask())
for task in topTasks
Actions.undoTaskId(task.id)
redoTasks = topTasks.map (t) -> t.createIdenticalTask()
@_redo.push(redoTasks)
redo: =>
redoTask = @_redo.pop()
return unless redoTask
Actions.queueTask(redoTask)
redoTasks = @_redo.pop()
return unless redoTasks
Actions.queueTasks(redoTasks)
getMostRecentTask: =>
getMostRecent: =>
for idx in [@_undo.length-1...-1]
return @_undo[idx] unless @_undo[idx]._isReverting
allReverting = _.every @_undo[idx], (t) -> t._isReverting
return @_undo[idx] unless allReverting
print: ->
console.log("Undo Stack")

View file

@ -8,27 +8,43 @@ CategoryStore = require '../stores/category-store'
class TaskFactory
taskForApplyingCategory: ({threads, fromPerspective, category, exclusive}) =>
# TODO Can not apply to threads across more than one account for now
account = AccountStore.accountForItems(threads)
return unless account?
tasksForApplyingCategories: ({threads, categoriesToRemove, categoryToAdd}) =>
byAccount = {}
tasks = []
if account.usesFolders()
return null unless category
return new ChangeFolderTask
folder: category
threads: threads
else
labelsToRemove = []
if exclusive
currentLabel = fromPerspective?.category()
currentLabel ?= CategoryStore.getStandardCategory(account, "inbox")
labelsToRemove = [currentLabel]
for thread in threads
accountId = thread.accountId
byAccount[accountId] ?=
categoriesToRemove: categoriesToRemove?(accountId) ? []
categoryToAdd: categoryToAdd(accountId)
threads: []
byAccount[accountId].threads.push(thread)
return new ChangeLabelsTask
threads: threads
labelsToRemove: labelsToRemove
labelsToAdd: [category]
for accountId, {categoryToAdd, categoriesToRemove, threads} of byAccount
continue unless categoryToAdd and categoriesToRemove
account = AccountStore.accountForId(accountId)
if account.usesFolders()
tasks.push new ChangeFolderTask
folder: categoryToAdd
threads: threads
else
tasks.push new ChangeLabelsTask
threads: threads
labelsToRemove: categoriesToRemove
labelsToAdd: [categoryToAdd]
return tasks
taskForApplyingCategory: ({threads, category}) =>
tasks = @tasksForApplyingCategories
threads: threads
categoryToAdd: (accountId) -> category
if tasks.length > 1
throw new Error("taskForApplyingCategory: Threads must be from the same account.")
return tasks[0]
taskForRemovingCategory: ({threads, fromPerspective, category, exclusive}) =>
# TODO Can not apply to threads across more than one account for now
@ -51,21 +67,17 @@ class TaskFactory
labelsToRemove: [category]
labelsToAdd: labelsToAdd
taskForArchiving: ({threads, fromPerspective}) =>
category = @_getArchiveCategory(threads)
@taskForApplyingCategory({threads, fromPerspective, category, exclusive: true})
tasksForArchiving: ({threads, fromPerspective}) =>
@tasksForApplyingCategories
threads: threads,
categoriesToRemove: (accountId) -> _.filter(fromPerspective.categories(), _.matcher({accountId}))
categoryToAdd: (accountId) -> CategoryStore.getArchiveCategory(accountId)
taskForUnarchiving: ({threads, fromPerspective}) =>
category = @_getArchiveCategory(threads)
@taskForRemovingCategory({threads, fromPerspective, category, exclusive: true})
taskForMovingToTrash: ({threads, fromPerspective}) =>
category = @_getTrashCategory(threads)
@taskForApplyingCategory({threads, fromPerspective, category, exclusive: true})
taskForMovingFromTrash: ({threads, fromPerspective}) =>
category = @_getTrashCategory(threads)
@taskForRemovingCategory({threads, fromPerspective, category, exclusive: true})
tasksForMovingToTrash: ({threads, fromPerspective}) =>
@tasksForApplyingCategories
threads: threads,
categoriesToRemove: (accountId) -> _.filter(fromPerspective.categories(), _.matcher({accountId}))
categoryToAdd: (accountId) -> CategoryStore.getTrashCategory(accountId)
taskForInvertingUnread: ({threads}) =>
unread = _.every threads, (t) -> _.isMatch(t, {unread: false})
@ -75,14 +87,4 @@ class TaskFactory
starred = _.every threads, (t) -> _.isMatch(t, {starred: false})
return new ChangeStarredTask({threads, starred})
_getArchiveCategory: (threads) =>
account = AccountStore.accountForItems(threads)
return unless account?
CategoryStore.getArchiveCategory(account)
_getTrashCategory: (threads) =>
account = AccountStore.accountForItems(threads)
return unless account?
CategoryStore.getTrashCategory(account)
module.exports = new TaskFactory