-
Yuriy Maksymets authoredYuriy Maksymets authored
room.js 5.69 KiB
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
this.erasureIntervals = {}
}
disconnect() {
this._y.destroy()
}
addPath([x, y]) {
const id = uuidv4()
this._y.share.strokeAdd.set(id, Y.Array).push([[x, y]])
return id
}
insertIntoPath(id, index, [x, y]) {
this._y.share.strokeAdd.get(id).insert(index, [[x, y]])
}
extendPath(id, [x, y]) {
this._y.share.strokeAdd.get(id).push([[x, y]])
}
extendErasureIntervals(pathID, pointID, newIntervals) {
const pathIntervals = yGetOrSet(this.sharedMergeIntervals, pathID, Y.Map)
const pointIntervals = yGetOrSet(pathIntervals, pointID, Y.Array)
pointIntervals.delete(0, pointIntervals.length)
pointIntervals.push(newIntervals)
}
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)
}
get shared() {
return this._y.share
}
get sharedMergeIntervals() {
return this.shared.mergeIntervals
}
sharedMergeIntervalsForID(id) {
return this.sharedMergeIntervals.get(id)
}
// 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], !eraseSet.get(i.toString())])
}
_generateRemovedIntervals(id) {
const intervals = this.sharedMergeIntervals.get(id)
if (!intervals) return []
const json = toJSON(intervals)
return typeof json === "string" ? JSON.parse(json) : json
}
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",
mergeIntervals: "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 }))
}
})
}
})
this.sharedMergeIntervals.observe((lineEvent) => {
if (lineEvent.type == "add") {
const dispatchEvent = () => {
const id = lineEvent.name
const intervals = this._generateRemovedIntervals(id)
const points = this._generatePath(lineEvent.name)
this.dispatchEvent(
new CustomEvent("removedIntervalsChange", {
detail: { id, intervals, points },
}),
)
}
dispatchEvent()
lineEvent.value.observe((pointEvent) => {
if (pointEvent.type == "add") {
dispatchEvent()
pointEvent.value.observe(() => {
dispatchEvent()
})
}
})
}
})
}
}
function yGetOrSet(map, id, type) {
let data = map.get(id)
if (!data) {
data = map.set(id, type)
}
return data
}
function toJSON(map) {
var obj = {}
for (let key in map.contents) {
obj[key] = map.contents[key]
}
for (let key in map.opContents) {
let type = map.os.getType(map.opContents[key])
if (type.toJSON != null) {
obj[key] = type.toJSON()
} else if (type.toArray != null) {
obj[key] = type.toArray()
} else if (type.toString != null) {
obj[key] = type.toString()
}
}
return obj
}
export const connect = async (roomName) => {
const room = new Room(roomName)
await room._initialise()
return room
}