This commit is contained in:
sample-text-here 2022-01-06 17:49:46 -08:00
parent e5430032d6
commit d544244fdf
5 changed files with 220 additions and 200 deletions

View file

@ -13,17 +13,6 @@
<body>
<div id="loading">loading...</div>
<canvas id="canvas"></canvas>
<div class="bottom">
<div class="picker">
<button type="button" id="#F45B69"></button>
<button type="button" id="#FE7F2D"></button>
<button type="button" id="#FCCA46"></button>
<button type="button" id="#87FF65"></button>
<button type="button" id="#00A5CF"></button>
<button type="button" id="#7D5BA6"></button>
<button type="button" id="#312F2F"></button>
<button type="button" id="white"></button>
</div>
</div>
<div id="bottom"><div id="picker"></div></div>
</body>
</html>

View file

@ -8,12 +8,20 @@ const rooms = new Map();
app.use(express.static(__dirname + "/public"));
app.get("/", (req, res) => {
res.sendFile(__dirname + "/views/index.html");
app.get("/", (_, res) => {
const consonants = "bcdfghjklmnpqrstvwxyz";
const vowels = "aeiou";
const rnd = (arr) => arr[Math.floor(Math.random() * arr.length)];
let word = "";
for (let i = 0; i < Math.random() * 2 + 2; i++) {
word += rnd(consonants);
word += rnd(vowels);
}
res.redirect("/" + word);
});
app.get("/*", (req, res) => {
res.sendFile(__dirname + "/views/board.html");
app.get("/*", (_, res) => {
res.sendFile(__dirname + "/board.html");
});
const server = app.listen(3000);
@ -24,26 +32,13 @@ io.on("connection", (socket) => {
const { id } = socket;
socket.on("join", (msg) => {
socket.join(msg);
if(rooms.has(msg)) {
for(let ev of rooms.get(msg)) socket.emit(...ev);
} else {
rooms.set(msg, []);
}
socket.emit("sync");
if(!rooms.has(msg)) rooms.set(msg, []);
socket.emit("sync", rooms.get(msg));
});
socket.on("sync", () => {
if(rooms.has(room())) {
for(let ev of rooms.get(room())) socket.emit(...ev);
}
socket.emit("sync");
});
socket.on("disconnect", (msg) => emit("gc", id));
socket.on("drawmove", (msg) => emit("drawmove", { ...msg, id }));
socket.on("drawstart", (msg) => emit("drawstart", { ...msg, id }));
socket.on("drawend", (msg) => emit("drawend", { ...msg, id }));
function emit(...args) {
io.to(room()).emit(...args);
if(rooms.has(room())) rooms.get(room()).push(args);
}
socket.on("draw", (msg) => {
msg = { ...msg, id };
io.to(room()).emit("draw", msg);
if(rooms.has(room())) rooms.get(room()).push(msg);
});
});

View file

@ -1,29 +1,197 @@
const $ = e => document.getElementById(e);
const socket = io();
class Drawer {
constructor() {
this.canvas = new OffscreenCanvas(1e4, 1e4);
this.ctx = this.canvas.getContext("2d");
this.history = [];
this.pens = new Map();
this.ctx.lineCap = "round";
this.ctx.lineJoin = "round";
}
chain(event) {
const { pens } = this;
if(event.type === "drawstart") {
const path = new Path2D();
path.moveTo(event.x, event.y);
pens.set(event.id, { ...event, path });
} else if(event.type === "drawmove") {
if(!pens.has(event.id)) return;
const pen = pens.get(event.id);
pen.path.lineTo(event.x, event.y);
this.ctx.strokeStyle = pen.color;
this.ctx.lineWidth = pen.stroke;
this.ctx.stroke(pen.path);
} else if(event.type === "drawend") {
if(!pens.has(event.id)) return;
const pen = pens.get(event.id);
pen.path.lineTo(event.x, event.y);
this.ctx.strokeStyle = pen.color;
this.ctx.lineWidth = pen.stroke;
this.ctx.stroke(pen.path);
}
}
render() {
this.ctx.clearRect(0, 0, 1e9, 1e9);
for(let event of this.history) this.chain(event);
}
add(event) {
this.history.push(event);
this.chain(event);
}
}
class Renderer {
constructor(canvas) {
this.drawer = new Drawer();
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.pan = [0, 0];
this.resize();
}
resize() {
this.canvas.height = window.innerHeight;
this.canvas.width = window.innerWidth;
this.ctx.height = window.innerHeight;
this.ctx.width = window.innerWidth;
this.ctx.clearRect(0, 0, 99999, 99999);
this.render();
}
add(event) {
this.drawer.add(event);
this.render();
}
addAll(events) {
this.drawer.history.push(...events);
this.drawer.render();
this.render();
}
render() {
this.ctx.clearRect(0, 0, 99999, 99999);
this.ctx.drawImage(this.drawer.canvas, this.pan[0], this.pan[1]);
}
}
class ColorSelection {
constructor(parent) {
this.parent = parent;
this.color = "black";
this.current = null;
this.elements = [];
}
add(color) {
const button = document.createElement("button");
button.style.background = color;
button.addEventListener("click", () => {
if(this.current) this.current.classList.remove("selected");
button.classList.add("selected");
this.color = color;
this.current = button;
});
this.elements.push(button);
this.parent.append(button);
}
addAll(colors) {
for(let color of colors) this.add(color);
}
random() {
const el = this.elements[Math.floor(Math.random() * this.elements.length)];
el.click();
}
}
const renderer = new Renderer($("canvas"));
const colors = new ColorSelection($("picker"));
colors.addAll([
"#F45B69",
"#FE7F2D",
"#FCCA46",
"#87FF65",
"#00A5CF",
"#7D5BA6",
"#312F2F",
]);
colors.random();
colors.add("white");
let pressed = false;
function mousedown(e) {
e = e.clientX ? e : e.touches?.[0];
if(!e) return;
pressed = true;
if(e.which !== 1) return;
const event = {
type: "drawstart",
x: e.clientX - renderer.pan[0],
y: e.clientY - renderer.pan[1],
color: colors.color,
stroke: colors.color === "white" ? 30 : 5,
};
renderer.add(event);
socket.emit("draw", event);
}
function mousemove(e) {
if(!pressed) return;
e = e.clientX ? e : e.touches?.[0];
if(!e) return;
if(e.which !== 1) {
renderer.pan[0] += e.movementX;
renderer.pan[1] += e.movementY;
renderer.render();
return;
}
const event = {
type: "drawmove",
x: e.clientX - renderer.pan[0],
y: e.clientY - renderer.pan[1],
};
renderer.add(event);
socket.emit("draw", event);
}
function mouseup(e) {
e = e.clientX ? e : e.touches?.[0];
if(!e) return;
pressed = false;
if(e.which !== 1) return;
const event = {
type: "drawend",
x: e.clientX - renderer.pan[0],
y: e.clientY - renderer.pan[1],
};
renderer.add(event);
socket.emit("draw", event);
}
socket.on("draw", (event) => {
if(socket.id === event.id) return;
renderer.add(event);
});
socket.on("sync", (data) => {
renderer.addAll(data);
if($("loading")) $("loading").remove();
});
socket.on("connect", () => {
socket.emit("join", location.pathname);
});
const canvas = $("canvas");
const ctx = canvas.getContext("2d");
const peers = new Map();
let penColor = "black";
const colors = document.querySelectorAll(".picker button");
let colorel = colors[Math.floor(Math.random() * colors.length)];
select(colorel);
for (let el of colors) {
el.addEventListener("click", () => select(el));
el.style.background = el.id;
}
function select(el) {
penColor = el.id;
if (colorel) colorel.classList.remove("selected");
el.classList.add("selected");
colorel = el;
}
ctx.lineCap = "round";
ctx.lineJoin = "round";
canvas.addEventListener("mousedown", mousedown);
canvas.addEventListener("mouseup", mouseup);
canvas.addEventListener("mousemove", mousemove);
@ -31,81 +199,5 @@ canvas.addEventListener("touchstart", mousedown);
canvas.addEventListener("touchmove", mousemove);
canvas.addEventListener("touchend", mouseup);
canvas.addEventListener("touchcancel", mouseup);
window.addEventListener("resize", resize);
document.addEventListener("contextmenu", e => e.preventDefault());
window.addEventListener("resize", renderer.resize);
resize();
function resize() {
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
ctx.height = window.innerHeight;
ctx.width = window.innerWidth;
ctx.clearRect(0, 0, 99999, 99999);
socket.emit("sync");
}
function mousedown(e) {
e = e.clientX ? e : e.touches[0];
socket.emit("drawstart", {
x: e.clientX,
y: e.clientY,
color: penColor,
stroke: penColor === "white" ? 30 : 5,
});
}
function mousemove(e) {
e = e.clientX ? e : e.touches[0];
socket.emit("drawmove", {
x: e.clientX,
y: e.clientY,
});
}
function mouseup(e) {
e = e.clientX ? e : e.touches[0];
socket.emit("drawend", {
x: e.clientX,
y: e.clientY,
});
}
socket.on("drawstart", (msg) => {
const path = new Path2D();
path.moveTo(msg.x, msg.y);
peers.set(msg.id, {
color: msg.color,
stroke: msg.stroke,
path,
});
});
socket.on("drawmove", (msg) => {
if(!peers.has(msg.id)) return;
const peer = peers.get(msg.id);
peer.path.lineTo(msg.x, msg.y);
ctx.strokeStyle = peer.color;
ctx.lineWidth = peer.stroke;
ctx.stroke(peer.path);
});
socket.on("drawend", (msg) => {
if(!peers.has(msg.id)) return;
const peer = peers.get(msg.id);
peer.path.lineTo(msg.x, msg.y);
ctx.strokeStyle = peer.color;
ctx.lineWidth = peer.stroke;
ctx.stroke(peer.path);
peers.delete(msg.id);
});
socket.on("gc", (id) => {
peers.delete(id);
});
socket.on("sync", (id) => {
if($("loading")) $("loading").remove();
});
socket.emit("join", location.pathname);

View file

@ -8,24 +8,17 @@ body {
font-family: "Trebuchet MS", Arial, sans-serif;
}
.bottom {
#bottom {
position: fixed;
bottom: 0;
left: 50%;
transform: translateX(-50%);
left: 0;
width: 100%;
border-top: solid #cccccc 2px;
background: #f5f5f5;
padding: 8px;
}
.picker {
display: flex;
justify-content: space-evenly;
padding: 4px;
border: solid #cccccc 2px;
border-bottom: none;
border-radius: 5px 5px 0 0;
background: white;
}
.picker button {
#picker button {
height: 2em;
width: 2em;
border-radius: 50%;
@ -36,19 +29,10 @@ body {
transition: all 0.1s;
}
.picker button.selected {
#picker button.selected {
transform: scale(0.8);
}
#stroke {
width: 100%;
display: inline;
}
#stroke-display {
user-select: none;
}
#loading {
z-index: 9999;
position: fixed;

View file

@ -1,40 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>bored</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/style.css" />
<style>
body {
padding: 2em;
}
</style>
</head>
<body>
<p>public whiteboard service</p>
<ol>
<li>go to any path on this domain</li>
<li>share the same link with a friend</li>
<li>draw stuff</li>
<li>have a nice day</li>
</ol>
<script>
location.pathname = generate();
function generate() {
const consonants = "bcdfghjklmnpqrstvwxyz";
const vowels = "aeiou";
const rnd = (arr) => arr[Math.floor(Math.random() * arr.length)];
let word = "";
for (let i = 0; i < Math.random() * 2 + 2; i++) {
word += rnd(consonants);
word += rnd(vowels);
}
return word;
}
</script>
</body>
</html>