(() => { 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 = { mouseDraw: DrawEvent; netDraw: DrawEvent; }; const emitter = mitt(); let drawing = false; let previous: DrawPoint | null = null; const socket = initSocket("/draw/ws"); 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]) }, }; }; emitter.on("mouseDraw", (e) => sendMessage(e)); const sendMessage = (e: DrawEvent) => socket.send(serializeDraw(e)); const mouseDown = () => { drawing = true; previous = null; }; const mouseUp = () => { drawing = false; }; const mouseMove = (e: Pick) => { if (!drawing) return; if (previous) { emitter.emit("mouseDraw", { 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"); } const render = (data: DrawEvent) => { const { from, to } = data; if (recCtx.strokeStyle != "white") { recCtx.lineWidth = 2; recCtx.lineCap = "round"; recCtx.strokeStyle = "white"; } recCtx.beginPath(); recCtx.moveTo(from.x, from.y); recCtx.lineTo(to.x, to.y); recCtx.closePath(); recCtx.stroke(); }; emitter.on("mouseDraw", render); emitter.on("netDraw", render); socket.addMessageListener( (data) => { if (data === "ping" || data.startsWith("pong")) return; emitter.emit("netDraw", deserializeDraw(data)); }); 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); })();