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")