Skip to content
Snippets Groups Projects
room.js 3.57 KiB
Newer Older
import uuidv4 from "uuid/v4"
import yArray from "y-array"
import yMap from "y-map"
import yMemory from "y-memory"
import Y from "yjs"

import yWebrtc from "./y-webrtc/index.js"

yMemory(Y)
yMap(Y)
yArray(Y)
yWebrtc(Y)

class Room extends EventTarget {
  constructor(name) {
    super()
    this.name = name
    this._y = null
    this.ownID = null
  }

    const id = uuidv4()
    this._y.share.strokeAdd.set(id, Y.Array).push([[x, y, w]])
    return id
  }

  extendPath(id, [x, y, w]) {
    this._y.share.strokeAdd.get(id).push([[x, y, w]])
  }

  getPaths() {
    let paths = new Map()

    for (let id of this._y.share.strokeAdd.keys()) {
      paths.set(id, this._generatePath(id))
    }

    return paths
  }

  erasePoint(id, idx) {
    let eraseSet = this._y.share.strokeErase.get(id)

    if (!eraseSet) {
      eraseSet = this._y.share.strokeErase.set(id, Y.Map)
    }

    eraseSet.set(idx.toString(), true)
  }

  // Generate an array of points [x, y, exist] by merging the path's add and erase sets
  _generatePath(id) {
    let addSet = this._y.share.strokeAdd.get(id)

    if (addSet === undefined) {
      return []
    }

    let eraseSet = this._y.share.strokeErase.get(id) || { get: () => false }

    return addSet
      .toArray()
      .map((p = [], i) => [p[0], p[1], p[2], !eraseSet.get(i.toString())])
  }

  inviteUser(id) {
    this._y.connector.connectToPeer(id)
  }

  async _initialise() {
    this._y = await Y({
      db: {
        name: "memory",
      },
      connector: {
        name: "webrtc",
        url: "/",
        room: this.name,
        onUserEvent: (event) => {
          if (event.action == "userID") {
            const { id } = event
            this.ownID = id
            this.dispatchEvent(new CustomEvent("allocateOwnID", { detail: id }))
          } else if (event.action == "userJoined") {
            const { user: id } = event
            this.dispatchEvent(new CustomEvent("userJoin", { detail: id }))
          } else if (event.action == "userLeft") {
            const { user: id } = event
            this.dispatchEvent(new CustomEvent("userLeave", { detail: id }))
          }
        },
      },
      share: {
        strokeAdd: "Map",
        strokeErase: "Map",
    this._y.share.strokeAdd.observe((lineEvent) => {
      if (lineEvent.type == "add") {
        const points = this._generatePath(lineEvent.name)
        const detail = { id: lineEvent.name, points }
        this.dispatchEvent(new CustomEvent("addOrUpdatePath", { detail }))
        lineEvent.value.observe((pointEvent) => {
          if (pointEvent.type == "insert") {
            const points = this._generatePath(lineEvent.name)
            const detail = { id: lineEvent.name, points }
            this.dispatchEvent(new CustomEvent("addOrUpdatePath", { detail }))
          }
        })
      }
    })
    this._y.share.strokeErase.observe((lineEvent) => {
      if (lineEvent.type == "add") {
        const points = this._generatePath(lineEvent.name)
        const detail = { id: lineEvent.name, points }
        this.dispatchEvent(new CustomEvent("addOrUpdatePath", { detail }))
        lineEvent.value.observe((pointEvent) => {
          if (pointEvent.type == "add") {
            const points = this._generatePath(lineEvent.name)
            const detail = { id: lineEvent.name, points }
            this.dispatchEvent(new CustomEvent("addOrUpdatePath", { detail }))
          }
        })
      }
    })
  }
}

export const connect = async (roomName) => {
  const room = new Room(roomName)
  await room._initialise()
  return room
}