Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • hlgr/drawing-app
  • sweng-group-15/drawing-app
2 results
Show changes
Commits on Source (146)
Showing with 8853 additions and 2214 deletions
......@@ -5,11 +5,13 @@
"node": true,
"jest": true
},
"extends": ["eslint:recommended", "prettier"],
"plugins": ["testcafe"],
"extends": ["eslint:recommended", "prettier", "plugin:testcafe/recommended"],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
......
# Created by https://www.gitignore.io/api/vim,node,macos,visualstudiocode
# Edit at https://www.gitignore.io/?templates=vim,node,macos,visualstudiocode
......@@ -10,6 +9,54 @@ src/liowebrtc
src/rtcpeerconnection
src/signalbuddy
src/yjs
src/drawing-crdt
# Temporary benchmark dump files
.dot-seq-add-packets.json
.dot-seq-erase-packets.json
.dot-seq-sync-packets.json
.dot-seq-add-events.json
.dot-seq-erase-events.json
.dot-seq-sync-events.json
.dot-par-add-packets.json
.dot-par-erase-packets.json
.dot-par-sync-packets.json
.dot-par-add-events.json
.dot-par-erase-events.json
.dot-par-sync-events.json
.path-seq-add-packets.json
.path-seq-erase-packets.json
.path-seq-sync-packets.json
.path-seq-add-events.json
.path-seq-erase-events.json
.path-seq-sync-events.json
.path-par-add-packets.json
.path-par-erase-packets.json
.path-par-sync-packets.json
.path-par-add-events.json
.path-par-erase-events.json
.path-par-sync-events.json
.dot-ver-add-packets.json
.dot-ver-erase-packets.json
.dot-ver-sync-packets.json
.dot-ver-add-events.json
.dot-ver-erase-events.json
.dot-ver-sync-events.json
.path-ver-add-packets.json
.path-ver-erase-packets.json
.path-ver-sync-packets.json
.path-ver-add-events.json
.path-ver-erase-events.json
.path-ver-sync-events.json
# Benchmark output files
plots/*.tsv
plots/*.pdf
### macOS ###
# General
......@@ -118,8 +165,10 @@ typings/
.nuxt
# react / gatsby (customised)
public/js/app.js
public/js/queue.js
public/service-worker.js
public/js
public/benchmarks.html
public/assets/fonts/font-awesome
# vuepress build output
.vuepress/dist
......@@ -155,10 +204,6 @@ tags
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### VisualStudioCode Patch ###
# Ignore all local history of files
......
default:
image: node:12
image: amio/node-chrome
stages:
- fetch
......@@ -8,19 +8,21 @@ stages:
- build
- test
- deploy
- benchmark
submodule_fetch:
stage: fetch
script:
- chmod 600 .drawing-app-deploy.rsa
- git submodule sync --recursive
- GIT_SSH_COMMAND='ssh -i .drawing-app-deploy.rsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' git submodule update --init
- GIT_SSH_COMMAND='ssh -i '`pwd`'/.drawing-app-deploy.rsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' git submodule update --init --recursive
artifacts:
paths:
- src/liowebrtc
- src/rtcpeerconnection
- src/signalbuddy
- src/yjs
- src/drawing-crdt/pkg
npm_install_prod:
stage: deps
......@@ -31,10 +33,7 @@ npm_install_prod:
artifacts:
paths:
- node_modules
- src/liowebrtc
- src/rtcpeerconnection
- src/signalbuddy
- src/yjs
npm_install:
stage: deps
......@@ -47,8 +46,9 @@ npm_install:
- node_modules
- src/liowebrtc
- src/rtcpeerconnection
- src/signalbuddy
- src/yjs
- src/drawing-crdt/pkg
- src/signalbuddy
format_check:
stage: check
......@@ -70,6 +70,7 @@ build:
- npm_install
script:
- npm run build
- gcc -E -P -traditional-cpp -o /dev/stdout -DFILES_TO_CACHE_LIST=`find public/ -type f "!" -iname service-worker.js -and "!" -name '.*' | cut -c6- | sed 's_._"_' | sed 's/$/",/' | sort | tr -d '\n'` - < src/service-worker.js | npx prettier --parser babel > public/service-worker.js
artifacts:
paths:
- public/
......@@ -79,7 +80,16 @@ test:
dependencies:
- npm_install
script:
- npm test
- npm run test
chrome_test:
stage: test
dependencies:
- npm_install
- build
script:
- npm run start-bg
- npm run test-e2e
deploy:
stage: deploy
......@@ -92,3 +102,20 @@ deploy:
- apt-get update -qq && apt-get install -qq zip
- zip -r application.zip *
- curl -X POST -u "$DEPLOYMENT_USERNAME:$DEPLOYMENT_PASSWORD" $DEPLOYMENT_ENDPOINT -T application.zip
benchmark:
stage: benchmark
dependencies:
- npm_install
only:
- master
script:
- apt-get -y install gnuplot
- npm run build:bench
- cp __benchmarks__/benchmarks.html public/benchmarks.html
- npm run start-bg
- npm run benchmarks
- npm run plot
artifacts:
paths:
- plots/
......@@ -10,3 +10,6 @@
[submodule "src/yjs"]
path = src/yjs
url = git@gitlab.doc.ic.ac.uk:sweng-group-15/yjs.git
[submodule "src/drawing-crdt"]
path = src/drawing-crdt
url = git@gitlab.doc.ic.ac.uk:sweng-group-15/drawing-crdt.git
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
</head>
<body>
<script src="js/benchmarks.js"></script>
</body>
</html>
This diff is collapsed.
export const handshake = Uint8Array.of(
133,
164,
117,
117,
105,
100,
217,
36,
97,
54,
100,
57,
50,
55,
97,
56,
45,
53,
50,
57,
101,
45,
52,
55,
50,
54,
45,
97,
102,
48,
98,
45,
56,
48,
57,
55,
98,
48,
48,
102,
55,
99,
49,
55,
167,
109,
101,
115,
115,
97,
103,
101,
196,
3,
162,
109,
108,
165,
115,
108,
105,
99,
101,
0,
166,
108,
101,
110,
103,
116,
104,
3,
170,
99,
111,
109,
112,
114,
101,
115,
115,
101,
100,
194,
)
export const dotDraw = [[209, 88, 5.0, "#0000ff"]]
export const dotErase = [[0, [[0, 0]]]]
export const pathDraw = [
[229, 147, 5.0, "#0000ff"],
[239, 149, 5.0, "#0000ff"],
[265, 154, 5.0, "#0000ff"],
[329, 158, 5.0, "#0000ff"],
[428, 168, 5.0, "#0000ff"],
[559, 172, 5.0, "#0000ff"],
[689, 176, 5.0, "#0000ff"],
[789, 176, 5.0, "#0000ff"],
[871, 178, 5.0, "#0000ff"],
[915, 179, 5.0, "#0000ff"],
[937, 179, 5.0, "#0000ff"],
[942, 179, 5.0, "#0000ff"],
]
export const pathErase = [
[0, [[0, 0.030367582231477598]]],
[0, [[0, 0.21377102974828613]]],
[0, [[0, 0.2537463508623564]]],
[0, [[0, 0.34615384615384587]]],
[0, [[0, 0.4383613354449363]]],
[0, [[0, 0.5303675822314777]]],
[0, [[0, 0.6221712759429692]]],
[0, [[0, 0.7137710297482863]]],
[0, [[0, 0.8051653782004998]]],
[0, [[0, 0.9383613354449363]]],
[0, [[0, 1]]],
[1, [[0, 0.011716311379342382]]],
[1, [[0, 0.11776520948773353]]],
[1, [[0, 0.24006055181058228]]],
[1, [[0, 0.3107159148890084]]],
[1, [[0, 0.3810746303975673]]],
[1, [[0, 0.4329788671985464]]],
[1, [[0, 0.46835801309405006]]],
[1, [[0, 0.5036636541863274]]],
[1, [[0, 0.5740522382479196]]],
[1, [[0, 0.6441399664465288]]],
[1, [[0, 0.731854882371686]]],
[1, [[0, 0.8021237085400632]]],
[1, [[0, 0.8371448744989799]]],
[1, [[0, 0.88955025220044]]],
[1, [[0, 0.9599984343180302]]],
[1, [[0, 1]]],
[2, [[0, 0.028177027652500065]]],
[2, [[0, 0.03543420592413073]]],
[2, [[0, 0.06582991759489924]]],
[2, [[0, 0.10275381734879]]],
[2, [[0, 0.11799692411252251]]],
[2, [[0, 0.13323648198760243]]],
[2, [[0, 0.16370490408198182]]],
[2, [[0, 0.2154080009026301]]],
[2, [[0, 0.24595364724469834]]],
[2, [[0, 0.27648544742767633]]],
[2, [[0, 0.31247481764398494]]],
[2, [[0, 0.3478072470007028]]],
[2, [[0, 0.3785134252130592]]],
[2, [[0, 0.3938616312677431]]],
[2, [[0, 0.42869266182685783]]],
[2, [[0, 0.44408271983476183]]],
[2, [[0, 0.4594695941376009]]],
[2, [[0, 0.4902337694168022]]],
[2, [[0, 0.505611059005356]]],
[2, [[0, 0.5246181146997148]]],
[2, [[0, 0.5400397300348875]]],
[2, [[0, 0.6047746406708809]]],
[2, [[0, 0.6202304708442001]]],
[2, [[0, 0.6356832087546725]]],
[2, [[0, 0.6535687967630837]]],
[2, [[0, 0.7000469301249926]]],
[2, [[0, 0.7155335204468358]]],
[2, [[0, 0.7484433368793898]]],
[2, [[0, 0.8415551828545537]]],
[2, [[0, 0.8896745156013701]]],
[2, [[0, 0.9207665061568425]]],
[2, [[0, 0.9518463520584276]]],
[2, [[0, 0.9840324922550672]]],
[2, [[0, 1]]],
[3, [[0, 0.01983903263791853]]],
[3, [[0, 0.029876622283678104]]],
[3, [[0, 0.03990912701215377]]],
[3, [[0, 0.0806437862168895]]],
[3, [[0, 0.09070123866218649]]],
[3, [[0, 0.10101007524615979]]],
[3, [[0, 0.11110799523965204]]],
[3, [[0, 0.12120077057322706]]],
[3, [[0, 0.13128840847448436]]],
[3, [[0, 0.15144829885129574]]],
[3, [[0, 0.16152056375068494]]],
[3, [[0, 0.1818098260284825]]],
[3, [[0, 0.20202009896967427]]],
[3, [[0, 0.21211750416023725]]],
[3, [[0, 0.2727084630640253]]],
[3, [[0, 0.29292802997685985]]],
[3, [[0, 0.3030300711783685]]],
[3, [[0, 0.33330532017010783]]],
[3, [[0, 0.3636029076253441]]],
[3, [[0, 0.37371993253130364]]],
[3, [[0, 0.3838317824361028]]],
[3, [[0, 0.40403999188002426]]],
[3, [[0, 0.41413636769223433]]],
[3, [[0, 0.42422760105585006]]],
[3, [[0, 0.43431369907567324]]],
[3, [[0, 0.4443662699271226]]],
[3, [[0, 0.4544931529993546]]],
[3, [[0, 0.46461484324950236]]],
[3, [[0, 0.4747313502489194]]],
[3, [[0, 0.5050498610823838]]],
[3, [[0, 0.5151457223183467]]],
[3, [[0, 0.5353220266951074]]],
[3, [[0, 0.5454024835564785]]],
[3, [[0, 0.5858535321955943]]],
[3, [[0, 0.5959591836309699]]],
[3, [[0, 0.616155025527601]]],
[3, [[0, 0.6463920624411829]]],
[3, [[0, 0.6666385590106888]]],
[3, [[0, 0.6767540304709369]]],
[3, [[0, 0.6969694650467834]]],
[3, [[0, 0.7474179601261248]]],
[3, [[0, 0.7675614683096057]]],
[3, [[0, 0.7776255556078067]]],
[3, [[0, 0.8282626561208872]]],
[3, [[0, 0.8484256216002729]]],
[3, [[0, 0.8584994213492495]]],
[3, [[0, 0.8685681068775302]]],
[3, [[0, 0.9087918074364576]]],
[3, [[0, 0.9188349938933553]]],
[3, [[0, 0.9288730921857218]]],
[3, [[0, 0.9889949697600137]]],
[3, [[0, 0.99974858647489]]],
[3, [[0, 1]]],
[4, [[0, 0.015052512188419602]]],
[4, [[0, 0.03029426962088413]]],
[4, [[0, 0.06839243749770468]]],
[4, [[0, 0.0836292133608997]]],
[4, [[0, 0.0912470672285808]]],
[4, [[0, 0.09886456497394153]]],
[4, [[0, 0.13734696870310906]]],
[4, [[0, 0.14497397901135212]]],
[4, [[0, 0.16022693354357556]]],
[4, [[0, 0.16785287776755597]]],
[4, [[0, 0.2061009857691912]]],
[4, [[0, 0.2213715432205954]]],
[4, [[0, 0.2290062878232259]]],
[4, [[0, 0.23664067642184725]]],
[4, [[0, 0.31287013784360607]]],
[4, [[0, 0.33579551026422]]],
[4, [[0, 0.3434365859114192]]],
[4, [[0, 0.37399731542592607]]],
[4, [[0, 0.3892755376794567]]],
[4, [[0, 0.3969141134976991]]],
[4, [[0, 0.41219019494203474]]],
[4, [[0, 0.47328026274975304]]],
[4, [[0, 0.48854921795242295]]],
[4, [[0, 0.5267153779648435]]],
[4, [[0, 0.5343475426943275]]],
[4, [[0, 0.5419793517647892]]],
[4, [[0, 0.5496108052135158]]],
[4, [[0, 0.5801330634496403]]],
[4, [[0, 0.5877627392618375]]],
[4, [[0, 0.5953920596237392]]],
[4, [[0, 0.610649634073643]]],
[4, [[0, 0.6487873509046499]]],
[4, [[0, 0.6564138281707615]]],
[4, [[0, 0.6640399500759696]]],
[4, [[0, 0.7174128527857359]]],
[4, [[0, 0.7326590547531385]]],
[4, [[0, 0.7402816225030254]]],
[4, [[0, 0.7555256913985305]]],
[4, [[0, 0.8469602239274668]]],
[4, [[0, 0.8621943289183245]]],
[4, [[0, 0.8698108469953234]]],
[4, [[0, 0.8774270087010998]]],
[4, [[0, 0.9078880903271799]]],
[4, [[0, 0.9155024690411608]]],
[4, [[0, 0.9231164909119645]]],
[4, [[0, 0.9307301558520772]]],
[4, [[0, 0.9769418956440951]]],
[4, [[0, 0.9845647300195252]]],
[4, [[0, 0.992187208890867]]],
[4, [[0, 0.9998093322301707]]],
[4, [[0, 1]]],
[5, [[0, 0.03820574873127833]]],
[5, [[0, 0.045884233047770166]]],
[5, [[0, 0.061240109456200745]]],
[5, [[0, 0.1226490440345489]]],
[5, [[0, 0.13032352057732857]]],
[5, [[0, 0.14567137935938093]]],
[5, [[0, 0.15334476144043718]]],
[5, [[0, 0.19170619534531316]]],
[5, [[0, 0.19937738618962128]]],
[5, [[0, 0.2070482114971963]]],
[5, [[0, 0.2453968513383652]]],
[5, [[0, 0.26073374476033434]]],
[5, [[0, 0.26840164189649224]]],
[5, [[0, 0.3067356268350702]]],
[5, [[0, 0.322066651341349]]],
[5, [[0, 0.32973161244332394]]],
[5, [[0, 0.34506043162274563]]],
[5, [[0, 0.39869970096661816]]],
[5, [[0, 0.4063609788597506]]],
[5, [[0, 0.41402188753199426]]],
[5, [[0, 0.4676378950251564]]],
[5, [[0, 0.47529584340574377]]],
[5, [[0, 0.5518548940788806]]],
[5, [[0, 0.5595087511478618]]],
[5, [[0, 0.56716223496407]]],
[5, [[0, 0.5824680819078778]]],
[5, [[0, 0.6686904305673842]]],
[5, [[0, 0.6917061953453132]]],
[5, [[0, 0.7070482114971963]]],
[5, [[0, 0.7766873397479431]]],
[5, [[0, 0.7923021861323849]]],
[5, [[0, 0.7999923098661218]]],
[5, [[0, 0.8076820697480561]]],
[5, [[0, 0.8614794103478556]]],
[5, [[0, 0.8768764254973112]]],
[5, [[0, 0.8845743850827851]]],
[5, [[0, 0.8922719794659787]]],
[5, [[0, 0.9458689803376649]]],
[5, [[0, 0.9612817605421127]]],
[5, [[0, 0.9689875986837584]]],
[5, [[0, 1]]],
[6, [[0, 0.029749843554381775]]],
[6, [[0, 0.059749843554381774]]],
[6, [[0, 0.06974984355438178]]],
[6, [[0, 0.13974984355438178]]],
[6, [[0, 0.14974984355438178]]],
[6, [[0, 0.15974984355438177]]],
[6, [[0, 0.16974984355438177]]],
[6, [[0, 0.17974984355438178]]],
[6, [[0, 0.21974984355438176]]],
[6, [[0, 0.22974984355438177]]],
[6, [[0, 0.23974984355438178]]],
[6, [[0, 0.2497498435543818]]],
[6, [[0, 0.3197498435543818]]],
[6, [[0, 0.3397498435543818]]],
[6, [[0, 0.3497498435543818]]],
[6, [[0, 0.35974984355438183]]],
[6, [[0, 0.468997487421324]]],
[6, [[0, 0.48899748742132404]]],
[6, [[0, 0.49899748742132405]]],
[6, [[0, 0.518997487421324]]],
[6, [[0, 0.6277371993328519]]],
[6, [[0, 0.6377371993328519]]],
[6, [[0, 0.6477371993328519]]],
[6, [[0, 0.6677371993328519]]],
[6, [[0, 0.7777371993328519]]],
[6, [[0, 0.7877371993328519]]],
[6, [[0, 0.8077371993328519]]],
[6, [[0, 0.9377371993328519]]],
[6, [[0, 0.957737199332852]]],
[6, [[0, 0.977737199332852]]],
[6, [[0, 1]]],
[7, [[0, 0.08434122461529223]]],
[7, [[0, 0.09736319249220814]]],
[7, [[0, 0.12177670236609432]]],
[7, [[0, 0.13398291047706393]]],
[7, [[0, 0.2560176149231152]]],
[7, [[0, 0.26820494288749264]]],
[7, [[0, 0.2803919084377965]]],
[7, [[0, 0.30476475227840155]]],
[7, [[0, 0.42434250381943905]]],
[7, [[0, 0.4364947880419225]]],
[7, [[0, 0.44864670195280104]]],
[7, [[0, 0.4607982453870106]]],
[7, [[0, 0.5822932659066377]]],
[7, [[0, 0.5944407226857343]]],
[7, [[0, 0.6065878068658583]]],
[7, [[0, 0.7298557216513387]]],
[7, [[0, 0.7420139098657976]]],
[7, [[0, 0.7541717301678039]]],
[7, [[0, 0.7663291824206042]]],
[7, [[0, 0.8284299052782537]]],
[7, [[0, 0.8406001691299525]]],
[7, [[0, 0.8649396036740605]]],
[7, [[0, 0.9753340606845997]]],
[7, [[0, 0.9875148632580261]]],
[7, [[0, 0.9996953030971739]]],
[7, [[0, 1]]],
[8, [[0, 0.022133273001338587]]],
[8, [[0, 0.20418547065439502]]],
[8, [[0, 0.22693303640624127]]],
[8, [[0, 0.24968001232099726]]],
[8, [[0, 0.272426398521291]]],
[8, [[0, 0.47595107564942674]]],
[8, [[0, 0.5214835194176608]]],
[8, [[0, 0.5442488489860375]]],
[8, [[0, 0.5670135839399206]]],
[8, [[0, 0.5897777244732638]]],
[8, [[0, 0.749110080215568]]],
[8, [[0, 0.7718694720767475]]],
[8, [[0, 0.7946282711542776]]],
[8, [[0, 0.8173864776180217]]],
[8, [[0, 1]]],
[9, [[0, 0.04431747070173535]]],
[9, [[0, 0.0897720161562808]]],
[9, [[0, 0.18068110706537172]]],
[9, [[0, 0.4988629252471899]]],
[9, [[0, 0.5897720161562808]]],
[9, [[0, 0.6352265616108262]]],
[9, [[0, 0.7261356525199172]]],
[9, [[0, 1]]],
[10, [[0, 1]]],
[11, [[0, 1]]],
]
import puppeteer from "puppeteer-core"
import fs from "fs"
;(async () => {
const browser = await puppeteer.launch({
executablePath: "google-chrome",
headless: true,
args: [
"--js-flags=--expose-gc",
"--no-sandbox",
"--enable-precise-memory-info",
],
})
const page = await browser.newPage()
const done = new Promise((resolve) => {
page.on("console", (msg) => {
if (msg.type() == "debug") {
process.stderr.write(msg.text())
} else if (msg.type() == "info") {
const { filename, title, iterations, results } = JSON.parse(msg.text())
if (title) {
const columns = ["iterations"]
for (const title of results) {
columns.push(`${title}_timeLoc`)
columns.push(`${title}_encodeRAM`)
columns.push(`${title}_packets`)
columns.push(`${title}_size`)
columns.push(`${title}_timeRem`)
columns.push(`${title}_decodeRAM`)
columns.push(`${title}_events`)
}
fs.writeFileSync(
filename,
`# Benchmark: ${title}\n# ${columns.join("\t")}\n`,
)
} else {
const columns = [iterations]
for (const title in results) {
const {
timeLoc,
encodeRAM,
packets,
size,
timeRem,
decodeRAM,
events,
} = results[title]
columns.push(timeLoc)
columns.push(encodeRAM)
columns.push(packets)
columns.push(size)
columns.push(timeRem)
columns.push(decodeRAM)
columns.push(events)
}
fs.appendFileSync(filename, `${columns.join("\t")}\n`)
}
} else if (msg.type() == "log") {
if (msg.text().startsWith("# failure")) {
resolve()
}
process.stdout.write(msg.text() + "\n")
}
})
})
await page.goto("http://localhost:3000/benchmarks.html").catch(console.error)
await done
await browser.close()
})()
import { Selector } from "testcafe"
const host = "127.0.0.1"
const port = process.env.PORT || 3000
fixture`Peer 1`.page`${host}:${port}`
const idAppearTimeout = 1000
const syncTimeout = 5000
const selectorOptions = { timeout: 1000 }
test("Connection id appears", async (t) => {
const userId = async () =>
await Selector("#user-avatar")
.child(1)
.addCustomDOMProperties({ innerHTML: (el) => el.innerHTML })
.with(selectorOptions).innerHTML
await t
.wait(idAppearTimeout)
.expect((await userId()).length)
.gt(0)
})
test("Clicking and dragging on canvas creates a single child element", async (t) => {
const canvas = Selector("#canvas").with(selectorOptions)
await t
.drag(canvas, 10, 10)
.wait(syncTimeout)
.expect(canvas.childElementCount)
// first draw also creates last recognised path placeholder
.eql(2)
})
import { Selector } from "testcafe"
const host = "127.0.0.1"
const port = 3000
fixture`Peer 2`.page`${host}:${port}`
const idAppearTimeout = 1000
const syncTimeout = 15000
const selectorOptions = { timeout: 1000 }
test("Connection id appears", async (t) => {
const userId = async () =>
await Selector("#user-avatar")
.child(1)
.addCustomDOMProperties({ innerHTML: (el) => el.innerHTML })
.with(selectorOptions).innerHTML
await t
.wait(idAppearTimeout)
.expect((await userId()).length)
.gt(0)
})
test("Canvas eventually gets a child element", async (t) => {
const canvas = Selector("#canvas").with(selectorOptions)
await t.expect(canvas.childElementCount).eql(1, { timeout: syncTimeout })
})
This diff is collapsed.
......@@ -13,45 +13,69 @@
"build": "webpack --config webpack.prod.js",
"build:analyze": "webpack --env.analyze --config webpack.prod.js",
"build:dev": "webpack --config webpack.dev.js",
"build:bench": "webpack --config webpack.bench.js",
"watch": "webpack --watch --config webpack.dev.js",
"start": "node --experimental-modules src/server.js",
"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",
"test": "jest --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/drawing-crdt",
"test-changed": "jest --only-changed --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/drawing-crdt",
"test-coverage": "jest --coverage --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/drawing-crdt",
"benchmarks": "node --experimental-modules __benchmarks__/puppeteer.js | npx tap-summary --no-progress",
"test-e2e:peer1": "testcafe chrome:headless __e2e_tests__/peer1.e2e.js",
"test-e2e:peer2": "testcafe chrome:headless __e2e_tests__/peer2.e2e.js",
"test-e2e": "run-p test-e2e:*",
"test-e2e-start": "run-s start-bg test-e2e",
"start-bg": "npm run start > /dev/null 2>&1 &",
"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"
"validate": "npm ls",
"plot": "find plot-scripts/ -maxdepth 1 -type f -name '*.p' -exec gnuplot {} \\;"
},
"dependencies": {
"@ungap/event-target": "^0.1.0",
"d3-shape": "^1.3.5",
"@xmpp/client": "^0.9.2",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"humanhash": "^1.0.4",
"jdenticon": "^2.2.0",
"liowebrtc": "file:src/liowebrtc",
"pako": "^1.0.10",
"signalbuddy": "file:src/signalbuddy",
"rtcpeerconnection": "file:src/rtcpeerconnection",
"uuid": "^3.3.3",
"webrtc-adapter": "^7.3.0",
"what-the-pack": "^2.0.3",
"y-array": "^10.1.4",
"y-map": "^10.1.3",
"y-memory": "^8.0.9",
"yjs": "file:src/yjs"
"uuid": "^3.3.3"
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.6.0",
"@fortawesome/fontawesome-free": "^5.12.0",
"@ungap/event-target": "^0.1.0",
"array-flat-polyfill": "^1.0.1",
"babel-eslint": "^10.0.3",
"chalk": "^3.0.0",
"css-loader": "^3.4.1",
"d3-shape": "^1.3.5",
"drawing-crdt": "file:src/drawing-crdt/pkg",
"eslint": "^6.5.1",
"eslint-config-prettier": "^6.5.0",
"eslint-plugin-testcafe": "^0.2.1",
"fastbitset": "^0.2.8",
"file-loader": "^5.0.2",
"humanhash": "^1.0.4",
"jdenticon": "^2.2.0",
"jest": "^24.9.0",
"liowebrtc": "file:src/liowebrtc",
"npm-run-all": "^4.1.5",
"pako": "^1.0.10",
"prettier": "^1.18.2",
"puppeteer-core": "^2.0.0",
"rtcpeerconnection": "file:src/rtcpeerconnection",
"style-loader": "^1.1.2",
"tap-summary": "^4.0.0",
"testcafe": "^1.5.0",
"webpack": "^4.41.0",
"webpack-bundle-analyzer": "^3.6.0",
"webpack-cli": "^3.3.9",
"webpack-merge": "^4.2.2"
"webpack-merge": "^4.2.2",
"webrtc-adapter": "^7.3.0",
"what-the-pack": "^2.0.3",
"y-array": "^10.1.4",
"y-map": "^10.1.3",
"y-memory": "^8.0.9",
"yjs": "file:src/yjs",
"zora": "^3.1.8"
},
"pre-commit": [
"lint",
......
set xlabel "Iterations"
set ylabel "Time [ms]"
set title "addPath() performance scalability"
set key inside bottom right
set terminal dumb size 120, 30
set autoscale
plot "plots/dot-seq-benchmark.tsv" using 1:($2/(1e6*$1)) with lines title "dot [sequential]"
set terminal pdf
set output "plots/dot-seq-benchmark.pdf"
plot "plots/dot-seq-benchmark.tsv" using 1:($2/(1e6*$1)) with lines title "dot [sequential]"
public/favicon.ico

1.12 KiB

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 417.7 150.7" style="enable-background:new 0 0 417.7 150.7;" xml:space="preserve">
<style type="text/css">
.st0{fill:#003C70;}
</style>
<path class="st0" d="M28.4,68.2V30.3h7.4v37.9H28.4z"/>
<path class="st0" d="M46.7,44.3h0.1c1.8-2.4,5.2-3.8,8.3-3.8c3.3,0,6.2,1.4,7.8,3.8c2.2-2.2,5.6-3.8,8.8-3.8c6-0.1,9.1,3.7,9.2,9.9
v17.8h-7V51.7c0-3.1-1.1-6.4-4.5-6.4c-3.4,0-5.5,2.1-5.5,6.8v16.2h-7V51.7c0-3.8-1.6-6.4-4.6-6.4c-3.3,0-5.4,2.3-5.4,6.9v16h-7V41.1
h7V44.3z"/>
<path class="st0" d="M98.5,44.8c4,0,6,4.7,6,9.3c0,4.8-1.6,10.4-6.3,10.4c-3.8,0-6.5-3.8-6.5-9.5C91.8,48.7,94.1,44.8,98.5,44.8z
M91.8,41.1h-7v40.7h7V64.8c2,2.4,4.7,4,7.9,4c7.5,0,12.3-7.6,12.3-14.8c0-6-3.4-13.6-11.7-13.6c-3.2,0-6.6,1.6-8.4,4.4h-0.1V41.1z"
/>
<path class="st0" d="M122,51.4c-0.1-3.6,1.7-6.9,5.8-6.9c3.5,0,4.9,2.8,4.6,6.9H122z M139,55.3c0.5-8.1-2.9-14.8-11.1-14.8
c-7.5,0-13.5,5.3-13.5,13.7c0,8.9,6,14.7,13.9,14.7c3,0,6.8-0.9,10.3-3l-2-3.8c-1.7,1.2-4.5,2.2-7.1,2.2c-4.4,0-7.9-3.8-7.7-8.9H139
z"/>
<path class="st0" d="M149.4,45.9h0.1l1.4-2.1c0.8-1.1,2.4-3.3,4.5-3.3c1.6,0,3.3,0.9,4.8,2.4l-2.6,5c-1.2-0.6-1.9-0.9-3.3-0.9
c-2.4,0-4.8,2-4.8,7.9v13.4h-7V41.1h7V45.9z"/>
<path class="st0" d="M169.4,41.1v27.1h-7V41.1H169.4z M161.7,33c0-2.1,1.7-4.2,4.1-4.2c2.4,0,4.3,2.1,4.3,4.2c0,2.3-1.6,4.5-4.2,4.5
C163.4,37.5,161.7,35.3,161.7,33z"/>
<path class="st0" d="M188.5,52.5c0.5,8-3.1,11.5-5.9,11.5c-1.7,0-3.2-1.6-3.2-4c0-3.1,1.7-5.1,5.2-6.2L188.5,52.5z M188.4,64.2
c0,1.5,0.2,3,0.7,4h7.4c-0.8-1.9-1.1-4.4-1.1-6.6V50.5c0-8.5-6-10.1-10.8-10.1c-3.6,0-6.9,0.9-10.1,3.7l2.3,3.4c1.8-1.6,4-2.8,7-2.8
c2.3,0,4.4,1.6,4.8,4.1l-6.2,1.9c-6.1,1.8-9.8,4.7-9.8,9.9c0,5,3.4,8.2,7.5,8.2c2.4,0,4.8-1.7,6.8-3.3L188.4,64.2z"/>
<path class="st0" d="M199.8,68.2V28.4h7v39.8H199.8z"/>
<path class="st0" d="M253.1,37.1c-1.8-1-4.8-2.1-7.7-2.1c-7.6,0-13.1,5.5-13.1,14.1c0,9,6.1,14.3,13.3,14.3c2.9,0,5.5-0.8,7.4-1.9
l2.1,4.7c-2.3,1.4-6.2,2.6-9.8,2.6c-12.8,0-20.9-8.7-20.9-19.8c0-10.4,8-19.4,21-19.4c3.9,0,7.4,1.4,10,2.9L253.1,37.1z"/>
<path class="st0" d="M269.1,64.8c-5,0-6.4-5.7-6.4-10.5c0-4.5,1.6-9.9,6.4-9.9c4.9,0,6.4,5.4,6.4,9.9
C275.5,59.1,274.2,64.8,269.1,64.8z M269.1,68.9c8.3,0,13.9-6.1,13.9-14.5c0-8.8-6.7-13.9-13.9-13.9c-7.1,0-13.8,5.1-13.8,13.9
C255.3,62.7,260.8,68.9,269.1,68.9z"/>
<path class="st0" d="M286,68.2V28.4h7v39.8H286z"/>
<path class="st0" d="M297.4,68.2V28.4h7v39.8H297.4z"/>
<path class="st0" d="M314.9,51.4c-0.1-3.6,1.7-6.9,5.7-6.9c3.5,0,4.9,2.8,4.6,6.9H314.9z M331.9,55.3c0.5-8.1-2.9-14.8-11.1-14.8
c-7.5,0-13.5,5.3-13.5,13.7c0,8.9,6,14.7,13.9,14.7c3,0,6.8-0.9,10.3-3l-2-3.8c-1.7,1.2-4.5,2.2-7.1,2.2c-4.4,0-7.9-3.8-7.7-8.9
H331.9z"/>
<path class="st0" d="M372.2,51.4c-0.1-3.6,1.7-6.9,5.8-6.9c3.5,0,4.9,2.8,4.6,6.9H372.2z M389.2,55.3c0.5-8.1-2.9-14.8-11.1-14.8
c-7.5,0-13.5,5.3-13.5,13.7c0,8.9,6,14.7,13.9,14.7c3,0,6.8-0.9,10.3-3l-2-3.8c-1.7,1.2-4.5,2.2-7.1,2.2c-4.4,0-7.9-3.8-7.7-8.9
H389.2z"/>
<path class="st0" d="M348.3,44.8c4.2,0,6.4,4.4,6.4,9.1c0,5.4-2,10.6-6.7,10.6c-4.1,0-6.1-5.1-6.1-10.4
C342,48.9,343.8,44.8,348.3,44.8z M361.7,41.1h-7v3.3h-0.1c-1.5-2.4-4.6-4-7.9-4c-7.2,0-12.2,6.8-12.2,13.9
c0,8.7,4.6,14.5,11.6,14.5c4,0,6.8-2.3,8.5-4.6h0.1v3.6c0,6.3-3.6,9.2-8.2,9.2c-3.6,0-6.6-0.7-9.2-2.3l-1.4,4.4
c3.1,1.6,7.1,2.6,11.1,2.6c7.9,0,14.7-4,14.7-16V41.1z"/>
<path class="st0" d="M28.4,84.5h7.5v32.8h15.8v5.1H28.4V84.5z"/>
<path class="st0" d="M92.8,98.5c2.5-2.6,6-3.8,9.5-3.8c6.5,0,9.6,3.5,9.6,10.3v17.5h-7.1v-16.6c0-3.8-1.7-6.3-5.6-6.3
c-3.6,0-6.4,2.3-6.4,6.8v16.2h-7.1V95.3h7V98.5z"/>
<path class="st0" d="M130.2,99c4.4,0,6.7,4.4,6.7,9.1c0,5.4-2.1,10.6-7.1,10.6c-4.3,0-6.4-5.1-6.4-10.4S125.4,99,130.2,99z
M136.9,122.4h7.1V82.6h-7.1v15.9h-0.1c-1.7-2.4-4.9-3.8-8.5-3.8c-7.4,0-12.7,6.8-12.7,13.9c0,8.7,4.8,14.5,12.1,14.5
c4.3,0,7.2-2.3,9-4.6h0.1V122.4z"/>
<path class="st0" d="M162.3,119c-5.3,0-6.7-5.7-6.7-10.5c0-4.5,1.7-9.9,6.7-9.9c5.2,0,6.8,5.4,6.8,9.9
C169,113.3,167.6,119,162.3,119z M162.3,123.1c8.7,0,14.6-6.1,14.6-14.5c0-8.8-7.1-13.9-14.6-13.9c-7.4,0-14.5,5.1-14.5,13.9
C147.7,116.9,153.6,123.1,162.3,123.1z"/>
<path class="st0" d="M187.7,98.5c2.5-2.6,6-3.8,9.5-3.8c6.5,0,9.6,3.5,9.6,10.3v17.5h-7.1v-16.6c0-3.8-1.7-6.3-5.6-6.3
c-3.6,0-6.4,2.3-6.4,6.8v16.2h-7.1V95.3h7V98.5z"/>
<path class="st0" d="M67.2,119c-5.3,0-6.7-5.7-6.7-10.5c0-4.5,1.7-9.9,6.7-9.9c5.2,0,6.8,5.4,6.8,9.9C74,113.3,72.6,119,67.2,119z
M67.2,123.1c8.7,0,14.6-6.1,14.6-14.5c0-8.8-7.1-13.9-14.6-13.9c-7.4,0-14.5,5.1-14.5,13.9C52.7,116.9,58.6,123.1,67.2,123.1z"/>
</svg>
......@@ -6,11 +6,6 @@
<link rel="shortcut icon" href="logo.png" />
<link rel="apple-touch-icon" href="logo.png" />
<link rel="stylesheet" href="styles.css" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<script>
if (navigator.serviceWorker) {
navigator.serviceWorker.register("service-worker.js").then(
......@@ -25,17 +20,10 @@
</script>
</head>
<body>
<div style="display:none">
Your client ID: <input id="user-id" type="text" value="" readonly="" />
</div>
<div style="display:none">
Enter peer ID: <input id="peer-id" type="text" value="" />
<button id="peer-connect">Connect</button>
</div>
<div id="top-panel">
<div class="top-bar">
<div class="dropdown">
<button class="dropdown-peers"><i class="fa fa-bars"></i></button>
<button class="dropdown-peers"><i class="fas fa-bars"></i></button>
<div class="peers">
<ul id="connected-peers">
No peers are connected
......@@ -57,11 +45,11 @@
size="25"
color="gray"
/>
<button id="room-connect">Connect <i class="fa fa-link"></i></button>
<button id="room-connect">Connect <i class="fas fa-link"></i></button>
</div>
<div id="tools-panel">
<button id="pen-tool" class="selectable-tool selected">
<i class="fa fa-paint-brush"></i>
<i class="fas fa-paint-brush"></i>
</button>
<div id="pen-properties" class="properties">
<div class="pen-contents">
......@@ -86,8 +74,7 @@
<input
type="range"
min="1"
max="100"
value="10"
max="10"
class="slider"
id="range"
/>
......@@ -192,36 +179,51 @@
<b>Other colours</b>
</div>
<label id="colours">
<input id="other-colours" type="color" value="#0000ff" />
<input id="other-colours" type="color" />
</label>
</div>
</div>
</div>
<button id="eraser-tool" class="selectable-tool">
<i class="fa fa-eraser"></i>
<i class="fas fa-eraser"></i>
</button>
<button id="dragging-tool" class="selectable-tool">
<i class="far fa-hand-paper"></i>
</button>
<button id="canvas-center" class="selectable-tool">
<i class="fas fa-crosshairs"></i>
</button>
<button id="recognition-mode" class="selectable-tool">
<i class="fa fa-square"></i>
<i class="fas fa-square"></i>
</button>
<div class="spacer"></div>
<div id="status-info">
<button id="fast-undo-tool" class="disabled selectable-tool">
<i class="fa fa-fast-backward"></i>
<i class="fas fa-fast-backward"></i>
</button>
<button id="undo-tool" class="disabled selectable-tool">
<i class="fa fa-backward"></i>
<i class="fas fa-step-backward"></i>
</button>
<div id="connected-room-info">
Room:&nbsp;
<span id="connected-room-id"></span>
</div>
<div id="user-avatar"></div>
<div id="imperial-logo">
<img
src="imperial.svg"
alt="Imperial College London Logo"
height="42px"
/>
</div>
</div>
</div>
</div>
<svg id="canvas"></svg>
<div id="canvas-container">
<svg id="canvas"></svg>
</div>
<script src="js/app.js"></script>
</body>
......
......@@ -14,11 +14,21 @@ body {
flex-direction: column;
}
#canvas {
#canvas-container {
width: calc(100vw - 8px);
background-color: white;
border: 4px solid red;
flex-grow: 1;
position: relative;
overflow: hidden;
}
#canvas {
position: absolute;
left: -5000px;
top: -5000px;
width: 10000px;
height: 10000px;
}
#tools-panel {
......@@ -71,7 +81,7 @@ button.selected {
cursor: pointer;
border-radius: 4px;
transition-duration: 0.4s;
height: 100%;
height: 36px;
width: 36px;
}
......@@ -265,6 +275,10 @@ button.selected {
border: none;
cursor: pointer;
border-radius: 4px;
width: 78px;
height: 36px;
white-space: nowrap;
overflow: hidden;
}
.selectable-tool {
......@@ -282,19 +296,59 @@ button.selected {
transition-duration: 0.4s;
}
#pen-tool {
width: 39px;
height: 39px;
margin-right: 8px;
}
#pen-tool > i {
padding: 0 1px;
padding: 0 1.5px;
}
#eraser-tool {
width: 39px;
height: 39px;
margin-right: 8px;
}
#eraser-tool > i {
padding: 0 0.5px;
padding: 0 1.5px;
}
#dragging-tool {
width: 39px;
height: 39px;
margin-right: 8px;
}
#dragging-tool > i {
padding: 0 2.5px;
}
#canvas-center {
width: 39px;
height: 39px;
margin-right: 8px;
}
#canvas-center > i {
padding: 0 1.5px;
}
#recognition-mode {
width: 39px;
height: 39px;
margin-right: 8px;
}
#recognition-mode > i {
padding: 0 2.5px;
}
#undo-tool {
width: 39px;
height: 39px;
margin-right: 8px;
}
......@@ -303,10 +357,12 @@ button.selected {
}
#undo-tool > i {
padding: 0 3px 0 0;
padding: 0 2.5px;
}
#fast-undo-tool {
width: 39px;
height: 39px;
margin-right: 8px;
}
......@@ -315,7 +371,7 @@ button.selected {
}
#fast-undo-tool > i {
padding: 0 1px;
padding: 0 1.5px;
}
.properties {
......@@ -574,3 +630,12 @@ button.selected {
align-items: center;
justify-content: right;
}
#imperial-logo {
background-color: white;
display: flex;
align-items: center;
border-radius: 4px;
margin-left: 0.75em;
padding: 0;
}
File added
......@@ -3,10 +3,20 @@
// selections and send to the room.
// Get back room updates and invoke the local canvas renderer.
import "@fortawesome/fontawesome-free/css/fontawesome.css"
import "@fortawesome/fontawesome-free/css/regular.css"
import "@fortawesome/fontawesome-free/css/solid.css"
import * as canvas from "./canvas.js"
import * as HTML from "./elements.js"
import { computeErasureIntervals, combineErasureIntervals } from "./erasure.js"
import { computeErasureIntervals } from "./erasure.js"
import { connect } from "./room.js"
import CRDT from "./wasm-crdt.js"
//import CRDT from "./y-crdt.js"
import Exfiltrator from "./intelligence-exfiltrator.js"
import WebRTCConnection from "./connection/WebRTC.js"
import * as toolSelection from "./tool-selection.js"
import recognizeFromPoints, { Shapes } from "./shapes.js"
import * as humanhash from "humanhash"
......@@ -14,21 +24,42 @@ import jdenticon from "jdenticon"
const DEFAULT_ROOM = "imperial"
const navigateToRoom = (roomID) => {
const url = new URL(location.href)
url.searchParams.set("room", roomID)
location.href = url
}
const initialRoom = new URLSearchParams(location.search).get("room")
if (initialRoom == null) {
navigateToRoom(DEFAULT_ROOM)
}
const MIN_PRESSURE_FACTOR = 0.1
const MAX_PRESSURE_FACTOR = 1.5
const UNDO_RATE = 24
let undoInterval = null
let spy = null
let room = null
const humanHasher = new humanhash()
const PREDICTED_POINT_COLOR = "#00000044"
const LAST_RECOGNIZED_PATH_ID = "LSP"
const pathIDsByPointerID = new Map()
// Safari reports mouse touches as events with 0 pressure, but the standard requires
// it to be 0.5
function getNormalizedPressure(e) {
if (e.pressure === 0 && e.buttons) {
return 0.5
}
return e.pressure
}
// This is a quadratic such that:
// - getPressureFactor(0.0) = MIN_PRESSURE_FACTOR
// - getPressureFactor(0.5) = 1.0
......@@ -54,54 +85,29 @@ function selectedRadiusPoint(x, y, color, pressure = 0) {
]
}
function faintPredictionPoint(x, y, pressure = 0) {
function faintPredictionPoint(x, y, pressure = 0.5) {
return selectedRadiusPoint(x, y, PREDICTED_POINT_COLOR, pressure)
}
function selectedColorAndRadiusPoint(x, y, pressure = 0) {
function selectedColorAndRadiusPoint(x, y, pressure = 0.5) {
return selectedRadiusPoint(x, y, toolSelection.getStrokeColour(), pressure)
}
function eraseEverythingAtPosition(x, y, radius, room) {
const mousePos = [x, y]
room.getPaths().forEach((points, pathID) => {
const prevPathIntervals =
(room.erasureIntervals || { [pathID]: {} })[pathID] || {}
const newPathIntervals = computeErasureIntervals(
points,
mousePos,
radius,
prevPathIntervals,
)
const newPathIntervals = computeErasureIntervals(points, mousePos, radius)
const erasureIntervalsForPath = combineErasureIntervals(
prevPathIntervals,
newPathIntervals,
)
Object.keys(erasureIntervalsForPath).forEach((pointID) =>
room.extendErasureIntervals(
pathID,
pointID,
erasureIntervalsForPath[pointID],
),
Object.keys(newPathIntervals).forEach((pointID) =>
room.extendErasureIntervals(pathID, pointID, newPathIntervals[pointID]),
)
})
}
const onRoomConnect = (room_) => {
room = room_
HTML.connectedRoomID.textContent = room.name
HTML.userIDElem.value = room.ownID || ""
room.addEventListener("allocateOwnID", ({ detail: id }) => {
HTML.userIDElem.value = id
})
const onUserIDAllocated = (uid) => {
const userID = uid.replace(/-/g, "")
// Create user account
const userID = HTML.userIDElem.value
const avatarImage = document.createElement("svg")
avatarImage.innerHTML = jdenticon.toSvg(userID, 40)
avatarImage.className = "avatar"
......@@ -112,13 +118,27 @@ const onRoomConnect = (room_) => {
HTML.userInfo.innerHTML = ""
HTML.userInfo.appendChild(avatarImage)
HTML.userInfo.appendChild(userAccount)
}
room.addEventListener("userJoin", ({ detail: id }) => {
if (HTML.connectedPeers.children.length == 0) {
HTML.connectedPeers.innerHTML = ""
}
const onRoomConnect = (room_) => {
spy = new Exfiltrator(room_.name, room_)
room = room_
HTML.connectedRoomID.textContent = room.name
const uid = room.getUserID()
if (uid) {
onUserIDAllocated(uid)
}
room.addEventListener("allocateOwnID", ({ detail: id }) =>
onUserIDAllocated(id),
)
room.addEventListener("userJoin", ({ detail: id }) => {
getOrInsertPeerById(id)
updateOverallStatusIcon()
})
......@@ -171,17 +191,15 @@ const onRoomConnect = (room_) => {
})
room.addEventListener("addOrUpdatePath", ({ detail: { id, points } }) => {
canvas.renderPath(id, points, room.erasureIntervals[id] || [])
spy.onAddOrUpdatePath(id, points)
canvas.renderPath(id, points, room.getErasureIntervals(id))
})
room.addEventListener(
"removedIntervalsChange",
({ detail: { id, intervals, points } }) => {
room.erasureIntervals[id] = combineErasureIntervals(
room.erasureIntervals[id] || {},
intervals,
)
canvas.renderPath(id, points, room.erasureIntervals[id] || [])
({ detail: { id, intervals } }) => {
spy.onRemovedIntervalsChange(id, intervals)
canvas.renderPath(id, room.getPathPoints(id), intervals)
},
)
......@@ -189,6 +207,113 @@ const onRoomConnect = (room_) => {
HTML.fastUndoButton.classList.remove("disabled")
HTML.undoButton.classList.remove("disabled")
})
// TODO: Move this iPhone specific code to the general user interaction to avoid double events
/*HTML.canvas.addEventListener("touchstart", (e) => {
e.preventDefault()
let pressure = 0
let x, y
const topPanelHeight = HTML.topPanel.offsetHeight
if (
e.touches &&
e.touches[0] &&
typeof e.touches[0]["force"] !== "undefined"
) {
if (e.touches[0]["force"] > 0) {
pressure = e.touches[0]["force"]
}
x = e.touches[0].pageX
y = e.touches[0].pageY - topPanelHeight
} else {
pressure = 1.0
x = e.pageX
y = e.pageY - topPanelHeight
}
clearRecognizedUpcoming()
if (room == null) {
return
}
const currentTool = toolSelection.getTool()
const mousePos = [x, y]
if (currentTool == toolSelection.Tools.PEN) {
pathIDsByPointerID.set(
e.pointerId,
room.addPath(selectedColorAndRadiusPoint(...mousePos, pressure)),
)
} else if (currentTool == toolSelection.Tools.ERASER) {
eraseEverythingAtPosition(
...mousePos,
toolSelection.getEraseRadius(),
room,
)
}
})
HTML.canvas.addEventListener("touchmove", (e) => {
let pressure = 0
let x, y
const topPanelHeight = HTML.topPanel.offsetHeight
if (
e.touches &&
e.touches[0] &&
typeof e.touches[0]["force"] !== "undefined"
) {
if (e.touches[0]["force"] > 0) {
pressure = e.touches[0]["force"]
}
x = e.touches[0].pageX
y = e.touches[0].pageY - topPanelHeight
} else {
pressure = 1.0
x = e.pageX
y = e.pageY - topPanelHeight
}
if (room == null) {
return
}
const currentTool = toolSelection.getTool()
const mousePos = [x, y]
if (currentTool == toolSelection.Tools.PEN) {
const pathID = pathIDsByPointerID.get(e.pointerId)
room.extendPath(
pathID,
selectedColorAndRadiusPoint(...mousePos, pressure),
)
if (toolSelection.isRecognitionModeSet()) {
drawRecognizedUpcoming(room.getPoints(pathID), pressure)
}
} else if (currentTool == toolSelection.Tools.ERASER) {
eraseEverythingAtPosition(
...mousePos,
toolSelection.getEraseRadius(),
room,
)
}
})
HTML.canvas.addEventListener("touchend", (e) => {
const { pointerId } = e
const pressure = getNormalizedPressure(e)
const pathID = pathIDsByPointerID.get(pointerId)
if (toolSelection.isRecognitionModeSet()) {
drawRecognized(pathID, room.getPoints(pathID), pressure)
}
pathIDsByPointerID.delete(pointerId)
clearRecognizedUpcoming()
})
HTML.canvas.addEventListener("touchleave", (e) => {
const { pointerId } = e
const pressure = getNormalizedPressure(e)
const pathID = pathIDsByPointerID.get(pointerId)
if (toolSelection.isRecognitionModeSet()) {
drawRecognized(pathID, room.getPoints(pathID), pressure)
}
pathIDsByPointerID.delete(pointerId)
clearRecognizedUpcoming()
})*/
}
function getRecognizedShapePoints(points) {
......@@ -219,7 +344,7 @@ function drawIfRecognized(points, callback, notRecCallback) {
}
function clearRecognizedUpcoming() {
canvas.renderPath(LAST_RECOGNIZED_PATH_ID, [], [])
canvas.renderPath(canvas.LAST_RECOGNIZED_PATH_ID, [], [])
}
function drawRecognizedUpcoming(points) {
......@@ -227,7 +352,7 @@ function drawRecognizedUpcoming(points) {
points,
(recognizedPoints) =>
canvas.renderPath(
LAST_RECOGNIZED_PATH_ID,
canvas.LAST_RECOGNIZED_PATH_ID,
recognizedPoints.map((point) =>
faintPredictionPoint(point[0], point[1]),
),
......@@ -248,37 +373,15 @@ function drawRecognized(pathID, points) {
}
const tryRoomConnect = async (roomID) => {
return await connect(roomID)
return await connect(roomID, CRDT, WebRTCConnection, {
wasm: { interval: 16 },
})
.then(onRoomConnect)
.catch((err) => alert(`Error connecting to a room:\n${err}`))
}
HTML.peerButton.addEventListener("click", () => {
const peerID = HTML.peerIDElem.value
if (room == null || peerID == "") {
return
}
room.inviteUser(peerID)
HTML.peerIDElem.value = ""
})
const onRoomJoinEnter = () => {
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"
HTML.fastUndoButton.classList.add("disabled")
HTML.undoButton.classList.add("disabled")
tryRoomConnect(selectedRoomID)
navigateToRoom(HTML.roomIDElem.value)
}
HTML.roomConnectButton.addEventListener("click", onRoomJoinEnter)
......@@ -294,15 +397,9 @@ HTML.fastUndoButton.addEventListener("click", () => {
}
})
HTML.undoButton.addEventListener("mouseup", () => {
clearInterval(undoInterval)
})
HTML.undoButton.addEventListener("mouseleave", () => {
clearInterval(undoInterval)
})
const undoButtonEnd = () => clearInterval(undoInterval)
HTML.undoButton.addEventListener("mousedown", () => {
const undoButtonStart = () => {
undoInterval = setInterval(function() {
if (room == null) return
......@@ -313,7 +410,13 @@ HTML.undoButton.addEventListener("mousedown", () => {
HTML.undoButton.classList.add("disabled")
}
}, 1000 / UNDO_RATE)
})
}
HTML.undoButton.addEventListener("pointerup", undoButtonEnd)
HTML.undoButton.addEventListener("pointerleave", undoButtonEnd)
HTML.undoButton.addEventListener("mouseup", undoButtonEnd)
HTML.undoButton.addEventListener("pointerdown", undoButtonStart)
HTML.roomIDElem.addEventListener("keydown", (event) => {
if (event.key == "Enter") {
......@@ -324,6 +427,8 @@ HTML.roomIDElem.addEventListener("keydown", (event) => {
})
const getOrInsertPeerById = (id) => {
id = id.replace(/-/g, "")
for (const peerElem of HTML.connectedPeers.children) {
const peerId = peerElem.children[4].id
if (peerId == id) {
......@@ -338,7 +443,7 @@ const getOrInsertPeerById = (id) => {
avatarImage.className = "avatar"
const quality = document.createElement("img")
quality.src = "/quality-low.svg"
quality.src = "/quality-high.svg"
quality.alt = "Peer quality icon"
quality.className = "peer-quality"
......@@ -361,6 +466,10 @@ const getOrInsertPeerById = (id) => {
peerElem.appendChild(theirStatus)
peerElem.appendChild(peerId)
if (HTML.connectedPeers.children.length == 0) {
HTML.connectedPeers.innerHTML = ""
}
HTML.connectedPeers.appendChild(peerElem)
return peerElem
......@@ -381,65 +490,124 @@ const updateOverallStatusIcon = () => {
HTML.overallStatusIconImage.src = "synchronised.svg"
}
let canvasDraggingStart = null
function startCanvasDragging(mousePos) {
if (canvasDraggingStart == null) {
canvasDraggingStart = mousePos
}
}
function stopCanvasDragging() {
canvasDraggingStart = null
}
function dragCanvas(mousePos) {
if (canvasDraggingStart) {
const [x0, y0] = canvasDraggingStart
const [x1, y1] = mousePos
const offset = [x1 - x0, y1 - y0]
canvasDraggingStart = mousePos
toolSelection.applyCanvasOffset(offset)
}
}
function getOffsets(e) {
return [e.offsetX, e.offsetY]
}
function getScreenOffsets(e) {
return [e.screenX, e.screenY]
}
canvas.input.addEventListener("strokestart", ({ detail: e }) => {
e.preventDefault()
const pressure = getNormalizedPressure(e)
clearRecognizedUpcoming()
if (room == null) {
return
}
const currentTool = toolSelection.getTool()
const mousePos = [e.offsetX, e.offsetY]
if (currentTool == toolSelection.Tools.PEN) {
pathIDsByPointerID.set(
e.pointerId,
room.addPath([
const mousePos = getOffsets(e)
switch (currentTool) {
case toolSelection.Tools.PEN:
if (!pathIDsByPointerID.has(e.pointerId)) {
pathIDsByPointerID.set(
e.pointerId,
room.addPath(selectedColorAndRadiusPoint(...mousePos, pressure)),
)
}
break
case toolSelection.Tools.ERASER:
eraseEverythingAtPosition(
...mousePos,
toolSelection.getStrokeRadius() * getPressureFactor(e.pressure),
toolSelection.getStrokeColour(),
]),
)
} else if (currentTool == toolSelection.Tools.ERASER) {
eraseEverythingAtPosition(
mousePos[0],
mousePos[1],
toolSelection.getEraseRadius(),
room,
)
toolSelection.getEraseRadius(),
room,
)
break
case toolSelection.Tools.DRAGGER:
startCanvasDragging(getScreenOffsets(e))
break
}
})
canvas.input.addEventListener("strokeend", ({ detail: e }) => {
const { pressure, pointerId } = e
const { pointerId } = e
const pressure = getNormalizedPressure(e)
const pathID = pathIDsByPointerID.get(pointerId)
if (toolSelection.isRecognitionModeSet()) {
drawRecognized(pathID, room.getPoints(pathID), pressure)
if (room != null && pathID) {
const currentTool = toolSelection.getTool()
if (
currentTool === toolSelection.Tools.PEN &&
toolSelection.isRecognitionModeSet()
) {
drawRecognized(pathID, room.getPathPoints(pathID), pressure)
}
room.endPath(pathID)
}
pathIDsByPointerID.delete(pointerId)
clearRecognizedUpcoming()
stopCanvasDragging()
})
canvas.input.addEventListener("strokemove", ({ detail: e }) => {
if (room == null) {
return
}
const pressure = getNormalizedPressure(e)
const currentTool = toolSelection.getTool()
const mousePos = [e.offsetX, e.offsetY]
if (currentTool == toolSelection.Tools.PEN) {
const pathID = pathIDsByPointerID.get(e.pointerId)
room.extendPath(
pathID,
selectedColorAndRadiusPoint(...mousePos, e.pressure),
)
if (toolSelection.isRecognitionModeSet()) {
drawRecognizedUpcoming(room.getPoints(pathID), e.pressure)
const mousePos = getOffsets(e)
switch (currentTool) {
case toolSelection.Tools.PEN: {
const pathID = pathIDsByPointerID.get(e.pointerId)
if (pathID) {
room.extendPath(
pathID,
selectedColorAndRadiusPoint(...mousePos, pressure),
)
} else {
pathIDsByPointerID.set(
e.pointerId,
room.addPath(selectedColorAndRadiusPoint(...mousePos, pressure)),
)
}
if (toolSelection.isRecognitionModeSet()) {
drawRecognizedUpcoming(room.getPathPoints(pathID), pressure)
}
break
}
} else if (currentTool == toolSelection.Tools.ERASER) {
eraseEverythingAtPosition(
mousePos[0],
mousePos[1],
toolSelection.getEraseRadius(),
room,
)
case toolSelection.Tools.ERASER:
eraseEverythingAtPosition(
...mousePos,
toolSelection.getEraseRadius(),
room,
)
break
case toolSelection.Tools.DRAGGER:
dragCanvas(getScreenOffsets(e))
break
}
})
......@@ -449,4 +617,7 @@ window.addEventListener("unload", () => {
}
})
tryRoomConnect(DEFAULT_ROOM)
// Add the recognition hint element in advance for consistency during tests.
clearRecognizedUpcoming()
tryRoomConnect(initialRoom)