/** * @module types */ import { Item } from '../structs/Item.mjs' import { Type } from '../structs/Type.mjs' import { ItemJSON } from '../structs/ItemJSON.mjs' import { logItemHelper } from '../protocols/syncProtocol.mjs' import { YEvent } from '../utils/YEvent.mjs' /** * Event that describes the changes on a YMap. */ export class YMapEvent extends YEvent { /** * @param {YMap} ymap The YArray that changed. * @param {Set} subs The keys that changed. * @param {boolean} remote Whether the change was created by a remote peer. */ constructor (ymap, subs, remote) { super(ymap) this.keysChanged = subs this.remote = remote } } /** * A shared Map implementation. */ export class YMap extends Type { /** * Creates YMap Event and calls observers. * * @private */ _callObserver (transaction, parentSubs, remote) { this._callEventHandler(transaction, new YMapEvent(this, parentSubs, remote)) } /** * Transforms this Shared Type to a JSON object. * * @return {Object} */ toJSON () { const map = {} for (let [key, item] of this._map) { if (!item._deleted) { let res if (item instanceof Type) { if (item.toJSON !== undefined) { res = item.toJSON() } else { res = item.toString() } } else { res = item._content[0] } map[key] = res } } return map } /** * Returns the keys for each element in the YMap Type. * * @return {Array} */ keys () { // TODO: Should return either Iterator or Set! let keys = [] for (let [key, value] of this._map) { if (!value._deleted) { keys.push(key) } } return keys } /** * Remove a specified element from this YMap. * * @param {string} key The key of the element to remove. */ delete (key) { this._transact((y) => { let c = this._map.get(key) if (y !== null && c !== undefined) { c._delete(y) } }) } /** * Adds or updates an element with a specified key and value. * * @param {string} key The key of the element to add to this YMap * @param {Object | string | number | Type} value The value of the element to add */ set (key, value) { this._transact(y => { const old = this._map.get(key) || null if (old !== null) { if ( old.constructor === ItemJSON && !old._deleted && old._content[0] === value ) { // Trying to overwrite with same value // break here return value } if (y !== null) { old._delete(y) } } let v if (typeof value === 'function') { v = new value() // eslint-disable-line new-cap value = v } else if (value instanceof Item) { v = value } else { v = new ItemJSON() v._content = [value] } v._right = old v._right_origin = old v._parent = this v._parentSub = key if (y !== null) { v._integrate(y) } else { this._map.set(key, v) } }) return value } /** * Returns a specified element from this YMap. * * @param {string} key The key of the element to return. */ get (key) { let v = this._map.get(key) if (v === undefined || v._deleted) { return undefined } if (v instanceof Type) { return v } else { return v._content[v._content.length - 1] } } /** * Returns a boolean indicating whether the specified key exists or not. * * @param {string} key The key to test. */ has (key) { let v = this._map.get(key) if (v === undefined || v._deleted) { return false } else { return true } } /** * Transform this YXml Type to a readable format. * Useful for logging as all Items and Delete implement this method. * * @private */ _logString () { return logItemHelper('YMap', this, `mapSize:${this._map.size}`) } }