Newer
Older
// Room connection and synchronisation.
// Translate local canvas input events to draw messages and send to the room.
// Get back room updates and invoke the local canvas renderer.
import * as HTML from "./elements.js"
const TEST_ROOM = "imperial"
const {
computeErasureIntervals,
combineErasureIntervals,
} = require("./erasure")
function eraseEverythingAtPosition(x, y, radius, room) {
room.getPaths().forEach((points, pathID) => {
const prevPathIntervals =
(room.erasureIntervals || { [pathID]: {} })[pathID] || {}
const newPathIntervals = computeErasureIntervals(
points,
mousePos,
radius,
prevPathIntervals,
)
const erasureIntervalsForPath = combineErasureIntervals(
prevPathIntervals,
newPathIntervals,
)
Object.keys(erasureIntervalsForPath).forEach((pointID) =>
room.extendErasureIntervals(
pathID,
pointID,
erasureIntervalsForPath[pointID],
),
)
})
}
const onRoomConnect = (room_) => {
room = room_
HTML.connectedRoomID.textContent = room.name
HTML.connectedRoomInfoContainer.style.display = "block"
HTML.userIDElem.value = room.ownID || ""
room.addEventListener("allocateOwnID", ({ detail: id }) => {
})
room.addEventListener("userJoin", ({ detail: id }) => {
if (HTML.connectedPeers.children.length == 0) {
Moritz Langenstein
committed
HTML.connectedPeers.innerHTML = ""
}
insertHTMLPeerElement(id)
})
room.addEventListener("userLeave", ({ detail: id }) => {
HTML.connectedPeers.removeChild(getPeerById(id))
Moritz Langenstein
committed
if (HTML.connectedPeers.children.length == 0) {
Moritz Langenstein
committed
HTML.connectedPeers.innerHTML = "No peers are connected"
}
room.addEventListener("weSyncedWithPeer", ({ detail: id }) => {
getPeerById(id).children[1].className = "peer-status synced"
})
room.addEventListener("waitingForSyncStep", ({ detail: id }) => {
getPeerById(id).children[2].className = "peer-status negotiating"
})
room.addEventListener("peerSyncedWithUs", ({ detail: id }) => {
getPeerById(id).children[2].className = "peer-status synced"
room.addEventListener("addOrUpdatePath", ({ detail: { id, points } }) => {
canvas.renderPath(id, points, room.erasureIntervals)
room.addEventListener(
"removedIntervalsChange",
({ detail: { id, intervals, points } }) => {
room.erasureIntervals[id] = combineErasureIntervals(
room.erasureIntervals[id] || {},
canvas.renderPath(id, points, room.erasureIntervals)
const tryRoomConnect = async (roomID) => {
return await connect(roomID)
.then(onRoomConnect)
.catch((err) => alert(`Error connecting to a room:\n${err}`))
}
Moritz Langenstein
committed
const tools = {
PEN: Symbol("pen"),
ERASER: Symbol("eraser"),
}
let currentTool = tools.PEN
const pathIDsByPointerID = new Map()
Moritz Langenstein
committed
HTML.penButton.addEventListener("click", () => {
if (currentTool == tools.PEN) {
} else {
currentTool = tools.PEN
HTML.penButton.classList.add("selected")
HTML.eraserButton.classList.remove("selected")
Moritz Langenstein
committed
window.addEventListener("click", (event) => {
if (event.target == HTML.penProperties) {
hideElement(HTML.penProperties)
hideElement(HTML.palette)
hideElement(HTML.penProperties)
Moritz Langenstein
committed
const svg = HTML.wheel.children
for (let i = 1; i < svg.length; i++) {
svg[i].addEventListener("click", (event) => {
const paletteColour = event.target.getAttribute("fill")
HTML.rectangle.style.backgroundColor = paletteColour
HTML.picker.value = paletteColour
HTML.labelColour.style.backgroundColor = paletteColour
function showElement(element) {
element.style.display = "block"
}
function hideElement(element) {
element.style.display = "none"
}
HTML.picker.addEventListener("change", () => {
HTML.labelColour.style.backgroundColor = paletteColour
HTML.eraserButton.addEventListener("click", () => {
currentTool = tools.ERASER
HTML.penButton.classList.remove("selected")
HTML.eraserButton.classList.add("selected")
})
Moritz Langenstein
committed
HTML.peerButton.addEventListener("click", () => {
const peerID = HTML.peerIDElem.value
if (room == null || peerID == "") {
return
room.inviteUser(peerID)
HTML.peerIDElem.value = ""
})
HTML.roomConnectButton.addEventListener("click", () => {
const selectedRoomID = HTML.roomIDElem.value
if (!selectedRoomID || selectedRoomID == room.name) {
return
if (room != null) {
room.disconnect()
room = null
canvas.clear()
HTML.connectedPeers.innerHTML = "No peers are connected"
const insertHTMLPeerElement = (id) => {
const peerElem = document.createElement("li")
const peerId = document.createElement("div")
peerId.style.display = "inline"
peerId.innerHTML = id
const ourStatus = document.createElement("div")
ourStatus.className = "peer-status unsynced"
const theirStatus = document.createElement("div")
theirStatus.className = "peer-status unsynced"
peerElem.appendChild(peerId)
peerElem.appendChild(ourStatus)
peerElem.appendChild(theirStatus)
HTML.connectedPeers.appendChild(peerElem)
}
const getPeerById = (id) => {
for (const peerElem of HTML.connectedPeers.children) {
const peerId = peerElem.children[0].innerHTML
if (peerId == id) {
const updateOverallStatusIcon = () => {
for (const peerElem of HTML.connectedPeers.children) {
if (
!peerElem.children[1].classList.contains("synced") ||
!peerElem.children[2].classList.contains("synced")
) {
HTML.overallStatusIcon.className = "synchronising"
HTML.overallStatusIconImage.src = "synchronising.svg"
}
HTML.overallStatusIcon.className = "synchronised"
HTML.overallStatusIconImage.src = "synchronised.svg"
}
canvas.input.addEventListener("strokestart", ({ detail: e }) => {
if (room == null) {
return
}
room.addPath([...mousePos, e.pressure, canvas.getStrokeColour()]),
eraseEverythingAtPosition(mousePos[0], mousePos[1], ERASER_RADIUS, room)
canvas.input.addEventListener("strokeend", ({ detail: e }) => {
pathIDsByPointerID.delete(e.pointerId)
})
canvas.input.addEventListener("strokemove", ({ detail: e }) => {
if (room == null) {
return
}
room.extendPath(pathIDsByPointerID.get(e.pointerId), [
...mousePos,
e.pressure,
eraseEverythingAtPosition(mousePos[0], mousePos[1], ERASER_RADIUS, room)