diff options
author | Dennis Kobert <d-kobert@web.de> | 2019-06-12 21:51:39 +0200 |
---|---|---|
committer | Dennis Kobert <d-kobert@web.de> | 2019-06-12 21:51:39 +0200 |
commit | 9e9c1c822a64c0a65033b7eed07ea661a385cecc (patch) | |
tree | ee699d4e93bb4204f5f4e04cd14f6d77365b81b4 /WebInterface/src/js/modules/ui | |
parent | 304437b834e8c87687f68333ae67a13ee1377a73 (diff) | |
parent | 3a3d0fc3d4733f8908e23a03f860d76340479ec4 (diff) |
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'WebInterface/src/js/modules/ui')
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; + } +} |