Created
June 3, 2022 20:09
-
-
Save shawnco/b4cdabacfb7d229fb5f48e437c647604 to your computer and use it in GitHub Desktop.
Collaborative Drawing App files as of article 7
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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