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 0fce8a059f
commit 8f7f63af3a
12 changed files with 137 additions and 103 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -90,6 +90,14 @@ class Actions
### ###
@queueTask: ActionScopeWorkWindow @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 @undoTaskId: ActionScopeWorkWindow
### ###

View file

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

View file

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

View file

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

View file

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

View file

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