Skip to content
Snippets Groups Projects
Forked from sweng-group-15 / drawing-app
333 commits behind the upstream repository.
app.js 3.74 KiB
// 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 canvas from "./canvas.js"
import * as HTML from "./elements.js"
import { connect } from "./room.js"

const TEST_ROOM = "imperial"

let room = null

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 }) => {
    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 (const peerElem of HTML.connectedPeers.children) {
      if (peerElem.innerHTML == id) {
        HTML.connectedPeers.removeChild(peerElem)
      }
    }

    if (HTML.connectedPeers.children.length == 0) {
      HTML.connectedPeers.innerHTML = "No peers are connected"
    }
  })

  room.addEventListener("addOrUpdatePath", ({ detail: { id, points } }) => {
    canvas.renderPath(id, points)
  })
}

const tryRoomConnect = async (roomID) => {
  return await connect(roomID)
    .then(onRoomConnect)
    .catch((err) => alert(`Error connecting to a room:\n${err}`))
}

const ERASER_RADIUS = canvas.STROKE_RADIUS * 2
const tools = {
  PEN: Symbol("pen"),
  ERASER: Symbol("eraser"),
}
let currentTool = tools.PEN
const pathIDsByPointerID = new Map()

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

  tryRoomConnect(selectedRoomID)
})

const getDistance = (a, b) => {
  return Math.sqrt(
    (a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]),
  )
}

const erasePoint = ([x, y]) => {
  if (room == null) {
    return
  }
  room.getPaths().forEach((points, pathID) => {
    points.forEach((point, i) => {
      if (getDistance([x, y], point) <= ERASER_RADIUS) {
        room.erasePoint(pathID, i)
      }
    })
  })
}

canvas.input.addEventListener("strokestart", ({ detail: e }) => {
  if (room == null) {
    return
  }

  const mousePos = [e.offsetX, e.offsetY]

  if (currentTool == tools.PEN) {
    pathIDsByPointerID.set(e.pointerId, room.addPath(mousePos))
  } else if (currentTool == tools.ERASER) {
    erasePoint(mousePos)
  }
})

canvas.input.addEventListener("strokeend", ({ detail: e }) => {
  pathIDsByPointerID.delete(e.pointerId)
})

canvas.input.addEventListener("strokemove", ({ detail: e }) => {
  if (room == null) {
    return
  }

  const mousePos = [e.offsetX, e.offsetY]

  if (currentTool == tools.PEN) {
    room.extendPath(pathIDsByPointerID.get(e.pointerId), mousePos)
  } else if (currentTool == tools.ERASER) {
    erasePoint(mousePos)
  }
})

tryRoomConnect(TEST_ROOM)