summaryrefslogtreecommitdiff
path: root/WebInterface/src/js/modules/ui
diff options
context:
space:
mode:
authorDennis Kobert <d-kobert@web.de>2019-06-12 21:51:39 +0200
committerDennis Kobert <d-kobert@web.de>2019-06-12 21:51:39 +0200
commit9e9c1c822a64c0a65033b7eed07ea661a385cecc (patch)
treeee699d4e93bb4204f5f4e04cd14f6d77365b81b4 /WebInterface/src/js/modules/ui
parent304437b834e8c87687f68333ae67a13ee1377a73 (diff)
parent3a3d0fc3d4733f8908e23a03f860d76340479ec4 (diff)
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'WebInterface/src/js/modules/ui')
-rw-r--r--WebInterface/src/js/modules/ui/collections/about.js14
-rw-r--r--WebInterface/src/js/modules/ui/collections/login.js24
-rw-r--r--WebInterface/src/js/modules/ui/collections/play.js19
-rw-r--r--WebInterface/src/js/modules/ui/components/backdrop.js65
-rw-r--r--WebInterface/src/js/modules/ui/components/modal/login-modal.js139
-rw-r--r--WebInterface/src/js/modules/ui/components/modal/modal.js66
-rw-r--r--WebInterface/src/js/modules/ui/components/notification-banner.js133
-rw-r--r--WebInterface/src/js/modules/ui/components/router.js44
-rw-r--r--WebInterface/src/js/modules/ui/components/server-listing.js91
-rw-r--r--WebInterface/src/js/modules/ui/uiManager.js60
10 files changed, 655 insertions, 0 deletions
diff --git a/WebInterface/src/js/modules/ui/collections/about.js b/WebInterface/src/js/modules/ui/collections/about.js
new file mode 100644
index 0000000..dac8f01
--- /dev/null
+++ b/WebInterface/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/src/js/modules/ui/collections/login.js b/WebInterface/src/js/modules/ui/collections/login.js
new file mode 100644
index 0000000..98a6b30
--- /dev/null
+++ b/WebInterface/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/src/js/modules/ui/collections/play.js b/WebInterface/src/js/modules/ui/collections/play.js
new file mode 100644
index 0000000..cdea777
--- /dev/null
+++ b/WebInterface/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/src/js/modules/ui/components/backdrop.js b/WebInterface/src/js/modules/ui/components/backdrop.js
new file mode 100644
index 0000000..82ca64f
--- /dev/null
+++ b/WebInterface/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/src/js/modules/ui/components/modal/login-modal.js b/WebInterface/src/js/modules/ui/components/modal/login-modal.js
new file mode 100644
index 0000000..941fd84
--- /dev/null
+++ b/WebInterface/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/src/js/modules/ui/components/modal/modal.js b/WebInterface/src/js/modules/ui/components/modal/modal.js
new file mode 100644
index 0000000..c4c5119
--- /dev/null
+++ b/WebInterface/src/js/modules/ui/components/modal/modal.js
@@ -0,0 +1,66 @@
+/**
+ * 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/src/js/modules/ui/components/notification-banner.js b/WebInterface/src/js/modules/ui/components/notification-banner.js
new file mode 100644
index 0000000..a527725
--- /dev/null
+++ b/WebInterface/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/src/js/modules/ui/components/router.js b/WebInterface/src/js/modules/ui/components/router.js
new file mode 100644
index 0000000..c01c21b
--- /dev/null
+++ b/WebInterface/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/src/js/modules/ui/components/server-listing.js b/WebInterface/src/js/modules/ui/components/server-listing.js
new file mode 100644
index 0000000..2af56ac
--- /dev/null
+++ b/WebInterface/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/src/js/modules/ui/uiManager.js b/WebInterface/src/js/modules/ui/uiManager.js
new file mode 100644
index 0000000..ddbb152
--- /dev/null
+++ b/WebInterface/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;
+ }
+}