├── README.md ├── draw.js ├── draw.php ├── global.php ├── index.js ├── index.php └── save.py /README.md: -------------------------------------------------------------------------------- 1 | # Big Canvas Demo 2 | Multiplayer drawing web app (similar to the "Million Dollar Homepage" or Reddit's "Place") in Javascript, Firebase, PHP, Python, and Linux/Apache. 3 | 4 | YouTube video walkthrough: https://youtu.be/t1aXuJkmTg8
5 | Demo: http://techleadpro.com/bigcanvas/ 6 | 7 | Looking for more coding tips? 8 | Join ex-Google/ex-Facebook engineers at http://coderpro.com for 100+ coding video explanations for Google, Facebook, and FANG companies. 9 | 10 | # Installation 11 | If you wish to deploy your own instance, here are a few changes necessary: 12 | - Create a Firebase project and make a file `creds.json` for your credentials. 13 | - Bring in the color-picker https://github.com/Simonwep/pickr using `git clone`. 14 | - Comment out the validation check in `draw.php`. 15 | - Create a folder "tmp/" and run `chmod 777 tmp/` 16 | -------------------------------------------------------------------------------- /draw.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | let canvas = $("#mycanvas"); 3 | let ctx = canvas.get(0).getContext("2d"); 4 | let canvasWidth = canvas.width(); 5 | let canvasHeight = canvas.height(); 6 | let PIXELSIZE = canvasWidth / DIMENSION; 7 | let selectedColor = '#222244'; 8 | let enabled = true; 9 | let filledPixels = {}; 10 | 11 | ctx.strokeStyle = 'rgba(0,0,0,0.1)'; 12 | for (let i = 0; i < DIMENSION; ++i) { 13 | x = Math.floor(i * canvasWidth / DIMENSION); 14 | ctx.beginPath(); 15 | ctx.moveTo(x, 0); 16 | ctx.lineTo(x, canvasHeight); 17 | ctx.stroke(); 18 | 19 | y = Math.floor(i * canvasHeight / DIMENSION); 20 | ctx.beginPath(); 21 | ctx.moveTo(0, y); 22 | ctx.lineTo(canvasWidth, y); 23 | ctx.stroke(); 24 | } 25 | 26 | // firefox canvas mousedown fix 27 | let firefoxmd = false; 28 | 29 | window.addEventListener('mousedown', e => { 30 | firefoxmd = true; 31 | }); 32 | 33 | window.addEventListener('mouseup', e => { 34 | if (firefoxmd === true) { 35 | firefoxmd = false; 36 | } 37 | }); 38 | 39 | canvas.on('mousemove touchmove touchstart mousedown', mouseFill); 40 | function mouseFill(e) { 41 | e.preventDefault(); // Disables scrolling for touch events. 42 | 43 | var touchstart = e.type === 'touchstart' || e.type === 'touchmove'; 44 | e = touchstart ? e.originalEvent : e; 45 | var rect = $("#mycanvas"); 46 | var offsetX = touchstart ? e.targetTouches[0].pageX - rect.offset().left : e.offsetX; 47 | var offsetY = touchstart ? e.targetTouches[0].pageY - rect.offset().top : e.offsetY; 48 | 49 | if (!enabled) return; 50 | if (!firefoxmd) return; 51 | if (e.which != 1 && !touchstart) return; 52 | 53 | pixel = [Math.floor(offsetX / PIXELSIZE), Math.floor(offsetY / PIXELSIZE)]; 54 | fillPixel(pixel); 55 | } 56 | 57 | function fillPixel(pixel) { 58 | let key = pixel[0] + ',' + pixel[1]; 59 | filledPixels[key] = selectedColor; 60 | 61 | ctx.fillStyle = selectedColor; 62 | ctx.fillRect(pixel[0] * PIXELSIZE, pixel[1] * PIXELSIZE, PIXELSIZE - 1, PIXELSIZE - 1); 63 | } 64 | 65 | const PICKR = Pickr.create({ 66 | el: '#picker', 67 | comparison: false, 68 | components: { 69 | opacity: false, 70 | hue: true, 71 | palette: true, 72 | interaction: { 73 | input: true, 74 | } 75 | } 76 | }); 77 | 78 | PICKR.on('init', function () { 79 | PICKR.setColor(selectedColor); 80 | }); 81 | PICKR.on('show', function () { 82 | enabled = false; 83 | }); 84 | PICKR.on('hide', function () { 85 | setTimeout(function () { 86 | enabled = true; 87 | }, 300); 88 | }); 89 | PICKR.on('change', function () { 90 | selectedColor = PICKR.getColor().toHEXA().toString(); 91 | }); 92 | 93 | window.save = function (x, y) { 94 | var data = {}; 95 | data['x'] = x; 96 | data['y'] = y; 97 | data['data'] = filledPixels; 98 | $("#saveButton").attr('disabled', 'true'); 99 | $("#spinner").html("Saving..."); 100 | 101 | $.post('draw.php?submit=1', data, function (rsp) { 102 | $('body').append(rsp); 103 | $("#saveButton").attr('disabled', false); 104 | }); 105 | } 106 | window.PICKR = PICKR; 107 | }); 108 | -------------------------------------------------------------------------------- /draw.php: -------------------------------------------------------------------------------- 1 | $data"; 17 | return; 18 | } 19 | 20 | # Save to file. 21 | $key = "$x,$y"; 22 | $filename = "tmp/" . $key . '-' . rand()%100; 23 | file_put_contents($filename, json_encode($data)); 24 | 25 | # Send to firestore. 26 | $result = trim(shell_exec("python save.py '$x' '$y' '$filename' 2>&1")); 27 | if ($result != 1) { 28 | die("Error saving. $result
"); 29 | } 30 | 31 | print ""; 32 | return; 33 | } 34 | 35 | print << 37 | 38 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 71 | 74 | 75 |
69 |
70 |
72 | 73 |
76 |
77 | 78 |
79 | 80 |
81 | 82 | 83 | EOF; 84 | -------------------------------------------------------------------------------- /global.php: -------------------------------------------------------------------------------- 1 | = REPEATSX || pixel[1] >= REPEATSY) { 74 | return; 75 | } 76 | if (!selectedBox) { 77 | selectedBox = $("
10 | 11 | 12 | 13 | 14 | 15 | 39 | 42 | 43 | 44 |

Big Canvas Demo

45 | 46 |
47 | YouTube video walkthrough: https://youtu.be/t1aXuJkmTg8
48 | Source code: https://github.com/techleadhd/bigcanvasdemo/
49 |
50 | Looking for more coding tips?
51 | Join ex-Google/ex-Facebook engineers at 52 | http://techinterviewpro.com for interview prep to FANG companies. 53 |
54 | 55 |
56 | 57 |
58 | 59 | 60 | 61 | 62 | EOF; 63 | -------------------------------------------------------------------------------- /save.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import firebase_admin 4 | from firebase_admin import credentials 5 | from firebase_admin import firestore 6 | import sys 7 | 8 | # Use the application default credentials 9 | cred = credentials.Certificate("creds.json") 10 | firebase_admin.initialize_app(cred) 11 | 12 | db = firestore.client() 13 | 14 | key = sys.argv[1] + ',' + sys.argv[2] 15 | filename = sys.argv[3] 16 | 17 | f = open(filename, 'r') 18 | value = "\n".join(f.readlines()) 19 | data = { 20 | unicode('data') : unicode(value) 21 | } 22 | 23 | db.collection(u'app').document(unicode(key)).set(data) 24 | 25 | print 1 26 | --------------------------------------------------------------------------------