Skip to content
Snippets Groups Projects
y-union.js 3.83 KiB
Newer Older
/* global Y */

import { combineErasureIntervals } from "./erasure.js"

export const Union = {
  create: function(id) {
    return {
      id: id,
      struct: "Union",
    }
  },
  encode: function(op) {
    const e = {
      struct: "Union",
      type: op.type,
      id: op.id,
    }
    if (op.requires != null) {
      e.requires = op.requires
    }
    if (op.info != null) {
      e.info = op.info
    }
    return e
  },
  requiredOps: function() {
    return []
  },
  execute: function*() {},
}

export default function extendYUnion(Y) {
  class YUnion extends Y.utils.CustomType {
    constructor(os, model, contents) {
      super()
      this._model = model.id
      this._parent = null
      this._deepEventHandler = new Y.utils.EventListenerHandler()
      this.os = os
      this.union = model.union ? Y.utils.copyObject(model.union) : null
      this.contents = contents
      this.eventHandler = new Y.utils.EventHandler((op) => {
        // compute op event
        if (op.struct === "Insert") {
          if (!Y.utils.compareIds(op.id, this.union)) {
            const mergedContents = this._merge(JSON.parse(op.content[0]))
            this.union = op.id
            if (this.contents == mergedContents) {
              return
            }
            this.contents = mergedContents
            Y.utils.bubbleEvent(this, {
              object: this,
              type: "merge",
            })
          }
        } else {
          throw new Error("Unexpected Operation!")
        }
      })
    }
    _getPathToChild(/*childId*/) {
      return undefined
    }
    _destroy() {
      this.eventHandler.destroy()
      this.eventHandler = null
      this.contents = null
      this._model = null
      this._parent = null
      this.os = null
      this.union = null
    }
    get() {
      return JSON.parse(this.contents)
    }
    _merge(newIntervals) {
      const prevIntervals = this.get()

      const mergedIntervals = combineErasureIntervals(
        [prevIntervals],
        [newIntervals],
      )[0]

      return JSON.stringify(mergedIntervals)
    }
    merge(newIntervals) {
      const mergedContents = this._merge(newIntervals)

      if (this.contents == mergedContents) {
        return
      }

      const insert = {
        id: this.os.getNextOpId(1),
        left: null,
        origin: null,
        parent: this._model,
        content: [mergedContents],
        struct: "Insert",
      }

      const eventHandler = this.eventHandler
      this.os.requestTransaction(function*() {
        yield* eventHandler.awaitOps(this, this.applyCreatedOperations, [
          [insert],
        ])
      })
      // always remember to do that after this.os.requestTransaction
      // (otherwise values might contain a undefined reference to type)
      eventHandler.awaitAndPrematurelyCall([insert])
    }
    observe(f) {
      this.eventHandler.addEventListener(f)
    }
    observeDeep(f) {
      this._deepEventHandler.addEventListener(f)
    }
    unobserve(f) {
      this.eventHandler.removeEventListener(f)
    }
    unobserveDeep(f) {
      this._deepEventHandler.removeEventListener(f)
    }
    // eslint-disable-next-line require-yield
    *_changed(transaction, op) {
      this.eventHandler.receivedOp(op)
    }
  }
  Y.extend(
    "Union",
    new Y.utils.CustomTypeDefinition({
      name: "Union",
      class: YUnion,
      struct: "Union",
      initType: function* YUnionInitializer(os, model) {
        const union = model.union
        const contents =
          union !== null ? (yield* this.getOperation(union)).content[0] : "[]"
        return new YUnion(os, model, contents)
      },
      createType: function YUnionCreator(os, model) {
        const union = new YUnion(os, model, "[]")
        return union
      },
    }),
  )
}

if (typeof Y !== "undefined") {
  extendYUnion(Y)
}