Code coverage report for code/utils/undo-redo.coffee

Statements: 52.5% (63 / 120)      Branches: 23.33% (7 / 30)      Functions: 42.42% (14 / 33)      Lines: 52.22% (47 / 90)      Ignored: none     

All files » code/utils/ » undo-redo.coffee
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162  1   1   1   1   1 1 1 1 1 1 1                                 15   15 15   15     15 15 15     15 15 15 15 15                                                                 1 1 1 1 1                                         16 16     15     16                 1 15     15 15 15 15       15       1                       1 1 8 8 8 8   1        
# based on https://github.com/jzaefferer/undo/blob/master/undo.js
CodapConnect = require '../models/codap-connect'
 
DEFAULT_CONTEXT_NAME = 'building-models'
 
internalEndCommandBatchAction = Reflux.createAction()
 
class Manager
  constructor: (options = {}) ->
    {@debug} = options
    @commands = []
    @stackPosition = -1
    @savePosition = -1
    @changeListeners = []
    @currentBatch = null
    internalEndCommandBatchAction.listen @_finalizeEndComandBatch, @
 
  startCommandBatch: ->
    @currentBatch = new CommandBatch() unless @currentBatch
 
  endCommandBatch: ->
    # calling this via an action pushes it to the end of the
    # command stack, allowing any other commands to execute first
    internalEndCommandBatchAction()
 
  _finalizeEndComandBatch: ->
    if @currentBatch
      @commands.push @currentBatch
      @stackPosition++
      @currentBatch = null
 
  createAndExecuteCommand: (name, methods) ->
    result = @execute (new Command name, methods)
 
    codapConnect = CodapConnect.instance DEFAULT_CONTEXT_NAME
    codapConnect.sendUndoableActionPerformed()
 
    result
 
  execute: (command) ->
    @_clearRedo()
    result = command.execute @debug
    Iif @currentBatch
      @currentBatch.push command
    else
      @commands.push command
      @stackPosition++
    @_changed()
    E@log() if @debug
    result
 
  undo: ->
    if @canUndo()
      result = @commands[@stackPosition].undo @debug
      @stackPosition--
      @_changed()
      @log() if @debug
      result
    else
      false
 
  canUndo: ->
    return @stackPosition >= 0
 
  redo: ->
    if @canRedo()
      @stackPosition++
      result = @commands[@stackPosition].redo @debug
      @_changed()
      @log() if @debug
      result
    else
      false
 
  canRedo: ->
    return @stackPosition < @commands.length - 1
 
  save: ->
    @savePosition = @stackPosition
    @_changed()
 
  clearHistory: ->
    @commands = []
    @stackPosition = -1
    @savePosition = -1
    @_changed()
    E@log() if @debug
 
  dirty: ->
    return @stackPosition isnt @savePosition
 
  saved: ->
    @savePosition isnt -1
 
  revertToOriginal: ->
    @undo() while @canUndo()
 
  revertToLastSave: ->
    if @stackPosition > @savePosition
      @undo() while @dirty()
    else if @stackPosition < @savePosition
      @redo() while @dirty()
 
  addChangeListener: (listener) ->
    @changeListeners.push listener
 
  log: ->
    log.info "Undo Stack: [#{(_.pluck (@commands.slice 0, @stackPosition + 1), 'name').join ', '}]"
    log.info "Redo Stack: [#{(_.pluck (@commands.slice @stackPosition + 1), 'name').join ', '}]"
 
  _clearRedo: ->
    @commands = @commands.slice 0, @stackPosition + 1
 
  _changed: ->
    Iif @changeListeners.length > 0
      status =
        dirty: @dirty()
        canUndo: @canUndo()
        canRedo: @canRedo()
        saved: @saved()
      for listener in @changeListeners
        listener status
 
class Command
  constructor: (@name, @methods) -> undefined
 
  _call: (method, debug, via) ->
    Eif debug
      log.info("Command: #{@name}.#{method}()#{Iif via then " via #{via}" else ''}")
    Eif @methods.hasOwnProperty method
      @methods[method]()
    else
      throw new Error "Undefined #{method} method for #{@name} command"
 
  execute: (debug) -> @_call 'execute', debug
  undo: (debug) -> @_call 'undo', debug
  redo: (debug) -> @_call 'execute', debug, 'redo'
 
class CommandBatch
  constructor: ->
    @commands = []
 
  push: (command) ->
    @commands.push command
 
  undo: (debug) ->
    command.undo(debug) for command in @commands by -1
  redo: (debug) ->
    command.redo(debug) for command in @commands
 
instances = {}
instance  = (opts={}) ->
  {contextName, debug} = opts
  contextName ||= DEFAULT_CONTEXT_NAME
  instances[contextName] ||= new Manager(opts)
  instances[contextName]
 
module.exports =
  instance: instance
  constructor: Manager
  command: Command