-
Yuriy Maksymets authoredYuriy Maksymets authored
app.js 5.69 KiB
import { line, curveLinear } from "d3-shape"
import { HTML } from "./elements.js"
import { connect } from "./room.js"
const {
canvas,
connectedPeers,
peerButton,
peerIDElem,
userIDElem,
roomIDElem,
penButton,
eraserButton,
} = HTML
// 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)")
canvas.appendChild(pathElem)
pathElems.set(id, pathElem)
}
pathElem.innerHTML = ""
if (points.length == 0) {
return pathElem
}
// Push a fake path split to generate the last path
points.push([-1, -1, false])
let subpath = []
for (let point of points) {
if (point[0] === undefined) {
continue
}
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)
}
subpath = []
continue
}
subpath.push(point)
}
return pathElem
}
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"
}
function hideConnectedRoom() {
setElemVisible(HTML.connectedRoomInfoContainer, false)
}
function showConnectedRoom(roomID) {
HTML.connectedRoomID.textContent = roomID
setElemVisible(HTML.connectedRoomInfoContainer)
}
function connectToARoom(roomID) {
connect(roomID).then((room) => {
showConnectedRoom(roomID)
let userInput = false
let currentTool = tools.PEN
let currentPathID = null
userIDElem.value = room.ownID || ""
room.addEventListener("allocateOwnID", ({ detail: id }) => {
userIDElem.value = id
})
room.addEventListener("userJoin", ({ detail: id }) => {
const peerElem = document.createElement("li")
peerElem.innerHTML = id
connectedPeers.appendChild(peerElem)
})
room.addEventListener("userLeave", ({ detail: id }) => {
for (const peerElem of connectedPeers.children) {
if (peerElem.innerHTML == id) {
connectedPeers.removeChild(peerElem)
}
}
})
room.addEventListener("addOrUpdatePath", ({ detail: { id, points } }) => {
addOrUpdatePathElem(id, points)
})
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)
}
})
})
}
})
canvas.addEventListener("mouseleave", () => {
userInput = false
})
canvas.addEventListener("mouseup", () => {
userInput = false
})
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)
}
})
})
}
})
peerButton.addEventListener("click", () => {
const peerID = peerIDElem.value
if (peerID == "") {
return
}
room.inviteUser(peerID)
peerIDElem.value = ""
})
penButton.addEventListener("click", () => {
currentTool = tools.PEN
penButton.classList.add("selected")
eraserButton.classList.remove("selected")
})
eraserButton.addEventListener("click", () => {
currentTool = tools.ERASER
penButton.classList.remove("selected")
eraserButton.classList.add("selected")
})
HTML.roomConnectButton.addEventListener("click", () => {
room = undefined
const selectedRoomID = roomIDElem.value
if (!selectedRoomID) return
connectToARoom(selectedRoomID)
})
})
}
HTML.roomConnectButton.addEventListener("click", () => {
const selectedRoomID = roomIDElem.value
if (!selectedRoomID) return
connectToARoom(selectedRoomID)
})
hideConnectedRoom()
// connectToARoom("imperial")