Website Structure

This commit is contained in:
supalerk-ar66 2026-01-13 10:46:40 +07:00
parent 62812f2090
commit 71f0676a62
22365 changed files with 4265753 additions and 791 deletions

View file

@ -0,0 +1,8 @@
import {HANDLED_ARRAY_METHODS} from '../methods/array.js';
import CloneObject from './clone-object.js';
export default class CloneArray extends CloneObject {
static isHandledMethod(name) {
return HANDLED_ARRAY_METHODS.has(name);
}
}

View file

@ -0,0 +1,11 @@
import CloneObject from './clone-object.js';
export default class CloneDate extends CloneObject {
undo(object) {
object.setTime(this.clone.getTime());
}
isChanged(value, equals) {
return !equals(this.clone.valueOf(), value.valueOf());
}
}

View file

@ -0,0 +1,20 @@
import {HANDLED_MAP_METHODS} from '../methods/map.js';
import CloneObject from './clone-object.js';
export default class CloneMap extends CloneObject {
static isHandledMethod(name) {
return HANDLED_MAP_METHODS.has(name);
}
undo(object) {
for (const [key, value] of this.clone.entries()) {
object.set(key, value);
}
for (const key of object.keys()) {
if (!this.clone.has(key)) {
object.delete(key);
}
}
}
}

View file

@ -0,0 +1,115 @@
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, _equals) {
return this._onIsChanged === undefined
? this._isChanged
: this._onIsChanged(this.clone, value);
}
isPathApplicable(changePath) {
return path.isRootPath(this._path) || path.isSubPath(changePath, this._path);
}
}

View file

@ -0,0 +1,20 @@
import {HANDLED_SET_METHODS} from '../methods/set.js';
import CloneObject from './clone-object.js';
export default class CloneSet extends CloneObject {
static isHandledMethod(name) {
return HANDLED_SET_METHODS.has(name);
}
undo(object) {
for (const value of this.clone) {
object.add(value);
}
for (const value of object) {
if (!this.clone.has(value)) {
object.delete(value);
}
}
}
}

View file

@ -0,0 +1,27 @@
import CloneObject from './clone-object.js';
export default class CloneWeakMap extends CloneObject {
constructor(value, path, argumentsList, hasOnValidate) {
super(undefined, path, argumentsList, hasOnValidate);
this._weakKey = argumentsList[0];
this._weakHas = value.has(this._weakKey);
this._weakValue = value.get(this._weakKey);
}
isChanged(value, _equals) {
return this._weakValue !== value.get(this._weakKey);
}
undo(object) {
const weakHas = object.has(this._weakKey);
if (this._weakHas && !weakHas) {
object.set(this._weakKey, this._weakValue);
} else if (!this._weakHas && weakHas) {
object.delete(this._weakKey);
} else if (this._weakValue !== object.get(this._weakKey)) {
object.set(this._weakKey, this._weakValue);
}
}
}

View file

@ -0,0 +1,22 @@
import CloneObject from './clone-object.js';
export default class CloneWeakSet extends CloneObject {
constructor(value, path, argumentsList, hasOnValidate) {
super(undefined, path, argumentsList, hasOnValidate);
this._argument1 = argumentsList[0];
this._weakValue = value.has(this._argument1);
}
isChanged(value, _equals) {
return this._weakValue !== value.has(this._argument1);
}
undo(object) {
if (this._weakValue && !object.has(this._argument1)) {
object.add(this._argument1);
} else {
object.delete(this._argument1);
}
}
}

View file

@ -0,0 +1,8 @@
export default function isDiffArrays(clone, value) {
if (clone === value) {
return false;
}
return clone.length !== value.length
|| clone.some((item, index) => value[index] !== item);
}

View file

@ -0,0 +1,3 @@
export default function isDiffCertain() {
return true;
}

View file

@ -0,0 +1,19 @@
export default function isDiffMaps(clone, value) {
if (clone === value) {
return false;
}
if (clone.size !== value.size) {
return true;
}
for (const [key, aValue] of clone) {
const bValue = value.get(key);
// Distinguish missing vs undefined and catch strict inequality
if (bValue !== aValue || (bValue === undefined && !value.has(key))) {
return true;
}
}
return false;
}

View file

@ -0,0 +1,17 @@
export default function isDiffSets(clone, value) {
if (clone === value) {
return false;
}
if (clone.size !== value.size) {
return true;
}
for (const element of clone) {
if (!value.has(element)) {
return true;
}
}
return false;
}

View file

@ -0,0 +1,31 @@
import isDiffCertain from '../diff/is-diff-certain.js';
import isDiffArrays from '../diff/is-diff-arrays.js';
import {IMMUTABLE_OBJECT_METHODS} from './object.js';
const IMMUTABLE_ARRAY_METHODS = new Set([
'concat',
'includes',
'indexOf',
'join',
'keys',
'lastIndexOf',
]);
export const MUTABLE_ARRAY_METHODS = {
push: isDiffCertain,
pop: isDiffCertain,
shift: isDiffCertain,
unshift: isDiffCertain,
copyWithin: isDiffArrays,
reverse: isDiffArrays,
sort: isDiffArrays,
splice: isDiffArrays,
flat: isDiffArrays,
fill: isDiffArrays,
};
export const HANDLED_ARRAY_METHODS = new Set([
...IMMUTABLE_OBJECT_METHODS,
...IMMUTABLE_ARRAY_METHODS,
...Object.keys(MUTABLE_ARRAY_METHODS),
]);

View file

@ -0,0 +1,17 @@
import isDiffMaps from '../diff/is-diff-maps.js';
import {IMMUTABLE_SET_METHODS, COLLECTION_ITERATOR_METHODS} from './set.js';
const IMMUTABLE_MAP_METHODS = new Set([...IMMUTABLE_SET_METHODS, 'get']);
export const MUTABLE_MAP_METHODS = {
set: isDiffMaps,
clear: isDiffMaps,
delete: isDiffMaps,
forEach: isDiffMaps,
};
export const HANDLED_MAP_METHODS = new Set([
...IMMUTABLE_MAP_METHODS,
...Object.keys(MUTABLE_MAP_METHODS),
...COLLECTION_ITERATOR_METHODS,
]);

View file

@ -0,0 +1,8 @@
export const IMMUTABLE_OBJECT_METHODS = new Set([
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'toLocaleString',
'toString',
'valueOf',
]);

View file

@ -0,0 +1,25 @@
import isDiffSets from '../diff/is-diff-sets.js';
export const COLLECTION_ITERATOR_METHODS = [
'keys',
'values',
'entries',
];
export const IMMUTABLE_SET_METHODS = new Set([
'has',
'toString',
]);
export const MUTABLE_SET_METHODS = {
add: isDiffSets,
clear: isDiffSets,
delete: isDiffSets,
forEach: isDiffSets,
};
export const HANDLED_SET_METHODS = new Set([
...IMMUTABLE_SET_METHODS,
...Object.keys(MUTABLE_SET_METHODS),
...COLLECTION_ITERATOR_METHODS,
]);

View file

@ -0,0 +1,99 @@
import isArray from '../is-array.js';
import {isBuiltinWithMutableMethods} from '../is-builtin.js';
import isObject from '../is-object.js';
import CloneObject from './clone/clone-object.js';
import CloneArray from './clone/clone-array.js';
import CloneDate from './clone/clone-date.js';
import CloneSet from './clone/clone-set.js';
import CloneMap from './clone/clone-map.js';
import CloneWeakSet from './clone/clone-weakset.js';
import CloneWeakMap from './clone/clone-weakmap.js';
export default class SmartClone {
constructor(hasOnValidate) {
this._stack = [];
this._hasOnValidate = hasOnValidate;
}
static isHandledType(value) {
return isObject(value)
|| isArray(value)
|| isBuiltinWithMutableMethods(value);
}
static isHandledMethod(target, name) {
if (isObject(target)) {
return CloneObject.isHandledMethod(name);
}
if (isArray(target)) {
return CloneArray.isHandledMethod(name);
}
if (target instanceof Set) {
return CloneSet.isHandledMethod(name);
}
if (target instanceof Map) {
return CloneMap.isHandledMethod(name);
}
return isBuiltinWithMutableMethods(target);
}
get isCloning() {
return this._stack.length > 0;
}
start(value, path, argumentsList) {
let CloneClass = CloneObject;
if (isArray(value)) {
CloneClass = CloneArray;
} else if (value instanceof Date) {
CloneClass = CloneDate;
} else if (value instanceof Set) {
CloneClass = CloneSet;
} else if (value instanceof Map) {
CloneClass = CloneMap;
} else if (value instanceof WeakSet) {
CloneClass = CloneWeakSet;
} else if (value instanceof WeakMap) {
CloneClass = CloneWeakMap;
}
this._stack.push(new CloneClass(value, path, argumentsList, this._hasOnValidate));
}
update(fullPath, property, value) {
this._stack.at(-1).update(fullPath, property, value);
}
preferredThisArg(target, thisArgument, thisProxyTarget) {
const {name} = target;
const isHandledMethod = SmartClone.isHandledMethod(thisProxyTarget, name);
return this._stack.at(-1)
.preferredThisArg(isHandledMethod, name, thisArgument, thisProxyTarget);
}
isChanged(value, equals) {
return this._stack.at(-1).isChanged(value, equals);
}
isPartOfClone(changePath) {
return this._stack.at(-1).isPathApplicable(changePath);
}
undo(object) {
if (this._previousClone !== undefined) {
this._previousClone.undo(object);
}
}
stop() {
this._previousClone = this._stack.pop();
return this._previousClone.clone;
}
}