Last active
March 25, 2024 10:41
-
-
Save TheLucifurry/2f85e28076464eec68ff38adb168fb4b to your computer and use it in GitHub Desktop.
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
| import { isEqual, isPlainObject as lodashIsPlainObject } from '../lodash' | |
| type If<V extends boolean, Then, Else> = V extends true ? Then : Else | |
| function isPlainObject(value: unknown): value is object { | |
| return lodashIsPlainObject(value) | |
| } | |
| function getDifference<V>(_source: V, changed: Partial<V>): Partial<V> { | |
| return changed // TODO: реализовать | |
| } | |
| function getMerged<V>(source: V, changed: Partial<V>): V { | |
| return { ...source, ...changed } | |
| } | |
| type ItemEventHandler<K, V> = (key: K, value: V) => void | |
| /** | |
| * Создает ассоциативный массив с контролем изменений | |
| * | |
| * @example ``` | |
| * const dm = new DiffMap() | |
| * | |
| * // Запись объекта | |
| * dm.set(0, { a: 123, b: 'original' }, true) // Или `dm.setSource(0, {...})` | |
| * dm.get(0) // => { a: 123, b: 'original' } | |
| * | |
| * // Частичное изменение объекта | |
| * dm.set(0, { b:'changed' }, false) | |
| * dm.getSource(0) // => { a: 123, b: 'original' } | |
| * dm.getDiff(0) // => { b: 'changed' } | |
| * dm.get(0) // => { a: 123, b: 'changed' } | |
| * dm.sizeDiffs // => 1 | |
| * | |
| * // Фиксация изменений объекта | |
| * dm.commit(0) | |
| * dm.getDiff(0) // => undefined | |
| * dm.getSource(0) // => { a: 123, b: 'changed' } | |
| * dm.sizeDiffs // => 0 | |
| * ``` | |
| */ | |
| export class DiffMap<K extends string | number | symbol, V> implements Map<K, V> { | |
| private _src: Map<K, V> | |
| private _diff: Map<K, Partial<V>> = new Map() | |
| private _emitChange = (key: K) => { | |
| this.onchange?.(key, this.get(key)!) | |
| } | |
| constructor(data?: Record<string | number | symbol, V>) { | |
| this._src = new Map(data ? Object.entries(data) : undefined) as Map<K, V> | |
| } | |
| [Symbol.toStringTag]: string = DiffMap.name; | |
| [Symbol.iterator](): IterableIterator<[K, V]> { | |
| throw new Error('Method not implemented.') | |
| } | |
| // Стандартные методы Map | |
| clear = (): void => { | |
| this._src.clear() | |
| this._diff.clear() | |
| } | |
| delete = (key: K) => { | |
| this._diff.delete(key) | |
| return this._src.delete(key) | |
| } | |
| forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: unknown): void { | |
| callbackfn.call(thisArg, {} as V, '_' as K, this._src) | |
| throw new Error('Method not implemented.') | |
| } | |
| entries(): IterableIterator<[K, V]> { | |
| throw new Error('Method not implemented.') | |
| } | |
| keys = (): IterableIterator<K> => this._src.keys() | |
| values(): IterableIterator<V> { | |
| throw new Error('Method not implemented.') | |
| } | |
| set = <ICreateSrcMode extends boolean = true>( | |
| key: K, | |
| value: If<ICreateSrcMode, V, Partial<V>>, | |
| createIfNoSource: ICreateSrcMode = true as ICreateSrcMode, | |
| ) => { | |
| if (!this._src.has(key)) { | |
| if (createIfNoSource) { | |
| this.setSource(key, value as V) | |
| } | |
| } else { | |
| const sourceValue = this._src.get(key) | |
| if (isPlainObject(value)) { | |
| this._diff.set(key, isPlainObject(sourceValue) ? getDifference(sourceValue, value) : value) | |
| } else { | |
| this._diff.set(key, value) | |
| } | |
| if (isEqual(sourceValue, value)) { | |
| this._diff.delete(key) | |
| } | |
| } | |
| this._emitChange(key) | |
| return this | |
| } | |
| get = (key: K) => { | |
| const srcValue = this._src.get(key) | |
| if (!this._diff.has(key)) { | |
| return srcValue as V | undefined | |
| } | |
| const deltaValue = this._diff.get(key)! | |
| return isPlainObject(srcValue) && isPlainObject(deltaValue) | |
| ? getMerged(srcValue, deltaValue) | |
| : deltaValue as V | |
| } | |
| has = (key: K) => this._src.has(key) | |
| get size() { | |
| return this._src.size | |
| } | |
| // Дополнительные методы | |
| /** Событие изменения объекта */ | |
| onchange?: ItemEventHandler<K, V> | null | |
| /** Устанавливает исходное значение для объекта, удаляя его изменения */ | |
| setSource = (key: K, item: V) => { | |
| this._src.set(key, item) | |
| this._diff.delete(key) | |
| this._emitChange(key) | |
| return this | |
| } | |
| /** Возвращает исходное значение объекта */ | |
| getSource = (key: K) => this._src.get(key) | |
| /** Возвращает изменения объекта */ | |
| getDiff = (key: K) => this._diff.get(key) | |
| /** Возвращает список изменений */ | |
| getDiffs = () => Object.fromEntries(this._diff.entries()) as Record<K, Partial<V>> | |
| /** Проверяет наличие изменений у объекта */ | |
| hasDiff = (key: K) => this._diff.has(key) | |
| /** Проверяет наличие любых изменений */ | |
| hasDiffs = () => !!this._diff.size | |
| /** Содержит количество измененных объектов */ | |
| get sizeDiffs() { | |
| return this._diff.size | |
| } | |
| /** Фиксирует изменения объекта, сохраняя как исходное значение и удаляя измененное */ | |
| commit = (key: K) => { | |
| if (this._diff.has(key)) { | |
| this._src.set(key, this.get(key)!) | |
| this._diff.delete(key) | |
| } | |
| } | |
| /** Фиксирует все изменения, помещая в исходные значения, очищая список изменений */ | |
| commitAll = () => { | |
| this._diff.forEach((_value, key) => this.commit(key)) | |
| } | |
| /** Удаляет изменения объекта */ | |
| reset = (key: K) => { | |
| if (this._diff.has(key)) { | |
| this._diff.delete(key) | |
| this._emitChange(key) | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment