116 lines
2.8 KiB
JavaScript
116 lines
2.8 KiB
JavaScript
import path from '../../path.js';
|
|
import isArray from '../../is-array.js';
|
|
import isObject from '../../is-object.js';
|
|
import {MUTABLE_ARRAY_METHODS} from '../methods/array.js';
|
|
import {MUTABLE_SET_METHODS} from '../methods/set.js';
|
|
import {MUTABLE_MAP_METHODS} from '../methods/map.js';
|
|
import {IMMUTABLE_OBJECT_METHODS} from '../methods/object.js';
|
|
|
|
export default class CloneObject {
|
|
constructor(value, path, argumentsList, hasOnValidate) {
|
|
this._path = path;
|
|
this._isChanged = false;
|
|
this._clonedCache = new Set();
|
|
this._hasOnValidate = hasOnValidate;
|
|
this._changes = hasOnValidate ? [] : null;
|
|
|
|
this.clone = path === undefined ? value : this._shallowClone(value);
|
|
}
|
|
|
|
static isHandledMethod(name) {
|
|
return IMMUTABLE_OBJECT_METHODS.has(name);
|
|
}
|
|
|
|
_shallowClone(value) {
|
|
let clone = value;
|
|
|
|
if (isObject(value)) {
|
|
clone = {...value};
|
|
} else if (isArray(value) || ArrayBuffer.isView(value)) {
|
|
clone = [...value];
|
|
} else if (value instanceof Date) {
|
|
clone = new Date(value);
|
|
} else if (value instanceof Set) {
|
|
clone = new Set([...value].map(item => this._shallowClone(item)));
|
|
} else if (value instanceof Map) {
|
|
clone = new Map();
|
|
|
|
for (const [key, item] of value.entries()) {
|
|
clone.set(key, this._shallowClone(item));
|
|
}
|
|
}
|
|
|
|
this._clonedCache.add(clone);
|
|
|
|
return clone;
|
|
}
|
|
|
|
preferredThisArg(isHandledMethod, name, thisArgument, thisProxyTarget) {
|
|
if (isHandledMethod) {
|
|
if (isArray(thisProxyTarget)) {
|
|
this._onIsChanged = MUTABLE_ARRAY_METHODS[name];
|
|
} else if (thisProxyTarget instanceof Set) {
|
|
this._onIsChanged = MUTABLE_SET_METHODS[name];
|
|
} else if (thisProxyTarget instanceof Map) {
|
|
this._onIsChanged = MUTABLE_MAP_METHODS[name];
|
|
}
|
|
|
|
return thisProxyTarget;
|
|
}
|
|
|
|
return thisArgument;
|
|
}
|
|
|
|
update(fullPath, property, value) {
|
|
const changePath = path.after(fullPath, this._path);
|
|
|
|
if (property !== 'length') {
|
|
let object = this.clone;
|
|
|
|
path.walk(changePath, key => {
|
|
if (object?.[key]) {
|
|
if (!this._clonedCache.has(object[key])) {
|
|
object[key] = this._shallowClone(object[key]);
|
|
}
|
|
|
|
object = object[key];
|
|
}
|
|
});
|
|
|
|
if (this._hasOnValidate) {
|
|
this._changes.push({
|
|
path: changePath,
|
|
property,
|
|
previous: value,
|
|
});
|
|
}
|
|
|
|
if (object?.[property]) {
|
|
object[property] = value;
|
|
}
|
|
}
|
|
|
|
this._isChanged = true;
|
|
}
|
|
|
|
undo(object) {
|
|
let change;
|
|
|
|
for (let index = this._changes.length - 1; index !== -1; index--) {
|
|
change = this._changes[index];
|
|
|
|
path.get(object, change.path)[change.property] = change.previous;
|
|
}
|
|
}
|
|
|
|
isChanged(value) {
|
|
return this._onIsChanged === undefined
|
|
? this._isChanged
|
|
: this._onIsChanged(this.clone, value);
|
|
}
|
|
|
|
isPathApplicable(changePath) {
|
|
return path.isRootPath(this._path) || path.isSubPath(changePath, this._path);
|
|
}
|
|
}
|