From 7ed413648c43b0b657d0c282f4d01c4ed548e6c0 Mon Sep 17 00:00:00 2001 From: JurajKubrican Date: Fri, 28 Feb 2025 16:41:29 +0100 Subject: [PATCH] fullscreen drawing --- .gitignore | 4 +- .vscode/settings.json | 3 + js/draw.js | 194 +++++++++++++++++++++--------------------- js/draw.ts | 138 ++++++++++++++++++++++++++++++ js/global.d.ts | 4 + js/mitt/mitt.js | 2 + package.json | 27 ++++++ pnpm-lock.yaml | 33 +++++++ tsconfig.json | 10 +++ views/draw.html | 47 +++++----- views/drawIndex.html | 25 ------ 11 files changed, 345 insertions(+), 142 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 js/draw.ts create mode 100644 js/global.d.ts create mode 100644 js/mitt/mitt.js create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 2090de6..839ef77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .env -tmp \ No newline at end of file +tmp + +node_modules \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..55712c1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} \ No newline at end of file diff --git a/js/draw.js b/js/draw.js index 964498c..876e7f3 100644 --- a/js/draw.js +++ b/js/draw.js @@ -1,101 +1,101 @@ +"use strict"; (() => { - const canvas = document.getElementById("drawCanvas"); - const ctx = canvas.getContext("2d"); - - ctx.lineWidth = 2; - ctx.lineCap = "round"; - ctx.strokeStyle = "white"; - - let drawing = false; - let lineId = 0; - - const socketUrl = - (window.location.protocol.startsWith("https") ? "wss://" : "ws://") + - window.location.host + - "/draw/ws"; - - let socket = new WebSocket(socketUrl); - - socket.onerror = console.error; - socket.onclose = console.warn; - - setTimeout(() => socket.send("ping"), 200); - - // const queue = [] - - const sendMessage = (id, x, y) => { - // queue.push(`${id},${x},${y}`) - - // console.log(queue) - - if (socket.readyState !== socket.OPEN) { - socket = new WebSocket(socketUrl); + const drawElement = document.getElementById("drawDiv"); + if (!drawElement) { + throw new Error("canvas not found"); } - - socket.send(id + "," + x + "," + y); - }; - - const drawStart = () => { - ctx.beginPath(); - lineId++; - drawing = true; - }; - - const drawStop = () => { - drawing = false; - ctx.closePath(); - }; - - const drawMove = (e) => { - if (!drawing) return; - ctx.lineTo(e.offsetX, e.offsetY); - sendMessage(lineId, e.offsetX, e.offsetY); - ctx.stroke(); - }; - - const touchStart = (e) => drawStart(e.touches[0]); - const touchMove = (e) => { - drawMove({ - offsetX: e.touches[0].clientX - canvas.offsetLeft, - offsetY: e.touches[0].clientY - canvas.offsetTop, + const drawEmitter = mitt(); + let drawing = false; + const socketUrl = "/draw/ws"; + let socket = new WebSocket(socketUrl); + socket.onerror = console.error; + socket.onclose = console.warn; + const serializeDraw = ({ from, to }) => `${from.x},${from.y},${to.x},${to.y}`; + const deserializeDraw = (message) => { + const parts = message.split(","); + return { + from: { + x: Number(parts[0]), + y: Number(parts[1]), + }, + to: { + x: Number(parts[2]), + y: Number(parts[3]), + }, + }; + }; + drawEmitter.on("draw", (e) => sendMessage(e)); + const sendMessage = (e) => { + if (socket.readyState !== socket.OPEN) { + socket = new WebSocket(socketUrl); + } + socket.send(serializeDraw(e)); + }; + const mouseDown = () => { + drawing = true; + previous = null; + }; + const mouseUp = () => { + drawing = false; + }; + let previous = null; + const mouseMove = (e) => { + if (!drawing) + return; + if (previous) { + drawEmitter.emit("draw", { + from: previous, + to: { x: e.offsetX, y: e.offsetY }, + }); + } + previous = { x: e.offsetX, y: e.offsetY }; + }; + const touchMove = (e) => { + e.preventDefault(); + mouseMove({ + offsetX: e.touches[0].clientX - drawElement.offsetLeft, + offsetY: e.touches[0].clientY - drawElement.offsetTop, + }); + }; + drawElement.addEventListener("mousedown", mouseDown); + drawElement.addEventListener("mouseup", mouseUp); + drawElement.addEventListener("mousemove", mouseMove); + drawElement.addEventListener("touchstart", mouseDown); + drawElement.addEventListener("touchend", mouseUp); + drawElement.addEventListener("touchmove", touchMove); + const receiveCanvas = document.getElementById("receiveCanvas"); + const recCtx = receiveCanvas?.getContext("2d"); + if (!recCtx || !receiveCanvas) { + throw new Error("canvas not found"); + } + //@ts-expect-error + window.ctx = recCtx; + setTimeout(() => { + recCtx.lineWidth = 2; + recCtx.lineCap = "round"; + recCtx.strokeStyle = "white"; + }, 10); + socket.addEventListener("message", (event) => { + if (event.data === "ping" || event.data.startsWith('pong')) + return; + const { from, to } = deserializeDraw(event.data); + recCtx.beginPath(); + recCtx.moveTo(from.x, from.y); + recCtx.lineTo(to.x, to.y); + recCtx.closePath(); + recCtx.stroke(); + console.log(from, to); }); - e.preventDefault(); - }; - const touchEnd = (e) => drawStop(e.changedTouches[0]); - - canvas.addEventListener("mousedown", drawStart); - canvas.addEventListener("mouseup", drawStop); - canvas.addEventListener("mousemove", drawMove); - - canvas.addEventListener("touchstart", touchStart); - canvas.addEventListener("touchend", touchEnd); - canvas.addEventListener("touchmove", touchMove); - - const receiveCanvas = document.getElementById("receiveCanvas"); - const recCtx = receiveCanvas.getContext("2d"); - recCtx.lineWidth = 2; - recCtx.lineCap = "round"; - recCtx.strokeStyle = "white"; - - let receiveLineId = 0; - - socket.addEventListener("message", function (event) { - const parts = event.data.split(","); - if (parts[0] != receiveLineId) { - recCtx.closePath(); - recCtx.beginPath(); - receiveLineId = parts[0]; - recCtx.moveTo(parts[1], parts[2]); - } else { - recCtx.lineTo(parts[1], parts[2]); - recCtx.stroke(); - } - }); - - setInterval(() => { - if (socket.readyState !== socket.OPEN) { - socket = new WebSocket(socketUrl); - } - socket.send("ping"); - }, 10000); + setInterval(() => { + if (socket.readyState !== socket.OPEN) { + socket = new WebSocket(socketUrl); + } + socket.send("ping"); + }, 10000); + const resizeObserver = new ResizeObserver((entries) => { + const entry = entries.at(0); + receiveCanvas.height = entry?.contentRect.height ?? 500; + receiveCanvas.width = entry?.contentRect.width ?? 500; + }); + resizeObserver.observe(drawElement); })(); diff --git a/js/draw.ts b/js/draw.ts new file mode 100644 index 0000000..9ddb810 --- /dev/null +++ b/js/draw.ts @@ -0,0 +1,138 @@ +(() => { + const drawElement = document.getElementById( + "drawDiv" + ) as HTMLDivElement | null; + if (!drawElement) { + throw new Error("canvas not found"); + } + + type DrawPoint = { + x: number; + y: number; + }; + type DrawEvent = { + from: DrawPoint; + to: DrawPoint; + }; + type Events = { + draw: DrawEvent; + }; + + const drawEmitter = mitt(); + + let drawing = false; + const socketUrl = "/draw/ws"; + + let socket = new WebSocket(socketUrl); + + socket.onerror = console.error; + socket.onclose = console.warn; + + const serializeDraw = ({from, to }: DrawEvent) => + `${from.x},${from.y},${to.x},${to.y}`; + + const deserializeDraw = (message: string): DrawEvent => { + const parts = message.split(","); + return { + from: { + x: Number(parts[0]), + y: Number(parts[1]), + }, + to: { + x: Number(parts[2]), + y: Number(parts[3]), + }, + }; + }; + + drawEmitter.on("draw", (e) => sendMessage(e)); + const sendMessage = (e: DrawEvent) => { + if (socket.readyState !== socket.OPEN) { + socket = new WebSocket(socketUrl); + } + socket.send(serializeDraw(e)); + }; + + const mouseDown = () => { + drawing = true; + previous=null + }; + + const mouseUp = () => { + drawing = false; + }; + + let previous: DrawPoint | null = null; + const mouseMove = (e: Pick) => { + if (!drawing) return; + if (previous) { + drawEmitter.emit("draw", { + from: previous, + to: { x: e.offsetX, y: e.offsetY }, + }); + } + previous = { x: e.offsetX, y: e.offsetY }; + }; + + const touchMove = (e: TouchEvent) => { + e.preventDefault(); + mouseMove({ + offsetX: e.touches[0].clientX - drawElement.offsetLeft, + offsetY: e.touches[0].clientY - drawElement.offsetTop, + }); + }; + + drawElement.addEventListener("mousedown", mouseDown); + drawElement.addEventListener("mouseup", mouseUp); + drawElement.addEventListener("mousemove", mouseMove); + + drawElement.addEventListener("touchstart", mouseDown); + drawElement.addEventListener("touchend", mouseUp); + drawElement.addEventListener("touchmove", touchMove); + + const receiveCanvas = document.getElementById( + "receiveCanvas" + ) as HTMLCanvasElement | null; + const recCtx = receiveCanvas?.getContext("2d"); + + if (!recCtx || !receiveCanvas) { + throw new Error("canvas not found"); + } + //@ts-expect-error + window.ctx = recCtx; + + setTimeout(()=>{ + recCtx.lineWidth = 2; + recCtx.lineCap = "round"; + recCtx.strokeStyle = "white"; + },10) + + + + + socket.addEventListener("message", (event) => { + if(event.data==="ping" || event.data.startsWith('pong')) return + const { from, to } = deserializeDraw(event.data); + + recCtx.beginPath(); + recCtx.moveTo(from.x, from.y); + recCtx.lineTo(to.x, to.y); + recCtx.closePath(); + recCtx.stroke(); + console.log(from, to); + }); + + setInterval(() => { + if (socket.readyState !== socket.OPEN) { + socket = new WebSocket(socketUrl); + } + socket.send("ping"); + }, 10000); + + const resizeObserver = new ResizeObserver((entries) => { + const entry = entries.at(0); + receiveCanvas.height = entry?.contentRect.height ?? 500; + receiveCanvas.width = entry?.contentRect.width ?? 500; + }); + resizeObserver.observe(drawElement); +})(); diff --git a/js/global.d.ts b/js/global.d.ts new file mode 100644 index 0000000..87c4675 --- /dev/null +++ b/js/global.d.ts @@ -0,0 +1,4 @@ +declare const mitt: { + //@ts-expect-error + (): import("mitt").Emitter; + }; \ No newline at end of file diff --git a/js/mitt/mitt.js b/js/mitt/mitt.js new file mode 100644 index 0000000..79739c7 --- /dev/null +++ b/js/mitt/mitt.js @@ -0,0 +1,2 @@ +function mitt (n){return{all:n=n||new Map,on:function(e,t){var i=n.get(e);i?i.push(t):n.set(e,[t])},off:function(e,t){var i=n.get(e);i&&(t?i.splice(i.indexOf(t)>>>0,1):n.set(e,[]))},emit:function(e,t){var i=n.get(e);i&&i.slice().map(function(n){n(t)}),(i=n.get("*"))&&i.slice().map(function(n){n(e,t)})}}}; +//# sourceMappingURL=mitt.js.map diff --git a/package.json b/package.json new file mode 100644 index 0000000..bfd48b0 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "knet", + "version": "1.0.0", + "description": "", + "homepage": "https://github.com/JurajKubrican/knet#readme", + "bugs": { + "url": "https://github.com/JurajKubrican/knet/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/JurajKubrican/knet.git" + }, + "license": "ISC", + "author": "", + "type": "commonjs", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "tsc --project tsconfig.json --watch" + }, + "devDependencies": { + "typescript": "^5.7.3" + }, + "dependencies": { + "mitt": "^3.0.1" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..81f998f --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,33 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + mitt: + specifier: ^3.0.1 + version: 3.0.1 + devDependencies: + typescript: + specifier: ^5.7.3 + version: 5.7.3 + +packages: + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + +snapshots: + + mitt@3.0.1: {} + + typescript@5.7.3: {} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6f5dbe1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "esModuleInterop": true, + "types": ["./js/global.d.ts"] + } +} \ No newline at end of file diff --git a/views/draw.html b/views/draw.html index f660494..7b0a3fb 100644 --- a/views/draw.html +++ b/views/draw.html @@ -1,21 +1,30 @@ {{block "draw" .}} -
- - -
- - -{{end}} + id="receiveCanvas" + width="500" + height="500" + style=" + position: absolute; + background-color: transparent; + top: 0; + left: 0; + right: 0; + bottom: 0; + " + > +
+ + + {{end}} +
+
diff --git a/views/drawIndex.html b/views/drawIndex.html index ad40428..4698ff3 100644 --- a/views/drawIndex.html +++ b/views/drawIndex.html @@ -8,36 +8,11 @@ -
- -
-
{{template "draw" }}
- - {{end}}