diff --git a/package-lock.json b/package-lock.json index d11011cb7ad6274b62eb0a08a9dc6e9b94bb27b1..f62d6684bbbd84f7ebb2cfa68c3f50e106924ee7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,15 +14,15 @@ } }, "@babel/core": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.3.tgz", - "integrity": "sha512-QfQ5jTBgXLzJuo7Mo8bZK/ePywmgNRgk/UQykiKwEtZPiFIn8ZqE6jB+AnD1hbB1S2xQyL4//it5vuAUOVAMTw==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz", + "integrity": "sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==", "dev": true, "requires": { "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.3", + "@babel/generator": "^7.6.4", "@babel/helpers": "^7.6.2", - "@babel/parser": "^7.6.3", + "@babel/parser": "^7.6.4", "@babel/template": "^7.6.0", "@babel/traverse": "^7.6.3", "@babel/types": "^7.6.3", @@ -32,7 +32,7 @@ "lodash": "^4.17.13", "resolve": "^1.3.2", "semver": "^5.4.1", - "source-map": "^0.6.1" + "source-map": "^0.5.0" }, "dependencies": { "debug": { @@ -58,33 +58,19 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, "@babel/generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.3.tgz", - "integrity": "sha512-hLhYbAb3pHwxjlijC4AQ7mqZdcoujiNaW7izCT04CIowHK8psN0IN8QjDv0iyFtycF5FowUOTwDloIheI25aMw==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz", + "integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==", "dev": true, "requires": { "@babel/types": "^7.6.3", "jsesc": "^2.5.1", "lodash": "^4.17.13", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "source-map": "^0.5.0" } }, "@babel/helper-function-name": { @@ -176,9 +162,9 @@ } }, "@babel/parser": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.3.tgz", - "integrity": "sha512-sUZdXlva1dt2Vw2RqbMkmfoImubO0D0gaCrNngV6Hi0DA4x3o4mlrq0tbfY0dZEUIccH8I6wQ4qgEtwcpOR6Qg==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz", + "integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==", "dev": true }, "@babel/plugin-syntax-object-rest-spread": { @@ -2676,11 +2662,6 @@ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" - }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -7851,8 +7832,7 @@ "uuid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", - "dev": true + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, "v8-compile-cache": { "version": "2.0.3", @@ -8202,19 +8182,16 @@ "resolved": "https://registry.npmjs.org/y-array/-/y-array-10.1.4.tgz", "integrity": "sha1-4TGlsDDW3LyhAmjUKT7PRL2uezQ=" }, + "y-map": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/y-map/-/y-map-10.1.3.tgz", + "integrity": "sha1-oVgCztusNp5Qa5b2je+PCi6DYZY=" + }, "y-memory": { "version": "8.0.9", "resolved": "https://registry.npmjs.org/y-memory/-/y-memory-8.0.9.tgz", "integrity": "sha512-OrcReh6DgZhz5R7JGXqAH53T0Ygw24qcxKj4jN9w2DIi2eIiKFCD5Y6apBTTNxiw2FaVP15F+M8phRRIMXFGBQ==" }, - "y-text": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/y-text/-/y-text-9.5.1.tgz", - "integrity": "sha512-uwNLY4LeLLR7Cu4xfvh9ZY4gmOYyjii4irDHYRuhPDs1XFcm6krVaALeggA42LevTQ+k5q1eHpdv5jnJha8r6g==", - "requires": { - "fast-diff": "^1.1.1" - } - }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/package.json b/package.json index 1c18e418eaf3809f7e449c201ee6acf2f4ce4fb1..8f4ee17620e57bc4c1c599f843ea466d9aa70e69 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,10 @@ "http-server": "^0.11.1", "peer": "git+https://github.com/peers/peerjs-server.git", "peerjs": "^1.1.0", + "uuid": "^3.3.3", "y-array": "^10.1.4", + "y-map": "^10.1.3", "y-memory": "^8.0.9", - "y-text": "^9.5.1", "yjs": "^12.3.3" }, "devDependencies": { diff --git a/public/index.html b/public/index.html index 338342d8f9d5ace189dab9a6805568aa89fe193f..369b33f73eb472472a8c77aab87f8ec6866611df 100644 --- a/public/index.html +++ b/public/index.html @@ -2,6 +2,22 @@ <html lang="en"> <head> <meta charset="UTF-8" /> + <link rel="manifest" href="manifest.json"> + <link rel="shortcut icon" href="logo.png"> + <script> + if (navigator.serviceWorker) { + navigator.serviceWorker + .register("service-worker.js") + .then( + (registration) => + console.log( + `Service worker registered on scope ${registration.scope}`, + ), + (reason) => + console.log(`Service worker failed to register ~ ${reason}`), + ) + } + </script> </head> <body> <div> @@ -16,52 +32,13 @@ <ul id="connected-peers"></ul> </div> - <textarea style="width: 100%; height: 500px" id="textfield"></textarea> - <script src="js/app.js"></script> - - <svg id="whiteboard" width="100%" height="100%"></svg> - <script> - var whiteboard = document.getElementById("whiteboard") - - var painting = false - var path - - whiteboard.onmousedown = function(e) { - painting = true - - var mouse = { - x: e.offsetX, - y: e.offsetY, - } - - path = document.createElementNS("http://www.w3.org/2000/svg", "path") + <svg + id="whiteboard" + width="100%" + height="100%" + style="position: fixed" + ></svg> - path.setAttribute("d", "M" + mouse.x + " " + mouse.y) - path.setAttribute("stroke", "blue") - path.setAttribute("stroke-width", 3) - path.setAttribute("fill", "none") - path.setAttribute("pointer-events", "none") - - whiteboard.appendChild(path) - } - - whiteboard.onmouseup = function(e) { - painting = false - } - - whiteboard.onmousemove = function(e) { - var mouse = { - x: e.offsetX, - y: e.offsetY, - } - - if (painting) { - path.setAttribute( - "d", - path.getAttribute("d") + " L" + mouse.x + " " + mouse.y, - ) - } - } - </script> + <script src="js/app.js"></script> </body> </html> diff --git a/public/js/.gitkeep b/public/js/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e4266dca24a4059dcb0a7eec22db9384378d0bf3 Binary files /dev/null and b/public/logo.png differ diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..1b94f0d658806221b9b2bec412b6311ba6a108a3 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,13 @@ +{ + "short_name": "Drawing App", + "name": "Drawing App", + "icons": [ + { + "src": "/logo.png", + "type": "image/png", + "sizes": "144x144" + } + ], + "display": "standalone", + "start_url": "/index.html" +} diff --git a/public/service-worker.js b/public/service-worker.js new file mode 100644 index 0000000000000000000000000000000000000000..006388a3b8328861fec68f7f1d5c7d0bf1c82c9b --- /dev/null +++ b/public/service-worker.js @@ -0,0 +1,66 @@ +self.addEventListener("install", (event) => { + console.info("Service worker installed.", event) +}) + +self.addEventListener("activate", (event) => { + console.info("Service worker activated.", event) +}) + +const CACHE_NAME = "APP-V0" +const FILES_TO_CACHE = [ + "/index.html", + "/js/app.js", + "/manifest.json", +] +const FILE_ALIASES = new Map([ + ["/", "/index.html"] +]) + +const normalizeUrl = (url) => { + const url_ = new URL(url) + url_.pathname = url_.pathname.replace(/\/+/g, "/").replace(/\/$/, "") + if (FILE_ALIASES.has(url_.pathname)) { + url_.pathname = FILE_ALIASES.get(url_.pathname) + } + return url_.href +} + +self.addEventListener("install", async (event) => { + const cache = await caches.open(CACHE_NAME) + const additions = cache.addAll(FILES_TO_CACHE) + await additions + console.info(`Files cached: [\n ${FILES_TO_CACHE.join(`,\n `)}\n]`) +}) + +self.addEventListener("activate", async (event) => { + const oldCacheKeys = (await caches.keys()).filter((key) => key != CACHE_NAME) + oldCacheKeys.forEach((key) => caches.delete(key)) +}) + + +self.addEventListener("fetch", (event) => { + const normalizedUrl = normalizeUrl(event.request.url) + let response = fetch(event.request) + if (FILES_TO_CACHE.includes(normalizedUrl)) { + response = response + .then(async (response) => { + const cache = await caches.open(CACHE_NAME) + await cache.put(normalizedUrl, response.clone()) + return response + }) + .catch(() => caches.match(normalizedUrl)) + .catch(e => null) + } + event.respondWith( + fetch(event.request) + .then(async (response) => { + if (FILES_TO_CACHE.includes(new URL(normalizedUrl).pathname)) { + const cache = await caches.open(CACHE_NAME) + await cache.put(normalizedUrl, response.clone()) + } + return response + }) + .catch(() => caches.match(normalizedUrl)) + .catch(e => null) + ) +}) diff --git a/src/app.js b/src/app.js index 978f335c162f1d1fb4924e2729c0a488e28ee396..9d426d03ebf1db53bc478ea14c0410b074b52561 100644 --- a/src/app.js +++ b/src/app.js @@ -1,25 +1,25 @@ const Y = require("yjs") require("y-memory")(Y) +require("y-map")(Y) require("y-array")(Y) -require("y-text")(Y) require("./y-webrtc")(Y) +const uuidv4 = require("uuid/v4") + Y({ db: { - name: "memory" + name: "memory", }, connector: { name: "webrtc", host: "localhost", port: 3000, - path: "/api" + path: "/api", }, share: { - textfield: "Text" - } -}).then(y => { - y.share.textfield.bind(document.getElementById("textfield")) - + drawing: "Map", + }, +}).then((y) => { const userIDElem = document.getElementById("user-id") const peerIDElem = document.getElementById("peer-id") const peerButton = document.getElementById("peer-connect") @@ -58,4 +58,95 @@ Y({ peerIDElem.value = "" } + + const whiteboard = document.getElementById("whiteboard") + + var painting = false + var paths = new Map() + var pathID + + function createOrUpdatePath(uid, points) { + var path = paths.get(uid) + + if (path === undefined) { + path = document.createElementNS("http://www.w3.org/2000/svg", "path") + + path.setAttribute("stroke", "blue") + path.setAttribute("stroke-width", 3) + path.setAttribute("fill", "none") + path.setAttribute("pointer-events", "none") + + whiteboard.appendChild(path) + + paths.set(uid, path) + } + + points = points.toArray().filter((point) => point !== undefined) + + if (points.length <= 0) { + path.removeAttribute("d") + + return path + } + + var pathString = "M" + points[0][0] + " " + points[0][1] + + for (var i = 1; i < points.length; i++) { + pathString += " L" + points[i][0] + " " + points[i][1] + } + + path.setAttribute("d", pathString) + + return path + } + + whiteboard.onmousedown = function(e) { + painting = true + + const mouse = { + x: e.offsetX, + y: e.offsetY, + } + + pathID = uuidv4() + + const sharedPath = y.share.drawing.set(pathID, Y.Array) + sharedPath.push([[mouse.x, mouse.y]]) + } + + whiteboard.onmouseup = function() { + painting = false + } + + whiteboard.onmousemove = function(e) { + const mouse = { + x: e.offsetX, + y: e.offsetY, + } + + if (painting) { + const sharedPath = y.share.drawing.get(pathID) + sharedPath.push([[mouse.x, mouse.y]]) + } + } + + y.share.drawing.observe(function(lineEvent) { + const lineID = lineEvent.name + + switch (lineEvent.type) { + case "add": + createOrUpdatePath(lineID, lineEvent.value) + + lineEvent.value.observe(function(pointEvent) { + switch (pointEvent.type) { + case "insert": + console.log(pointEvent) + createOrUpdatePath(lineID, pointEvent.object) + break + } + }) + + break + } + }) }) diff --git a/src/y-webrtc/index.js b/src/y-webrtc/index.js index 0d2f040985880cf3d130ac7d038cf3a53b56654d..17e0ebaa7cea5903192828edc985a1a519371072 100644 --- a/src/y-webrtc/index.js +++ b/src/y-webrtc/index.js @@ -2,7 +2,7 @@ "use strict" var { - peerjs: { Peer } + peerjs: { Peer }, } = require("peerjs") function extend(Y) { @@ -23,7 +23,7 @@ function extend(Y) { var peer = new Peer({ host: this.webrtcOptions.host, port: this.webrtcOptions.port, - path: this.webrtcOptions.path + path: this.webrtcOptions.path, }) this.peer = peer @@ -31,7 +31,7 @@ function extend(Y) { this.peers = new Map() peer.on("open", function(id) { - console.log("My peer ID is: " + id) + //console.log("My peer ID is: " + id) for (var f of self.userEventListeners) { f({ action: "userID", id: id }) @@ -53,20 +53,20 @@ function extend(Y) { var self = this dataConnection.on("open", function() { - console.log("Connected to peer " + dataConnection.peer) + //console.log("Connected to peer " + dataConnection.peer) self.peers.set(dataConnection.peer, dataConnection) self.userJoined(dataConnection.peer, "master") }) dataConnection.on("data", function(data) { - console.log("Message from peer " + dataConnection.peer + ":", data) + //console.log("Message from peer " + dataConnection.peer + ":", data) self.receiveMessage(dataConnection.peer, data) }) dataConnection.on("close", function() { - console.log("Disconnected from peer " + dataConnection.peer) + //console.log("Disconnected from peer " + dataConnection.peer) self.peers.delete(dataConnection.peer) self.userLeft(dataConnection.peer) @@ -87,7 +87,7 @@ function extend(Y) { } send(uid, message) { - console.log("Sending message", message, "to " + uid) + //console.log("Sending message", message, "to " + uid) var self = this @@ -109,7 +109,7 @@ function extend(Y) { } broadcast(message) { - console.log("Broadcasting message", message) + //console.log("Broadcasting message", message) for (const uid of this.peers.keys()) { this.send(uid, message)