fullscreen drawing

This commit is contained in:
JurajKubrican
2025-02-28 16:41:29 +01:00
committed by Juraj Kubrican
parent 0505964854
commit 7ed413648c
11 changed files with 345 additions and 142 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.env .env
tmp tmp
node_modules

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@@ -1,101 +1,101 @@
"use strict";
(() => { (() => {
const canvas = document.getElementById("drawCanvas"); const drawElement = document.getElementById("drawDiv");
const ctx = canvas.getContext("2d"); if (!drawElement) {
throw new Error("canvas not found");
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 drawEmitter = mitt();
socket.send(id + "," + x + "," + y); let drawing = false;
}; const socketUrl = "/draw/ws";
let socket = new WebSocket(socketUrl);
const drawStart = () => { socket.onerror = console.error;
ctx.beginPath(); socket.onclose = console.warn;
lineId++; const serializeDraw = ({ from, to }) => `${from.x},${from.y},${to.x},${to.y}`;
drawing = true; const deserializeDraw = (message) => {
}; const parts = message.split(",");
return {
const drawStop = () => { from: {
drawing = false; x: Number(parts[0]),
ctx.closePath(); y: Number(parts[1]),
}; },
to: {
const drawMove = (e) => { x: Number(parts[2]),
if (!drawing) return; y: Number(parts[3]),
ctx.lineTo(e.offsetX, e.offsetY); },
sendMessage(lineId, e.offsetX, e.offsetY); };
ctx.stroke(); };
}; drawEmitter.on("draw", (e) => sendMessage(e));
const sendMessage = (e) => {
const touchStart = (e) => drawStart(e.touches[0]); if (socket.readyState !== socket.OPEN) {
const touchMove = (e) => { socket = new WebSocket(socketUrl);
drawMove({ }
offsetX: e.touches[0].clientX - canvas.offsetLeft, socket.send(serializeDraw(e));
offsetY: e.touches[0].clientY - canvas.offsetTop, };
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(); setInterval(() => {
}; if (socket.readyState !== socket.OPEN) {
const touchEnd = (e) => drawStop(e.changedTouches[0]); socket = new WebSocket(socketUrl);
}
canvas.addEventListener("mousedown", drawStart); socket.send("ping");
canvas.addEventListener("mouseup", drawStop); }, 10000);
canvas.addEventListener("mousemove", drawMove); const resizeObserver = new ResizeObserver((entries) => {
const entry = entries.at(0);
canvas.addEventListener("touchstart", touchStart); receiveCanvas.height = entry?.contentRect.height ?? 500;
canvas.addEventListener("touchend", touchEnd); receiveCanvas.width = entry?.contentRect.width ?? 500;
canvas.addEventListener("touchmove", touchMove); });
resizeObserver.observe(drawElement);
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);
})(); })();

138
js/draw.ts Normal file
View File

@@ -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<Events>();
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<MouseEvent, "offsetX" | "offsetY">) => {
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);
})();

4
js/global.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare const mitt: {
//@ts-expect-error
<T = any>(): import("mitt").Emitter<T>;
};

2
js/mitt/mitt.js Normal file
View File

@@ -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

27
package.json Normal file
View File

@@ -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"
}
}

33
pnpm-lock.yaml generated Normal file
View File

@@ -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: {}

10
tsconfig.json Normal file
View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"esModuleInterop": true,
"types": ["./js/global.d.ts"]
}
}

View File

@@ -1,21 +1,30 @@
{{block "draw" .}} {{block "draw" .}}
<div style="width:500px; height:500px">
<canvas <canvas
id="receiveCanvas" id="receiveCanvas"
width="500" width="500"
height="500" height="500"
style="position:absolute; border: 1px solid black ;background-color: transparent" style="
></canvas> position: absolute;
<canvas background-color: transparent;
id="drawCanvas" top: 0;
width="500" left: 0;
height="500" right: 0;
style="position:absolute; border: 1px solid black; background-color: transparent;" bottom: 0;
></canvas> "
></canvas>
<div> <div
id="drawDiv"
<script src="/js/draw.js"> style="
position: absolute;
</script> background-color: transparent;
{{end}} top: 0;
left: 0;
right: 0;
bottom: 0;
"
></div>
<script src="/js/mitt/mitt.js"></script>
<script src="/js/draw.js"></script>
{{end}}
</div>
</div>

View File

@@ -8,36 +8,11 @@
<link rel="stylesheet" href="/css/main.css" /> <link rel="stylesheet" href="/css/main.css" />
</head> </head>
<body> <body>
<header>
<nav>
<ul>
<li>
<h1><a href="/">K</a></h1>
</li>
</ul>
</nav>
</header>
<main> <main>
<section> <section>
{{template "draw" }} {{template "draw" }}
</section> </section>
</main> </main>
<footer>
<nav>
<ul>
<li>
<a href="https://github.com/JurajKubrican">Github</a>
</li>
<li>
<a href="https://www.linkedin.com/in/juraj-kubri%C4%8Dan-614b3274/"
>LinkedIn</a
>
</li>
</ul>
</nav>
</footer>
</body> </body>
</html> </html>
{{end}} {{end}}