/** * @module YMap */ import { YEvent, AbstractType, typeMapDelete, typeMapSet, typeMapGet, typeMapHas, createMapIterator, YMapRefID, callTypeObservers, transact, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line } from '../internals.js' import * as iterator from 'lib0/iterator' /** * @template T * @extends YEvent> * Event that describes the changes on a YMap. */ export class YMapEvent extends YEvent { /** * @param {YMap} ymap The YArray that changed. * @param {Transaction} transaction * @param {Set} subs The keys that changed. */ constructor (ymap, transaction, subs) { super(ymap, transaction) this.keysChanged = subs } } /** * @template MapType * A shared Map implementation. * * @extends AbstractType> * @implements {Iterable<[string, MapType]>} */ export class YMap extends AbstractType { /** * * @param {Iterable=} entries - an optional iterable to initialize the YMap */ constructor (entries) { super() /** * @type {Map?} * @private */ this._prelimContent = null if (entries === undefined) { this._prelimContent = new Map() } else { this._prelimContent = new Map(entries) } } /** * Integrate this type into the Yjs instance. * * * Save this struct in the os * * This type is sent to other client * * Observer functions are fired * * @param {Doc} y The Yjs instance * @param {Item} item */ _integrate (y, item) { super._integrate(y, item) ;/** @type {Map} */ (this._prelimContent).forEach((value, key) => { this.set(key, value) }) this._prelimContent = null } /** * @return {YMap} */ _copy () { return new YMap() } /** * @return {YMap} */ clone () { /** * @type {YMap} */ const map = new YMap() this.forEach((value, key) => { map.set(key, value instanceof AbstractType ? /** @type {typeof value} */ (value.clone()) : value) }) return map } /** * Creates YMapEvent and calls observers. * * @param {Transaction} transaction * @param {Set} parentSubs Keys changed on this type. `null` if list was modified. */ _callObserver (transaction, parentSubs) { callTypeObservers(this, transaction, new YMapEvent(this, transaction, parentSubs)) } /** * Transforms this Shared Type to a JSON object. * * @return {Object} */ toJSON () { /** * @type {Object} */ const map = {} this._map.forEach((item, key) => { if (!item.deleted) { const v = item.content.getContent()[item.length - 1] map[key] = v instanceof AbstractType ? v.toJSON() : v } }) return map } /** * Returns the size of the YMap (count of key/value pairs) * * @return {number} */ get size () { return [...createMapIterator(this._map)].length } /** * Returns the keys for each element in the YMap Type. * * @return {IterableIterator} */ keys () { return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[0]) } /** * Returns the values for each element in the YMap Type. * * @return {IterableIterator} */ values () { return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[1].content.getContent()[v[1].length - 1]) } /** * Returns an Iterator of [key, value] pairs * * @return {IterableIterator<[string, MapType]>} */ entries () { return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => /** @type {any} */ ([v[0], v[1].content.getContent()[v[1].length - 1]])) } /** * Executes a provided function on once on every key-value pair. * * @param {function(MapType,string,YMap):void} f A function to execute on every element of this YArray. */ forEach (f) { this._map.forEach((item, key) => { if (!item.deleted) { f(item.content.getContent()[item.length - 1], key, this) } }) } /** * Returns an Iterator of [key, value] pairs * * @return {IterableIterator<[string, MapType]>} */ [Symbol.iterator] () { return this.entries() } /** * Remove a specified element from this YMap. * * @param {string} key The key of the element to remove. */ delete (key) { if (this.doc !== null) { transact(this.doc, transaction => { typeMapDelete(transaction, this, key) }) } else { /** @type {Map} */ (this._prelimContent).delete(key) } } /** * Adds or updates an element with a specified key and value. * @template {MapType} VAL * * @param {string} key The key of the element to add to this YMap * @param {VAL} value The value of the element to add * @return {VAL} */ set (key, value) { if (this.doc !== null) { transact(this.doc, transaction => { typeMapSet(transaction, this, key, /** @type {any} */ (value)) }) } else { /** @type {Map} */ (this._prelimContent).set(key, value) } return value } /** * Returns a specified element from this YMap. * * @param {string} key * @return {MapType|undefined} */ get (key) { return /** @type {any} */ (typeMapGet(this, key)) } /** * Returns a boolean indicating whether the specified key exists or not. * * @param {string} key The key to test. * @return {boolean} */ has (key) { return typeMapHas(this, key) } /** * Removes all elements from this YMap. */ clear () { if (this.doc !== null) { transact(this.doc, transaction => { this.forEach(function (_value, key, map) { typeMapDelete(transaction, map, key) }) }) } else { /** @type {Map} */ (this._prelimContent).clear() } } /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder */ _write (encoder) { encoder.writeTypeRef(YMapRefID) } } /** * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder * * @private * @function */ export const readYMap = _decoder => new YMap()