-
Notifications
You must be signed in to change notification settings - Fork 1
/
UndoRedoer.coffee
84 lines (76 loc) · 2.43 KB
/
UndoRedoer.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
`/*!
* UndoRedoer
* @description Simple and robust CoffeeScript/JavaScript library for undo/redo features on plain state object. Full test coverage. For Node.JS and Browser (with AMD support).
* @author Se7enSky studio <[email protected]>
* @url http://www.se7ensky.com/
* @version 1.2.4
* @repository https://github.com/Se7enSky/UndoRedoer
* @license MIT
*/
`
((root, factory) ->
if typeof exports is 'object'
module.exports = factory require 'lodash'
else if typeof define is 'function' and define.amd
define ['lodash'], factory
else
root.returnExports = factory root._
) @, (_) ->
class UndoRedoer
constructor: (@state = {}, @changes = []) ->
@resetCursor()
@reverseChanges = []
pushChanges: (change) ->
@changes.push change
reverseChange = @mergeChange @state, change
@reverseChanges.push reverseChange
@resetCursor()
undo: ->
throw new Error 'undo is disabled' if not @canUndo()
reverseChange = @reverseChanges[@cursor - 1]
@mergeChange @state, reverseChange
@cursor--
reverseChange
redo: ->
throw new Error 'redo is disabled' if not @canRedo()
forwardChange = @changes[@cursor]
@mergeChange @state, forwardChange
@cursor++
return forwardChange
canUndo: -> @cursor > 0
canRedo: -> @cursor < @changes.length
dirty: -> @cursor < @changes.length
clearRedo: -> @changes.splice @cursor, @changes.length - @cursor
save: -> @clearRedo()
resetCursor: -> @cursor = @changes.length
###
@param {Object} dst The destination object.
@param {Object} changes The source object.
@returns reverse change
###
mergeChange: (dst, change) ->
reverseChange = {}
if _(change).isObject() and typeof change isnt 'function'
for key, value of change
if typeof value is 'undefined' # delete
if _(dst).has key
reverseChange[key] = dst[key]
delete dst[key]
else
# ignore deleting inexistent entries
else
if _(dst).has key
if typeof value in ['boolean', 'string', 'number']
reverseChange[key] = dst[key]
dst[key] = value
else if _(value).isArray()
reverseChange[key] = _(dst[key]).clone()
dst[key] = _(value).clone()
else if _(value).isObject() and typeof value isnt 'function'
reverseChange[key] = @mergeChange dst[key], value
else
# ignore incompatible objects
else
reverseChange[key] = undefined
dst[key] = _(value).clone()
return reverseChange