From f3bcf63860a10fcbcc9f5c128cd2f859a842a3b8 Mon Sep 17 00:00:00 2001 From: grey Date: Thu, 12 May 2022 21:01:49 +0200 Subject: [PATCH] Add language settings in UI --- .gitignore | 2 + helpers.js | 25 +++++++++- index.js | 82 +++++++++++++++++++++++++++++--- lang/de_de.json | 2 +- lang/en_uk.json | 7 ++- log-journal.json | 2 +- package-lock.json | 11 +++++ package.json | 1 + static/css/styles.css | 2 +- static/js/interface.js | 13 +++++ templates/brokenTranslation.html | 56 ++++++++++++++++------ templates/newAdminPanel.html | 20 ++++++-- 12 files changed, 191 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 09ff856..a7639ed 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules rename.sh data-persistence.json log-journal.json +config.json +log-journal.json diff --git a/helpers.js b/helpers.js index 7ffcad1..00c81aa 100644 --- a/helpers.js +++ b/helpers.js @@ -29,4 +29,27 @@ function wrapBooleanConverter(stringBoolean, res) { } } -module.exports = { convertStringBooleanToBoolean, wrapBooleanConverter }; \ No newline at end of file +/** + * Tries to parse a string to a JSON object. Returns false if invalid. Taken from https://stackoverflow.com/a/20392392/11317151 + * @param {String} jsonString A JSON String to parse + * @returns {Object/Boolean} JSON Object if valid or false otherwise + */ +function tryToParseJson(jsonString) { + try { + var o = JSON.parse(jsonString); + + // Handle non-exception-throwing cases: + // Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking, + // but... JSON.parse(null) returns null, and typeof null === "object", + // so we must check for that, too. Thankfully, null is falsey, so this suffices: + if (o && typeof o === "object") { + return o; + } + } + catch (e) { } + + return false; +} + + +module.exports = { convertStringBooleanToBoolean, wrapBooleanConverter, tryToParseJson }; \ No newline at end of file diff --git a/index.js b/index.js index 405fc7c..96e65d5 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ const ws = require('ws'); const helper = require("./helpers.js"); const loggy = require("./logging") const Eta = require("eta"); +const _ = require("underscore") loggy.init(true) @@ -51,16 +52,34 @@ currentState = { srvTime: 0, enableColoredText: true, debug: false, - sessionToken: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15), + sessionToken: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) }; -const dataToBeWritten = {}; +let configObject = { + language: "en_uk" +} + +const tempJsonText = JSON.parse(fs.readFileSync("config.json", "utf8")); +configObject = _.extend(configObject, tempJsonText); +fs.writeFileSync("config.json", JSON.stringify(configObject)); currentState = Object.assign({}, currentState, loadedData); currentState.textColors = currentState.colorSegments +loggy.log("Searching for languages", "info", "Language") +const languagesRaw = fs.readdirSync("./lang"); +const languages = []; +for (let i = 0; i < languagesRaw.length; i++) { + if (languagesRaw[i].endsWith(".json")) { + languages.push(languagesRaw[i].replace(".json", "")); + } +} +loggy.log("Found " + languages.length + " languages", "info", "Language") + + + loggy.log("Reading language file", "info", "Language") -const languageProfile = JSON.parse(fs.readFileSync("lang/en_uk.json", "utf8")); +let languageProfile = JSON.parse(fs.readFileSync("lang/" + configObject.language + ".json", "utf8")); loggy.log("Preparing websocket", "info", "Websocket"); const wsServer = new ws.Server({ noServer: true }); @@ -95,11 +114,20 @@ app.get("/", function (req, res) { try { res.send( Eta.render(data, { - lang: languageProfile + lang: languageProfile, + additional: { + languages: languages + } })); } catch (e) { - const data = fs.readFileSync("templates/brokenTranslation.html", "utf8"); - res.send(data); + loggy.log("Error rendering template", "error", "Server"); + const dataN = fs.readFileSync("templates/brokenTranslation.html", "utf8"); + res.send( + Eta.render(dataN, { + additional: { + languages: languages + } + })); } }); @@ -311,6 +339,48 @@ app.get("/api/v1/storage/delete", function (req, res) { updatedData() }); +// UI Routes +// Returns an object containg all available languages +app.get("/api/ui/v1/lang/list", function handleLangList(req, res){ + const tempRespObject = { + status: "ok", + languages: languages + } + res.json(tempRespObject); +}) + +app.get("/api/ui/v1/lang/set", function (req, res) { + if(req.query.lang == undefined || req.query.lang == ""){ + res.json({ status: "error", reason: "Missing language" }); + return; + } + const testLang = req.query.lang; + loggy.log("Reloading language file", "info", "Language") + if(!fs.existsSync("lang/" + testLang + ".json")){ + loggy.log("Language reload failed, file does not exist", "error", "Language") + res.status(500).json({ status: "error", reason: "Language file not found" }); + return + } + const tempLang = fs.readFileSync("lang/" + testLang + ".json", "utf8"); + const tempLangObj = helper.tryToParseJson(tempLang); + if(!tempLangObj){ + loggy.log("Language reload failed, file is not valid", "error", "Language") + res.status(500).json({ status: "error", reason: "Language file is not valid" }); + return + } + if(tempLangObj._metadata == undefined){ + loggy.log("Language reload failed, file is not valid, metadata missing", "error", "Language") + res.status(500).json({ status: "error", reason: "Language file is not valid" }); + return + } + loggy.log("Language reloaded, loaded " + tempLangObj._metadata.lang + "@" + tempLangObj._metadata.version, "info", "Language") + configObject.language = req.query.lang; + languageProfile = tempLangObj; + res.status(200).json({ status: "ok" }); + fs.writeFileSync("config.json", JSON.stringify(configObject)); +}); + + app.use(function (req, res, next) { res.status(404); loggy.log("Server responded with 404 error", "warn", "Server", true); diff --git a/lang/de_de.json b/lang/de_de.json index 137af60..9c14a61 100644 --- a/lang/de_de.json +++ b/lang/de_de.json @@ -21,7 +21,7 @@ "messagingHint": "Zeigt eine Nachricht auf der Timeransicht an.", "copyHint": "Kopiert den Link zu der Countdown-Seite.", "openInNewTab": "Öffnet den Link in einem neuen Tab.", - "selectMode": "Wählt einen Modus für die Timeransicht.", + "selectMode": "Wählt einen Modus für die Timeransicht." }, "placeholders": { "msgHere": "Message here" diff --git a/lang/en_uk.json b/lang/en_uk.json index 31e4d53..0c0f6a4 100644 --- a/lang/en_uk.json +++ b/lang/en_uk.json @@ -6,7 +6,8 @@ "messaging": "Messaging", "countdownToTime": "Countdown to time", "attention": "Attention", - "hostinfo": "Host information" + "hostinfo": "Host information", + "uiSettings": "UI settings" }, "sidebar": { "home": "Home", @@ -45,7 +46,9 @@ "enableTextClrs": "Enable Text Colours:", "textClrs": "Text Colours", "addRow": "Add Row", - "timeVar": "Enable time variance display:" + "timeVar": "Enable time variance display:", + "language": "Select a language", + "apply": "Apply" }, "untis": { diff --git a/log-journal.json b/log-journal.json index 7ce5ed6..8dfc945 100644 --- a/log-journal.json +++ b/log-journal.json @@ -1 +1 @@ -[{"timestamp":"2022-05-10 19:38:52.853","level":"info","module":"Logging","message":"2022-05-10 19:38:52.853 [info] [Logging] Logging initialized"},{"timestamp":"2022-05-10 19:38:52.854","level":"info","module":"Server","message":"2022-05-10 19:38:52.854 [info] [Server] Preparing server"},{"timestamp":"2022-05-10 19:38:52.856","level":"info","module":"Server","message":"2022-05-10 19:38:52.856 [info] [Server] Preparing static routes"},{"timestamp":"2022-05-10 19:38:52.857","level":"info","module":"Server","message":"2022-05-10 19:38:52.857 [info] [Server] Preparing middlewares"},{"timestamp":"2022-05-10 19:38:52.857","level":"info","module":"Config","message":"2022-05-10 19:38:52.857 [info] [Config] Loading config"},{"timestamp":"2022-05-10 19:38:52.857","level":"info","module":"Language","message":"2022-05-10 19:38:52.857 [info] [Language] Reading language file"},{"timestamp":"2022-05-10 19:38:52.858","level":"info","module":"Websocket","message":"2022-05-10 19:38:52.858 [info] [Websocket] Preparing websocket"},{"timestamp":"2022-05-10 19:38:52.858","level":"info","module":"Server","message":"2022-05-10 19:38:52.858 [info] [Server] Preparing routes"},{"timestamp":"2022-05-10 19:38:52.859","level":"info","module":"Server","message":"2022-05-10 19:38:52.859 [info] [Server] Starting server"},{"timestamp":"2022-05-10 19:48:56.465","level":"info","module":"Shutdown","message":"2022-05-10 19:48:56.465 [info] [Shutdown] Caught interrupt signal and shutting down gracefully"}] \ No newline at end of file +[{"timestamp":"2022-05-12 18:53:40.121","level":"info","module":"Logging","message":"2022-05-12 18:53:40.121 [info] [Logging] Logging initialized"},{"timestamp":"2022-05-12 18:53:40.122","level":"info","module":"Server","message":"2022-05-12 18:53:40.122 [info] [Server] Preparing server"},{"timestamp":"2022-05-12 18:53:40.123","level":"info","module":"Server","message":"2022-05-12 18:53:40.123 [info] [Server] Preparing static routes"},{"timestamp":"2022-05-12 18:53:40.125","level":"info","module":"Server","message":"2022-05-12 18:53:40.125 [info] [Server] Preparing middlewares"},{"timestamp":"2022-05-12 18:53:40.125","level":"info","module":"Config","message":"2022-05-12 18:53:40.125 [info] [Config] Loading config"},{"timestamp":"2022-05-12 18:53:40.127","level":"info","module":"Language","message":"2022-05-12 18:53:40.127 [info] [Language] Searching for languages"},{"timestamp":"2022-05-12 18:53:40.127","level":"info","module":"Language","message":"2022-05-12 18:53:40.127 [info] [Language] Found 3 languages"},{"timestamp":"2022-05-12 18:53:40.127","level":"info","module":"Language","message":"2022-05-12 18:53:40.127 [info] [Language] Reading language file"},{"timestamp":"2022-05-12 18:53:40.128","level":"info","module":"Websocket","message":"2022-05-12 18:53:40.128 [info] [Websocket] Preparing websocket"},{"timestamp":"2022-05-12 18:53:40.128","level":"info","module":"Server","message":"2022-05-12 18:53:40.128 [info] [Server] Preparing routes"},{"timestamp":"2022-05-12 18:53:40.129","level":"info","module":"Server","message":"2022-05-12 18:53:40.129 [info] [Server] Starting server"},{"timestamp":"2022-05-12 18:53:41.756","level":"error","module":"Server","message":"2022-05-12 18:53:41.756 [error] [Server] Error rendering template"},{"timestamp":"2022-05-12 18:54:18.336","level":"error","module":"Server","message":"2022-05-12 18:54:18.336 [error] [Server] Error rendering template"},{"timestamp":"2022-05-12 18:54:20.275","level":"info","module":"Shutdown","message":"2022-05-12 18:54:20.275 [info] [Shutdown] Caught interrupt signal and shutting down gracefully"}] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5a6840b..9c669c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "js-cookie": "^3.0.1", "less": "^3.13", "mdbootstrap": "^4.20.0", + "underscore": "^1.13.3", "ws": "^8.5.0" } }, @@ -706,6 +707,11 @@ "node": ">= 0.6" } }, + "node_modules/underscore": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.3.tgz", + "integrity": "sha512-QvjkYpiD+dJJraRA8+dGAU4i7aBbb2s0S3jA45TFOvg2VgqvdCDd/3N6CqA8gluk1W91GLoXg5enMUx560QzuA==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1255,6 +1261,11 @@ "mime-types": "~2.1.24" } }, + "underscore": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.3.tgz", + "integrity": "sha512-QvjkYpiD+dJJraRA8+dGAU4i7aBbb2s0S3jA45TFOvg2VgqvdCDd/3N6CqA8gluk1W91GLoXg5enMUx560QzuA==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 80d774b..b751e7e 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "js-cookie": "^3.0.1", "less": "^3.13", "mdbootstrap": "^4.20.0", + "underscore": "^1.13.3", "ws": "^8.5.0" } } diff --git a/static/css/styles.css b/static/css/styles.css index 953a446..dcc820c 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -55,7 +55,7 @@ html, body { .progBar { appearance: none; height: 100%; - width: 80%; + width: 0%; border-radius: 0px; background-color: darkcyan; } diff --git a/static/js/interface.js b/static/js/interface.js index 4822c9e..be723bc 100644 --- a/static/js/interface.js +++ b/static/js/interface.js @@ -50,6 +50,19 @@ $(function () { jsonview.expand(tree2); }) + $("#applyLang").on("click", function (event) { + const lang = $("#lang").val() + saveOption("/api/ui/v1/lang/set?lang=" + lang, function handleLangSelect(event, xmlHttp) { + const temp = JSON.parse(xmlHttp.responseText) + if(temp.status == "error") { + alert("Request failed reason: " + temp.reason) + } else { + location.reload() + } + console.log(JSON.parse(xmlHttp.responseText)) + }) + }) + $("#addRow").on("click", function (event) { const tableEntryDom = document.getElementById("tableCopySource").cloneNode(true) let temp = tableEntryDom.innerHTML diff --git a/templates/brokenTranslation.html b/templates/brokenTranslation.html index 19d5c66..ee0cc76 100644 --- a/templates/brokenTranslation.html +++ b/templates/brokenTranslation.html @@ -81,24 +81,37 @@ - + - -
-
- -

Oh no!

-

- -

-
- There is a critical error with the current language template. Please select a diffrent language. -
- + +
+
+ +

Oh no!

+

+ +

+
+ There is a critical error with the current language template. Please select a diffrent + language. +
+ + + +
- + @@ -112,7 +125,20 @@ }); $(function () { $('[data-toggle="tooltip"]').tooltip({ container: "body" }) - }) + }) + $("#applyLang").on("click", function (event) { + const lang = $("#lang").val() + saveOption("/api/ui/v1/lang/set?lang=" + lang, function handleLangSelect(event, xmlHttp) { + const temp = JSON.parse(xmlHttp.responseText) + if (temp.status == "error") { + alert("Request failed reason: " + temp.reason) + } else { + location.reload() + } + console.log(JSON.parse(xmlHttp.responseText)) + }) + }) + diff --git a/templates/newAdminPanel.html b/templates/newAdminPanel.html index 38e331b..5d533b5 100644 --- a/templates/newAdminPanel.html +++ b/templates/newAdminPanel.html @@ -137,7 +137,7 @@

<%= it.lang.titles.mode %> data-toggle="tooltip"> + title="<%= it.lang.hints.selectMode %>" data-toggle="tooltip">

@@ -311,8 +311,21 @@ + +
+

<%= it.lang.titles.uiSettings %>

+ + + +