Skip to content

Instantly share code, notes, and snippets.

@hieunc229
Last active December 8, 2020 10:24
Show Gist options
  • Save hieunc229/ca6aeb507de42a933bafe31e0a174dd0 to your computer and use it in GitHub Desktop.
Save hieunc229/ca6aeb507de42a933bafe31e0a174dd0 to your computer and use it in GitHub Desktop.
Object with version control, used for undo/redo
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);
/**
* 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