├── .gitignore
├── .dockerignore
├── renovate.json
├── app.js
├── Dockerfile
├── package.json
├── README.md
├── public
├── index.html
├── styles.css
└── sketch.js
├── LICENSE
├── server.js
└── .eslintrc.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | /dist
2 | /node_modules
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 |
3 | const app = express()
4 |
5 | app.use(express.static('public'))
6 |
7 | module.exports = app
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY package*.json ./
6 |
7 | RUN npm install
8 |
9 | COPY . .
10 |
11 | EXPOSE 3000:3000
12 |
13 | CMD [ "node", "server.js" ]
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "socketio-drawing",
3 | "version": "1.0.0",
4 | "description": "A realtime drawing app using socketIO",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "Gabriel Tanner",
10 | "license": "MIT",
11 | "keywords": [
12 | "socket io",
13 | "drawing",
14 | "javascript",
15 | "sockets"
16 | ],
17 | "dependencies": {
18 | "body-parser": "^1.19.1",
19 | "cors": "^2.8.5",
20 | "express": "^4.17.2",
21 | "p5": "^1.4.0",
22 | "socket.io": "^4.4.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SocketIO drawing app
2 |
3 | Basic example of how to use socketIO to create simple communication between multiple browser instances.
4 |
5 | ## Getting started
6 |
7 | Start using Node
8 |
9 | ```bash
10 | # Install dependencies for server
11 | npm install
12 |
13 | # Run the server
14 | node server
15 | ```
16 |
17 | Start using Docker
18 |
19 | ```bash
20 | # Building the image
21 | docker build --tag socketiodrawing .
22 |
23 | # Run the image in a container
24 | docker run -d -p 3000:3000 socketiodrawing
25 | ```
26 |
27 | ## Author
28 |
29 | Gabriel Tanner
30 |
31 | ## Support me
32 |
33 |
34 |
35 | ## License
36 |
37 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE) file for details
38 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Choose color (# hex)
16 | 17 | 18 | 19 |Choose stroke width
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Tanner Gabriel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /public/styles.css: -------------------------------------------------------------------------------- 1 | input.call-picker { 2 | border: 1px solid #AAA; 3 | color: #666; 4 | text-transform: uppercase; 5 | float: left; 6 | outline: none; 7 | padding: 10px; 8 | text-transform: uppercase; 9 | width: 85px; 10 | } 11 | 12 | .color-picker { 13 | width: 130px; 14 | background: #F3F3F3; 15 | height: 81px; 16 | padding: 5px; 17 | border: 5px solid #fff; 18 | box-shadow: 0px 0px 3px 1px #DDD; 19 | position: absolute; 20 | top: 61px; 21 | left: 2px; 22 | } 23 | 24 | .color-holder { 25 | background: #fff; 26 | cursor: pointer; 27 | border: 1px solid #AAA; 28 | width: 40px; 29 | height: 36px; 30 | float: left; 31 | margin-left: 5px; 32 | } 33 | 34 | input.stroke_width_picker { 35 | border: 1px solid #AAA; 36 | color: #666; 37 | text-transform: uppercase; 38 | float: left; 39 | outline: none; 40 | padding: 10px; 41 | text-transform: uppercase; 42 | width: 85px; 43 | } 44 | 45 | p { 46 | margin-top: 2rem; 47 | margin-bottom: 2rem; 48 | color: #DDD; 49 | } 50 | 51 | button { 52 | margin-left: 1rem; 53 | } 54 | 55 | html, body { 56 | height: 100%; 57 | background-color: #373d55; 58 | } 59 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const app = require('./app') 3 | 4 | const normalizePort = val => { 5 | const port = parseInt(val, 10) 6 | 7 | if (isNaN(port)) { 8 | return val 9 | } 10 | if (port >= 0) { 11 | return port 12 | } 13 | return false 14 | } 15 | const port = normalizePort(process.env.PORT || '3000') 16 | app.set('port', port) 17 | 18 | const errorHandler = error => { 19 | if (error.syscall !== 'listen') { 20 | throw error 21 | } 22 | const address = server.address() 23 | const bind = typeof address === 'string' ? 'pipe ' + address : 'port: ' + port 24 | switch (error.code) { 25 | case 'EACCES': 26 | console.error(bind + ' requires elevated privileges.') 27 | process.exit(1) 28 | break 29 | case 'EADDRINUSE': 30 | console.error(bind + ' is already in use.') 31 | process.exit(1) 32 | break 33 | default: 34 | throw error 35 | } 36 | } 37 | 38 | const server = http.createServer(app) 39 | 40 | server.on('error', errorHandler) 41 | server.on('listening', () => { 42 | const address = server.address() 43 | const bind = typeof address === 'string' ? 'pipe ' + address : 'port ' + port 44 | console.log('Listening on ' + bind) 45 | }) 46 | 47 | 48 | // Web sockets 49 | const io = require('socket.io')(server) 50 | 51 | io.sockets.on('connection', (socket) => { 52 | console.log('Client connected: ' + socket.id) 53 | 54 | socket.on('mouse', (data) => socket.broadcast.emit('mouse', data)) 55 | 56 | socket.on('disconnect', () => console.log('Client has disconnected')) 57 | }) 58 | 59 | server.listen(port) -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 2017 9 | }, 10 | "rules": { 11 | "brace-style": ["error", "stroustrup", { 12 | "allowSingleLine": true 13 | }], 14 | "comma-dangle": ["error", "always-multiline"], 15 | "comma-spacing": "error", 16 | "comma-style": "error", 17 | "curly": ["error", "multi-line", "consistent"], 18 | "dot-location": ["error", "property"], 19 | "handle-callback-err": "off", 20 | "indent": ["error", "tab"], 21 | "max-nested-callbacks": ["error", { 22 | "max": 4 23 | }], 24 | "max-statements-per-line": ["error", { 25 | "max": 2 26 | }], 27 | "no-console": "off", 28 | "no-empty-function": "error", 29 | "no-floating-decimal": "error", 30 | "no-inline-comments": "error", 31 | "no-lonely-if": "error", 32 | "no-multi-spaces": "error", 33 | "no-multiple-empty-lines": ["error", { 34 | "max": 2, 35 | "maxEOF": 1, 36 | "maxBOF": 0 37 | }], 38 | "no-shadow": ["error", { 39 | "allow": ["err", "resolve", "reject"] 40 | }], 41 | "no-trailing-spaces": ["error"], 42 | "no-var": "error", 43 | "object-curly-spacing": ["error", "always"], 44 | "prefer-const": "error", 45 | "quotes": ["error", "single"], 46 | "semi": ["error", "never"], 47 | "space-before-blocks": "error", 48 | "space-before-function-paren": ["error", { 49 | "anonymous": "never", 50 | "named": "never", 51 | "asyncArrow": "always" 52 | }], 53 | "space-in-parens": "error", 54 | "space-infix-ops": "error", 55 | "space-unary-ops": "error", 56 | "spaced-comment": "error", 57 | "yoda": "error" 58 | } 59 | } -------------------------------------------------------------------------------- /public/sketch.js: -------------------------------------------------------------------------------- 1 | let socket 2 | let color = '#000' 3 | let strokeWidth = 4 4 | let cv 5 | 6 | function setup() { 7 | // Creating canvas 8 | cv = createCanvas(windowWidth / 2, windowHeight / 2) 9 | centerCanvas() 10 | cv.background(255, 255, 255) 11 | 12 | // Start the socket connection 13 | socket = io.connect('http://localhost:3000') 14 | 15 | // Callback function 16 | socket.on('mouse', data => { 17 | stroke(data.color) 18 | strokeWeight(data.strokeWidth) 19 | line(data.x, data.y, data.px, data.py) 20 | }) 21 | 22 | // Getting our buttons and the holder through the p5.js dom 23 | const color_picker = select('#pickcolor') 24 | const color_btn = select('#color-btn') 25 | const color_holder = select('#color-holder') 26 | 27 | const stroke_width_picker = select('#stroke-width-picker') 28 | const stroke_btn = select('#stroke-btn') 29 | 30 | // Adding a mousePressed listener to the button 31 | color_btn.mousePressed(() => { 32 | // Checking if the input is a valid hex color 33 | if (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(color_picker.value())) { 34 | color = color_picker.value() 35 | color_holder.style('background-color', color) 36 | } else { 37 | console.log('Enter a valid hex value') 38 | } 39 | }) 40 | 41 | // Adding a mousePressed listener to the button 42 | stroke_btn.mousePressed(() => { 43 | const width = parseInt(stroke_width_picker.value()) 44 | if (width > 0) strokeWidth = width 45 | }) 46 | } 47 | 48 | function windowResized() { 49 | centerCanvas() 50 | cv.resizeCanvas(windowWidth / 2, windowHeight / 2, false) 51 | } 52 | 53 | 54 | function centerCanvas() { 55 | const x = (windowWidth - width) / 2 56 | const y = (windowHeight - height) / 2 57 | cv.position(x, y) 58 | } 59 | 60 | 61 | function mouseDragged() { 62 | // Draw 63 | stroke(color) 64 | strokeWeight(strokeWidth) 65 | line(mouseX, mouseY, pmouseX, pmouseY) 66 | 67 | // Send the mouse coordinates 68 | sendmouse(mouseX, mouseY, pmouseX, pmouseY) 69 | } 70 | 71 | // Sending data to the socket 72 | function sendmouse(x, y, pX, pY) { 73 | const data = { 74 | x: x, 75 | y: y, 76 | px: pX, 77 | py: pY, 78 | color: color, 79 | strokeWidth: strokeWidth, 80 | } 81 | 82 | socket.emit('mouse', data) 83 | } --------------------------------------------------------------------------------