restructure EventHandler
This commit is contained in:
@@ -1,70 +1,66 @@
|
||||
/**
|
||||
* @module utils
|
||||
*/
|
||||
import { Transaction } from './Transaction.js' // eslint-disable-line
|
||||
import { YEvent } from './YEvent.js' // eslint-disable-line
|
||||
import * as f from 'lib0/function.js'
|
||||
|
||||
/**
|
||||
* General event handler implementation.
|
||||
*
|
||||
* @template ARG0, ARG1
|
||||
*/
|
||||
export class EventHandler {
|
||||
constructor () {
|
||||
this.eventListeners = []
|
||||
}
|
||||
|
||||
/**
|
||||
* To prevent memory leaks, call this method when the eventListeners won't be
|
||||
* used anymore.
|
||||
*/
|
||||
destroy () {
|
||||
this.eventListeners = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener that is called when
|
||||
* {@link EventHandler#callEventListeners} is called.
|
||||
*
|
||||
* @param {Function} f The event handler.
|
||||
*/
|
||||
addEventListener (f) {
|
||||
this.eventListeners.push(f)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event listener.
|
||||
*
|
||||
* @param {Function} f The event handler that was added with
|
||||
* {@link EventHandler#addEventListener}
|
||||
*/
|
||||
removeEventListener (f) {
|
||||
this.eventListeners = this.eventListeners.filter(g => f !== g)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event listeners.
|
||||
*/
|
||||
removeAllEventListeners () {
|
||||
this.eventListeners = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Call all event listeners that were added via
|
||||
* {@link EventHandler#addEventListener}.
|
||||
*
|
||||
* @param {Transaction} transaction The transaction object
|
||||
* @param {YEvent} event An event object that describes the change on a type.
|
||||
*/
|
||||
callEventListeners (transaction, event) {
|
||||
for (var i = 0; i < this.eventListeners.length; i++) {
|
||||
try {
|
||||
const f = this.eventListeners[i]
|
||||
f(event, transaction)
|
||||
} catch (e) {
|
||||
/*
|
||||
Your observer threw an error. This error was caught so that Yjs
|
||||
can ensure data consistency! In order to debug this error you
|
||||
have to check "Pause On Caught Exceptions" in developer tools.
|
||||
*/
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @type {Array<function(ARG0, ARG1):void>}
|
||||
*/
|
||||
this.l = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template ARG0,ARG1
|
||||
* @returns {EventHandler<ARG0,ARG1>}
|
||||
*/
|
||||
export const create = () => new EventHandler()
|
||||
|
||||
/**
|
||||
* Adds an event listener that is called when
|
||||
* {@link EventHandler#callEventListeners} is called.
|
||||
*
|
||||
* @template ARG0,ARG1
|
||||
* @param {EventHandler<ARG0,ARG1>} eventHandler
|
||||
* @param {function(ARG0,ARG1):void} f The event handler.
|
||||
*/
|
||||
export const addEventListener = (eventHandler, f) =>
|
||||
eventHandler.l.push(f)
|
||||
|
||||
/**
|
||||
* Removes an event listener.
|
||||
*
|
||||
* @template ARG0,ARG1
|
||||
* @param {EventHandler<ARG0,ARG1>} eventHandler
|
||||
* @param {function(ARG0,ARG1):void} f The event handler that was added with
|
||||
* {@link EventHandler#addEventListener}
|
||||
*/
|
||||
export const removeEventListener = (eventHandler, f) => {
|
||||
eventHandler.l = eventHandler.l.filter(g => f !== g)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event listeners.
|
||||
* @template ARG0,ARG1
|
||||
* @param {EventHandler<ARG0,ARG1>} eventHandler
|
||||
*/
|
||||
export const removeAllEventListeners = eventHandler => {
|
||||
eventHandler.l.length = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Call all event listeners that were added via
|
||||
* {@link EventHandler#addEventListener}.
|
||||
*
|
||||
* @template ARG0,ARG1
|
||||
* @param {EventHandler<ARG0,ARG1>} eventHandler
|
||||
* @param {[ARG0,ARG1]} args
|
||||
*/
|
||||
export const callEventListeners = (eventHandler, args) =>
|
||||
f.callAll(eventHandler.l, args)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import * as ID from './ID.js'
|
||||
import { isParentOf } from './isParentOf.js'
|
||||
|
||||
184
src/utils/Y.js
184
src/utils/Y.js
@@ -15,6 +15,7 @@ import { YText } from '../types/YText.js'
|
||||
import { YMap } from '../types/YMap.js'
|
||||
import { YXmlFragment } from '../types/YXmlElement.js'
|
||||
import { YEvent } from './YEvent.js' // eslint-disable-line
|
||||
import * as eventHandler from './EventHandler.js'
|
||||
|
||||
/**
|
||||
* A Yjs instance handles the state of shared data.
|
||||
@@ -74,108 +75,107 @@ export class Y extends Observable {
|
||||
}
|
||||
try {
|
||||
f(this._transaction)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
if (initialCall) {
|
||||
const transaction = this._transaction
|
||||
this._transaction = null
|
||||
// only call event listeners / observers if anything changed
|
||||
const transactionChangedContent = transaction.changedParentTypes.size !== 0
|
||||
if (transactionChangedContent) {
|
||||
this.emit('beforeObserverCalls', [this, this._transaction])
|
||||
// emit change events on changed types
|
||||
transaction.changed.forEach((subs, itemtype) => {
|
||||
itemtype._callObserver(transaction, subs)
|
||||
})
|
||||
transaction.changedParentTypes.forEach((events, type) => {
|
||||
events = events
|
||||
.filter(event =>
|
||||
event.target._item === null || !event.target._item.deleted
|
||||
)
|
||||
events
|
||||
.forEach(event => {
|
||||
event.currentTarget = type
|
||||
})
|
||||
// we don't have to check for events.length
|
||||
// because there is no way events is empty..
|
||||
type._deepEventHandler.callEventListeners(transaction, events)
|
||||
})
|
||||
// when all changes & events are processed, emit afterTransaction event
|
||||
this.emit('afterTransaction', [this, transaction])
|
||||
// transaction cleanup
|
||||
const store = transaction.y.store
|
||||
const ds = transaction.deleteSet
|
||||
// replace deleted items with ItemDeleted / GC
|
||||
sortAndMergeDeleteSet(ds)
|
||||
/**
|
||||
* @type {Set<ItemDeleted|GC>}
|
||||
*/
|
||||
const replacedItems = new Set()
|
||||
for (const [client, deleteItems] of ds.clients) {
|
||||
} finally {
|
||||
if (initialCall) {
|
||||
const transaction = this._transaction
|
||||
this._transaction = null
|
||||
// only call event listeners / observers if anything changed
|
||||
const transactionChangedContent = transaction.changedParentTypes.size !== 0
|
||||
if (transactionChangedContent) {
|
||||
this.emit('beforeObserverCalls', [this, this._transaction])
|
||||
// emit change events on changed types
|
||||
transaction.changed.forEach((subs, itemtype) => {
|
||||
itemtype._callObserver(transaction, subs)
|
||||
})
|
||||
transaction.changedParentTypes.forEach((events, type) => {
|
||||
events = events
|
||||
.filter(event =>
|
||||
event.target._item === null || !event.target._item.deleted
|
||||
)
|
||||
events
|
||||
.forEach(event => {
|
||||
event.currentTarget = type
|
||||
})
|
||||
// we don't need to check for events.length
|
||||
// because we know it has at least one element
|
||||
eventHandler.callEventListeners(type._dEH, [events, transaction])
|
||||
})
|
||||
// when all changes & events are processed, emit afterTransaction event
|
||||
this.emit('afterTransaction', [this, transaction])
|
||||
// transaction cleanup
|
||||
const store = transaction.y.store
|
||||
const ds = transaction.deleteSet
|
||||
// replace deleted items with ItemDeleted / GC
|
||||
sortAndMergeDeleteSet(ds)
|
||||
/**
|
||||
* @type {Array<AbstractStruct>}
|
||||
* @type {Set<ItemDeleted|GC>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const structs = store.clients.get(client)
|
||||
for (let di = 0; di < deleteItems.length; di++) {
|
||||
const deleteItem = deleteItems[di]
|
||||
for (let si = findIndexSS(structs, deleteItem.clock); si < structs.length; si++) {
|
||||
const struct = structs[si]
|
||||
if (deleteItem.clock + deleteItem.len < struct.id.clock) {
|
||||
break
|
||||
}
|
||||
if (struct.deleted && struct instanceof AbstractItem) {
|
||||
// check if we can GC
|
||||
replacedItems.add(struct.gc(this))
|
||||
const replacedItems = new Set()
|
||||
for (const [client, deleteItems] of ds.clients) {
|
||||
/**
|
||||
* @type {Array<AbstractStruct>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const structs = store.clients.get(client)
|
||||
for (let di = 0; di < deleteItems.length; di++) {
|
||||
const deleteItem = deleteItems[di]
|
||||
for (let si = findIndexSS(structs, deleteItem.clock); si < structs.length; si++) {
|
||||
const struct = structs[si]
|
||||
if (deleteItem.clock + deleteItem.len < struct.id.clock) {
|
||||
break
|
||||
}
|
||||
if (struct.deleted && struct instanceof AbstractItem) {
|
||||
// check if we can GC
|
||||
replacedItems.add(struct.gc(this))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {Array<AbstractStruct>} structs
|
||||
* @param {number} pos
|
||||
*/
|
||||
const tryToMergeWithLeft = (structs, pos) => {
|
||||
const left = structs[pos - 1]
|
||||
const right = structs[pos]
|
||||
if (left.deleted === right.deleted && left.constructor === right.constructor) {
|
||||
if (left.mergeWith(right)) {
|
||||
structs.splice(pos, 1)
|
||||
/**
|
||||
* @param {Array<AbstractStruct>} structs
|
||||
* @param {number} pos
|
||||
*/
|
||||
const tryToMergeWithLeft = (structs, pos) => {
|
||||
const left = structs[pos - 1]
|
||||
const right = structs[pos]
|
||||
if (left.deleted === right.deleted && left.constructor === right.constructor) {
|
||||
if (left.mergeWith(right)) {
|
||||
structs.splice(pos, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// on all affected store.clients props, try to merge
|
||||
for (const [client, clock] of transaction.stateUpdates) {
|
||||
/**
|
||||
* @type {Array<AbstractStruct>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const structs = store.clients.get(client)
|
||||
// we iterate from right to left so we can safely remove entries
|
||||
for (let i = structs.length - 1; i >= math.max(findIndexSS(structs, clock), 1); i--) {
|
||||
tryToMergeWithLeft(structs, i)
|
||||
// on all affected store.clients props, try to merge
|
||||
for (const [client, clock] of transaction.stateUpdates) {
|
||||
/**
|
||||
* @type {Array<AbstractStruct>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const structs = store.clients.get(client)
|
||||
// we iterate from right to left so we can safely remove entries
|
||||
for (let i = structs.length - 1; i >= math.max(findIndexSS(structs, clock), 1); i--) {
|
||||
tryToMergeWithLeft(structs, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
// try to merge replacedItems
|
||||
for (const replacedItem of replacedItems) {
|
||||
const id = replacedItem.id
|
||||
const client = id.client
|
||||
const clock = id.clock
|
||||
/**
|
||||
* @type {Array<AbstractStruct>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const structs = store.clients.get(client)
|
||||
const replacedStructPos = findIndexSS(structs, clock)
|
||||
if (replacedStructPos + 1 < structs.length) {
|
||||
tryToMergeWithLeft(structs, replacedStructPos + 1)
|
||||
}
|
||||
if (replacedStructPos > 0) {
|
||||
tryToMergeWithLeft(structs, replacedStructPos)
|
||||
// try to merge replacedItems
|
||||
for (const replacedItem of replacedItems) {
|
||||
const id = replacedItem.id
|
||||
const client = id.client
|
||||
const clock = id.clock
|
||||
/**
|
||||
* @type {Array<AbstractStruct>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const structs = store.clients.get(client)
|
||||
const replacedStructPos = findIndexSS(structs, clock)
|
||||
if (replacedStructPos + 1 < structs.length) {
|
||||
tryToMergeWithLeft(structs, replacedStructPos + 1)
|
||||
}
|
||||
if (replacedStructPos > 0) {
|
||||
tryToMergeWithLeft(structs, replacedStructPos)
|
||||
}
|
||||
}
|
||||
this.emit('afterTransactionCleanup', [this, transaction])
|
||||
}
|
||||
this.emit('afterTransactionCleanup', [this, transaction])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export class YEvent {
|
||||
* console.log(path) // might look like => [2, 'key1']
|
||||
* child === type.get(path[0]).get(path[1])
|
||||
*
|
||||
* @param {AbstractType<any> parent
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {AbstractItem} child target
|
||||
* @return {Array<string|number>} Path to the target
|
||||
*/
|
||||
|
||||
@@ -76,13 +76,13 @@ export class AbsolutePosition {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractType<any> type
|
||||
* @param {AbstractType<any>} type
|
||||
* @param {number} offset
|
||||
*/
|
||||
export const createAbsolutePosition = (type, offset) => new AbsolutePosition(type, offset)
|
||||
|
||||
/**
|
||||
* @param {AbstractType<any> type
|
||||
* @param {AbstractType<any>} type
|
||||
* @param {ID.ID|null} item
|
||||
*/
|
||||
export const createRelativePosition = (type, item) => {
|
||||
@@ -99,7 +99,7 @@ export const createRelativePosition = (type, item) => {
|
||||
/**
|
||||
* Create a relativePosition based on a absolute position.
|
||||
*
|
||||
* @param {AbstractType<any> type The base type (e.g. YText or YArray).
|
||||
* @param {AbstractType<any>} type The base type (e.g. YText or YArray).
|
||||
* @param {number} offset The absolute position.
|
||||
* @return {RelativePosition}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user