summaryrefslogtreecommitdiff
path: root/WebInterface/NodeJSServer/src/js
diff options
context:
space:
mode:
authorTrueKuehli <rctcoaster2000@hotmail.de>2018-11-27 12:16:33 +0100
committerTrueKuehli <rctcoaster2000@hotmail.de>2018-11-27 12:16:33 +0100
commit9f0b255f32dfa81bffe75f89335a78a659b4ce6a (patch)
treec4345ef6f98fee35f326bc06c29cada542a78e43 /WebInterface/NodeJSServer/src/js
parenta653a4efc60ef0bbc18e65cb11a4bd8c06c7ad5c (diff)
Reworked the code, but currently unable to test, so bugs are bound to be in there
Will test it sometime later. There also might still be stuff, that has yet to be reworked.
Diffstat (limited to 'WebInterface/NodeJSServer/src/js')
-rw-r--r--WebInterface/NodeJSServer/src/js/about.js6
-rw-r--r--WebInterface/NodeJSServer/src/js/index.js12
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/interface.js82
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/networking/commands/_command.js28
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/networking/commands/login/createServer.js23
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/networking/commands/login/listServers.js43
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/networking/commands/login/login.js54
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/networking/commands/loginCmds.js36
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/networking/commands/playCmds.js31
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/networking/hash.js20
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/networking/networker.js89
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/ui/collections/about.js14
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/ui/collections/login.js24
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/ui/collections/play.js19
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/ui/components/backdrop.js65
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/ui/components/modal/login-modal.js139
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/ui/components/modal/modal.js67
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/ui/components/notification-banner.js133
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/ui/components/router.js44
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/ui/components/server-listing.js91
-rw-r--r--WebInterface/NodeJSServer/src/js/modules/ui/uiManager.js60
-rw-r--r--WebInterface/NodeJSServer/src/js/play.js12
-rw-r--r--WebInterface/NodeJSServer/src/js/src_old/modules/playModule.js46
-rw-r--r--WebInterface/NodeJSServer/src/js/src_old/modules/server-client.js93
-rw-r--r--WebInterface/NodeJSServer/src/js/src_old/modules/ui/login-modal.js181
-rw-r--r--WebInterface/NodeJSServer/src/js/src_old/modules/ui/modal.js67
-rw-r--r--WebInterface/NodeJSServer/src/js/src_old/modules/ui/server-listing.js67
27 files changed, 1546 insertions, 0 deletions
diff --git a/WebInterface/NodeJSServer/src/js/about.js b/WebInterface/NodeJSServer/src/js/about.js
new file mode 100644
index 0000000..23351cf
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/about.js
@@ -0,0 +1,6 @@
+import Interface from './modules/interface';
+import UIManager from './modules/ui/uiManager';
+
+let iface = new Interface();
+let uiMan = new UIManager(iface);
+uiMan.initAbout();
diff --git a/WebInterface/NodeJSServer/src/js/index.js b/WebInterface/NodeJSServer/src/js/index.js
new file mode 100644
index 0000000..6d84e7d
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/index.js
@@ -0,0 +1,12 @@
+import Interface from './modules/interface';
+import UIManager from './modules/ui/uiManager';
+import Networker from './modules/networking/networker';
+
+const SERVERURL = 'http://127.0.0.1:5000/chatHub';
+
+let iface = new Interface();
+let uiMan = new UIManager(iface);
+uiMan.initLogin();
+
+let netMan = new Networker(iface, SERVERURL, true); // TODO: Remove debug flag
+netMan.initLogin();
diff --git a/WebInterface/NodeJSServer/src/js/modules/interface.js b/WebInterface/NodeJSServer/src/js/modules/interface.js
new file mode 100644
index 0000000..ac5ea93
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/interface.js
@@ -0,0 +1,82 @@
+/**
+ * Stores an object and it's public methods
+ */
+class InterfaceAccessor {
+ /**
+ * Creates new accessor for object with publicMethods being exposed
+ * @param {object} object
+ * @param {array<String>} publicMethods
+ */
+ constructor(object, publicMethods) {
+ this.object = object;
+ this.publicMethods = publicMethods;
+ }
+
+ /**
+ * Executes method if it is a public method
+ * @param {string} method Name of method to call
+ * @return {number} 0 success, 1 method not public, 2 method not found
+ */
+ execute(method, ...args) {
+ if (!this.publicMethods.includes(method)) return 1;
+ if (typeof this.object[method] != 'function') return 2;
+
+ this.object[method](...args);
+ return 0;
+ }
+}
+
+/**
+ * Implements communication between objects
+ */
+export default class Interface {
+ /**
+ * Initializes interface
+ */
+ constructor() {
+ this.objects = {};
+ }
+
+ /**
+ * Adds a new object to array at objKey and assigns public methods
+ * @param {object} object Object to reference in Interface
+ * @param {String} objKey Key to reference the object under
+ * @param {Array} publicMethods Names of public methods
+ */
+ addObject(object, objKey, publicMethods) {
+ if (!this.objects[objKey]) this.objects[objKey] = [];
+ this.objects[objKey].push(new InterfaceAccessor(object, publicMethods));
+ }
+
+ /**
+ * Unregisters object
+ * @param {Object} object
+ * @param {String} objKey
+ */
+ removeObject(object, objKey) {
+ if (!this.objects[objKey]) return;
+
+ // Remove all instances of object from objKey
+ objects[objKey] = objects[objKey].filter(elt => elt.object != object);
+
+ // Remove reference, if none remain
+ if (objects[objKey].length == 0) objects[objKey] = undefined;
+ }
+
+ /**
+ * Calls a method on all objects with the key objKey
+ * @param {String} objKey Object Key of objects to call method on
+ * @param {String} method Method name to call on the objects
+ * @param {...*} args Arguments to pass
+ * @return {number} 0 Success, 1 no objects with objKey, 2 method not public
+ */
+ callMethod(objKey, method, ...args) {
+ if (!this.objects[objKey]) return 1;
+
+ let returnCode = 0;
+ for (let obj of this.objects[objKey]) {
+ if (obj.execute(method, ...args) != 0) returnCode = 2;
+ }
+ return returnCode;
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/networking/commands/_command.js b/WebInterface/NodeJSServer/src/js/modules/networking/commands/_command.js
new file mode 100644
index 0000000..46a1a14
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/networking/commands/_command.js
@@ -0,0 +1,28 @@
+/**
+ * Parent Command class which all commands inherit from
+ */
+export default class Command {
+ /**
+ * Constructs basic command object
+ * @param {Interface} iface Interface to communicate over
+ */
+ constructor(iface) {
+ this.iface = iface;
+ }
+
+ /**
+ * Registers public command names to interface
+ * @param {String} name Name to register under
+ * @param {...String} commandNames Names of public commands
+ */
+ registerPublic(name, ...commandNames) {
+ this.iface.addObject(this, name, ['destroy'].concat(commandNames));
+ }
+
+ /**
+ * Removes from iface
+ */
+ destroy() {
+ this.iface.removeObject(this);
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/networking/commands/login/createServer.js b/WebInterface/NodeJSServer/src/js/modules/networking/commands/login/createServer.js
new file mode 100644
index 0000000..78b2a1b
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/networking/commands/login/createServer.js
@@ -0,0 +1,23 @@
+import Command from '../_command';
+
+/**
+ * Handles creation of Servers
+ */
+export default class CreateServer extends Command {
+ /**
+ * Registers interface for communication with other objects
+ * @param {Interface} iface
+ */
+ constructor(iface) {
+ super(iface);
+ this.registerPublic('createServer', 'createServer');
+ this.refreshing = false;
+ }
+
+ /**
+ * TODO:
+ */
+ createServer() {
+
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/networking/commands/login/listServers.js b/WebInterface/NodeJSServer/src/js/modules/networking/commands/login/listServers.js
new file mode 100644
index 0000000..2c2bc11
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/networking/commands/login/listServers.js
@@ -0,0 +1,43 @@
+import Command from '../_command';
+
+/**
+ * Handles serverList commands
+ */
+export default class ListServers extends Command {
+ /**
+ * Registers interface for communication with other objects
+ * @param {Interface} iface
+ */
+ constructor(iface) {
+ super(iface);
+ this.registerPublic('listServers', 'listServers');
+ this.refreshing = false;
+ }
+
+ /**
+ * Requests server list from the server
+ */
+ listServers() {
+ if (this.refreshing) return; // If already refreshing, no new request
+
+ let listFn = (groups) => {
+ // Populate server listing
+ this.iface.callMethod('serverListing', 'flushElements');
+ this.iface.callMethod('serverListing', 'addElements', groups, this.iface);
+ // Unbind network function
+ this.iface.callMethod('networker', 'removeHandler', 'ListGroups');
+ this.refreshing = false;
+ };
+ let errorHandler = (err) => {
+ this.refreshing = false;
+ console.error(err.toString());
+ };
+
+ this.iface.callMethod('networker', 'registerHandler',
+ 'ListGroups', listFn);
+ this.iface.callMethod('networker', 'sendRequest',
+ 'GetGroups', errorHandler);
+
+ this.refreshing = true;
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/networking/commands/login/login.js b/WebInterface/NodeJSServer/src/js/modules/networking/commands/login/login.js
new file mode 100644
index 0000000..44a6c94
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/networking/commands/login/login.js
@@ -0,0 +1,54 @@
+import Command from '../_command';
+import LoginModal from '../../../ui/components/modal/login-modal';
+
+/**
+ * Handles login to server
+ */
+export default class Login extends Command {
+ /**
+ * Registers interface for communication with other objects
+ * @param {Interface} iface
+ */
+ constructor(iface) {
+ super(iface);
+ this.registerPublic('login', 'sendLogin', 'showLogin');
+ this.refreshing = false;
+ }
+
+ /**
+ * Shows a login modal
+ * @param {String} name
+ */
+ showLogin(name) {
+ new LoginModal(this.iface, name);
+ }
+
+ /**
+ * Registers login response method
+ */
+ registerLoginResponse() {
+ this.iface.callMethod('networker', 'registerHandler', 'LoginResponse',
+ (result) => {
+ if (result == 0) {
+ this.iface.callMethod('modal', 'close');
+ this.iface.callMethod('router', 'routePlay');
+ this.iface.callMethod('networker', 'removeHandler',
+ 'LoginResponse');
+ } else {
+ this.iface.callMethod('modal', 'loginFailed', result);
+ }
+ });
+ }
+
+ /**
+ * Sends a login request
+ * @param {string} group Group name to join
+ * @param {string} password Password to send as SHA-256 Base64 String
+ * @param {string} username Display name to use
+ */
+ sendLogin(group, password, username) {
+ this.registerLoginResponse();
+ this.iface.callMethod('networker', 'sendRequest', 'Login',
+ (err) => console.error(err), group, username, password);
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/networking/commands/loginCmds.js b/WebInterface/NodeJSServer/src/js/modules/networking/commands/loginCmds.js
new file mode 100644
index 0000000..bc5d8a7
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/networking/commands/loginCmds.js
@@ -0,0 +1,36 @@
+import ListServers from './login/listServers';
+import CreateServer from './login/createServer';
+import Login from './login/login';
+
+/**
+ * Manages commands related to the login page
+ */
+export default class LoginCommands {
+ /**
+ * Initializes the login commands
+ * @param {Interface} iface Interface for inter-object communication
+ */
+ constructor(iface) {
+ this.iface = iface;
+ this.cmds = [];
+ this.registerCommands();
+ }
+
+ /**
+ * Registers all the available commands
+ */
+ registerCommands() {
+ this.cmds.push(new ListServers(this.iface));
+ this.cmds.push(new CreateServer(this.iface));
+ this.cmds.push(new Login(this.iface));
+ }
+
+ /**
+ * Destroys all attached commands
+ */
+ destroy() {
+ for (let cmd of this.cmds) {
+ cmd.destroy();
+ }
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/networking/commands/playCmds.js b/WebInterface/NodeJSServer/src/js/modules/networking/commands/playCmds.js
new file mode 100644
index 0000000..94cd6ba
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/networking/commands/playCmds.js
@@ -0,0 +1,31 @@
+// import ListServers from './login/listServers';
+
+/**
+ * Manages commands related to the login page
+ */
+export default class LoginCommands {
+ /**
+ * Initializes the login commands
+ * @param {Interface} iface Interface for inter-object communication
+ */
+ constructor(iface) {
+ this.iface = iface;
+ this.cmds = [];
+ }
+
+ /**
+ * Registers all the available commands
+ */
+ registerCommands() {
+ // this.cmds.push(new ListServers(iface));
+ }
+
+ /**
+ * Destroys all attached commands
+ */
+ destroy() {
+ for (let cmd of this.cmds) {
+ cmd.destroy();
+ }
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/networking/hash.js b/WebInterface/NodeJSServer/src/js/modules/networking/hash.js
new file mode 100644
index 0000000..3abcc21
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/networking/hash.js
@@ -0,0 +1,20 @@
+/**
+ * Creates Base64 String with SHA-256 Hash of given string
+ */
+String.prototype.getHash = async function() {
+ let data = new ArrayBuffer(this.length * 2);
+ let bufferView = new Uint16Array(data);
+ for (let i = 0; i < this.length; i++) {
+ bufferView[i] = this.charCodeAt(i);
+ }
+
+ let encrypted = await crypto.subtle.digest('SHA-256', bufferView);
+ let byteArray = new Uint8Array(encrypted);
+ let base64String = '';
+
+ for (let byte of byteArray) {
+ base64String += String.fromCharCode(byte);
+ }
+
+ return btoa(base64String);
+};
diff --git a/WebInterface/NodeJSServer/src/js/modules/networking/networker.js b/WebInterface/NodeJSServer/src/js/modules/networking/networker.js
new file mode 100644
index 0000000..7667895
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/networking/networker.js
@@ -0,0 +1,89 @@
+import * as signalR from '@aspnet/signalr';
+import LoginCommands from './commands/loginCmds';
+import PlayCommands from './commands/playCmds';
+
+/**
+ * Class for communication to server
+ */
+export default class Networker {
+ /**
+ * Creates new Networker and connects it to the Interface
+ * @param {Interface} iface Interface for communication between objects
+ * @param {String} url URL of the server backend
+ * @param {Boolean} [debug=false] Should there be debug output
+ */
+ constructor(iface, url, debug = false) {
+ this.url = url;
+
+ // Register in Interface
+ iface.addObject(this, 'networker',
+ ['sendRequest', 'registerHandler', 'removeHandler']);
+ this.iface = iface;
+
+ const connectionBuilder = new signalR.HubConnectionBuilder()
+ .withUrl(url);
+
+ if (debug) {
+ connectionBuilder.configureLogging(signalR.LogLevel.Debug);
+ } else {
+ connectionBuilder.configureLogging(signalR.LogLevel.Error);
+ }
+
+ this.connection = connectionBuilder.build();
+ this.connection.start()
+ .then(() => this.iface.callMethod('listServers', 'listServers'))
+ .catch((err) => console.error(err.toString()));
+
+ // Initialize refreshing (blocks new refreshes if true)
+ this.refreshing = false;
+ }
+
+ /**
+ * Sending a network request to the server
+ * @param {String} methodName Method to call on server
+ * @param {function} errorHandler Function to call on error
+ * @param {...*} args Arguments to pass to server
+ */
+ sendRequest(methodName, errorHandler, ...args) {
+ this.connection.invoke(methodName, ...args).catch(errorHandler);
+ }
+
+ /**
+ * Register a new function to be called upon receival of message from server
+ * @param {String} name Name of invoked method
+ * @param {function} fn function to call with received data
+ */
+ registerHandler(name, fn) {
+ this.connection.on(name, fn);
+ }
+
+ /**
+ * Removes handler for receiving messages from the server
+ * @param {String} name Name of the invoked method
+ */
+ removeHandler(name) {
+ this.connection.off(name);
+ }
+
+ /**
+ * Initializes Login Commands
+ */
+ initLogin() {
+ this.loginCmd = new LoginCommands(this.iface);
+ }
+
+ /**
+ * Initializes play commands
+ */
+ initPlay() {
+ this.playCmd = new PlayCommands(this.iface);
+ }
+
+ /**
+ * Clears all currently registered commands
+ */
+ clearCommands() {
+ if (this.loginCmd) this.loginCmd.destroy();
+ if (this.playCmd) this.playCmd.destroy();
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/ui/collections/about.js b/WebInterface/NodeJSServer/src/js/modules/ui/collections/about.js
new file mode 100644
index 0000000..dac8f01
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/ui/collections/about.js
@@ -0,0 +1,14 @@
+import Backdrop from '../components/backdrop';
+
+/**
+ * UI Loader for about page
+ */
+export default class About {
+ /**
+ * Registers components for about page
+ */
+ constructor() {
+ this.backdrop = new Backdrop('menu', 'front-layer', 'show-menu');
+ this.backdrop.initialize();
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/ui/collections/login.js b/WebInterface/NodeJSServer/src/js/modules/ui/collections/login.js
new file mode 100644
index 0000000..98a6b30
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/ui/collections/login.js
@@ -0,0 +1,24 @@
+import Backdrop from '../components/backdrop';
+import BannerController from '../components/notification-banner';
+import ServerListing from '../components/server-listing';
+
+/**
+ * UI Loader for login page
+ */
+export default class Login {
+ /**
+ * Registers components for login page
+ * @param {Interface} iface Interface to enable comm. with notifications
+ */
+ constructor(iface) {
+ this.backdrop = new Backdrop('menu', 'front-layer', 'show-menu');
+ this.bannerController = new BannerController(iface, 'notifications',
+ 'banner-info', 'dismiss-banner', 'notification-amount');
+ this.serverListing = new ServerListing(iface, 'server-list',
+ 'refresh-button');
+
+ this.backdrop.initialize();
+ this.bannerController.initialize();
+ this.serverListing.initialize();
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/ui/collections/play.js b/WebInterface/NodeJSServer/src/js/modules/ui/collections/play.js
new file mode 100644
index 0000000..cdea777
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/ui/collections/play.js
@@ -0,0 +1,19 @@
+import Backdrop from '../components/backdrop';
+import BannerController from '../components/notification-banner';
+
+/**
+ * UI Loader for play page
+ */
+export default class Play {
+ /**
+ * Registers components for play page
+ */
+ constructor() {
+ this.backdrop = new Backdrop('menu', 'front-layer', 'show-menu');
+ this.bannerController = new BannerController(iface, 'notifications',
+ 'banner-info', 'dismiss-banner', 'notification-amount');
+
+ this.backdrop.initialize();
+ this.bannerController.initialize();
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/ui/components/backdrop.js b/WebInterface/NodeJSServer/src/js/modules/ui/components/backdrop.js
new file mode 100644
index 0000000..82ca64f
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/ui/components/backdrop.js
@@ -0,0 +1,65 @@
+/**
+ * Class for adding functionality to backdrop elements
+ */
+export default class Backdrop {
+ /**
+ * Registers all important elements in the backdrop
+ * @param {string} backdropMenu ID of Backdrop Menu
+ * @param {string} frontLayer ID of Front Layer
+ * @param {string} menuButton ID of Show / Hide Menu Button
+ */
+ constructor(backdropMenu, frontLayer, menuButton) {
+ this.ids = {backdropMenu, frontLayer, menuButton};
+ }
+
+ /**
+ * Initializes the components from the ids defined in the constructor
+ */
+ initialize() {
+ this.open = false;
+ this.backdrop = document.getElementById(this.ids.backdropMenu);
+ this.frontLayer = document.getElementById(this.ids.frontLayer);
+ this.menuButton = document.getElementById(this.ids.menuButton);
+
+ this.registerEvents();
+ }
+
+ /**
+ * Registers all neccessary events
+ */
+ registerEvents() {
+ this.registerButtonEvent();
+ this.registerFrontLayerEvent();
+ }
+
+ /**
+ * Registers showing / hiding through menu button
+ */
+ registerButtonEvent() {
+ this.menuButton.addEventListener('click', () => {
+ // Change open state
+ this.open = !this.open;
+
+ // Hide / Unhide Backdrop Menu
+ this.open ? this.backdrop.classList.remove('hidden') :
+ this.backdrop.classList.add('hidden');
+
+ // Set open state for menu button
+ this.open ? this.menuButton.classList.add('open') :
+ this.menuButton.classList.remove('open');
+ });
+ }
+
+ /**
+ * Registers hiding upon front layer interaction
+ */
+ registerFrontLayerEvent() {
+ this.frontLayer.addEventListener('click', () => {
+ if (!this.open) return; // It's already closed
+
+ this.open = false;
+ this.backdrop.classList.add('hidden');
+ this.menuButton.classList.remove('open');
+ });
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/ui/components/modal/login-modal.js b/WebInterface/NodeJSServer/src/js/modules/ui/components/modal/login-modal.js
new file mode 100644
index 0000000..941fd84
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/ui/components/modal/login-modal.js
@@ -0,0 +1,139 @@
+import Modal from './modal';
+import '../../../networking/hash';
+
+/**
+ * Class to implement a login modal from the parent modal class
+ */
+export default class LoginModal extends Modal {
+ /**
+ * Creates necessary elements for login modal
+ * @param {Interface} iface Interface for Interactions with other Objects
+ * @param {string} serverName Name of the server to connect to
+ */
+ constructor(iface, serverName) {
+ super(serverName);
+ this.serverName = serverName;
+
+ iface.addObject(this, 'modal', ['loginFailed', 'close']);
+ this.iface = iface;
+
+ let passBox = document.createElement('div');
+ let nameBox = document.createElement('div');
+ let sendBox = document.createElement('div');
+
+ let passwordLabel = document.createElement('label');
+ let passwordInput = document.createElement('input');
+ let passwordInvalid = document.createElement('span');
+ passwordLabel.setAttribute('for', 'password-input');
+ passwordLabel.textContent = 'Passwort:';
+ passwordLabel.title = 'Das Passwort des Spiels';
+ passwordInput.id = 'password-input';
+ passwordInput.type = 'password';
+ passwordInput.placeholder = 'Passwort';
+ passwordInput.title = 'Das Passwort des Spiels';
+ passwordInvalid.className = 'invalid hidden';
+ passwordInvalid.textContent = 'Das eingegebene Passwort ist falsch.';
+
+ let nameLabel = document.createElement('label');
+ let nameInput = document.createElement('input');
+ let nameInvalid = document.createElement('span');
+ nameLabel.setAttribute('for', 'name-input');
+ nameLabel.textContent = 'Benutzername:';
+ nameLabel.title = 'Dein Anzeigename';
+ nameInput.id = 'name-input';
+ nameInput.type = 'text';
+ nameInput.autocomplete = 'on';
+ nameInput.placeholder = 'Name';
+ nameInput.title = 'Dein Anzeigename';
+ nameInvalid.className = 'invalid hidden';
+ nameInvalid.textContent =
+ 'Der eingegebene Nutzername ist bereits vergeben.';
+
+ let sendButton = document.createElement('button');
+ sendButton.className = 'btn';
+ sendButton.textContent = 'Login';
+ sendButton.id = 'login-button';
+
+ passBox.appendChild(passwordLabel);
+ passBox.appendChild(passwordInput);
+ passBox.appendChild(passwordInvalid);
+ nameBox.appendChild(nameLabel);
+ nameBox.appendChild(nameInput);
+ nameBox.appendChild(nameInvalid);
+ sendBox.appendChild(sendButton);
+
+ this.body.appendChild(passBox);
+ this.body.appendChild(nameBox);
+ this.body.appendChild(sendBox);
+
+ this.nameInput = nameInput;
+ this.passwordInput = passwordInput;
+ this.loginButton = sendButton;
+
+ this.passwordInvalid = passwordInvalid;
+ this.nameInvalid = nameInvalid;
+
+ this.registerLoginBtnEvent();
+ }
+
+ /**
+ * Method that gets called, if login fails
+ * @param {number} result Error Code
+ */
+ loginFailed(result) {
+ if (result == 1) {
+ this.invalid('Name');
+ this.loginButton.addEventListener('click', this.event);
+ } else if (result == 2) {
+ this.invalid('Pass');
+ this.loginButton.addEventListener('click', this.event);
+ } else {
+ this.iface.callMethod('notifications', 'show', 'failed',
+ 'Ein unbekannter Fehler ist aufgetreten');
+ this.close();
+ }
+ }
+
+ /**
+ * Registers event to send login, on button press
+ */
+ registerLoginBtnEvent() {
+ this.event = () => {
+ this.invalid(); // Remove 'invalid' messages
+ this.loginButton.removeEventListener('click', this.event);
+ this.userName = this.nameInput.value;
+ this.passwordInput.value.getHash()
+ .then((result) => {
+ this.iface.callMethod('login', 'sendLogin', this.serverName,
+ result, this.userName);
+ });
+ };
+ this.loginButton.addEventListener('click', this.event);
+ }
+
+ /**
+ * Displays text under invalid password / username
+ * @param {string} inv Which field to display under (Pass / Name)
+ * Blank inv will hide both
+ */
+ invalid(inv) {
+ this.body.classList.remove('frst-warning');
+ this.body.classList.remove('scnd-warning');
+
+ this.passwordInvalid.classList.add('hidden');
+ this.nameInvalid.classList.add('hidden');
+
+ this.passwordInput.style.borderColor = 'none';
+ this.nameInput.style.borderColor = 'none';
+
+ if (inv == 'Pass') {
+ this.body.classList.add('frst-warning');
+ this.passwordInvalid.classList.remove('hidden');
+ this.passwordInput.style.borderColor = '#ef5350';
+ } else if (inv == 'Name') {
+ this.body.classList.add('scnd-warning');
+ this.nameInvalid.classList.remove('hidden');
+ this.nameInput.style.borderColor = '#ef5350';
+ }
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/ui/components/modal/modal.js b/WebInterface/NodeJSServer/src/js/modules/ui/components/modal/modal.js
new file mode 100644
index 0000000..10a1be5
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/ui/components/modal/modal.js
@@ -0,0 +1,67 @@
+/**
+ * Parent class to create Modals on the screen
+ * Contains no content, as that is implemented by child classes
+ */
+export default class Modal {
+ /**
+ * Creates a new modal with a title and empty content
+ * @param {string} titleString Title to show at the top of the modal
+ */
+ constructor(titleString) {
+ let modalBackground = document.createElement('div');
+ let modal = document.createElement('div');
+ let title = document.createElement('h1');
+ let body = document.createElement('div');
+
+ modalBackground.className = 'modal-container';
+ modal.className = 'modal';
+ title.className = 'modal-title';
+ body.className = 'modal-body';
+
+ title.textContent = titleString;
+
+ modal.appendChild(title);
+ modal.appendChild(body);
+ modalBackground.appendChild(modal);
+ document.body.appendChild(modalBackground);
+
+ this.bg = modalBackground;
+ this.modal = modal;
+ this.title = title;
+ this.body = body;
+
+ this.registerEvents();
+ }
+
+ /**
+ * Register event to close if clicked outside of modal
+ * Clicking on the modal itself should not close it though
+ */
+ registerEvents() {
+ this.modal.addEventListener('click', (e) => {
+ e.stopPropagation();
+ });
+
+ this.bg.addEventListener('click', () => {
+ this.close();
+ });
+ }
+
+ /**
+ * Fades modal out and removes it from the flow of the document
+ */
+ close() {
+ this.bg.classList.add('hidden');
+ this.bg.addEventListener('transitionend', () => {
+ document.body.removeChild(this.bg);
+ });
+ }
+
+ /**
+ * Puts text in the body
+ * @param {string} text Text to put into the body
+ */
+ setBodyText(text) {
+ this.body.textContent = text;
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/ui/components/notification-banner.js b/WebInterface/NodeJSServer/src/js/modules/ui/components/notification-banner.js
new file mode 100644
index 0000000..a527725
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/ui/components/notification-banner.js
@@ -0,0 +1,133 @@
+/**
+ * Object containing a message for the notification banner
+ */
+class BannerItem {
+ /**
+ * Creates new Banner Message Items
+ * @param {String} name Name the message will be referenced under
+ * @param {String} content Content, either formatted as plain text or html
+ * @param {Boolean} html Is content formatted as html?
+ */
+ constructor(name, content, html) {
+ this.name = name;
+ this.content = content;
+ this.html = html;
+ }
+}
+
+/**
+ * Class for controlling the Notification banner
+ */
+export default class BannerController {
+ /**
+ * Creates references to objects and hides notification banner
+ * @param {Interface} iface Interface to receive comm. from
+ * @param {string} bannerId ID of Notification Banner
+ * @param {string} textP ID of Notification Banner text field
+ * @param {string} dismissBtn ID of dismiss button
+ * @param {string} badge ID of badge (# of notifications)
+ */
+ constructor(iface, bannerId, textP, dismissBtn, badge) {
+ iface.addObject(this, 'notifications', ['show', 'hide']);
+ this.iface = iface;
+
+ this.ids = {bannerId, textP, dismissBtn, badge};
+ }
+
+ /**
+ * Initializes the Banner in the DOM
+ */
+ initialize() {
+ this.banner = document.getElementById(this.ids.bannerId);
+ this.bannerText = document.getElementById(this.ids.textP);
+ this.dismissBtn = document.getElementById(this.ids.dismissBtn);
+ this.notificationBadge = document.getElementById(this.ids.badge);
+ this.bannerMsgs = [];
+
+ this.banner.classList.add('hidden'); // Hide banner by default
+ this.registerEvents();
+ }
+
+ /**
+ * Registers events for notification banner
+ */
+ registerEvents() {
+ this.registerDismissEvent();
+ }
+
+ /**
+ * Registers dismissing via dismiss button
+ */
+ registerDismissEvent() {
+ this.dismissBtn.addEventListener('click', () => {
+ this.dismissCurrent();
+ });
+ }
+
+ /**
+ * Pushes a new message to the notification banner and shows it
+ * @param {string} name Name to register notification (referenced in hide)
+ * @param {string} text Notification text
+ */
+ show(name, text) {
+ let bannerItem = new BannerItem(name, text, false);
+ this.bannerMsgs.push(bannerItem);
+
+ this.update();
+ }
+
+ /**
+ * Removes notification from banner
+ * @param {string} name The name the notification was registered under
+ */
+ hide(name) {
+ if (name) this.bannerMsgs = this.bannerMsgs.filter(elt => elt.name != name);
+ else this.bannerMsgs = [];
+
+ this.update();
+ }
+
+ /**
+ * Dismisses the currently shown message
+ */
+ dismissCurrent() {
+ this.hide(this.current);
+ }
+
+ /**
+ * Updates the notification banner with the most recent message
+ */
+ update() {
+ if (this.bannerMsgs.length === 0) {
+ this.banner.classList.add('hidden');
+ return;
+ }
+
+ const lastNotification = this.bannerMsgs[this.bannerMsgs.length - 1];
+ const name = lastNotification.name;
+ const text = lastNotification.content;
+ const isHtml = lastNotification.html;
+ this.banner.classList.remove('hidden');
+
+ if (isHtml) this.bannerText.innerHTML = text;
+ else this.bannerText.innerText = text;
+
+ this.current = name;
+ this.updateNotificationBadge();
+ }
+
+ /**
+ * Updates the notification badge number
+ */
+ updateNotificationBadge() {
+ if (this.bannerMsgs.length < 2) {
+ this.notificationBadge.classList.add('hidden');
+ } else if (this.bannerMsgs.length > 9) {
+ this.notificationBadge.classList.remove('hidden');
+ this.notificationBadge.textContent = '∞';
+ } else {
+ this.notificationBadge.classList.remove('hidden');
+ this.notificationBadge.textContent = this.bannerMsgs.length.toString();
+ }
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/ui/components/router.js b/WebInterface/NodeJSServer/src/js/modules/ui/components/router.js
new file mode 100644
index 0000000..c01c21b
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/ui/components/router.js
@@ -0,0 +1,44 @@
+/**
+ * Class for routing between pages
+ */
+export default class Router {
+ /**
+ * @param {Interface} iface Interface for comm. with other objects
+ */
+ constructor(iface) {
+ iface.addObject(this, 'serverListing', ['routePlay']);
+ this.iface = iface;
+ }
+
+ /**
+ * Routes to the play page
+ * @param {HubConnection} connection Connection to the server
+ */
+ routePlay(connection) {
+ window.history.pushState('object or string', 'Game Page',
+ 'play#game=' + this.serverName);
+ fetch('play').then((response) => {
+ response.text().then((htmlString) => {
+ // Replace all references, since we're starting one level farther up
+ htmlString = htmlString.replace(/\.\.\//g, './');
+ htmlString = /<body>((.)|(\n))*<\/body>/g.exec(htmlString)[0];
+ htmlString = htmlString.replace(/<script src=".*"><\/script>/, '');
+ htmlString = htmlString.replace(
+ /<remove_if_redirected>((.)|\n)*?<\/remove_if_redirected>/g, '');
+ document.body.innerHTML = htmlString;
+
+ let stylesheet = document.createElement('link');
+ stylesheet.rel = 'stylesheet';
+ stylesheet.type = 'text/css';
+ stylesheet.href = './style/play.css';
+ document.head.appendChild(stylesheet);
+
+
+ this.iface.callMethod('uiMananger', 'initPlay');
+ for (let ui of this.pageUI) {
+ ui.refresh();
+ }
+ });
+ });
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/ui/components/server-listing.js b/WebInterface/NodeJSServer/src/js/modules/ui/components/server-listing.js
new file mode 100644
index 0000000..2af56ac
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/ui/components/server-listing.js
@@ -0,0 +1,91 @@
+/**
+ * Class for handling the server list
+ */
+export default class ServerListing {
+ /**
+ * Creates reference to container
+ * @param {Interface} iface Interface for comm. with other objects
+ * @param {string} serverListId ID of the server list div
+ * @param {string} refreshBtnId ID of the refresh btn
+ */
+ constructor(iface, serverListId, refreshBtnId) {
+ this.ids = {serverListId, refreshBtnId};
+
+ iface.addObject(this, 'serverListing', ['flushElements', 'addElements']);
+ this.iface = iface;
+ }
+
+ /**
+ * Initializes Server List DOM Element
+ */
+ initialize() {
+ this.serverListing = document.getElementById(this.ids.serverListId);
+ this.refreshBtn = document.getElementById(this.ids.refreshBtnId);
+ this.registerEvents();
+ }
+
+ /**
+ * Registers events associated with server list UI
+ */
+ registerEvents() {
+ this.registerRefreshEvent();
+ }
+
+ /**
+ * Registers event for pushing the refresh button
+ */
+ registerRefreshEvent() {
+ this.refreshBtn.addEventListener('click', () => {
+ this.iface.callMethod('listServers', 'listServers');
+ });
+ }
+
+ /**
+ * Removes all elements currently in the server listing
+ */
+ flushElements() {
+ this.serverListing.innerHTML = '';
+ }
+
+ /**
+ * Populates servers from a given array of games
+ * @param {array} array Array of available games
+ */
+ addElements(array) {
+ for (let server of array) {
+ const name = server['name'];
+ const playerAmount = server['userCount'];
+
+ let serverDiv = document.createElement('div');
+ let nameSpan = document.createElement('span');
+ let rightAlignDiv = document.createElement('div');
+ let onlineDot = document.createElement('div');
+ let playerCountSpan = document.createElement('span');
+ let playerCountStaticSpan = document.createElement('span');
+ let joinButton = document.createElement('button');
+ serverDiv.className = 'server';
+ nameSpan.className = 'server-name';
+ rightAlignDiv.className = 'right-aligned-items';
+ onlineDot.className = 'player-count-dot';
+ playerCountSpan.className = 'player-count';
+ playerCountStaticSpan.className = 'player-count-static';
+ joinButton.className = 'btn join-btn';
+ joinButton.id = 'join';
+ nameSpan.textContent = name;
+ playerCountSpan.textContent = playerAmount;
+ playerCountStaticSpan.textContent = 'Spieler online';
+ joinButton.textContent = 'Beitreten';
+ joinButton.addEventListener('click', () => {
+ this.iface.callMethod('login', 'showLogin', name);
+ });
+
+ rightAlignDiv.appendChild(onlineDot);
+ rightAlignDiv.appendChild(playerCountSpan);
+ rightAlignDiv.appendChild(playerCountStaticSpan);
+ rightAlignDiv.appendChild(joinButton);
+ serverDiv.appendChild(nameSpan);
+ serverDiv.appendChild(rightAlignDiv);
+ this.serverListing.appendChild(serverDiv);
+ }
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/modules/ui/uiManager.js b/WebInterface/NodeJSServer/src/js/modules/ui/uiManager.js
new file mode 100644
index 0000000..ddbb152
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/modules/ui/uiManager.js
@@ -0,0 +1,60 @@
+import About from './collections/about';
+import Login from './collections/login';
+import Play from './collections/play';
+
+/**
+ * Controller class for Page UI
+ */
+export default class UIManager {
+ /**
+ * Initializes new UI Manager
+ * @param {Interface} iface Interface for inter-object communication
+ */
+ constructor(iface) {
+ this.currentUI = null;
+
+ iface.addObject(this, 'uiMananger', ['initAbout', 'initLogin', 'initPlay']);
+ this.iface = iface;
+ }
+
+ /**
+ * Initializes UI Components of About Page
+ */
+ initAbout() {
+ this.clearComponents();
+ this.about = new About(this.iface);
+ this.currentUI = 'about';
+ }
+
+ /**
+ * Initializes UI Components of Login page
+ */
+ initLogin() {
+ this.clearComponents();
+ this.login = new Login(this.iface);
+ this.currentUI = 'login';
+ }
+
+ /**
+ * Initializes UI Components of Play page
+ */
+ initPlay() {
+ this.clearComponents();
+ this.play = new Play(this.iface);
+ this.currentUI = 'play';
+ }
+
+ /**
+ * Clears currently loaded components
+ */
+ clearComponents() {
+ switch (this.currentUI) {
+ case null: return;
+ case 'about': this.about = null; break;
+ case 'login': this.login = null; break;
+ case 'play': this.play = null; break;
+ }
+
+ this.currentUI = null;
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/play.js b/WebInterface/NodeJSServer/src/js/play.js
new file mode 100644
index 0000000..f7cb255
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/play.js
@@ -0,0 +1,12 @@
+import Interface from './modules/interface';
+import UIManager from './modules/ui/uiManager';
+import Networker from './modules/networking/networker';
+
+const SERVERURL = 'http://127.0.0.1:5000/chatHub';
+
+let iface = new Interface();
+let uiMan = new UIManager(iface);
+uiMan.initPlay();
+// Create Network Manager as well
+
+// TODO: Implement login from the play page
diff --git a/WebInterface/NodeJSServer/src/js/src_old/modules/playModule.js b/WebInterface/NodeJSServer/src/js/src_old/modules/playModule.js
new file mode 100644
index 0000000..931f598
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/src_old/modules/playModule.js
@@ -0,0 +1,46 @@
+// TODO: Handle disconnect
+
+/**
+ * Handles ingame networking;
+ */
+export default class GameClient {
+ /**
+ * Defines basic attributes
+ * @param {string} user The username of the player
+ * @param {HubConnection} connection Already established connection to the
+ * server
+ */
+ constructor(user, connection) {
+ this.user = user;
+ this.connection = connection;
+ }
+
+ /**
+ * Registers chat html component
+ * @param {string} chatId Id of chat component
+ */
+ registerChat(chatId) {
+ this.chat = document.getElementById(chatId);
+ this.messageList = this.chat.querySelector('#message-list');
+ this.messageInput = this.chat.querySelector('#input-message');
+ this.messageSend = this.chat.querySelector('#send-message');
+
+ this.connection.on('ReceiveMessage', (user, message) => {
+ let msg = message.replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+ let encodedMsg = user + ' sagt: ' + msg;
+
+ let messageP = document.createElement('p');
+ messageP.class = 'message';
+ messageP.textContent = encodedMsg;
+
+ this.messageList.appendChild(messageP);
+ });
+
+ this.messageSend.addEventListener('click', () => {
+ let message = this.messageInput.value;
+ this.connection.invoke('SendMessage', this.user, message);
+ });
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/src_old/modules/server-client.js b/WebInterface/NodeJSServer/src/js/src_old/modules/server-client.js
new file mode 100644
index 0000000..2f712b5
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/src_old/modules/server-client.js
@@ -0,0 +1,93 @@
+import * as signalR from '@aspnet/signalr';
+import ServerListing from './ui/server-listing.js';
+
+/**
+ * Class for communication to server
+ */
+export default class ServerClient {
+ /**
+ * Creates new connection
+ * @param {string} url URL of server running signalR
+ * @param {string} serverListingId HTML ID of server-listing element,
+ * to populate with available games
+ * @param {BannerController} notifications Notification Manager
+ * @param {array} ui UI Elements to reload on login
+ * @param {boolean} [debug=false] Enable debug output?
+ */
+ constructor(url, serverListingId, notifications, ui, debug = false) {
+ this.ui = ui;
+ const connectionBuilder = new signalR.HubConnectionBuilder()
+ .withUrl(url);
+
+ if (debug) {
+ connectionBuilder.configureLogging(signalR.LogLevel.Debug);
+ } else {
+ connectionBuilder.configureLogging(signalR.LogLevel.Error);
+ }
+
+ this.connection = connectionBuilder.build();
+ this.connection.start()
+ .then(() => this.loadServers()) // Load games list, once connected
+ .catch((err) => console.error(err.toString()));
+
+ // Initialize refreshing (blocks new refreshes if true)
+ this.refreshing = false;
+
+ this.serverListing = new ServerListing(serverListingId, notifications);
+ }
+
+ /**
+ * Requests list of avalable games on the server
+ */
+ loadServers() {
+ if (this.refreshing) return; // If already refreshing, no new request
+
+ this.connection.on('ListGroups', (groups) => {
+ // Populate server listing
+ this.serverListing.flushElements();
+ this.serverListing.addElements(groups, this, this.ui);
+ this.connection.off('ListGroups');
+
+ this.refreshing = false;
+ });
+
+ this.connection.invoke('GetGroups')
+ .catch((err) => {
+ this.refreshing = false;
+ console.error(err.toString());
+ });
+ this.refreshing = true;
+ }
+
+ /**
+ * Sends a game creating request to the server
+ * @param {string} name Name of the new game
+ * @param {string} password Password
+ */
+ createServer(name, password) {
+ // TODO: Create
+ }
+
+ /**
+ * Sends a login request
+ * @param {string} group Group name to join
+ * @param {string} password Password to send as SHA-256 Base64 String
+ * @param {string} username Display name to use
+ * @param {ServerClient~loginCallback} callback Callback function to use
+ */
+ sendLogin(group, password, username, callback) {
+ this.connection.on('LoginResponse', (result) => {
+ callback(result, this.connection);
+ this.connection.off('LoginResponse');
+ });
+ this.connection.invoke('Login', group, username, password);
+ }
+}
+
+/**
+ * Callback to call with response to login request
+ * @callback ServerClient~loginCallback
+ * @param {number} result 0: Success, 1: PasswordError, 2:UsernameTaken,
+ * 3:Unknown Error
+ * @param {ConnectionHub} connection Connection to the server
+ */
diff --git a/WebInterface/NodeJSServer/src/js/src_old/modules/ui/login-modal.js b/WebInterface/NodeJSServer/src/js/src_old/modules/ui/login-modal.js
new file mode 100644
index 0000000..13de78e
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/src_old/modules/ui/login-modal.js
@@ -0,0 +1,181 @@
+import Modal from './modal.js';
+import '../hash.js';
+
+/**
+ * Class to implement a login modal from the parent modal class
+ */
+export default class LoginModal extends Modal {
+ /**
+ * Creates necessary elements for login modal
+ * @param {string} serverName Name of the server, used for login and displayed
+ * in title
+ * @param {ServerClient} serverClient Server client object used to send the
+ * login
+ * @param {BannerController} notificationManager Object controlling the main
+ * notification banners
+ * @param {array} ui UI elements to call refresh method on after login
+ */
+ constructor(serverName, serverClient, notificationManager, ui) {
+ super(serverName);
+ this.serverName = serverName;
+ this.serverClient = serverClient;
+ this.notificationManager = notificationManager;
+ this.pageUI = ui;
+
+ let passBox = document.createElement('div');
+ let nameBox = document.createElement('div');
+ let sendBox = document.createElement('div');
+
+ let passwordLabel = document.createElement('label');
+ let passwordInput = document.createElement('input');
+ let passwordInvalid = document.createElement('span');
+ passwordLabel.setAttribute('for', 'password-input');
+ passwordLabel.textContent = 'Passwort:';
+ passwordLabel.title = 'Das Passwort des Spiels';
+ passwordInput.id = 'password-input';
+ passwordInput.type = 'password';
+ passwordInput.placeholder = 'Passwort';
+ passwordInput.title = 'Das Passwort des Spiels';
+ passwordInvalid.className = 'invalid hidden';
+ passwordInvalid.textContent = 'Das eingegebene Passwort ist falsch.';
+
+ let nameLabel = document.createElement('label');
+ let nameInput = document.createElement('input');
+ let nameInvalid = document.createElement('span');
+ nameLabel.setAttribute('for', 'name-input');
+ nameLabel.textContent = 'Benutzername:';
+ nameLabel.title = 'Dein Anzeigename';
+ nameInput.id = 'name-input';
+ nameInput.type = 'text';
+ nameInput.autocomplete = 'on';
+ nameInput.placeholder = 'Name';
+ nameInput.title = 'Dein Anzeigename';
+ nameInvalid.className = 'invalid hidden';
+ nameInvalid.textContent =
+ 'Der eingegebene Nutzername ist bereits vergeben.';
+
+ let sendButton = document.createElement('button');
+ sendButton.className = 'btn';
+ sendButton.textContent = 'Login';
+ sendButton.id = 'login-button';
+
+ passBox.appendChild(passwordLabel);
+ passBox.appendChild(passwordInput);
+ passBox.appendChild(passwordInvalid);
+ nameBox.appendChild(nameLabel);
+ nameBox.appendChild(nameInput);
+ nameBox.appendChild(nameInvalid);
+ sendBox.appendChild(sendButton);
+
+ this.body.appendChild(passBox);
+ this.body.appendChild(nameBox);
+ this.body.appendChild(sendBox);
+
+ this.nameInput = nameInput;
+ this.passwordInput = passwordInput;
+ this.loginButton = sendButton;
+
+ this.passwordInvalid = passwordInvalid;
+ this.nameInvalid = nameInvalid;
+
+ this.registerLoginBtn();
+ }
+
+ /**
+ * Registers event to send login, on button press
+ */
+ registerLoginBtn() {
+ let eventListener;
+ let loginCallBack = (result, connection) => {
+ console.log(result);
+ if (result == 0) {
+ this.redirectToPlay(connection);
+ this.close();
+ } else if (result == 1) {
+ this.invalid('Name');
+ this.loginButton.addEventListener('click', eventListener);
+ } else if (result == 2) {
+ this.invalid('Pass');
+ this.loginButton.addEventListener('click', eventListener);
+ } else {
+ this.notificationManager.show('unknownLoginErr',
+ 'Ein unbekannter Fehler ist aufgetreten');
+ this.close();
+ }
+ };
+
+ eventListener = () => {
+ this.invalid(); // Remove 'invalid' messages
+ this.loginButton.removeEventListener('click', eventListener);
+ this.userName = this.nameInput.value;
+ this.passwordInput.value.getHash()
+ .then((result) => {
+ this.serverClient.sendLogin(this.serverName, result,
+ this.userName, loginCallBack);
+ });
+ };
+ this.loginButton.addEventListener('click', eventListener);
+ }
+
+ /**
+ * Displays text under invalid password / username
+ * @param {string} inv Which field to display under (Pass / Name)
+ * Blank inv will hide both
+ */
+ invalid(inv) {
+ this.body.classList.remove('frst-warning');
+ this.body.classList.remove('scnd-warning');
+
+ this.passwordInvalid.classList.add('hidden');
+ this.nameInvalid.classList.add('hidden');
+
+ this.passwordInput.style.borderColor = 'none';
+ this.nameInput.style.borderColor = 'none';
+
+ if (inv == 'Pass') {
+ this.body.classList.add('frst-warning');
+ this.passwordInvalid.classList.remove('hidden');
+ this.passwordInput.style.borderColor = '#ef5350';
+ } else if (inv == 'Name') {
+ this.body.classList.add('scnd-warning');
+ this.nameInvalid.classList.remove('hidden');
+ this.nameInput.style.borderColor = '#ef5350';
+ }
+ }
+
+ /**
+ * Loads play site
+ * @param {HubConnection} connection Connection to the server
+ */
+ redirectToPlay(connection) {
+ window.history.pushState('object or string', 'Game Page',
+ 'play#game=' + this.serverName);
+ fetch('play').then((response) => {
+ response.text().then((htmlString) => {
+ // Replace all references, since we're starting one level farther up
+ htmlString = htmlString.replace(/\.\.\//g, './');
+ htmlString = /<body>((.)|(\n))*<\/body>/g.exec(htmlString)[0];
+ htmlString = htmlString.replace(/<script src=".*"><\/script>/, '');
+ htmlString = htmlString.replace(
+ /<remove_if_redirected>((.)|\n)*?<\/remove_if_redirected>/g, '');
+ document.body.innerHTML = htmlString;
+
+ let stylesheet = document.createElement('link');
+ stylesheet.rel = 'stylesheet';
+ stylesheet.type = 'text/css';
+ stylesheet.href = './style/play.css';
+ document.head.appendChild(stylesheet);
+
+ for (let ui of this.pageUI) {
+ ui.refresh();
+ }
+
+ import(/* webpackChunkName: "/playModule" */ '../playModule')
+ .then(({default: GameClient}) => {
+ let gameClient = new GameClient(this.userName, connection);
+ gameClient.registerChat('chat');
+ });
+ });
+ });
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/src_old/modules/ui/modal.js b/WebInterface/NodeJSServer/src/js/src_old/modules/ui/modal.js
new file mode 100644
index 0000000..10a1be5
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/src_old/modules/ui/modal.js
@@ -0,0 +1,67 @@
+/**
+ * Parent class to create Modals on the screen
+ * Contains no content, as that is implemented by child classes
+ */
+export default class Modal {
+ /**
+ * Creates a new modal with a title and empty content
+ * @param {string} titleString Title to show at the top of the modal
+ */
+ constructor(titleString) {
+ let modalBackground = document.createElement('div');
+ let modal = document.createElement('div');
+ let title = document.createElement('h1');
+ let body = document.createElement('div');
+
+ modalBackground.className = 'modal-container';
+ modal.className = 'modal';
+ title.className = 'modal-title';
+ body.className = 'modal-body';
+
+ title.textContent = titleString;
+
+ modal.appendChild(title);
+ modal.appendChild(body);
+ modalBackground.appendChild(modal);
+ document.body.appendChild(modalBackground);
+
+ this.bg = modalBackground;
+ this.modal = modal;
+ this.title = title;
+ this.body = body;
+
+ this.registerEvents();
+ }
+
+ /**
+ * Register event to close if clicked outside of modal
+ * Clicking on the modal itself should not close it though
+ */
+ registerEvents() {
+ this.modal.addEventListener('click', (e) => {
+ e.stopPropagation();
+ });
+
+ this.bg.addEventListener('click', () => {
+ this.close();
+ });
+ }
+
+ /**
+ * Fades modal out and removes it from the flow of the document
+ */
+ close() {
+ this.bg.classList.add('hidden');
+ this.bg.addEventListener('transitionend', () => {
+ document.body.removeChild(this.bg);
+ });
+ }
+
+ /**
+ * Puts text in the body
+ * @param {string} text Text to put into the body
+ */
+ setBodyText(text) {
+ this.body.textContent = text;
+ }
+}
diff --git a/WebInterface/NodeJSServer/src/js/src_old/modules/ui/server-listing.js b/WebInterface/NodeJSServer/src/js/src_old/modules/ui/server-listing.js
new file mode 100644
index 0000000..78ca323
--- /dev/null
+++ b/WebInterface/NodeJSServer/src/js/src_old/modules/ui/server-listing.js
@@ -0,0 +1,67 @@
+import LoginModal from './login-modal.js';
+
+/**
+ * Class for handling the server list
+ */
+export default class ServerListing {
+ /**
+ * Creates reference to container
+ * @param {string} serverListId ID of the server list div
+ * @param {BannerController} notifications Notification Manager
+ */
+ constructor(serverListId, notifications) {
+ this.serverListing = document.getElementById(serverListId);
+ this.notifications = notifications;
+ }
+
+ /**
+ * Removes all elements currently in the server listing
+ */
+ flushElements() {
+ this.serverListing.innerHTML = '';
+ }
+
+ /**
+ * Populates servers from a given array of games
+ * @param {array} array Array of available games
+ * @param {ServerClient} serverClient Server Client to handle login
+ * @param {array} ui UI Elements to reload after login
+ */
+ addElements(array, serverClient, ui) {
+ for (let server of array) {
+ const name = server['name'];
+ const playerAmount = server['userCount'];
+
+ let serverDiv = document.createElement('div');
+ let nameSpan = document.createElement('span');
+ let rightAlignDiv = document.createElement('div');
+ let onlineDot = document.createElement('div');
+ let playerCountSpan = document.createElement('span');
+ let playerCountStaticSpan = document.createElement('span');
+ let joinButton = document.createElement('button');
+ serverDiv.className = 'server';
+ nameSpan.className = 'server-name';
+ rightAlignDiv.className = 'right-aligned-items';
+ onlineDot.className = 'player-count-dot';
+ playerCountSpan.className = 'player-count';
+ playerCountStaticSpan.className = 'player-count-static';
+ joinButton.className = 'btn join-btn';
+ joinButton.id = 'join';
+ nameSpan.textContent = name;
+ playerCountSpan.textContent = playerAmount;
+ playerCountStaticSpan.textContent = 'Spieler online';
+ joinButton.textContent = 'Beitreten';
+ joinButton.addEventListener('click', () => {
+ new LoginModal(name, serverClient, this.notifications, ui);
+ });
+
+ rightAlignDiv.appendChild(onlineDot);
+ rightAlignDiv.appendChild(playerCountSpan);
+ rightAlignDiv.appendChild(playerCountStaticSpan);
+ rightAlignDiv.appendChild(joinButton);
+ serverDiv.appendChild(nameSpan);
+ serverDiv.appendChild(rightAlignDiv);
+ this.serverListing.appendChild(serverDiv);
+ }
+ }
+}