Browse Source

running app

master
Rob Colbert 2 years ago
parent
commit
ceb42eb8e1
  1. 2
      .gitignore
  2. 25
      .jshintrc
  3. 9
      README.md
  4. BIN
      app/assets/icon/android-chrome-192x192.png
  5. BIN
      app/assets/icon/android-chrome-512x512.png
  6. BIN
      app/assets/icon/apple-touch-icon.png
  7. BIN
      app/assets/icon/favicon-16x16.png
  8. BIN
      app/assets/icon/favicon-32x32.png
  9. BIN
      app/assets/icon/favicon.ico
  10. BIN
      app/assets/icon/favicon.png
  11. BIN
      app/assets/img/nicecrew-banner.png
  12. 26
      app/views/layout.pug
  13. 4
      app/views/multiplayer-canvas.pug
  14. 50
      client/js/canvas-app.js
  15. 23
      client/less/lib/brand-link.less
  16. 11
      client/less/lib/container.less
  17. 26
      client/less/lib/main.less
  18. 16
      client/less/lib/multiplayer-canvas.less
  19. 9
      client/less/lib/navbar.less
  20. 13
      client/less/lib/variables.less
  21. 13
      client/less/style.less
  22. 104
      multiplayer-canvas.js
  23. 28
      package.json
  24. 102
      webpack.config.js
  25. 2986
      yarn.lock

2
.gitignore

@ -0,0 +1,2 @@
node_modules
.env

25
.jshintrc

@ -0,0 +1,25 @@
{
"bitwise": true,
"browser": true,
"curly": true,
"eqeqeq": true,
"latedef": true,
"noarg": true,
"node": true,
"strict": "global",
"undef": true,
"unused": true,
"futurehostile": true,
"esversion": 9,
"mocha": true,
"globals": {
"markdown": true,
"moment": true,
"numeral": true,
"io": true,
"Chart": true,
"CodeMirror": true,
"UIkit": true,
"twttr": true
}
}

9
README.md

@ -0,0 +1,9 @@
# Multiplayer Canvas
An HTML5 <canvas> to which multiple people can connect over Socket.io and make changes one pixel at a time.
## Running the Application
```sh
yarn start
```

BIN
app/assets/icon/android-chrome-192x192.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
app/assets/icon/android-chrome-512x512.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
app/assets/icon/apple-touch-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
app/assets/icon/favicon-16x16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

BIN
app/assets/icon/favicon-32x32.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

BIN
app/assets/icon/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
app/assets/icon/favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
app/assets/img/nicecrew-banner.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

26
app/views/layout.pug

@ -0,0 +1,26 @@
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title Multiplayer Canvas
link(rel="stylesheet", href="/dist/canvas-app.css")
link(rel="icon", type="image/x-icon", href="/dist/assets/icon/favicon.ico")
body(data-dtp-env= process.env.NODE_ENV)
.navbar
.brand-link
a(href="https://nicecrew.digital", target="_blank")
img(src="/dist/assets/img/nicecrew-banner.png", alt="NiceCrew Banner").brand-header
.container
block content-view
script(src=`${appModuleUrl}?v=${pkg.version}`)

4
app/views/multiplayer-canvas.pug

@ -0,0 +1,4 @@
extends layout
block content-view
.margin
canvas(id="multiplayer-canvas", width="32", height="32").multiplayer-canvas

50
client/js/canvas-app.js

@ -0,0 +1,50 @@
// canvas-app.js
// Copyright (C) 2022 Rob Colbert @[email protected]
// License: Apache-2.0
'use strict';
console.log('loaded');
const IMAGE_W = 32;
const IMAGE_H = 32;
const DTP_COMPONENT_NAME = 'canvas-app';
import '../less/style.less';
import { NiceAudio, NiceLog } from 'dtp-nice-game';
export default class CanvasApp {
constructor ( ) {
this.audio = new NiceAudio();
this.log = new NiceLog(DTP_COMPONENT_NAME);
this.log.info('Canvas app online');
this.canvas = document.querySelector('canvas');
this.ctx = this.canvas.getContext('2d');
this.imageData = this.ctx.createImageData(32, 32);
}
updatePixels (message) {
for (const pixel of message.pixels) {
if ((pixel.x < 0) ||
(pixel.x > (IMAGE_W - 1)) ||
(pixel.y < 0) ||
(pixel.y > (IMAGE_H - 1))) {
this.log.info('rejecting invalid pixel update', pixel);
return;
}
let pixelIdx = (pixel.y * IMAGE_W * 4) + (pixel.x * 4);
this.imageData[pixelIdx++] = pixel.r;
this.imageData[pixelIdx++] = pixel.g;
this.imageData[pixelIdx++] = pixel.b;
}
}
}
window.addEventListener('load', async ( ) => {
window.app = new CanvasApp();
console.log('running');
});

23
client/less/lib/brand-link.less

@ -0,0 +1,23 @@
/*
* lib/brand-link.less
* Copyright (C) 2022 Rob Colbert @[email protected]
* License: Apache-2.0
*/
.brand-link {
display: block;
width: 240px;
margin: 0 auto;
img.brand-header {
display: block;
width: 100%;
max-width: 960px;
height: auto;
margin: 0 auto;
}
.brand-prompt {
text-align: center;
}
}

11
client/less/lib/container.less

@ -0,0 +1,11 @@
/*
* lib/container.less
* Copyright (C) 2022 Rob Colbert @[email protected]
* License: Apache-2.0
*/
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
}

26
client/less/lib/main.less

@ -0,0 +1,26 @@
/*
* lib/main.less
* Copyright (C) 2022 Rob Colbert @[email protected]
* License: Apache-2.0
*/
html, body {
margin: 0;
padding: 0;
font-size: 14px;
background-color: var(--background-color);
color: var(--color);
}
body {
display: block;
width: 100%;
height: 100%;
overflow: auto;
}
.margin {
display: block;
margin: var(--default-margin) 0;
}

16
client/less/lib/multiplayer-canvas.less

@ -0,0 +1,16 @@
/*
* lib/multiplayer-canvas.less
* Copyright (C) 2022 Rob Colbert @[email protected]
* License: Apache-2.0
*/
canvas.multiplayer-canvas {
display: block;
box-sizing: border-box;
width: 100%;
max-width: 640px;
height: auto;
border: solid 1px white;
}

9
client/less/lib/navbar.less

@ -0,0 +1,9 @@
/*
* lib/navbar.less
* Copyright (C) 2022 Rob Colbert @[email protected]
* License: Apache-2.0
*/
.navbar {
padding: var(--default-margin) 0;
}

13
client/less/lib/variables.less

@ -0,0 +1,13 @@
/*
* lib/variables.less
* Copyright (C) 2022 Rob Colbert @[email protected]
* License: Apache-2.0
*/
* {
--background-color : #1a1a1a;
--color : #e8e8e8;
--brand-color : rgb(4, 130, 216);
--default-margin : 30px;
}

13
client/less/style.less

@ -0,0 +1,13 @@
/*
* style.less
* Copyright (C) 2022 Rob Colbert @[email protected]
* License: Apache-2.0
*/
@import 'lib/variables.less';
@import 'lib/main.less';
@import 'lib/brand-link.less';
@import 'lib/container.less';
@import 'lib/multiplayer-canvas.less';
@import 'lib/navbar.less';

104
multiplayer-canvas.js

@ -0,0 +1,104 @@
// multiplayer-canvas.js
// Copyright (C) 2022 Rob Colbert @[email protected]
// License: Apache-2.0
'use strict';
import 'dotenv/config'; // reads .env into process.env
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url)); // jshint ignore:line
import { createRequire } from 'module';
const require = createRequire(import.meta.url); // jshint ignore:line
import express from 'express';
import winston from 'winston';
import expressWinston from 'express-winston';
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import WEBPACK_CONFIG from './webpack.config.js';
const APP_CONFIG = {
pkg: require('./package.json'),
niceGameSdk: require('./node_modules/dtp-nice-game/package.json'),
};
class MultiplayerCanvasApp {
constructor ( ) {
this.app = express();
this.app.locals.config = APP_CONFIG;
this.app.locals.pkg = APP_CONFIG.pkg;
this.app.locals.niceGameSdk = APP_CONFIG.niceGameSdk;
this.app.set('view engine', 'pug');
this.app.set('views', path.join(__dirname, 'app', 'views'));
this.app.use(expressWinston.logger({
transports: [
new winston.transports.Console(),
],
format: winston.format.combine(
winston.format.colorize(),
// winston.format.json(),
),
meta: false,
msg: "HTTP ",
expressFormat: true,
colorize: false,
ignoreRoute: (/*req, res*/) => { return false; },
}));
this.app.use('/dist/assets', express.static(path.join(__dirname, 'app', 'assets')));
/*
* Webpack integration
*/
this.compiler = webpack(WEBPACK_CONFIG);
/*
* Webpack dev server middleware
*/
this.webpackDevMiddleware = webpackDevMiddleware(this.compiler, {
publicPath: WEBPACK_CONFIG.output.publicPath,
});
this.app.use(this.webpackDevMiddleware);
/*
* Application routes
*/
this.app.get('/', this.getCanvasView.bind(this));
}
async start ( ) {
return new Promise((resolve, reject) => {
this.app.listen(3000, (err) => {
if (err) {
return reject(err);
}
resolve();
});
});
}
async getCanvasView (req, res) {
res.locals.appModuleUrl = '/dist/canvas-app.bundle.js';
res.render('multiplayer-canvas');
}
}
(async ( ) => {
const app = new MultiplayerCanvasApp();
await app.start();
})();

28
package.json

@ -4,7 +4,33 @@
"description": "Interactive multi-user HTML5 <canvas> delivering online pixel war goodness.",
"main": "multiplayer-canvas.js",
"repository": "https://git.digitaltelepresence.com/rob/multiplayer-canvas.git",
"type": "module",
"scripts": {
"develop": "node multiplayer-canvas.js",
"bundle": "NODE_ENV=production yarn webpack --config webpack.config.js"
},
"author": "Rob Colbert",
"license": "Apache-2.0",
"private": false
"private": false,
"dependencies": {
"dotenv": "^16.0.0",
"dtp-nice-game": "https://git.digitaltelepresence.com/digital-telepresence/dtp-nice-game.git",
"express": "^4.17.3",
"express-winston": "^4.2.0",
"pug": "^3.0.2",
"winston": "^3.7.1"
},
"devDependencies": {
"browser-sync": "^2.27.9",
"browser-sync-webpack-plugin": "^2.3.0",
"css-loader": "^6.7.1",
"jshint": "^2.13.4",
"less": "^4.1.2",
"less-loader": "^10.2.0",
"mini-css-extract-plugin": "^2.6.0",
"style-loader": "^3.3.1",
"webpack": "^5.71.0",
"webpack-cli": "^4.9.2",
"webpack-dev-middleware": "^5.3.1"
}
}

102
webpack.config.js

@ -0,0 +1,102 @@
// webpack.config.js
// Copyright (C) 2022 Rob Colbert @[email protected]
// License: Apache-2.0
'use strict';
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url)); // jshint ignore:line
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import BrowserSyncPlugin from 'browser-sync-webpack-plugin';
const webpackMode = (process.env.NODE_ENV === 'production') ? 'production' : 'development';
console.log('Webpack mode:', webpackMode);
const plugins = [ ];
plugins.push(new MiniCssExtractPlugin());
if (webpackMode === 'development') {
plugins.push(
new BrowserSyncPlugin(
{
proxy: {
target: 'http://localhost:3000',
ws: true,
},
host: 'localhost',
open: 'local',
port: 3333,
cors: true,
ui: {
port: 3400,
},
notify: false,
ghostMode: {
clicks: false,
forms: false,
scroll: true,
},
logLevel: 'info',
files: [
'./dist/*.js',
'./dist/*.css',
],
},
),
);
}
export default {
entry: {
'canvas-app': './client/js/canvas-app.js',
},
mode: webpackMode,
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
publicPath: '/dist',
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
plugins,
module: {
rules: [
{
test: /\.less$/i,
use: [
{
loader: "style-loader",
},
{
loader: MiniCssExtractPlugin.loader,
options: {
esModule: false,
},
},
{
loader: "css-loader",
options: {
sourceMap: true,
},
},
{
loader: "less-loader",
options: {
sourceMap: true,
lessOptions: {
strictMath: true,
},
},
},
],
},
],
},
};

2986
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save