Skip to content
Snippets Groups Projects
app.js 7.66 KiB
Newer Older
  • Learn to ignore specific revisions
  • import { line, curveLinear } from "d3-shape"
    
    import * as HTML from "./elements.js"
    
    import { connect } from "./room.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)
    
        pathElems.set(id, pathElem)
    
    Giovanni Caruso's avatar
    Giovanni Caruso committed
    
    
      if (points.length == 0) {
        return pathElem
    
      // Push a fake path split to generate the last path
      points.push([-1, -1, false])
    
      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)
          }
    
      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 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 }) => {
    
        HTML.userIDElem.value = id
    
      })
    
      room.addEventListener("userJoin", ({ detail: id }) => {
    
        if (HTML.connectedPeers.children.length === 0) {
          HTML.connectedPeers.innerHTML = ""
        }
    
    
        const peerElem = document.createElement("li")
        peerElem.innerHTML = id
    
        HTML.connectedPeers.appendChild(peerElem)
    
      })
    
      room.addEventListener("userLeave", ({ detail: id }) => {
    
        for (let peerElem of HTML.connectedPeers.children) {
    
          if (peerElem.innerHTML == id) {
    
            HTML.connectedPeers.removeChild(peerElem)
    
    Giovanni Caruso's avatar
    Giovanni Caruso committed
          }
    
    
        if (HTML.connectedPeers.children.length === 0) {
          HTML.connectedPeers.innerHTML = "No peers are connected"
        }
    
      })
    
      room.addEventListener("addOrUpdatePath", ({ detail: { id, points } }) => {
        addOrUpdatePathElem(id, points)
      })
    
    
      const canvasOnMouseDown = (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("mousedown", canvasOnMouseDown)
    
      const canvasOnMouseLeave = () => {
    
      }
      HTML.canvas.addEventListener("mouseleave", canvasOnMouseLeave)
    
      const canvasOnMouseEnter = (e) => {
    
        if (e.buttons === 0) {
          userInput = false
    
          return
        }
    
        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("mouseenter", canvasOnMouseEnter)
    
      const canvasOnMouseUp = () => {
    
      }
      HTML.canvas.addEventListener("mouseup", canvasOnMouseUp)
    
      const canvasOnMouseMove = (e) => {
    
        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)
              }
    
    Giovanni Caruso's avatar
    Giovanni Caruso committed
            })
    
      }
      HTML.canvas.addEventListener("mousemove", canvasOnMouseMove)
    
      const peerButtonOnClick = () => {
    
        const peerID = HTML.peerIDElem.value
    
        if (peerID == "") {
          return
        }
        room.inviteUser(peerID)
    
        HTML.peerIDElem.value = ""
    
      }
      HTML.peerButton.addEventListener("click", peerButtonOnClick)
    
      const penButtonOnClick = () => {
    
        HTML.penButton.classList.add("selected")
        HTML.eraserButton.classList.remove("selected")
    
      }
      HTML.penButton.addEventListener("click", penButtonOnClick)
    
      const eraserButtonOnClick = () => {
    
        HTML.penButton.classList.remove("selected")
        HTML.eraserButton.classList.add("selected")
    
      }
      HTML.eraserButton.addEventListener("click", eraserButtonOnClick)
    
      HTML.roomConnectButton.removeEventListener("click", handleRoomConnectClick)
      const roomConnectButtonOnClick = () => {
        HTML.canvas.removeEventListener("mousedown", canvasOnMouseDown)
        HTML.canvas.removeEventListener("mouseleave", canvasOnMouseLeave)
        HTML.canvas.removeEventListener("mouseenter", canvasOnMouseEnter)
        HTML.canvas.removeEventListener("mouseup", canvasOnMouseUp)
        HTML.canvas.removeEventListener("mousemove", canvasOnMouseMove)
        HTML.peerButton.removeEventListener("click", peerButtonOnClick)
        HTML.penButton.removeEventListener("click", penButtonOnClick)
        HTML.eraserButton.removeEventListener("click", eraserButtonOnClick)
    
        handleRoomConnectClick()
    
      }
      HTML.roomConnectButton.addEventListener("click", roomConnectButtonOnClick, {
        once: true,
    
      })
    }
    
    function handleRoomConnectionError(err) {
      alert(`Error connecting to a room:\n${err}`)
    }
    
    function connectToARoom(roomID) {
      connect(roomID)
        .then(handleRoomConnectionEstablished)
        .catch(handleRoomConnectionError)
    
    HTML.roomConnectButton.addEventListener("click", handleRoomConnectClick, {
      once: true,
    
    connectToARoom(TEST_ROOM)