Newer
Older
import { line, curveLinear } from "d3-shape"
import * as HTML from "./elements.js"
const TEST_ROOM = "imperial"
// TODO: switch to curve interpolation that respects mouse points based on velocity
const lineFn = line()
.x((d) => d[0])
.y((d) => d[1])
.curve(curveLinear)
const tools = {
PEN: "pen",
ERASER: "eraser",
}
const STROKECOLOUR = "blue"
const STROKERADIUS = 2
const ERASERRADIUS = STROKERADIUS * 2
const pathElems = new Map()
const addOrUpdatePathElem = (id, points) => {
let pathElem = pathElems.get(id)
if (pathElem == null) {
pathElem = document.createElementNS("http://www.w3.org/2000/svg", "g")
pathElem.setAttribute("stroke", STROKECOLOUR)
pathElem.setAttribute("stroke-width", STROKERADIUS * 2)
pathElem.setAttribute("fill", "none")
pathElem.setAttribute("pointer-events", "none")
pathElem.setAttribute("marker-start", "url(#dot)")
pathElem.setAttribute("marker-end", "url(#dot)")
HTML.canvas.appendChild(pathElem)
Moritz Langenstein
committed
pathElem.innerHTML = ""
// Push a fake path split to generate the last path
points.push([-1, -1, false])
Moritz Langenstein
committed
let subpath = []
Moritz Langenstein
committed
for (let point of points) {
if (point[0] === undefined) {
continue
Moritz Langenstein
committed
}
if (point[2] === false) {
if (subpath.length === 1) {
let subpathElem = document.createElementNS(
"http://www.w3.org/2000/svg",
"circle",
)
subpathElem.setAttribute("stroke", "none")
subpathElem.setAttribute("fill", STROKECOLOUR)
subpathElem.setAttribute("cx", subpath[0][0])
subpathElem.setAttribute("cy", subpath[0][1])
subpathElem.setAttribute("r", STROKERADIUS)
pathElem.appendChild(subpathElem)
} else if (subpath.length > 0) {
let subpathElem = document.createElementNS(
"http://www.w3.org/2000/svg",
"path",
)
subpathElem.setAttribute("d", lineFn(subpath))
pathElem.appendChild(subpathElem)
}
Moritz Langenstein
committed
subpath = []
Moritz Langenstein
committed
continue
Moritz Langenstein
committed
}
subpath.push(point)
Moritz Langenstein
committed
}
const getDistance = (a, b) => {
return Math.sqrt(
(a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]),
)
}
function setElemVisible(elem, visible = true) {
if (!(elem && elem.style)) return
elem.style.display = visible ? "block" : "none"
}
Moritz Langenstein
committed
function showConnectedRoom(roomID) {
HTML.connectedRoomID.textContent = roomID
setElemVisible(HTML.connectedRoomInfoContainer)
}
function removeAllChildrenNodes(element) {
while (element.firstChild) {
element.removeChild(element.firstChild)
}
}
function setBlankUIState() {
removeAllChildrenNodes(HTML.canvas)
removeAllChildrenNodes(HTML.connectedPeers)
}
function handleRoomConnectClick() {
const selectedRoomID = HTML.roomIDElem.value
if (!selectedRoomID) return
setBlankUIState()
connectToARoom(selectedRoomID)
}
function handleRoomConnectionEstablished(room) {
showConnectedRoom(room.name)
let userInput = false
let currentTool = tools.PEN
let currentPathID = null
HTML.userIDElem.value = room.ownID || ""
room.addEventListener("allocateOwnID", ({ detail: id }) => {
})
room.addEventListener("userJoin", ({ detail: id }) => {
const peerElem = document.createElement("li")
peerElem.innerHTML = id
HTML.connectedPeers.appendChild(peerElem)
})
room.addEventListener("userLeave", ({ detail: id }) => {
for (const peerElem of HTML.connectedPeers.children) {
if (peerElem.innerHTML == id) {
HTML.connectedPeers.removeChild(peerElem)
}
})
room.addEventListener("addOrUpdatePath", ({ detail: { id, points } }) => {
addOrUpdatePathElem(id, points)
})
HTML.canvas.addEventListener("mousedown", (e) => {
userInput = true
let mouse = [e.offsetX, e.offsetY]
if (currentTool === tools.PEN) {
currentPathID = room.addPath(mouse)
} else if (currentTool === tools.ERASER) {
room.getPaths().forEach((points, pathID) => {
points.forEach((point, i) => {
if (getDistance(mouse, point) <= ERASERRADIUS) {
room.erasePoint(pathID, i)
}
})
}
})
HTML.canvas.addEventListener("mouseleave", () => {
userInput = false
})
Moritz Langenstein
committed
HTML.canvas.addEventListener("mouseup", () => {
userInput = false
})
Moritz Langenstein
committed
HTML.canvas.addEventListener("mousemove", (e) => {
if (!userInput) {
return
}
let mouse = [e.offsetX, e.offsetY]
if (currentTool === tools.PEN) {
room.extendPath(currentPathID, mouse)
} else if (currentTool === tools.ERASER) {
room.getPaths().forEach((points, pathID) => {
points.forEach((point, i) => {
if (getDistance(mouse, point) <= ERASERRADIUS) {
room.erasePoint(pathID, i)
}
HTML.peerButton.addEventListener("click", () => {
const peerID = HTML.peerIDElem.value
if (peerID == "") {
return
}
room.inviteUser(peerID)
HTML.penButton.addEventListener("click", () => {
currentTool = tools.PEN
HTML.penButton.classList.add("selected")
HTML.eraserButton.classList.remove("selected")
HTML.eraserButton.addEventListener("click", () => {
currentTool = tools.ERASER
HTML.penButton.classList.remove("selected")
HTML.eraserButton.classList.add("selected")
})
HTML.roomConnectButton.addEventListener("click", () => {
room = undefined
})
}
function handleRoomConnectionError(err) {
alert(`Error connecting to a room:\n${err}`)
}
function connectToARoom(roomID) {
connect(roomID)
.then(handleRoomConnectionEstablished)
.catch(handleRoomConnectionError)
HTML.roomConnectButton.addEventListener("click", () => {
connectToARoom(TEST_ROOM)