Skip to content

Instantly share code, notes, and snippets.

@shawnco
Created June 3, 2022 20:09
Show Gist options
  • Save shawnco/b4cdabacfb7d229fb5f48e437c647604 to your computer and use it in GitHub Desktop.
Save shawnco/b4cdabacfb7d229fb5f48e437c647604 to your computer and use it in GitHub Desktop.
Collaborative Drawing App files as of article 7
<html>
<head>
<title>Collaborative Drawing App</title>
<style type="text/css">
canvas {
border: 1px solid black;
}
.palette {
border: 1px solid black;
display: inline-block;
margin: 2px;
height: 25px;
width: 25px;
}
</style>
</head>
<body>
<h1>Collaborative Drawing App</h1>
<div>
<canvas id="canvas" height="500px" width="500px"></canvas><br />
<b>Mode:</b> <span id="mode"></span><br />
<b>Message:</b> <span id="message"></span>
</div>
<div id="palette">
<div id="row-1">
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
</div>
<div id="row-2">
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
<div class="palette"></div>
</div>
</div>
<div id="draw-methods">
<button onclick="canvas.setMode('Line')">Line</button>
<button onclick="canvas.setMode('Hollow Rectangle')">Hollow Rectangle</button>
<button onclick="canvas.setMode('Filled Rectangle')">Filled Rectangle</button>
<button onclick="canvas.setMode('Hollow Circle')">Hollow Circle</button>
<button onclick="canvas.setMode('Filled Circle')">Filled Circle</button>
</div>
<div>
<button onclick="canvas.clear()">Clear</button>
<button onclick="canvas.download()">Download</Button>
</div>
<script type="text/javascript" src="./script.js"></script>
<script type="text/javascript">
const room = new Room();
const socket = new Socket(room);
const canvas = new Canvas(socket, room);
socket.setCanvas(canvas);
const palette = new Palette();
palette.draw(canvas);
</script>
</body>
</html>
class Canvas {
constructor(socket, room) {
this.canvas = document.querySelector('#canvas');
this.ctx = this.canvas.getContext('2d');
this.activeColor = '#000000';
this.startPoint = null;
this.endPoint = null;
this.pointMode = 'start';
this.mode = 'Line';
this.handleDraw = this.handleDraw.bind(this);
this.canvas.addEventListener('click', this.handleDraw);
this.shapeMessages = [
{ type: 'line', start: 'Select the starting point', end: 'Select the ending point' },
{ type: 'rectangle', start: 'Select the first corner', end: 'Select the second corner' },
{ type: 'circle', start: 'Select the middle of the circle', end: 'Select the edge of the circle' }
];
this.socket = socket;
this.room = room;
this.displayMode();
this.displayMessage();
}
setColor(color) {
this.activeColor = color;
this.ctx.strokeStyle = color;
this.ctx.fillStyle = color;
}
setMode(mode) {
this.mode = mode;
this.displayMode();
this.displayMessage();
}
handleDraw(e) {
const rect = this.canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (this.pointMode == 'start') {
this.startPoint = [x, y];
this.pointMode = 'end';
} else if (this.pointMode == 'end') {
this.pointMode = 'start';
this.endPoint = [x, y];
// do the drawing
if (this.mode == 'Line') {
this.drawLine(this.startPoint, this.endPoint);
} else if (this.mode == 'Hollow Rectangle') {
this.drawHollowRectangle(this.startPoint, this.endPoint);
} else if (this.mode == 'Filled Rectangle') {
this.drawFilledRectangle(this.startPoint, this.endPoint);
} else if (this.mode == 'Hollow Circle') {
this.drawHollowCircle(this.startPoint, this.endPoint);
} else if (this.mode == 'Filled Circle') {
this.drawFilledCircle(this.startPoint, this.endPoint);
}
this.socket.sendDraw(this.room, this.mode, this.activeColor, this.startPoint, this.endPoint);
this.startPoint = null;
this.endPoint = null;
}
this.displayMessage();
}
drawMessage(msg) {
const {mode, color, startPoint, endPoint} = msg;
this.ctx.strokeStyle = color;
this.ctx.fillStyle = color;
if (mode === 'Line') {
this.drawLine(startPoint, endPoint);
} else if (mode === 'Hollow Rectangle') {
this.drawHollowRectangle(startPoint, endPoint);
} else if (mode === 'Filled Rectangle') {
this.drawFilledRectangle(startPoint, endPoint);
} else if (mode === 'Hollow Circle') {
this.drawHollowCircle(startPoint, endPoint);
} else if (mode === 'Filled Circle') {
this.drawFilledCircle(startPoint, endPoint);
}
this.ctx.strokeStyle = this.activeColor;
this.ctx.fillStyle = this.activeColor;
}
drawLine(startPoint, endPoint) {
this.ctx.beginPath();
this.ctx.moveTo(startPoint[0], startPoint[1]);
this.ctx.lineTo(endPoint[0], endPoint[1]);
this.ctx.stroke();
}
drawHollowRectangle(startPoint, endPoint) {
this.ctx.beginPath();
this.ctx.strokeRect(
startPoint[0],
startPoint[1],
endPoint[0] - startPoint[0],
endPoint[1] - startPoint[1]
);
}
drawFilledRectangle(startPoint, endPoint) {
this.ctx.beginPath();
this.ctx.fillRect(
startPoint[0],
startPoint[1],
endPoint[0] - startPoint[0],
endPoint[1] - startPoint[1]
);
}
drawHollowCircle(startPoint, endPoint) {
const x = startPoint[0] - endPoint[0];
const y = startPoint[1] - endPoint[1];
const radius = Math.sqrt(x * x + y * y);
this.ctx.beginPath();
this.ctx.arc(startPoint[0], startPoint[1], radius, 0, 2 * Math.PI, false);
this.ctx.stroke();
}
drawFilledCircle(startPoint, endPoint) {
const x = startPoint[0] - endPoint[0];
const y = startPoint[1] - endPoint[1];
const radius = Math.sqrt(x * x + y * y);
this.ctx.beginPath();
this.ctx.arc(startPoint[0], startPoint[1], radius, 0, 2 * Math.PI, false);
this.ctx.fill();
}
displayMode() {
const modeDiv = document.querySelector('#mode');
modeDiv.innerHTML = this.mode;
}
getModeType(mode) {
let type = '';
switch (mode) {
case 'Line':
type = 'line';
break;
case 'Hollow Rectangle':
case 'Filled Rectangle':
type = 'rectangle';
break;
case 'Hollow Circle':
case 'Filled Circle':
type = 'circle';
break;
}
return type;
}
displayMessage() {
const type = this.getModeType(this.mode);
const find = this.shapeMessages.find(m => m.type === type);
const msgDiv = document.querySelector('#message');
msgDiv.innerHTML = find[this.pointMode];
}
clear() {
this.ctx.clearRect(0, 0, 500, 500);
this.socket.sendClear();
}
download() {
const image = this.canvas.toDataURL('image/png', 1.0);
image.replace('image/png', 'image/octet-stream');
const link = document.createElement('a');
link.download = 'canvas-image.png';
link.href = image;
link.click();
}
clearMessage(msg) {
this.ctx.clearRect(0, 0, 500, 500);
}
}
class Palette {
constructor() {
this.colors = [
['#000000', '#FFFFFF', '#7F7F7F', '#C3C3C3', '#880015', '#B97A57', '#ED1C24', '#FFAEC9', '#FF7F27', '#FFC90E'],
['#FFF200', '#EFE4B0', '#22B14C', '#B5E61D', '#00A2E8', '#99D9EA', '#3F48CC', '#7092BE', '#A349A4', '#C8BFE7']
];
}
draw(canvas) {
const row1 = document.querySelectorAll('#row-1 .palette');
const row2 = document.querySelectorAll('#row-2 .palette');
row1.forEach((div, idx) => {
div.style.backgroundColor = this.colors[0][idx];
div.onclick = e => canvas.setColor(this.colors[0][idx]);
});
row2.forEach((div, idx) => {
div.style.backgroundColor = this.colors[1][idx];
div.onclick = e => canvas.setColor(this.colors[1][idx]);
});
}
}
class Socket {
constructor(room) {
this.socket = new WebSocket('ws://localhost:8080');
this.socket.onmessage = msg => this.handleMessage(msg);
this.room = room;
}
sendDraw(room, mode, color, startPoint, endPoint) {
this.socket.send(JSON.stringify({
type: 'draw',
roomId: room.id,
roomName: room.room,
mode,
color,
startPoint,
endPoint
}));
}
async handleMessage(msg) {
const msgDecoded = JSON.parse(await msg.data.text());
if (msgDecoded.roomName == this.room.room && msgDecoded.roomId !== this.room.id) {
if (msgDecoded.type === 'draw') {
this.canvas.drawMessage(msgDecoded);
} else if (msgDecoded.type === 'clear') {
this.canvas.clearMessage(msgDecoded);
}
}
}
setCanvas(canvas) {
this.canvas = canvas;
}
sendClear() {
this.socket.send(JSON.stringify({
type: 'clear',
roomId: this.room.id,
roomName: this.room.room
}));
}
}
class Room {
constructor() {
this.getRoom();
this.id = Math.round(Math.random() * 10000).toString();
}
getRoom() {
const {pathname} = location;
if (pathname !== '/') {
this.room = pathname.split('/')[1];
} else {
this.room = Math.round(Math.random() * 10000).toString();
history.pushState({}, '', '/' + this.room);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment