diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c334e6cb01556c29d2e001314e3e524aeb943ef1..3739d9e6b63681257df33e1c1ae685225651c4da 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,7 @@ submodule_fetch: - src/signalbuddy - src/yjs -node_install: +npm_install_prod: stage: deps dependencies: - submodule_fetch @@ -36,7 +36,7 @@ node_install: - src/signalbuddy - src/yjs -dev_node_install: +npm_install: stage: deps dependencies: - submodule_fetch @@ -50,42 +50,42 @@ dev_node_install: - src/signalbuddy - src/yjs -check_format: +format_check: stage: check dependencies: - - dev_node_install + - npm_install script: - - npx prettier --ignore-path .gitignore --check "**/*.{html,js,json,md}" + - npm run format-check lint: stage: check dependencies: - - dev_node_install + - npm_install script: - - npx eslint --ignore-path .gitignore "**/*.js" + - npm run lint -backend_build: +build: stage: build dependencies: - - dev_node_install + - npm_install script: - npm run build artifacts: paths: - public/ -backend_test: +test: stage: test dependencies: - - dev_node_install + - npm_install script: - npm test deploy: stage: deploy dependencies: - - node_install - - backend_build + - npm_install_prod + - build only: - master script: diff --git a/package-lock.json b/package-lock.json index 6c9afc89046916182815901a08b5a0c7745102ab..c9fc18114add6658b91187915966d9037935199f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -175,9 +175,9 @@ } }, "@babel/parser": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.2.tgz", - "integrity": "sha512-DDaR5e0g4ZTb9aP7cpSZLkACEBdoLGwJDWgHtBhrGX7Q1RjhdoMOfexICj5cqTAtpowjGQWfcvfnQG7G2kAB5w==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.3.tgz", + "integrity": "sha512-bqv+iCo9i+uLVbI0ILzKkvMorqxouI+GbV13ivcARXn9NNEabi2IEz912IgNpT/60BNXac5dgcfjb94NjsF33A==", "dev": true }, "@babel/plugin-syntax-object-rest-spread": { @@ -2332,9 +2332,9 @@ } }, "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -5393,9 +5393,9 @@ }, "dependencies": { "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "dev": true, "requires": { "base64-js": "^1.0.2", @@ -5542,9 +5542,9 @@ } }, "object-inspect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", "dev": true }, "object-keys": { @@ -5929,9 +5929,9 @@ "dev": true }, "prettier": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", - "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", "dev": true }, "pretty-bytes": { @@ -5976,9 +5976,9 @@ "dev": true }, "prompts": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.2.1.tgz", - "integrity": "sha512-VObPvJiWPhpZI6C5m60XOzTfnYg/xc/an+r9VYymj9WJW3B/DIH+REzjpAACPf8brwPeP+7vz3bIim3S+AaMjw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.0.tgz", + "integrity": "sha512-NfbbPPg/74fT7wk2XYQ7hAIp9zJyZp5Fu19iRbORqqy1BhtrkZ0fPafBU+7bmn8ie69DpT0R6QpJIN2oisYjJg==", "dev": true, "requires": { "kleur": "^3.0.3", @@ -6660,9 +6660,9 @@ } }, "sisteransi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.3.tgz", - "integrity": "sha512-SbEG75TzH8G7eVXFSN5f9EExILKfly7SUvVY5DhhYLvfhKqhDFY0OzevWa/zwak0RLRfWS5AvfMWpd9gJvr5Yg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.4.tgz", + "integrity": "sha512-/ekMoM4NJ59ivGSfKapeG+FWtrmWvA1p6FBZwXrqojw90vJu8lBmrTxCMuBCydKtkaUe2zt4PlxeTKpjwMbyig==", "dev": true }, "slash": { @@ -7246,9 +7246,9 @@ "dev": true }, "terser": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.9.tgz", - "integrity": "sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.4.0.tgz", + "integrity": "sha512-oDG16n2WKm27JO8h4y/w3iqBGAOSCtq7k8dRmrn4Wf9NouL0b2WpMHGChFGZq4nFAQy1FsNJrVQHfurXOSTmOA==", "dev": true, "requires": { "commander": "^2.20.0", diff --git a/package.json b/package.json index 7ba1899f33eb7f487e3977d7780476ac98985855..022fbe40914f4f187cb5994b1661c9cea97c6770 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "test": "jest --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs", "test-changed": "jest --only-changed --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs", "test-coverage": "jest --coverage --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs", - "lint": "jshint .", + "format": "prettier --ignore-path .gitignore --check --write '**/*.{html,js,json,md}'", + "format-check": "prettier --ignore-path .gitignore --check '**/*.{html,js,json,md}'", + "lint": "eslint --ignore-path .gitignore '**/*.js'", "validate": "npm ls" }, "dependencies": { diff --git a/public/index.html b/public/index.html index 2786830805f63ca7436be14656a952ed34ce5496..313dd88b7aed19bb13894317da3112fafce5c56d 100644 --- a/public/index.html +++ b/public/index.html @@ -4,6 +4,7 @@ <meta charset="UTF-8" /> <link rel="manifest" href="manifest.json" /> <link rel="shortcut icon" href="logo.png" /> + <link rel="apple-touch-icon" href="logo.png" /> <link rel="stylesheet" href="styles.css" /> <link @@ -193,7 +194,7 @@ <b>Other colours</b> </div> <label id="colours"> - <input id="other-colours" type="color" value="blue" /> + <input id="other-colours" type="color" value="#0000ff" /> </label> </div> </div> diff --git a/public/quality-high.svg b/public/quality-high.svg new file mode 100644 index 0000000000000000000000000000000000000000..4ad23ecaea7a2bdf6704352e16c64f0b6d5c0d8c --- /dev/null +++ b/public/quality-high.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="green" stroke="green" d="M8.213 16.984c.97-1.028 2.308-1.664 3.787-1.664s2.817.636 3.787 1.664l-3.787 4.016-3.787-4.016zm-1.747-1.854c1.417-1.502 3.373-2.431 5.534-2.431s4.118.929 5.534 2.431l2.33-2.472c-2.012-2.134-4.793-3.454-7.864-3.454s-5.852 1.32-7.864 3.455l2.33 2.471zm-4.078-4.325c2.46-2.609 5.859-4.222 9.612-4.222s7.152 1.613 9.612 4.222l2.388-2.533c-3.071-3.257-7.313-5.272-12-5.272s-8.929 2.015-12 5.272l2.388 2.533z"/></svg> diff --git a/public/quality-low.svg b/public/quality-low.svg new file mode 100644 index 0000000000000000000000000000000000000000..258dfb67e42c9de932a87a771cfa02035db0b74f --- /dev/null +++ b/public/quality-low.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="red" d="M8.213 16.984c.97-1.028 2.308-1.664 3.787-1.664s2.817.636 3.787 1.664l-3.787 4.016-3.787-4.016zm3.787-6.78c2.387 0 4.648.876 6.461 2.485l-.969 1.028c-1.556-1.308-3.472-2.018-5.492-2.018-2.021 0-3.937.71-5.492 2.018l-.969-1.028c1.813-1.609 4.075-2.485 6.461-2.485zm0-1c-3.071 0-5.852 1.32-7.864 3.455l2.33 2.472c1.417-1.502 3.373-2.431 5.534-2.431s4.117.929 5.534 2.431l2.33-2.472c-2.012-2.135-4.793-3.455-7.864-3.455zm0-5.204c3.949 0 7.682 1.517 10.607 4.291l-1.021 1.083c-2.656-2.452-6.023-3.791-9.586-3.791s-6.93 1.339-9.586 3.791l-1.021-1.083c2.926-2.774 6.658-4.291 10.607-4.291zm0-1c-4.687 0-8.929 2.015-12 5.272l2.388 2.533c2.46-2.609 5.859-4.222 9.612-4.222 3.754 0 7.152 1.613 9.611 4.222l2.389-2.533c-3.071-3.257-7.313-5.272-12-5.272z"/></svg> diff --git a/public/quality-medium.svg b/public/quality-medium.svg new file mode 100644 index 0000000000000000000000000000000000000000..b17cacdf887e379dfc57b7a85133813eefde66c5 --- /dev/null +++ b/public/quality-medium.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="orange" d="M8.213 16.984c.97-1.028 2.308-1.664 3.787-1.664s2.817.636 3.787 1.664l-3.787 4.016-3.787-4.016zm-1.747-1.854c1.417-1.502 3.373-2.431 5.534-2.431s4.118.929 5.534 2.431l2.33-2.472c-2.012-2.134-4.793-3.454-7.864-3.454s-5.852 1.32-7.864 3.455l2.33 2.471zm5.534-11.13c3.949 0 7.681 1.517 10.607 4.291l-1.021 1.083c-2.656-2.452-6.023-3.791-9.586-3.791s-6.93 1.339-9.586 3.791l-1.021-1.083c2.926-2.774 6.658-4.291 10.607-4.291zm0-1c-4.687 0-8.929 2.015-12 5.272l2.388 2.533c2.46-2.609 5.859-4.222 9.612-4.222s7.152 1.613 9.612 4.222l2.388-2.533c-3.071-3.257-7.313-5.272-12-5.272z"/></svg> diff --git a/public/service-worker.js b/public/service-worker.js index 971cfcd807b0b91b549b816f7b290d37174a8c4e..fd494a34e21a5d00a8c8200bb8e685e6753e57bf 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -22,6 +22,9 @@ const FILES_TO_CACHE = [ "/assets/fonts/martel-v4-latin/martel-v4-latin-regular.woff2", "/synchronising.svg", "/synchronised.svg", + "/quality-high.svg", + "/quality-medium.svg", + "/quality-low.svg", ] const FILE_ALIASES = new Map([["/", "/index.html"]]) diff --git a/public/styles.css b/public/styles.css index b8f3cc401e328a0a7e53753024afd5b171e7965c..e8fa5b3781bdabf27f4f90fa6ceb156d2c793126 100644 --- a/public/styles.css +++ b/public/styles.css @@ -111,6 +111,8 @@ button.selected { border-bottom: 1px solid black; padding: 4px 0; opacity: 0; + display: flex; + align-items: center; } #connected-peers li:nth-child(2) { @@ -145,24 +147,47 @@ button.selected { border-bottom-width: 0px; } +.peer-quality, .peer-status { width: 15px; height: 15px; - margin-left: 5px; - display: inline-block; + margin-right: 5px; +} + +.peer-status { border-radius: 10px; } +.peer-status::after { + font-size: 0.5em; + margin-left: 10px; + background-color: white; + border-radius: 50%; +} + +.peer-status.upload::after { + content: "â–²"; + padding: 0.5px; +} + +.peer-status.download::after { + content: "â–¼"; + padding: 1px; +} + .peer-status.unsynced { + color: gray; background-color: gray; } @keyframes peer-status-negotiating { from { + color: gray; background-color: gray; } 50%, to { + color: orange; background-color: orange; } } @@ -170,9 +195,11 @@ button.selected { .peer-status.negotiating { animation: peer-status-negotiating 0.5s step-end infinite; background-color: orange; + color: orange; } .peer-status.synced { + color: green; background-color: green; } diff --git a/src/app.js b/src/app.js index d909baf29730517c6984afad5422d5f6f1540f03..c0e3d85d1d07160b5f635a812d3145c6630b888f 100644 --- a/src/app.js +++ b/src/app.js @@ -1,15 +1,35 @@ // Room connection and synchronisation. -// Translate local canvas input events to draw messages and send to the room. +// Translate local canvas input events to draw messages in light of current tool +// selections 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 { computeErasureIntervals, combineErasureIntervals } from "./erasure.js" import { connect } from "./room.js" +import * as toolSelection from "./tool-selection.js" + +const DEFAULT_ROOM = "imperial" + +const MIN_PRESSURE_FACTOR = 0.1 +const MAX_PRESSURE_FACTOR = 1.5 + +// This is a quadratic such that: +// - getPressureFactor(0.0) = MIN_PRESSURE_FACTOR +// - getPressureFactor(0.5) = 1.0 +// - getPressureFactor(1.0) = MAX_PRESSURE_FACTOR +// For sensible results, maintain that: +// - 0.0 <= MIN_PRESSURE_FACTOR <= 1.0 +// - 1.0 <= MAX_PRESSURE_FACTOR +// For intuitive results, maintain that: +// - MAX_PRESSURE_FACTOR <= ~2.0 +const getPressureFactor = (pressure) => { + const a = 2 * (MAX_PRESSURE_FACTOR + MIN_PRESSURE_FACTOR) - 4 + const b = -MAX_PRESSURE_FACTOR - 3 * MIN_PRESSURE_FACTOR + 4 + const c = MIN_PRESSURE_FACTOR + return a * pressure ** 2 + b * pressure + c +} -const TEST_ROOM = "imperial" - -const ERASER_RADIUS = 20 let room = null function eraseEverythingAtPosition(x, y, radius, room) { @@ -69,18 +89,41 @@ const onRoomConnect = (room_) => { updateOverallStatusIcon() }) + room.addEventListener("userConnection", ({ detail: { id, quality } }) => { + const high = "/quality-high.svg" + const medium = "/quality-medium.svg" + const low = "/quality-low.svg" + + const peer = getOrInsertPeerById(id).children[0] + if (quality < 0.33) { + if (!peer.src.includes(high)) { + peer.src = high + } + } else if (quality < 0.66) { + if (!peer.src.includes(medium)) { + peer.src = medium + } + } else { + if (!peer.src.includes(low)) { + peer.src = low + } + } + }) + room.addEventListener("weSyncedWithPeer", ({ detail: id }) => { - getOrInsertPeerById(id).children[1].className = "peer-status synced" + getOrInsertPeerById(id).children[1].className = "peer-status upload synced" updateOverallStatusIcon() }) room.addEventListener("waitingForSyncStep", ({ detail: id }) => { - getOrInsertPeerById(id).children[2].className = "peer-status negotiating" + getOrInsertPeerById(id).children[2].className = + "peer-status download negotiating" updateOverallStatusIcon() }) room.addEventListener("peerSyncedWithUs", ({ detail: id }) => { - getOrInsertPeerById(id).children[2].className = "peer-status synced" + getOrInsertPeerById(id).children[2].className = + "peer-status download synced" updateOverallStatusIcon() }) @@ -106,101 +149,8 @@ const tryRoomConnect = async (roomID) => { .catch((err) => alert(`Error connecting to a room:\n${err}`)) } -const tools = { - PEN: Symbol("pen"), - ERASER: Symbol("eraser"), -} -let currentTool = tools.PEN const pathIDsByPointerID = new Map() -HTML.penButton.addEventListener("click", () => { - if (currentTool == tools.PEN) { - showElement(HTML.penProperties) - } else { - currentTool = tools.PEN - HTML.penButton.classList.add("selected") - HTML.eraserButton.classList.remove("selected") - } -}) - -HTML.closeButton.forEach((element) => { - element.addEventListener("click", () => { - hideElement(element.parentNode.parentNode.parentNode) - }) -}) - -window.addEventListener("click", (event) => { - if (event.target == HTML.penProperties) { - hideElement(HTML.penProperties) - } else if (event.target == HTML.palette) { - hideElement(HTML.palette) - hideElement(HTML.penProperties) - } -}) - -HTML.rectangle.addEventListener("click", () => { - showElement(HTML.palette) -}) - -const svg = HTML.wheel.children -for (let i = 1; i < svg.length; i++) { - svg[i].addEventListener("click", (event) => { - const paletteColour = event.target.getAttribute("fill") - HTML.rectangle.style.backgroundColor = paletteColour - HTML.picker.value = paletteColour - HTML.labelColours.style.backgroundColor = paletteColour - canvas.setStrokeColour(paletteColour) - hideElement(HTML.palette) - }) -} - -function showElement(element) { - element.style.display = "block" -} - -function hideElement(element) { - element.style.display = "none" -} - -HTML.picker.addEventListener("change", () => { - const paletteColour = event.target.value - HTML.rectangle.style.backgroundColor = paletteColour - HTML.labelColours.style.backgroundColor = paletteColour - canvas.setStrokeColour(paletteColour) -}) - -HTML.output.innerHTML = HTML.slider.value - -HTML.slider.oninput = function() { - HTML.output.innerHTML = this.value - canvas.setStrokeRadius(this.value / 10) -} - -const x = window.matchMedia( - "only screen and (orientation: landscape) and (max-width: 600px)", -) -x.addListener(() => { - if (x.matches) { - HTML.wheel.setAttribute("viewBox", "-50 10 200 100") - HTML.palette.setAttribute("style", "padding-top: 50px") - } else { - HTML.wheel.setAttribute("viewBox", "0 10 100 100") - } -}) - -HTML.picker.addEventListener("change", () => { - const paletteColour = event.target.value - HTML.rectangle.style.backgroundColor = paletteColour - HTML.labelColours.style.backgroundColor = paletteColour - canvas.setStrokeColour(paletteColour) -}) - -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 == "") { @@ -239,26 +189,32 @@ HTML.roomIDElem.addEventListener("keydown", (event) => { const getOrInsertPeerById = (id) => { for (const peerElem of HTML.connectedPeers.children) { - const peerId = peerElem.children[0].innerHTML + const peerId = peerElem.children[3].innerHTML if (peerId == id) { return peerElem } } const peerElem = document.createElement("li") - const peerId = document.createElement("div") - peerId.style.display = "inline" - peerId.innerHTML = id + const quality = document.createElement("img") + quality.src = "/quality-low.svg" + quality.alt = "Peer quality icon" + quality.className = "peer-quality" const ourStatus = document.createElement("div") - ourStatus.className = "peer-status unsynced" + ourStatus.className = "peer-status upload unsynced" const theirStatus = document.createElement("div") - theirStatus.className = "peer-status unsynced" + theirStatus.className = "peer-status download unsynced" - peerElem.appendChild(peerId) + const peerId = document.createElement("div") + peerId.style.marginLeft = "5px" + peerId.innerHTML = id + + peerElem.appendChild(quality) peerElem.appendChild(ourStatus) peerElem.appendChild(theirStatus) + peerElem.appendChild(peerId) HTML.connectedPeers.appendChild(peerElem) @@ -284,20 +240,24 @@ canvas.input.addEventListener("strokestart", ({ detail: e }) => { if (room == null) { return } - + const currentTool = toolSelection.getTool() const mousePos = [e.offsetX, e.offsetY] - - if (currentTool == tools.PEN) { + if (currentTool == toolSelection.Tools.PEN) { pathIDsByPointerID.set( e.pointerId, room.addPath([ ...mousePos, - canvas.getStrokeRadius(e.pressure), - canvas.getStrokeColour(), + toolSelection.getStrokeRadius() * getPressureFactor(e.pressure), + toolSelection.getStrokeColour(), ]), ) - } else if (currentTool == tools.ERASER) { - eraseEverythingAtPosition(mousePos[0], mousePos[1], ERASER_RADIUS, room) + } else if (currentTool == toolSelection.Tools.ERASER) { + eraseEverythingAtPosition( + mousePos[0], + mousePos[1], + toolSelection().getEraseRadius(), + room, + ) } }) @@ -309,18 +269,22 @@ canvas.input.addEventListener("strokemove", ({ detail: e }) => { if (room == null) { return } - + const currentTool = toolSelection.getTool() const mousePos = [e.offsetX, e.offsetY] - - if (currentTool == tools.PEN) { + if (currentTool == toolSelection.Tools.PEN) { room.extendPath(pathIDsByPointerID.get(e.pointerId), [ ...mousePos, - canvas.getStrokeRadius(e.pressure), - canvas.getStrokeColour(), + toolSelection.getStrokeRadius() * getPressureFactor(e.pressure), + toolSelection.getStrokeColour(), ]) - } else if (currentTool == tools.ERASER) { - eraseEverythingAtPosition(mousePos[0], mousePos[1], ERASER_RADIUS, room) + } else if (currentTool == toolSelection.Tools.ERASER) { + eraseEverythingAtPosition( + mousePos[0], + mousePos[1], + toolSelection.getEraseRadius(), + room, + ) } }) -tryRoomConnect(TEST_ROOM) +tryRoomConnect(DEFAULT_ROOM) diff --git a/src/canvas.js b/src/canvas.js index 042f5d8d960208487ba70dbeb4df9771ad19b720..b2ea47b763fc24c9cafa6df9a71ce2aec0d82b64 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -21,17 +21,12 @@ const smoothLine = line() const pathGroupElems = new Map() -let strokeColour = "blue" -let strokeRadius = 1 -export const MIN_PRESSURE = 0.1 -export const MAX_PRESSURE = 1.0 - const MAX_POINT_DISTANCE = 5 const MAX_RADIUS_DELTA = 0.05 // Interpolate a path so that: // - The distance between two adjacent points is capped at MAX_POINT_DISTANCE. -// - The pressure delta between two adjacent points is capped at +// - The radius delta between two adjacent points is capped at // MAX_RADIUS_DELTA // If paths are too choppy, try decreasing these constants. const smoothPath = ([...path]) => { @@ -291,23 +286,3 @@ canvas.addEventListener("pointerup", dispatchPointerEvent("strokeend")) canvas.addEventListener("pointerleave", dispatchPointerEvent("strokeend")) canvas.addEventListener("pointermove", dispatchPointerEvent("strokemove")) canvas.addEventListener("touchmove", (e) => e.preventDefault()) - -export const setStrokeColour = (colour) => { - strokeColour = colour -} - -export const getStrokeColour = () => { - return strokeColour -} - -export const setStrokeRadius = (radius) => { - strokeRadius = radius -} - -const calculateStrokeRadius = (pressure, radius) => { - return radius * (MIN_PRESSURE + pressure * (MAX_PRESSURE - MIN_PRESSURE)) -} - -export const getStrokeRadius = (pressure) => { - return calculateStrokeRadius(pressure, strokeRadius) -} diff --git a/src/room.js b/src/room.js index 1e3ce2ff317108446f190b97be3ca6c06217bded..84c1928e87125292f9f858e9dd04c0916ac46861 100644 --- a/src/room.js +++ b/src/room.js @@ -116,9 +116,9 @@ class Room extends EventTarget { }, onUserEvent: (event) => { if (event.action == "userConnection") { - const { quality } = event + const { id, quality } = event this.dispatchEvent( - new CustomEvent("userConnection", { detail: quality }), + new CustomEvent("userConnection", { detail: { id, quality } }), ) } else if (event.action == "userID") { const { user: id } = event diff --git a/src/tool-selection.js b/src/tool-selection.js new file mode 100644 index 0000000000000000000000000000000000000000..0daf77d92955e64cf65ce103d4f8f14092fd58f3 --- /dev/null +++ b/src/tool-selection.js @@ -0,0 +1,98 @@ +import * as HTML from "./elements.js" + +export const Tools = Object.freeze({ + PEN: Symbol("pen"), + ERASER: Symbol("eraser"), +}) + +let tool = Tools.PEN +let strokeColour = "#0000ff" +let strokeRadius = 5 +// TODO: The erase radius should also be selectable. +const ERASE_RADIUS = 20 + +export const getTool = () => tool +export const getStrokeColour = () => strokeColour +export const getStrokeRadius = () => strokeRadius +export const getEraseRadius = () => ERASE_RADIUS + +const showElement = (element) => { + element.style.display = "block" +} + +const hideElement = (element) => { + element.style.display = "none" +} + +HTML.penButton.addEventListener("click", () => { + if (tool == Tools.PEN) { + showElement(HTML.penProperties) + } else { + tool = Tools.PEN + HTML.penButton.classList.add("selected") + HTML.eraserButton.classList.remove("selected") + } +}) + +HTML.eraserButton.addEventListener("click", () => { + tool = Tools.ERASER + HTML.penButton.classList.remove("selected") + HTML.eraserButton.classList.add("selected") +}) + +HTML.picker.addEventListener("change", () => { + const paletteColour = event.target.value + HTML.rectangle.style.backgroundColor = paletteColour + HTML.labelColours.style.backgroundColor = paletteColour + strokeColour = paletteColour +}) + +HTML.slider.oninput = function() { + HTML.output.innerHTML = this.value + strokeRadius = this.value / 10 +} + +HTML.output.innerHTML = HTML.slider.value + +const x = window.matchMedia( + "only screen and (orientation: landscape) and (max-width: 600px)", +) +x.addListener(() => { + if (x.matches) { + HTML.wheel.setAttribute("viewBox", "-50 10 200 100") + HTML.palette.setAttribute("style", "padding-top: 50px") + } else { + HTML.wheel.setAttribute("viewBox", "0 10 100 100") + } +}) + +HTML.closeButton.forEach((element) => { + element.addEventListener("click", () => { + hideElement(element.parentNode.parentNode.parentNode) + }) +}) + +window.addEventListener("click", (event) => { + if (event.target == HTML.penProperties) { + hideElement(HTML.penProperties) + } else if (event.target == HTML.palette) { + hideElement(HTML.palette) + hideElement(HTML.penProperties) + } +}) + +HTML.rectangle.addEventListener("click", () => { + showElement(HTML.palette) +}) + +const svg = HTML.wheel.children +for (let i = 1; i < svg.length; i++) { + svg[i].addEventListener("click", (event) => { + const paletteColour = event.target.getAttribute("fill") + HTML.rectangle.style.backgroundColor = paletteColour + HTML.picker.value = paletteColour + HTML.labelColours.style.backgroundColor = paletteColour + strokeColour = paletteColour + hideElement(HTML.palette) + }) +} diff --git a/src/y-webrtc/index.js b/src/y-webrtc/index.js index ba19f1032078541c92eb502ba87b81012f637b11..3ef2c68d222b15dd7d18d028e451dad5b83d0be6 100644 --- a/src/y-webrtc/index.js +++ b/src/y-webrtc/index.js @@ -255,6 +255,7 @@ function extend(Y) { } this.raiseUserEvent("userConnection", { + id: peer.id, quality: 1.0 - (self.webrtcOptions.heartbeat.timeout -