Skip to content

Instantly share code, notes, and snippets.

@mindfullsilence
Last active March 10, 2021 17:38
Show Gist options
  • Save mindfullsilence/839f59a41d37ef79f6742796f7060577 to your computer and use it in GitHub Desktop.
Save mindfullsilence/839f59a41d37ef79f6742796f7060577 to your computer and use it in GitHub Desktop.
Object history tracking mixin with undo and redo abilities

Wrap any class with this mixin and it will track the objects history, including all property changes on the object, and gives the ability to undo and redo.

class Person extends KeepsHistory(class {}) {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
}

let jimmy = new Person('jimmy', 0)
jimmy.age = 50

console.log(jimmy.age) // 50
jimmy.undo()
console.log(jimmy.age) // 0
jimmy.redo()
console.log(jimmy.age) // 50
jimmy.age = 10
jimmy.age = 20
jimmy.age = 30
console.log(jimmy.age) // 30
jimmy.undo(2) // undo twice
console.log(jimmy.age) // 10
jimmy.redo(2) // redo twice
console.log(jimmy.age) // 30
const log = Symbol('log')
const present = Symbol('present')
const observer = Symbol('observer')
const real = Symbol('real')
const KeepsHistory = superclass => class extends superclass {
;[log] = []
;[present] = null
;[observer] = undefined
;[real] = undefined
constructor(...args) {
super(...args)
this[real] = this
this[observer] = new Proxy(this, {
set: (obj, prop, value) => {
let changeDescriptor = {
old: obj[prop],
new: value,
prop: prop
}
this[log].splice(this[present] + 1)
this[log].push(changeDescriptor)
this[present] = this[log].length - 1
this[prop] = value
return true
}
})
return this[observer]
}
undo(times = 1) {
times = Math.max(times, 1)
times = Math.min(times, this[present])
while(times) {
let changeDescriptor = this[log][this[present]]
this[real][changeDescriptor.prop] = changeDescriptor.old
this[real][present] = Math.max(0, this[present] - 1)
times--
}
return this[observer]
}
redo(times = 1) {
times = Math.max(times, 1)
times = Math.min(times, this[log].length - 1 - this[present])
while(times) {
this[real][present] = Math.min(this[log].length - 1, this[present] + 1)
let changeDescriptor = this[log][this[present]]
this[real][changeDescriptor.prop] = changeDescriptor.new
times--
}
return this[observer]
}
}
export { KeepsHistory }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment