Last active
December 8, 2020 10:24
-
-
Save hieunc229/ca6aeb507de42a933bafe31e0a174dd0 to your computer and use it in GitHub Desktop.
Object with version control, used for undo/redo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type ExampleTargetObject = { | |
components: { | |
[id: string]: { | |
name: string, | |
styles?: { [prop: string]: string } | |
} | |
} | |
}; | |
let data ={ components: { 'RandomDI': { name: "String", styles: { fontSize: "14px"} }}} | |
let obj = new VersionControlObject<ExampleTargetObject>(data); | |
let objRef = obj.data; | |
objRef.structures['jsd'] = { name: 'Number'}; | |
objRef.structures['RandomDI'].name = 'Date'; | |
objRef.structures['RandomDI'].value = new Date(); | |
objRef.structures['RandomDI'].styles.fontSize = `16px`; | |
objRef.structures['RandomDI'].styles.margin = `16px`; | |
console.log(obj.version, objRef); | |
obj.gotoVersion(1); | |
console.log(obj.version, objRef); | |
obj.gotoVersion(2); | |
console.log(obj.version, objRef); | |
obj.gotoVersion(3); | |
console.log(obj.version, objRef); | |
obj.gotoVersion(0); | |
console.log(obj.version, objRef); | |
obj.gotoVersion(1); | |
obj.gotoLastVersion(); | |
console.log(obj.version, objRef); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* VersionControlObject allow you versioning an object, | |
* which show number of updates, go back and ford between the version. | |
* --------------------------------------------------------------------------------------------------- | |
* This is a typescript version with minor updates based on trincot answer on stackoverflow | |
* https://stackoverflow.com/a/40498130/5775993 | |
*/ | |
export class VersionControlObject<T> { | |
targets: any[] = []; | |
version = 0; | |
hash: Map<any, any>; | |
handler: any; | |
data: T; | |
changeLog: any[]; | |
objTarget: any; | |
savedLength?: number; | |
constructor(obj: any, changeLog?: any[]) { | |
this.hash = new Map([[obj, []]]); | |
this.objTarget = obj; | |
this.handler = { | |
get: this.handlerGet, | |
set: this.update, | |
deleteProperty: this.update | |
}; | |
this.data = new Proxy(obj, this.handler); | |
this.changeLog = changeLog || []; | |
// apply change log | |
this.gotoLastVersion(); | |
} | |
private handlerGet = (target: any, property: string) => { | |
if (property === "__isProxy") { | |
return true; | |
} | |
let value = target[property]; | |
if (typeof value !== "object" || value.__isProxy) { | |
return value; | |
} | |
const isViolate = ['[object Object]', '[object Array]'] | |
.indexOf(Object.prototype.toString.call(value)) === -1; | |
if (isViolate) { | |
return value; | |
} | |
this.hash.set(value, this.hash.get(target).concat(property)); | |
return new Proxy(value, this.handler); | |
} | |
getVersion = () => { | |
return this.version; | |
} | |
getChangeLog = () => { | |
return this.changeLog | |
} | |
gotoVersion = (newVersion: number) => { | |
newVersion = Math.max(0, Math.min(this.changeLog.length, newVersion)); | |
let chg, target, path, property, val = newVersion > this.version ? 'newValue' : 'oldValue'; | |
while (this.version !== newVersion) { | |
if (this.version > newVersion) this.version--; | |
chg = this.changeLog[this.version]; | |
path = chg.path.slice(); | |
property = path.pop(); | |
// @ts-ignore | |
target = this.targets[this.version] || (this.targets[this.version] = path.reduce((o, p) => o[p], this.objTarget)); | |
if (chg.hasOwnProperty(val)) { | |
target[property] = chg[val]; | |
} else { | |
delete target[property]; | |
} | |
if (this.version < newVersion) this.version++; | |
} | |
return true; | |
} | |
gotoLastVersion = () => { | |
return this.gotoVersion(this.changeLog.length); | |
} | |
update = (...args: any[]) => { | |
let [target, property, value] = args; | |
this.gotoLastVersion(); // only last version can be modified | |
let change: any = { path: this.hash.get(target).concat([property]) }; | |
if (args.length > 2) change.newValue = value; | |
// Some care concerning the length property of arrays: | |
if (Array.isArray(target) && +property >= target.length && this.savedLength === undefined) { | |
this.savedLength = target.length; | |
} | |
if (property in target) { | |
if (property === 'length' && this.savedLength !== undefined) { | |
change.oldValue = this.savedLength; | |
this.savedLength = undefined; | |
} else { | |
change.oldValue = target[property]; | |
} | |
} | |
this.changeLog.push(change); | |
this.targets.push(target); | |
return this.gotoLastVersion(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment