From 5e3a0a466cfeea2c97b777c33b45665f9ebc203a Mon Sep 17 00:00:00 2001 From: NoobishSVK Date: Mon, 22 Jan 2024 20:33:45 +0100 Subject: [PATCH] refactor --- console.js | 17 ++ datahandler.js | 65 ++--- index.js | 35 +-- userconfig.js | 2 +- web/css/breadcrumbs.css | 47 ++++ web/css/buttons.css | 241 +++++++++++++++++ web/css/entry.css | 7 + web/css/helpers.css | 89 ++++++ web/css/main.css | 50 ++++ web/css/modal.css | 87 ++++++ web/css/panels.css | 77 ++++++ web/css/styles.css | 586 ---------------------------------------- web/index.html | 35 ++- web/js/main.js | 382 ++++++++++++++++++++++++++ web/js/modal.js | 33 +++ web/js/themes.js | 33 +++ web/js/webserver.js | 3 + web/main.js | 406 ---------------------------- web/modal.js | 33 --- web/stream.js | 20 -- web/themes.js | 57 ---- 21 files changed, 1125 insertions(+), 1180 deletions(-) create mode 100644 console.js create mode 100644 web/css/breadcrumbs.css create mode 100644 web/css/buttons.css create mode 100644 web/css/entry.css create mode 100644 web/css/helpers.css create mode 100644 web/css/main.css create mode 100644 web/css/modal.css create mode 100644 web/css/panels.css delete mode 100644 web/css/styles.css create mode 100644 web/js/main.js create mode 100644 web/js/modal.js create mode 100644 web/js/themes.js create mode 100644 web/js/webserver.js delete mode 100644 web/main.js delete mode 100644 web/modal.js delete mode 100644 web/stream.js delete mode 100644 web/themes.js diff --git a/console.js b/console.js new file mode 100644 index 0000000..181b3d0 --- /dev/null +++ b/console.js @@ -0,0 +1,17 @@ +const { verboseMode } = require('./userconfig'); + +const MESSAGE_PREFIX = { + INFO: "\x1b[32m[INFO]\x1b[0m", + DEBUG: "\x1b[36m[DEBUG]\x1b[0m", + }; + +const logInfo = (...messages) => console.log(MESSAGE_PREFIX.INFO, ...messages); +const logDebug = (...messages) => { + if (verboseMode) { + console.log(MESSAGE_PREFIX.DEBUG, ...messages); + } +}; + +module.exports = { + logInfo, logDebug +} \ No newline at end of file diff --git a/datahandler.js b/datahandler.js index 45c35ec..1a3720c 100644 --- a/datahandler.js +++ b/datahandler.js @@ -1,3 +1,4 @@ +/* Libraries / Imports */ const koffi = require('koffi'); const path = require('path'); const os = require('os'); @@ -5,6 +6,8 @@ const win32 = (os.platform() == "win32"); const unicode_type = (win32 ? 'int16_t' : 'int32_t'); const lib = koffi.load(path.join(__dirname, "librdsparser." + (win32 ? "dll" : "so"))); +var rdsBuffer = []; + koffi.proto('void callback_pi(void *rds, void *user_data)'); koffi.proto('void callback_pty(void *rds, void *user_data)'); koffi.proto('void callback_tp(void *rds, void *user_data)'); @@ -62,21 +65,6 @@ const rdsparser = { country_lookup_iso: lib.func('const char* rdsparser_country_lookup_iso(int country)') } -const decode_unicode = function(string) -{ - let content = rdsparser.string_get_content(string); - let length = rdsparser.string_get_length(string); - let array = koffi.decode(content, koffi.array(unicode_type, length)); - return Buffer.from(array, 'utf-8').toString(); -}; - -const decode_errors = function(string) { - let errors = rdsparser.string_get_errors(string); - let length = rdsparser.string_get_length(string); - let array = koffi.decode(errors, koffi.array('uint8_t', length)); - return Uint8Array.from(array).toString(); -}; - const callbacks = { pi: koffi.register(rds => ( value = rdsparser.get_pi(rds) @@ -178,6 +166,21 @@ rdsparser.register_rt(rds, callbacks.rt); rdsparser.register_ptyn(rds, callbacks.ptyn); rdsparser.register_ct(rds, callbacks.ct); +const decode_unicode = function(string) +{ + let content = rdsparser.string_get_content(string); + let length = rdsparser.string_get_length(string); + let array = koffi.decode(content, koffi.array(unicode_type, length)); + return Buffer.from(array, 'utf-8').toString(); +}; + +const decode_errors = function(string) { + let errors = rdsparser.string_get_errors(string); + let length = rdsparser.string_get_length(string); + let array = koffi.decode(errors, koffi.array('uint8_t', length)); + return Uint8Array.from(array).toString(); +}; + const updateInterval = 75; const clientUpdateIntervals = new Map(); // Store update intervals for each client @@ -197,27 +200,9 @@ var dataToSend = { country_iso: 'UN', users: '', }; - -const initialData = { - pi: '?', - freq: 87.500.toFixed(3), - signal: 0, - st: false, - ps: '', - tp: false, - pty: 0, - af: [], - rt0: '', - rt1: '', - country_name: '', - country_iso: 'UN', - users: '' -}; - +const initialData = { ...dataToSend }; const resetToDefault = dataToSend => Object.assign(dataToSend, initialData); -var rdsBuffer = []; - function handleBuffer() { for (let group of rdsBuffer) { @@ -233,15 +218,13 @@ function handleData(ws, receivedData) { const receivedLines = receivedData.split('\n'); for (const receivedLine of receivedLines) { - switch (true) { case receivedLine.startsWith('P'): modifiedData = receivedLine.slice(1); - if (dataToSend.pi.length > modifiedData.length || dataToSend.pi == '?') { + if (dataToSend.pi.length >= modifiedData.length || dataToSend.pi == '?') { dataToSend.pi = modifiedData; } break; - case receivedLine.startsWith('T'): rdsBuffer = []; resetToDefault(dataToSend); @@ -281,8 +264,8 @@ function handleData(ws, receivedData) { if (rdsBuffer.length > 1000) { rdsBuffer.shift(); } + rdsBuffer.push(modifiedData); - //console.log("\"" + modifiedData + "\","); if (rdsBuffer.length > 1) { handleBuffer(); @@ -293,9 +276,9 @@ function handleData(ws, receivedData) { // Send the updated data to the client const dataToSendJSON = JSON.stringify(dataToSend); - if (currentTime - lastUpdateTime >= updateInterval) { - clientUpdateIntervals.set(ws, currentTime); // Update the last update time for this client - ws.send(dataToSendJSON); + if (currentTime - lastUpdateTime >= updateInterval) { + clientUpdateIntervals.set(ws, currentTime); // Update the last update time for this client + ws.send(dataToSendJSON); } } diff --git a/index.js b/index.js index 7e75ab1..d595eb6 100644 --- a/index.js +++ b/index.js @@ -1,41 +1,34 @@ -// Libraries +/* Libraries / Imports */ const express = require('express'); +const app = express(); const http = require('http'); +const httpServer = http.createServer(app); const WebSocket = require('ws'); +const wss = new WebSocket.Server({ noServer: true }); const path = require('path'); const net = require('net'); +const client = new net.Socket(); const crypto = require('crypto'); const dataHandler = require('./datahandler'); +const consoleCmd = require('./console'); const config = require('./userconfig'); -/* Server settings */ const { webServerHost, webServerPort, webServerName, xdrdServerHost, xdrdServerPort, xdrdPassword, qthLatitude, qthLongitude } = config; - -const infoMsg = "\x1b[32m[INFO]\x1b[0m"; -const debugMsg = "\x1b[36m[DEBUG]\x1b[0m"; +const { logInfo, logDebug } = consoleCmd; let receivedSalt = ''; let receivedPassword = false; let currentUsers = 0; -const wss = new WebSocket.Server({ noServer: true }); - -const app = express(); -const httpServer = http.createServer(app); -/* connection to xdrd */ -const client = new net.Socket(); - /* webSocket handlers */ wss.on('connection', (ws, request) => { const clientIp = request.connection.remoteAddress; currentUsers++; dataHandler.showOnlineUsers(currentUsers); - console.log(infoMsg, `Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`); + consoleCmd.logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`); ws.on('message', (message) => { - if(config.verboseMode === true) { - console.log(debugMsg,'Received message from client:', message.toString()); - } + consoleCmd.logDebug('Received message from client:', message.toString()); newFreq = message.toString() * 1000; client.write("T" + newFreq + '\n'); }); @@ -43,14 +36,14 @@ wss.on('connection', (ws, request) => { ws.on('close', (code, reason) => { currentUsers--; dataHandler.showOnlineUsers(currentUsers); - console.log(infoMsg, `Web client \x1b[31mdisconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`); + consoleCmd.logInfo(`Web client \x1b[31mdisconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`); }); ws.on('error', console.error); }); -// Serve static files from the "web" folder +/* Serving of HTML files */ app.use(express.static(path.join(__dirname, 'web'))); // Function to authenticate with the xdrd server @@ -68,7 +61,7 @@ function authenticateWithXdrd(client, salt, password) { // WebSocket client connection client.connect(xdrdServerPort, xdrdServerHost, () => { - console.log(infoMsg, 'Connected to xdrd successfully.'); + consoleCmd.logInfo('Connected to xdrd successfully.'); client.once('data', (data) => { const receivedData = data.toString(); @@ -107,10 +100,10 @@ httpServer.on('upgrade', (request, socket, head) => { }); httpServer.listen(webServerPort, webServerHost, () => { - console.log(infoMsg, `Web server is running at \x1b[34mhttp://${webServerHost}:${webServerPort}\x1b[0m.`); + consoleCmd.logInfo(`Web server is running at \x1b[34mhttp://${webServerHost}:${webServerPort}\x1b[0m.`); }); - +/* Static data are being sent through here on connection - these don't change when the server is running */ app.get('/static_data', (req, res) => { res.json({ qthLatitude, qthLongitude, webServerName }); }); \ No newline at end of file diff --git a/userconfig.js b/userconfig.js index 415e3ac..236afd1 100644 --- a/userconfig.js +++ b/userconfig.js @@ -9,7 +9,7 @@ const xdrdPassword = 'changememe'; // xdrd password (optional) const qthLatitude = '50.357935'; // your latitude, useful for maps.fmdx.pl integration const qthLongitude = '15.924395'; // your longitude, useful for maps.fmdx.pl integration -const verboseMode = false; // if true, console will display extra messages +const verboseMode = true; // if true, console will display extra messages // DO NOT MODIFY ANYTHING BELOW THIS LINE module.exports = { diff --git a/web/css/breadcrumbs.css b/web/css/breadcrumbs.css new file mode 100644 index 0000000..f18de39 --- /dev/null +++ b/web/css/breadcrumbs.css @@ -0,0 +1,47 @@ +h2 { + color: var(--color-4); + margin-bottom: 0; +} + +h3 { + font-size: 22px; +} + +#data-ps, #data-rt0, #data-rt1 { + font-family: monospace; +} + +#color-settings, #settings { + background: transparent; + border: 0; + color: white; + position: absolute; + top: 15px; + right: 15px; + font-size: 16px; + width: 64px; + height: 64px; + line-height: 64px; + text-align: center; + border-radius: 50%; + transition: 500ms ease-in-out background; + cursor: pointer; +} + +#color-settings { + top: 96px; +} + +#settings:hover, #color-settings:hover { + background: var(--color-3); +} + +#af-list ul { + display:list-item; + padding: 0; + list-style-type: none; + height: 425px; + overflow-y: scroll; + overflow-x: hidden; + margin-bottom: 0; +} \ No newline at end of file diff --git a/web/css/buttons.css b/web/css/buttons.css new file mode 100644 index 0000000..56ba241 --- /dev/null +++ b/web/css/buttons.css @@ -0,0 +1,241 @@ + +#tune-buttons input[type="text"] { + width: 50%; + height: 100%; + min-height: 46px; + padding-left: 20px; + box-sizing: border-box; + border: 2px solid transparent; + outline: 0; + color: white; + background-color: var(--color-1); + font-family: 'Titillium Web', sans-serif; + } + + input[type="text"]:hover { + border: 2px solid var(--color-main-bright); + } + + #tune-buttons button { + box-sizing: border-box; + background-color: var(--color-4); + border: 0; + color: var(--color-1); + width: 25%; + height: 100%; + display: block; + padding: 14px; + cursor: pointer; + transition: background-color 0.3s ease-in-out; + } + + #tune-buttons button:hover { + background-color: var(--color-main-bright); + } + + #freq-down { + border-radius: 30px 0 0 30px; + } + + #freq-up { + border-radius: 0 30px 30px 0; + } + +input[type="range"] { + margin: 0; + /* removing default appearance */ + -webkit-appearance: none; + appearance: none; + /* creating a custom design */ + width: 100%; + cursor: pointer; + outline: none; + /* slider progress trick */ + overflow: hidden; + border-radius: 30px; + height: 100%; + background: transparent; +} + +/* Track: Mozilla Firefox */ +input[type="range"]::-moz-range-track { + height: 48px; + background: var(--color-1); + border-radius: 30px; + border: 0; +} + +/* Thumb: webkit */ +input[type="range"]::-webkit-slider-thumb { + /* removing default appearance */ + -webkit-appearance: none; + appearance: none; + /* creating a custom design */ + height: 48px; + width: 48px; + background-color: #fff; + border-radius: 10px; + border: 2px solid var(--color-4); + /* slider progress trick */ + box-shadow: -407px 0 0 400px var(--color-4); +} + +/* Thumb: Firefox */ +input[type="range"]::-moz-range-thumb { + box-sizing: border-box; + height: 48px; + width: 48px; + background-color: var(--color-4); + border-radius: 0px 30px 30px 0px; + border: 0; + outline: none; + /* slider progress trick */ + box-shadow: -420px 0 0 400px var(--color-4); +} + +/* Toggle Switch */ + +.toggleSwitch span span { + display: none; +} + +.toggleSwitch { + user-select: none; + display: inline-block; + height: 48px; + position: relative; + overflow: hidden; + padding: 0; + cursor: pointer; + width: 100%; + border-radius: 25px; + font-weight: bold; +} +.toggleSwitch * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.toggleSwitch input:focus ~ a, +.toggleSwitch input:focus + label { + outline: none; +} +.toggleSwitch label { + position: relative; + z-index: 3; + display: block; + width: 100%; +} +.toggleSwitch input { + position: absolute; + opacity: 0; + z-index: 5; +} +.toggleSwitch > span { + position: absolute; + left: 0; + width: calc(100% - 6px); + margin: 0; + text-align: left; + white-space: nowrap; + margin:0; +} +.toggleSwitch > span span { + position: absolute; + top: 0; + left: 0; + z-index: 5; + display: block; + width: 50%; + margin-left: 50px; + text-align: left; + font-size: 0.9em; + width: auto; + opacity: 1; + width: 40%; + text-align: center; + line-height:48px; +} +.toggleSwitch a { + position: absolute; + right: 50%; + z-index: 4; + display: block; + top: 0; + bottom: 0; + padding: 0; + left: 0; + width: 50%; + background-color: var(--color-4); + border-radius: 25px; + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} +.toggleSwitch > span span:first-of-type { + color: var(--color-1); + opacity: 1; + left: 0; + margin: 0; + width: 50%; +} +.toggleSwitch > span span:last-of-type { + left:auto; + right:0; + color: var(--color-4); + margin: 0; + width: 50%; +} +.toggleSwitch > span:before { + content: ''; + display: block; + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: -2px; + border-radius: 30px; + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; +} +.toggleSwitch input:checked ~ a { + left: calc(50% - 3px); +} + +.toggleSwitch input:checked ~ span span:first-of-type { + left:0; + color: var(--color-4); +} +.toggleSwitch input:checked ~ span span:last-of-type { + color: var(--color-1); +} + +/* End Toggle Switch */ + +select { + height: 42px; + width: 150px; + padding: 10px; + background: var(--color-4); + color: var(--color-1); + border: 0; + border-bottom: 4px solid var(--color-2); + cursor: pointer; + transition: 0.35s ease-in-out background; + font-family: inherit; + font-weight: bold; +} + +select option { + font-family: 'Titillium Web', sans-serif; + font-weight: 300; + padding: 10px; + border: 0; +} + +select:hover { + background: var(--color-5); +} diff --git a/web/css/entry.css b/web/css/entry.css new file mode 100644 index 0000000..5a460ec --- /dev/null +++ b/web/css/entry.css @@ -0,0 +1,7 @@ +@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap'); +@import url("main.css"); /* Root stuff that affects the entire webpage (body, wrapper etc.) */ +@import url("breadcrumbs.css"); /* Stuff that applies to random elements only once/twice */ +@import url("buttons.css"); /* Buttons, inputs, select boxes, checkboxes... */ +@import url("helpers.css"); /* Stuff that is used often such as text changers etc */ +@import url("panels.css"); /* Different panels and their sizes */ +@import url("modal.css"); /* Modal window */ \ No newline at end of file diff --git a/web/css/helpers.css b/web/css/helpers.css new file mode 100644 index 0000000..8884eb6 --- /dev/null +++ b/web/css/helpers.css @@ -0,0 +1,89 @@ +.auto { + margin: auto; +} + +.bg-dark { + background: #100d1f; +} + +.bg-none { + background: transparent !important; +} + +.color-1 { + color: var(--color-1); +} + +.color-2 { + color: var(--color-2); +} + +.color-3 { + color: var(--color-3); +} + +.color-4 { + color: var(--color-4); +} + +.flex-container { + display: flex; +} + +.flex-center { + display: flex; + justify-content: center; + align-items: center; +} + +.hover-brighten:hover { + cursor: pointer; + background-color: var(--color-2); +} + +.text-big { + font-size: 60px; + font-weight: 300; +} + +.text-medium { + font-size: 24px; + color: #aaa; + font-weight: 300; +} + +.text-medium-big { + font-size: 46px; +} + +.text-small { + font-size: 13px; +} + +.text-gray { + color: #666; +} + +.text-uppercase { + text-transform: uppercase; +} + +@media only screen and (max-width: 768px) { + .flex-container { + display: block; + } + .flex-phone { + display: flex; + } + .text-medium-big { + font-size: 32px; + } + .text-big { + font-size: 40px; + display: block; + margin-top: -25px; + } + .text-big#data-ps { + margin: 0; + } +} \ No newline at end of file diff --git a/web/css/main.css b/web/css/main.css new file mode 100644 index 0000000..6dc6808 --- /dev/null +++ b/web/css/main.css @@ -0,0 +1,50 @@ + +:root { + --color-main: #111; + --color-main-bright: #aaa; + + --color-1: color-mix(in srgb, var(--color-main) 95%, var(--color-main-bright)); + --color-2: color-mix(in srgb, var(--color-main) 75%, var(--color-main-bright)); + --color-3: color-mix(in srgb, var(--color-main) 50%, var(--color-main-bright)); + --color-4: color-mix(in srgb, var(--color-main) 20%, var(--color-main-bright)); + --color-5: color-mix(in srgb, var(--color-main) 0%, var(--color-main-bright)); +} + +* { + box-sizing: border-box; +} + +::selection { + background: var(--color-main-bright); + color: inherit; +} + +body { + font-family: 'Titillium Web', sans-serif; + color: white; + background-color: var(--color-main); + transition: 0.3s ease-in-out background-color; +} + +#wrapper { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: auto; + max-width: 1240px; +} + + +@media (max-width: 960px) { + #wrapper { + position: static; + transform: none; + margin: 0 auto; + } +} + +a { + text-decoration: none; + color: white; +} \ No newline at end of file diff --git a/web/css/modal.css b/web/css/modal.css new file mode 100644 index 0000000..6f718a6 --- /dev/null +++ b/web/css/modal.css @@ -0,0 +1,87 @@ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); /* Semi-transparent background */ + opacity: 0; + transition: opacity 0.3s ease-in-out; /* Fade-in/out transition */ + z-index: 20; /* Ensure the modal is above other content */ + color: var(--color-4); + backdrop-filter: blur(10px); +} + +/* Style for the modal content */ +.modal-content { + box-sizing: border-box; + position: absolute; + top: 50vh; + left: 50vw; + transform: translate(-50%, -50%); + background-color: var(--color-main); + padding: 30px; + border-radius: 30px; + opacity: 1; + transition: opacity 0.3s ease-in-out; /* Fade-in/out transition */ + z-index: 21; /* Ensure the modal content is above the modal background */ + min-width: 500px; +} + +.modal-content p { + margin: 0; +} + +.modal-title { + font-size: 20px; + position: absolute; + font-weight: 300; + top: 14px; + left: 30px; +} + +/* Style for the close button */ +.close { + position: absolute; + top: 17px; + right: 30px; + cursor: pointer; + transition: 0.3s ease-in-out color; +} + +.close:hover { + color: white; +} + +.modal-content .button-close { + position: absolute; + bottom: 25px; + right: 35px; + width: 100px; + height: 48px; + border-radius: 30px; + background: var(--color-4); + font-weight: bold; + border: 0; + transition: 0.35s ease-in-out background; + cursor: pointer; +} + +.modal-content .button-close:hover { + background: var(--color-5); +} + +.modal label { + font-size: 12px; + font-weight: bold; + text-transform: uppercase; + display: block; +} + +@media only screen and (max-width: 768px) { + .modal-content { + min-width: 90% !important; + margin: auto; + } +} \ No newline at end of file diff --git a/web/css/panels.css b/web/css/panels.css new file mode 100644 index 0000000..e493ad2 --- /dev/null +++ b/web/css/panels.css @@ -0,0 +1,77 @@ +.panel-10 { + width: 10%; + background: var(--color-1); + margin-left: 10px; + margin-right: 10px; + border-radius: 30px; + text-align: center; + margin-top: 30px; + margin-bottom: 30px; +} + +.panel-33 { + width: 33%; + background-color: var(--color-1); + margin-left: 10px; + margin-right: 10px; + border-radius: 30px; + text-align: center; + margin-top: 30px; + transition: 0.3s ease-in-out background-color; +} + +.panel-75 { + width: 68%; + background: var(--color-1); + margin-left: 10px; + margin-right: 10px; + border-radius: 30px; + text-align: center; + margin-top: 30px; + transition: 0.3s ease-in-out background-color; +} + +.panel-90 { + width: 88%; + background: var(--color-1); + margin-left: 10px; + margin-right: 10px; + border-radius: 30px; + text-align: center; + margin-top: 30px; +} + + +.panel-100 { + width: 98%; + background: var(--color-1); + margin-left: 10px; + margin-right: 10px; + min-height: 100px; + border-radius: 30px; + text-align: center; + margin-top: 30px; +} + + + +@media only screen and (max-width: 768px) { + .panel-10, .panel-33, .panel-90 { + width: 90%; + margin: auto; + margin-bottom: 20px; + } + .panel-75 { + margin: 80px auto 0 auto !important; + width: 90%; + } + .panel-33 h2 { + padding: 20px; + padding-top: 5px; + } + .panel-100 { + width: 90%; + margin: auto; + } + +} \ No newline at end of file diff --git a/web/css/styles.css b/web/css/styles.css deleted file mode 100644 index 5704fd2..0000000 --- a/web/css/styles.css +++ /dev/null @@ -1,586 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap'); - -:root { - --color-main: #111; - --color-main-bright: #aaa; - - --color-1: color-mix(in srgb, var(--color-main) 95%, var(--color-main-bright)); - --color-2: color-mix(in srgb, var(--color-main) 75%, var(--color-main-bright)); - --color-3: color-mix(in srgb, var(--color-main) 50%, var(--color-main-bright)); - --color-4: color-mix(in srgb, var(--color-main) 20%, var(--color-main-bright)); - --color-5: color-mix(in srgb, var(--color-main) 0%, var(--color-main-bright)); -} - -.color-1 { - color: var(--color-1); -} - -.color-2 { - color: var(--color-2); -} - -.color-3 { - color: var(--color-3); -} - -.color-4 { - color: var(--color-4); -} - -::selection { - background: var(--color-main-bright); - color: inherit; -} - -body { - font-family: 'Titillium Web', sans-serif; - color: white; - background-color: var(--color-main); - transition: 0.3s ease-in-out background-color; -} - -a { - text-decoration: none; - color: white; -} - -#data-pi { - text-transform: uppercase; -} - -#wrapper { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: auto; - max-width: 1240px; -} - -#color-settings, #settings { - background: transparent; - border: 0; - color: white; - position: absolute; - top: 15px; - right: 15px; - font-size: 16px; - width: 64px; - height: 64px; - line-height: 64px; - text-align: center; - border-radius: 50%; - transition: 500ms ease-in-out background; - cursor: pointer; -} - -#color-settings { - top: 96px; -} - -#settings:hover, #color-settings:hover { - background: var(--color-3); -} - -h2 { - color: var(--color-4); - margin-bottom: 0; -} - -h3 { - font-size: 22px; -} - -.flex-container { - display: flex; -} - -.flex-center { - display: flex; - justify-content: center; - align-items: center; -} - -.no-bg { - background: transparent !important; -} - -.panel-10 { - width: 10%; - background: var(--color-1); - margin-left: 10px; - margin-right: 10px; - border-radius: 30px; - text-align: center; - margin-top: 30px; - margin-bottom: 30px; -} - -.panel-33 { - width: 33%; - background-color: var(--color-1); - margin-left: 10px; - margin-right: 10px; - border-radius: 30px; - text-align: center; - margin-top: 30px; - transition: 0.3s ease-in-out background-color; -} - -.hover-brighten:hover { - cursor: pointer; - background-color: var(--color-2); -} - -.panel-75 { - width: 68%; - background: var(--color-1); - margin-left: 10px; - margin-right: 10px; - border-radius: 30px; - text-align: center; - margin-top: 30px; - transition: 0.3s ease-in-out background-color; -} - -.panel-90 { - width: 88%; - background: var(--color-1); - margin-left: 10px; - margin-right: 10px; - border-radius: 30px; - text-align: center; - margin-top: 30px; -} - - -.panel-100 { - width: 98%; - background: var(--color-1); - margin-left: 10px; - margin-right: 10px; - min-height: 100px; - border-radius: 30px; - text-align: center; - margin-top: 30px; -} - -#af-list ul { - display:list-item; - padding: 0; - list-style-type: none; - height: 425px; - overflow-y: scroll; - overflow-x: hidden; - margin-bottom: 0; -} - -.auto { - margin: auto; -} - -.text-big { - font-size: 60px; - font-weight: 300; -} - -.text-medium { - font-size: 24px; - color: #aaa; - font-weight: 300; -} - -.text-medium-big { - font-size: 46px; -} - -.text-small { - font-size: 13px; -} - -.text-gray { - color: #666; -} - -.bg-dark { - background: #100d1f; -} - -#tune-buttons input[type="text"] { - width: 50%; - height: 100%; - min-height: 46px; - padding-left: 20px; - box-sizing: border-box; - border: 2px solid transparent; - outline: 0; - color: white; - background-color: var(--color-1); - font-family: 'Titillium Web', sans-serif; -} - -input[type="text"]:hover { - border: 2px solid var(--color-main-bright); -} - -#tune-buttons button { - box-sizing: border-box; - background-color: var(--color-4); - border: 0; - color: var(--color-1); - width: 25%; - height: 100%; - display: block; - padding: 14px; - cursor: pointer; - transition: background-color 0.3s ease-in-out; -} - -#tune-buttons button:hover { - background-color: var(--color-main-bright); -} - -#freq-down { - border-radius: 30px 0 0 30px; -} - -#freq-up { - border-radius: 0 30px 30px 0; -} - -@media only screen and (max-width: 768px) { - .flex-container { - display: block; - } - .flex-phone { - display: flex; - } - .modal-content { - min-width: 90% !important; - margin: auto; - } - .panel-10, .panel-33, .panel-90 { - width: 90%; - margin: auto; - margin-bottom: 20px; - } - .panel-75 { - margin: 80px auto 0 auto !important; - width: 90%; - } - .panel-33 h2 { - padding: 20px; - padding-top: 5px; - } - .text-medium-big { - font-size: 32px; - } - .text-big { - font-size: 40px; - display: block; - margin-top: -25px; - } - .text-big#data-ps { - margin: 0; - } - .panel-100 { - width: 90%; - margin: auto; - } - -} - -@media (max-width: 960px) { - #wrapper { - position: static; - transform: none; - margin: 0 auto; - } -} - -input[type="range"] { - margin: 0; - /* removing default appearance */ - -webkit-appearance: none; - appearance: none; - /* creating a custom design */ - width: 100%; - cursor: pointer; - outline: none; - /* slider progress trick */ - overflow: hidden; - border-radius: 30px; - height: 100%; - background: transparent; -} - -/* Track: Mozilla Firefox */ -input[type="range"]::-moz-range-track { - height: 48px; - background: var(--color-1); - border-radius: 30px; - border: 0; -} - -/* Thumb: webkit */ -input[type="range"]::-webkit-slider-thumb { - /* removing default appearance */ - -webkit-appearance: none; - appearance: none; - /* creating a custom design */ - height: 48px; - width: 48px; - background-color: #fff; - border-radius: 10px; - border: 2px solid var(--color-4); - /* slider progress trick */ - box-shadow: -407px 0 0 400px var(--color-4); -} - -/* Thumb: Firefox */ -input[type="range"]::-moz-range-thumb { - box-sizing: border-box; - height: 48px; - width: 48px; - background-color: var(--color-4); - border-radius: 0px 30px 30px 0px; - border: 0; - outline: none; - /* slider progress trick */ - box-shadow: -420px 0 0 400px var(--color-4); -} - -/* Toggle Switch */ - -.toggleSwitch span span { - display: none; -} - -.toggleSwitch { - user-select: none; - display: inline-block; - height: 48px; - position: relative; - overflow: hidden; - padding: 0; - cursor: pointer; - width: 100%; - border-radius: 25px; - font-weight: bold; -} -.toggleSwitch * { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.toggleSwitch input:focus ~ a, -.toggleSwitch input:focus + label { - outline: none; -} -.toggleSwitch label { - position: relative; - z-index: 3; - display: block; - width: 100%; -} -.toggleSwitch input { - position: absolute; - opacity: 0; - z-index: 5; -} -.toggleSwitch > span { - position: absolute; - left: 0; - width: calc(100% - 6px); - margin: 0; - text-align: left; - white-space: nowrap; - margin:0; -} -.toggleSwitch > span span { - position: absolute; - top: 0; - left: 0; - z-index: 5; - display: block; - width: 50%; - margin-left: 50px; - text-align: left; - font-size: 0.9em; - width: auto; - opacity: 1; - width: 40%; - text-align: center; - line-height:48px; -} -.toggleSwitch a { - position: absolute; - right: 50%; - z-index: 4; - display: block; - top: 0; - bottom: 0; - padding: 0; - left: 0; - width: 50%; - background-color: var(--color-4); - border-radius: 25px; - -webkit-transition: all 0.2s ease-out; - -moz-transition: all 0.2s ease-out; - transition: all 0.2s ease-out; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} -.toggleSwitch > span span:first-of-type { - color: var(--color-1); - opacity: 1; - left: 0; - margin: 0; - width: 50%; -} -.toggleSwitch > span span:last-of-type { - left:auto; - right:0; - color: var(--color-4); - margin: 0; - width: 50%; -} -.toggleSwitch > span:before { - content: ''; - display: block; - width: 100%; - height: 100%; - position: absolute; - left: 0; - top: -2px; - border-radius: 30px; - -webkit-transition: all 0.2s ease-out; - -moz-transition: all 0.2s ease-out; - transition: all 0.2s ease-out; -} -.toggleSwitch input:checked ~ a { - left: calc(50% - 3px); -} - -.toggleSwitch input:checked ~ span span:first-of-type { - left:0; - color: var(--color-4); -} -.toggleSwitch input:checked ~ span span:last-of-type { - color: var(--color-1); -} - -/* End Toggle Switch */ - - - -/* Style for the modal container */ -.modal { - display: none; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.6); /* Semi-transparent background */ - opacity: 0; - transition: opacity 0.3s ease-in-out; /* Fade-in/out transition */ - z-index: 20; /* Ensure the modal is above other content */ - color: var(--color-4); - backdrop-filter: blur(10px); -} - -/* Style for the modal content */ -.modal-content { - box-sizing: border-box; - position: absolute; - top: 50vh; - left: 50vw; - transform: translate(-50%, -50%); - background-color: var(--color-main); - padding: 30px; - border-radius: 30px; - opacity: 1; - transition: opacity 0.3s ease-in-out; /* Fade-in/out transition */ - z-index: 21; /* Ensure the modal content is above the modal background */ - min-width: 500px; -} - -.modal-content p { - margin: 0; -} - -.modal-title { - font-size: 20px; - position: absolute; - font-weight: 300; - top: 14px; - left: 30px; -} - -/* Style for the close button */ -.close { - position: absolute; - top: 17px; - right: 30px; - cursor: pointer; - transition: 0.3s ease-in-out color; -} - -.close:hover { - color: white; -} - -.modal-content .button-close { - position: absolute; - bottom: 25px; - right: 35px; - width: 100px; - height: 48px; - border-radius: 30px; - background: var(--color-4); - font-weight: bold; - border: 0; - transition: 0.35s ease-in-out background; - cursor: pointer; -} - -.modal-content .button-close:hover { - background: var(--color-5); -} - -.modal label { - font-size: 12px; - font-weight: bold; - text-transform: uppercase; - display: block; -} - -select { - height: 42px; - width: 150px; - padding: 10px; - background: var(--color-4); - color: var(--color-1); - border: 0; - border-bottom: 4px solid var(--color-2); - cursor: pointer; - transition: 0.35s ease-in-out background; - font-family: inherit; - font-weight: bold; -} - -select option { - font-family: 'Titillium Web', sans-serif; - font-weight: 300; - padding: 10px; - border: 0; -} - -select:hover { - background: var(--color-5); -} - -#data-ps, #data-rt0, #data-rt1 { - font-family: monospace; -} diff --git a/web/index.html b/web/index.html index cbe2748..9c1e616 100644 --- a/web/index.html +++ b/web/index.html @@ -2,7 +2,7 @@ FM-DX Webserver - + @@ -14,7 +14,7 @@
-
+
@@ -38,7 +38,7 @@

PI CODE

- +
@@ -58,12 +58,10 @@
-
- @@ -81,16 +79,26 @@
-
-

RADIOTEXT

-
-
- +
+
+

RADIOTEXT

+
+
+ +
+ +
+

+
+

+

+

+
-
+

AF

@@ -129,9 +137,6 @@
- - - - + \ No newline at end of file diff --git a/web/js/main.js b/web/js/main.js new file mode 100644 index 0000000..884da5d --- /dev/null +++ b/web/js/main.js @@ -0,0 +1,382 @@ +$(document).ready(function() { + var hostParts = window.location.host.split(':'); + var hostname = hostParts[0]; // Extract the hostname + var port = hostParts[1] || '8080'; // Extract the port or use a default (e.g., 8080) + var socketAddress = 'ws://' + hostname + ':' + port + '/text'; // Use 'wss' for secure WebSocket connections (recommended for external access) + var socket = new WebSocket(socketAddress); + + var dataContainer = $('#data-container'); + var canvas = $('#signal-canvas')[0]; + var context = canvas.getContext('2d'); + + var signalToggle = $("#signal-units-toggle"); + + canvas.width = canvas.parentElement.clientWidth; + + var data = []; + var maxDataPoints = 250; + var pointWidth = (canvas.width - 80) / maxDataPoints; + + var europe_programmes = [ + "No PTY", "News", "Current Affairs", "Info", + "Sport", "Education", "Drama", "Culture", "Science", "Varied", + "Pop M", "Rock M", "Easy Listening", "Light Classical", + "Serious Classical", "Other Music", "Weather", "Finance", + "Children's Programmes", "Social Affairs", "Religion", "Phone-in", + "Travel", "Leisure", "Jazz Music", "Country Music", "National Music", + "Oldies Music", "Folk Music", "Documentary", "Alarm Test" + ]; + + function getInitialSettings() { + $.ajax({ + url: '/static_data', + dataType: 'json', + success: function(data) { + // Use the received data (data.qthLatitude, data.qthLongitude) as needed + localStorage.setItem('qthLatitude', data.qthLatitude); + localStorage.setItem('qthLongitude', data.qthLongitude); + localStorage.setItem('webServerName', data.webServerName); + + document.title = 'FM-DX Webserver [' + data.webServerName + ']'; + }, + error: function(error) { + console.error('Error:', error); + } + }); + } + + getInitialSettings(); + // Start updating the canvas + updateCanvas(); + + function updateCanvas() { + const color2 = getComputedStyle(document.documentElement).getPropertyValue('--color-2').trim(); + const color4 = getComputedStyle(document.documentElement).getPropertyValue('--color-4').trim(); + + while (data.length >= maxDataPoints) { + data.shift(); + } + + // Modify the WebSocket onmessage callback + socket.onmessage = (event) => { + const parsedData = JSON.parse(event.data); + + updatePanels(parsedData); + // Push the new signal data to the array + data.push(parsedData.signal); + const actualLowestValue = Math.min(...data); + const actualHighestValue = Math.max(...data); + zoomMinValue = actualLowestValue - ((actualHighestValue - actualLowestValue) / 2); + zoomMaxValue = actualHighestValue + ((actualHighestValue - actualLowestValue) / 2); + zoomAvgValue = (zoomMaxValue - zoomMinValue) / 2 + zoomMinValue; + + // Clear the canvas + context.clearRect(0, 0, canvas.width, canvas.height); + + // Draw the signal graph with zoom + context.beginPath(); + context.moveTo(50, canvas.height - (data[0] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue))); + + for (let i = 1; i < data.length; i++) { + const x = i * pointWidth; + const y = canvas.height - (data[i] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); + context.lineTo(x + 40, y); + } + + context.strokeStyle = color4; + context.lineWidth = 1; + context.stroke(); + + // Draw horizontal lines for lowest, highest, and average values + context.strokeStyle = color2; // Set line color + context.lineWidth = 1; + + // Draw the lowest value line + const lowestY = canvas.height - (zoomMinValue - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); + context.beginPath(); + context.moveTo(40, lowestY - 18); + context.lineTo(canvas.width - 40, lowestY - 18); + context.stroke(); + + // Draw the highest value line + const highestY = canvas.height - (zoomMaxValue - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); + context.beginPath(); + context.moveTo(40, highestY + 10); + context.lineTo(canvas.width - 40, highestY + 10); + context.stroke(); + + const avgY = canvas.height / 2; + context.beginPath(); + context.moveTo(40, avgY - 7); + context.lineTo(canvas.width - 40, avgY - 7); + context.stroke(); + + // Label the lines with their values + context.fillStyle = color4; + context.font = '12px Titillium Web'; + + const offset = signalToggle.prop('checked') ? 11.75 : 0; + context.textAlign = 'right'; + context.fillText(`${(zoomMinValue - offset).toFixed(1)}`, 35, lowestY - 14); + context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, 35, highestY + 14); + context.fillText(`${(zoomAvgValue - offset).toFixed(1)}`, 35, avgY - 3); + + context.textAlign = 'left'; + context.fillText(`${(zoomMinValue - offset).toFixed(1)}`, canvas.width - 35, lowestY - 14); + context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, canvas.width - 35, highestY + 14); + context.fillText(`${(zoomAvgValue - offset).toFixed(1)}`, canvas.width - 35, avgY - 3); + + // Update the data container with the latest data + dataContainer.html(event.data + '
'); + }; + requestAnimationFrame(updateCanvas); + } + + + + function compareNumbers(a, b) { + return a - b; + } + + function escapeHTML(unsafeText) { + let div = document.createElement('div'); + div.innerText = unsafeText; + return div.innerHTML.replace(' ', ' '); + } + + function processString(string, errors) { + var output = ''; + const max_alpha = 70; + const alpha_range = 50; + const max_error = 10; + errors = errors?.split(','); + + for (let i = 0; i < string.length; i++) { + alpha = parseInt(errors[i]) * (alpha_range / (max_error + 1)); + if (alpha) { + output += "" + escapeHTML(string[i]) + ""; + } else { + output += escapeHTML(string[i]); + } + } + + return output; + } + + function updatePanels(parsedData) { + const sortedAf = parsedData.af.sort(compareNumbers); + const scaledArray = sortedAf.map(element => element / 1000); + const listContainer = $('#af-list'); + const scrollTop = listContainer.scrollTop(); + let ul = listContainer.find('ul'); + + if (!ul.length) { + ul = $('
    '); + listContainer.append(ul); + } + ul.html(''); + + const listItems = scaledArray.map(element => { + return $('
  • ').text(element.toFixed(1))[0]; + }); + + ul.append(listItems); + listContainer.scrollTop(scrollTop); + + $('#data-frequency').text(parsedData.freq); + $('#data-pi').html(parsedData.pi === '?' ? "?" : parsedData.pi); + $('#data-ps').html(parsedData.ps === '?' ? "?" : processString(parsedData.ps, parsedData.ps_errors)); + $('#data-tp').html(parsedData.tp === false ? "TP" : "TP"); + $('#data-pty').html(europe_programmes[parsedData.pty]); + $('#data-st').html(parsedData.st === false ? "ST" : "ST"); + $('#data-rt0').html(processString(parsedData.rt0, parsedData.rt0_errors)); + $('#data-rt1').html(processString(parsedData.rt1, parsedData.rt1_errors)); + $('#data-flag').html(''); + + const signalValue = signalToggle.is(':checked') ? (parsedData.signal - 11.75) : parsedData.signal; + const integerPart = Math.floor(signalValue); + const decimalPart = (signalValue - integerPart).toFixed(1).slice(1); // Adjusted this line + + $('#data-signal').text(integerPart); + $('#data-signal-decimal').text(decimalPart); + $('#users-online').text(parsedData.users); + } + + signalToggle.on("change", function() { + const signalText = $('#signal-units'); + if (signalToggle.prop('checked')) { + signalText.text('dBµV'); + } else { + signalText.text('dBf'); + } + }); + + const textInput = $('#commandinput'); + + textInput.on('change', function (event) { + const inputValue = textInput.val(); + // Check if the user agent contains 'iPhone' + if (/iPhone/i.test(navigator.userAgent) && socket.readyState === WebSocket.OPEN) { + socket.send(inputValue); + // Clear the input field if needed + textInput.val(''); + } + }); + + textInput.on('keyup', function (event) { + if (event.key !== 'Backspace') { + let inputValue = textInput.val(); + inputValue = inputValue.replace(/[^0-9.]/g, ''); + + if (inputValue.includes("..")) { + inputValue = inputValue.slice(0, inputValue.lastIndexOf('.')) + inputValue.slice(inputValue.lastIndexOf('.') + 1); + textInput.val(inputValue); + } + + if (!inputValue.includes(".")) { + if (inputValue.startsWith('10') && inputValue.length > 2) { + inputValue = inputValue.slice(0, 3) + '.' + inputValue.slice(3); + textInput.val(inputValue); + } else if (inputValue.length > 2) { + inputValue = inputValue.slice(0, 2) + '.' + inputValue.slice(2); + textInput.val(inputValue); + } + } + } + if (event.key === 'Enter') { + const inputValue = textInput.val(); + if (socket.readyState === WebSocket.OPEN) { + socket.send(inputValue); + } + textInput.val(''); + } + }); + + document.onkeydown = checkKey; + + function checkKey(e) { + e = e || window.event; + + getCurrentFreq(); + + if (socket.readyState === WebSocket.OPEN) { + if (e.keyCode == '38') { + socket.send((currentFreq + 0.01).toFixed(2)); + } + else if (e.keyCode == '40') { + socket.send((currentFreq - 0.01).toFixed(2)); + } + else if (e.keyCode == '37') { + socket.send((currentFreq - 0.10).toFixed(1)); + } + else if (e.keyCode == '39') { + socket.send((currentFreq + 0.10).toFixed(1)); + } + } + } + + function getCurrentFreq() { + currentFreq = $('#data-frequency').text(); + currentFreq = parseFloat(currentFreq).toFixed(3); + currentFreq = parseFloat(currentFreq); + + return currentFreq; + } + + var freqUpButton = $('#freq-up')[0]; + var freqDownButton = $('#freq-down')[0]; + var psContainer = $('#ps-container')[0]; + var rtContainer = $('#rt-container')[0]; + var piCodeContainer = $('#pi-code-container')[0]; + var freqContainer = $('#freq-container')[0]; + + $(freqUpButton).on("click", tuneUp); + $(freqDownButton).on("click", tuneDown); + $(psContainer).on("click", copyPs); + $(rtContainer).on("click", copyRt); + $(piCodeContainer).on("click", findOnMaps); + $(freqContainer).on("click", function() { + textInput.focus(); + }); + + + function tuneUp() { + if (socket.readyState === WebSocket.OPEN) { + getCurrentFreq(); + socket.send((currentFreq + 0.10).toFixed(1)); + } + } + + function tuneDown() { + if (socket.readyState === WebSocket.OPEN) { + getCurrentFreq(); + socket.send((currentFreq - 0.10).toFixed(1)); + } + } + + async function copyPs() { + var frequency = $('#data-frequency').text(); + var pi = $('#data-pi').text(); + var ps = $('#data-ps').text(); + var signal = $('#data-signal').text(); + var signalDecimal = $('#data-signal-decimal').text(); + var signalUnit = $('#signal-units').text(); + + try { + await copyToClipboard(frequency + " - " + pi + " | " + ps + " [" + signal + signalDecimal + " " + signalUnit + "]"); + } catch(error) { + console.error(error); + } + } + + async function copyRt() { + var rt0 = $('#data-rt0').text(); + var rt1 = $('#data-rt1').text(); + + try { + await copyToClipboard("[0] RT: " + rt0 + "\n[1] RT: " + rt1); + } catch(error) { + console.error(error); + } + } + + + function findOnMaps() { + var frequency = $('#data-frequency').text(); + var pi = $('#data-pi').text(); + var latitude = localStorage.getItem('qthLongitude'); + var longitude = localStorage.getItem('qthLatitude'); + frequency = parseFloat(frequency).toFixed(1); + + var url = "https://maps.fmdx.pl/#qth=" + longitude + "," + latitude + "&freq=" + frequency + "&pi=" + pi; + window.open(url, "_blank"); + } + + function copyToClipboard(textToCopy) { + // Navigator clipboard api needs a secure context (https) + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(textToCopy) + .catch(function(err) { + console.error('Error:', err); + }); + } else { + var textArea = $(''); + textArea.val(textToCopy); + textArea.css({ + 'position': 'absolute', + 'left': '-999999px' + }); + + $('body').prepend(textArea); + textArea.select(); + + try { + document.execCommand('copy'); + } catch (error) { + console.error('Error:', error); + } finally { + textArea.remove(); + } + } + } +}); \ No newline at end of file diff --git a/web/js/modal.js b/web/js/modal.js new file mode 100644 index 0000000..830dc43 --- /dev/null +++ b/web/js/modal.js @@ -0,0 +1,33 @@ +$(document).ready(function() { + // Cache jQuery objects for reuse + var modal = $("#myModal"); + var openBtn = $("#settings"); + var closeBtn = $("#closeModal, #closeModalButton"); + + // Function to open the modal + function openModal() { + modal.css("display", "block"); + setTimeout(function() { + modal.css("opacity", 1); + }, 10); + } + + // Function to close the modal + function closeModal() { + modal.css("opacity", 0); + setTimeout(function() { + modal.css("display", "none"); + }, 300); + } + + // Event listeners for the open and close buttons + openBtn.on("click", openModal); + closeBtn.on("click", closeModal); + + // Close the modal when clicking outside of it + $(document).on("click", function(event) { + if ($(event.target).is(modal)) { + closeModal(); + } + }); +}); \ No newline at end of file diff --git a/web/js/themes.js b/web/js/themes.js new file mode 100644 index 0000000..e834a26 --- /dev/null +++ b/web/js/themes.js @@ -0,0 +1,33 @@ +const themes = { + theme1: ['#1d1838', '#8069fa'], + theme2: ['#381818', '#ff7070'], + theme3: ['#121c0c', '#a9ff70'], + theme4: ['#0c1c1b', '#68f7ee'], + theme5: ['#171106', '#f5b642'], + theme6: ['#21091d', '#ed51d3'], + theme7: ['#111', '#aaa'] +}; + +function setTheme(themeName) { + const themeColors = themes[themeName]; + if (themeColors) { + $(':root').css('--color-main', themeColors[0]); + $(':root').css('--color-main-bright', themeColors[1]); + } +} + +$(document).ready(() => { + const themeSelector = $('#theme-selector'); + const savedTheme = localStorage.getItem('theme'); + + if (savedTheme && themes[savedTheme]) { + setTheme(savedTheme); + themeSelector.val(savedTheme); + } + + themeSelector.on('change', (event) => { + const selectedTheme = event.target.value; + setTheme(selectedTheme); + localStorage.setItem('theme', selectedTheme); + }); +}); \ No newline at end of file diff --git a/web/js/webserver.js b/web/js/webserver.js new file mode 100644 index 0000000..35f3df4 --- /dev/null +++ b/web/js/webserver.js @@ -0,0 +1,3 @@ +$.getScript('/js/main.js'); +$.getScript('/js/modal.js'); +$.getScript('/js/themes.js'); \ No newline at end of file diff --git a/web/main.js b/web/main.js deleted file mode 100644 index c88bfd4..0000000 --- a/web/main.js +++ /dev/null @@ -1,406 +0,0 @@ -const hostParts = window.location.host.split(':'); -const hostname = hostParts[0]; // Extract the hostname -const port = hostParts[1] || '8080'; // Extract the port or use a default (e.g., 8080) -const socketAddress = `ws://${hostname}:${port}/text`; // Use 'wss' for secure WebSocket connections (recommended for external access) -const socket = new WebSocket(socketAddress); - -const dataContainer = document.querySelector('#data-container'); -const canvas = document.querySelector('#signal-canvas'); -const context = canvas.getContext('2d'); - -var signalToggle = document.getElementById("signal-units-toggle"); - -canvas.width = canvas.parentElement.clientWidth; - -const data = []; -const maxDataPoints = 250; -const pointWidth = (canvas.width - 80) / maxDataPoints; - -var europe_programmes = [ - "No PTY", "News", "Current Affairs", "Info", - "Sport", "Education", "Drama", "Culture", "Science", "Varied", - "Pop M", "Rock M", "Easy Listening", "Light Classical", - "Serious Classical", "Other Music", "Weather", "Finance", - "Children's Programmes", "Social Affairs", "Religion", "Phone-in", - "Travel", "Leisure", "Jazz Music", "Country Music", "National Music", - "Oldies Music", "Folk Music", "Documentary", "Alarm Test" -]; - -// Function to handle zoom in -function zoomIn() { - zoomMinValue *= 0.9; - zoomMaxValue *= 0.9; -} - -// Function to handle zoom out -function zoomOut() { - zoomMinValue *= 1.1; - zoomMaxValue *= 1.1; -} - -function getInitialSettings() { - fetch('/static_data') - .then(response => response.json()) - .then(data => { - // Use the received data (data.qthLatitude, data.qthLongitude) as needed - localStorage.setItem('qthLatitude', data.qthLatitude); - localStorage.setItem('qthLongitude', data.qthLongitude); - localStorage.setItem('webServerName', data.webServerName); - - document.title = 'FM-DX Webserver [' + data.webServerName + ']'; - }) - .catch(error => console.error('Error:', error)); -} - -getInitialSettings(); - -function updateCanvas() { - // Remove old data when it exceeds the maximum data points - - const color2 = getComputedStyle(document.documentElement).getPropertyValue('--color-2').trim(); - const color4 = getComputedStyle(document.documentElement).getPropertyValue('--color-4').trim(); - - while (data.length >= maxDataPoints) { - data.shift(); - } - // Modify the WebSocket onmessage callback - socket.onmessage = (event) => { - const parsedData = JSON.parse(event.data); - - updatePanels(parsedData); - // Push the new signal data to the array - data.push(parsedData.signal); - const actualLowestValue = Math.min(...data); - const actualHighestValue = Math.max(...data); - zoomMinValue = actualLowestValue - ((actualHighestValue - actualLowestValue) / 2); - zoomMaxValue = actualHighestValue + ((actualHighestValue - actualLowestValue) / 2); - zoomAvgValue = (zoomMaxValue - zoomMinValue) / 2 + zoomMinValue; - - // Clear the canvas - context.clearRect(0, 0, canvas.width, canvas.height); - - // Draw the signal graph with zoom - context.beginPath(); - context.moveTo(50, canvas.height - (data[0] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue))); - - for (let i = 1; i < data.length; i++) { - const x = i * pointWidth; - const y = canvas.height - (data[i] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); - context.lineTo(x + 40, y); - } - - context.strokeStyle = color4; - context.lineWidth = 1; - context.stroke(); - - // Draw horizontal lines for lowest, highest, and average values - context.strokeStyle = color2; // Set line color - context.lineWidth = 1; - - // Draw the lowest value line - const lowestY = canvas.height - (zoomMinValue - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); - context.beginPath(); - context.moveTo(40, lowestY - 18); - context.lineTo(canvas.width - 40, lowestY - 18); - context.stroke(); - - // Draw the highest value line - const highestY = canvas.height - (zoomMaxValue - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); - context.beginPath(); - context.moveTo(40, highestY + 10); - context.lineTo(canvas.width - 40, highestY + 10); - context.stroke(); - - const avgY = canvas.height / 2; - context.beginPath(); - context.moveTo(40, avgY - 7); - context.lineTo(canvas.width - 40, avgY - 7); - context.stroke(); - - // Label the lines with their values - context.fillStyle = color4; - context.font = '12px Titillium Web'; - - const offset = signalToggle.checked ? 11.75 : 0; - context.textAlign = 'right'; - context.fillText(`${(zoomMinValue - offset).toFixed(1)}`, 35, lowestY - 14); - context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, 35, highestY + 14); - context.fillText(`${(zoomAvgValue - offset).toFixed(1)}`, 35, avgY - 3); - - context.textAlign = 'left'; - context.fillText(`${(zoomMinValue - offset).toFixed(1)}`, canvas.width - 35, lowestY - 14); - context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, canvas.width - 35, highestY + 14); - context.fillText(`${(zoomAvgValue - offset).toFixed(1)}`, canvas.width - 35, avgY - 3); - - // Update the data container with the latest data - dataContainer.innerHTML = event.data + '
    '; - }; - - requestAnimationFrame(updateCanvas); -} - -// Start updating the canvas -updateCanvas(); - -function compareNumbers(a, b) { - return a - b; -} - -function divideByHundred(a) { - a = a / 100; -} - -function escapeHTML(unsafeText) { - let div = document.createElement('div'); - div.innerText = unsafeText; - return div.innerHTML.replace(' ', ' '); -} - -function processString(string, errors) { - var output = ''; - const max_alpha = 70; - const alpha_range = 50; - const max_error = 10; - errors = errors.split(','); - - for (let i = 0; i < string.length; i++) { - alpha = parseInt(errors[i]) * (alpha_range / (max_error + 1)); - if (alpha) { - output += "" + escapeHTML(string[i]) + ""; - } else { - output += escapeHTML(string[i]); - } - } - - return output; -} - -function updatePanels(parsedData) { - // Assuming sortedAf is your array - const sortedAf = parsedData.af.sort(compareNumbers); - - // Convert the values in the array (dividing by 1000) - const scaledArray = sortedAf.map(element => element / 1000); - - // Get the container element where you want to display the list - const listContainer = document.querySelector('#af-list'); - - // Preserve the current scroll position - const scrollTop = listContainer.scrollTop; - - // Get the existing ul element - const ul = listContainer.querySelector('ul'); - - // If ul doesn't exist, create a new one - if (!ul) { - ul = document.createElement('ul'); - listContainer.appendChild(ul); - } - - // Remove existing list items - ul.innerHTML = ''; - - // Create an array of list items - const listItems = scaledArray.map(element => { - const li = document.createElement('li'); - li.textContent = element.toFixed(1); - return li; - }); - - // Append the list items to the unordered list - listItems.forEach(li => ul.appendChild(li)); - - // Restore the scroll position - listContainer.scrollTop = scrollTop; - document.querySelector('#data-frequency').textContent = parsedData.freq; - document.querySelector('#data-pi').innerHTML = parsedData.pi === '?' ? "?" : parsedData.pi; - document.querySelector('#data-ps').innerHTML = parsedData.ps === '?' ? "?" : processString(parsedData.ps, parsedData.ps_errors); - document.querySelector('#data-tp').innerHTML = parsedData.tp === false ? "TP" : "TP"; - document.querySelector('#data-pty').innerHTML = europe_programmes[parsedData.pty]; - document.querySelector('#data-st').innerHTML = parsedData.st === false ? "ST" : "ST"; - document.querySelector('#data-rt0').innerHTML = processString(parsedData.rt0, parsedData.rt0_errors); - document.querySelector('#data-rt1').innerHTML = processString(parsedData.rt1, parsedData.rt1_errors); - - document.querySelector('#data-flag').innerHTML = ''; - - const signalValue = signalToggle.checked ? (parsedData.signal - 11.75) : parsedData.signal; - const integerPart = Math.floor(signalValue); - const decimalPart = (signalValue - integerPart).toFixed(1).slice(1); // Adjusted this line - - document.querySelector('#data-signal').textContent = integerPart; - document.querySelector('#data-signal-decimal').textContent = decimalPart; - document.querySelector('#users-online').textContent = parsedData.users; -} - -signalToggle.addEventListener("change", function() { - signalText = document.querySelector('#signal-units'); - if (signalToggle.checked) { - signalText.textContent = 'dBµV'; - } else { - // Checkbox is unchecked - signalText.textContent = 'dBf'; - } -}); - -const textInput = document.getElementById('commandinput'); - -textInput.addEventListener('change', function (event) { - const inputValue = textInput.value; - // Check if the user agent contains 'iPhone' - if (/iPhone/i.test(navigator.userAgent) && socket.readyState === WebSocket.OPEN) { - socket.send(inputValue); - // Clear the input field if needed - textInput.value = ''; - } -}); - -textInput.addEventListener('keyup', function (event) { - // Check if the pressed key is 'Backspace' (key code 8) - if (event.key !== 'Backspace') { - // Get the current input value - let inputValue = textInput.value; - - // Remove non-digit characters (excluding dot) - inputValue = inputValue.replace(/[^0-9.]/g, ''); - - // Remove the last dot if there are two consecutive dots - if (inputValue.includes("..")) { - inputValue = inputValue.slice(0, inputValue.lastIndexOf('.')) + inputValue.slice(inputValue.lastIndexOf('.') + 1); - textInput.value = inputValue; - } - - // Determine where to add the dot based on the frequency range - if (!inputValue.includes(".")) { - if (inputValue.startsWith('10') && inputValue.length > 2) { - // For frequencies starting with '10', add the dot after the third digit - inputValue = inputValue.slice(0, 3) + '.' + inputValue.slice(3); - textInput.value = inputValue; - } else if (inputValue.length > 2) { - // For other frequencies, add the dot after the second digit - inputValue = inputValue.slice(0, 2) + '.' + inputValue.slice(2); - textInput.value = inputValue; - } - } - } - - // Update the input value - - // Check if the pressed key is 'Enter' (key code 13) - if (event.key === 'Enter') { - // Retrieve the input value - const inputValue = textInput.value; - - // Send the input value to the WebSocket - if (socket.readyState === WebSocket.OPEN) { - socket.send(inputValue); - } - - // Clear the input field if needed - textInput.value = ''; - } -}); - -document.onkeydown = checkKey; - -function checkKey(e) { - e = e || window.event; - - getCurrentFreq(); - - if (socket.readyState === WebSocket.OPEN) { - if (e.keyCode == '38') { - socket.send((currentFreq + 0.01).toFixed(2)); - } - else if (e.keyCode == '40') { - socket.send((currentFreq - 0.01).toFixed(2)); - } - else if (e.keyCode == '37') { - socket.send((currentFreq - 0.10).toFixed(1)); - } - else if (e.keyCode == '39') { - socket.send((currentFreq + 0.10).toFixed(1)); - } - } -} - -function getCurrentFreq() { - currentFreq = document.getElementById("data-frequency").textContent; - currentFreq = parseFloat(currentFreq).toFixed(3); - currentFreq = parseFloat(currentFreq); - - return currentFreq; -} - -freqUpButton = document.getElementById('freq-up'); -freqDownButton = document.getElementById('freq-down'); -psContainer = document.getElementById('ps-container'); -piCodeContainer = document.getElementById('pi-code-container'); -freqContainer = document.getElementById('freq-container'); - -freqUpButton.addEventListener("click", tuneUp); -freqDownButton.addEventListener("click", tuneDown); -psContainer.addEventListener("click", copyPs); -piCodeContainer.addEventListener("click", findOnMaps); -freqContainer.addEventListener("click", function() { - textInput.focus(); -}); - -function tuneUp() { - if (socket.readyState === WebSocket.OPEN) { - getCurrentFreq(); - socket.send((currentFreq + 0.10).toFixed(1)); - } -} - -function tuneDown() { - if (socket.readyState === WebSocket.OPEN) { - getCurrentFreq(); - socket.send((currentFreq - 0.10).toFixed(1)); - } -} - -async function copyPs() { - let frequency = document.querySelector('#data-frequency').textContent; - let pi = document.querySelector('#data-pi').textContent; - let ps = document.querySelector('#data-ps').textContent; - let signal = document.querySelector('#data-signal').textContent; - let signalDecimal = document.querySelector('#data-signal-decimal').textContent; - let signalUnit = document.querySelector('#signal-units').textContent; - try { - await copyToClipboard(frequency + " - " + pi + " | " + ps + " [" + signal + signalDecimal + " " + signalUnit + "]"); - } catch(error) { - console.error(error); - } -} - -function findOnMaps() { - let frequency = document.querySelector('#data-frequency').textContent; - let pi = document.querySelector('#data-pi').textContent; - let latitude = localStorage.getItem('qthLongitude'); - let longitude = localStorage.getItem('qthLatitude'); - frequency = parseFloat(frequency).toFixed(1); - window.open("https://maps.fmdx.pl/#qth=" + longitude + "," + latitude + "&freq=" + frequency + "&pi=" + pi, "_blank"); -} - -async function copyToClipboard(textToCopy) { - // Navigator clipboard api needs a secure context (https) - if (navigator.clipboard && window.isSecureContext) { - await navigator.clipboard.writeText(textToCopy); - } else { - const textArea = document.createElement("textarea"); - textArea.value = textToCopy; - textArea.style.position = "absolute"; - textArea.style.left = "-999999px"; - - document.body.prepend(textArea); - textArea.select(); - - try { - document.execCommand('copy'); - } catch (error) { - console.error(error); - } finally { - textArea.remove(); - } - } -} diff --git a/web/modal.js b/web/modal.js deleted file mode 100644 index 33c1b80..0000000 --- a/web/modal.js +++ /dev/null @@ -1,33 +0,0 @@ -// Get the modal element and the buttons to open and close it -var modal = document.getElementById("myModal"); -var openBtn = document.getElementById("settings"); -var closeBtn = document.getElementById("closeModal"); -var closeBtnFull = document.getElementById("closeModalButton"); - -// Function to open the modal -function openModal() { - modal.style.display = "block"; - setTimeout(function() { - modal.style.opacity = 1; - }, 10); -} - -// Function to close the modal -function closeModal() { - modal.style.opacity = 0; - setTimeout(function() { - modal.style.display = "none"; - }, 300); // This delay should match the transition duration (0.3s). -} - -// Event listeners for the open and close buttons -openBtn.addEventListener("click", openModal); -closeBtn.addEventListener("click", closeModal); -closeBtnFull.addEventListener("click", closeModal); - -// Close the modal when clicking outside of it -window.addEventListener("click", function(event) { - if (event.target == modal) { - closeModal(); - } -}); \ No newline at end of file diff --git a/web/stream.js b/web/stream.js deleted file mode 100644 index 5143e6e..0000000 --- a/web/stream.js +++ /dev/null @@ -1,20 +0,0 @@ - const audioElement = document.getElementById("myAudio"); - const volumeSlider = document.getElementById("volumeSlider"); - const audioStream = "/audio-proxy"; - const uniqueTimestamp = Date.now(); // Create a unique timestamp - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const audioSource = audioContext.createMediaElementSource(audioElement); - - audioSource.connect(audioContext.destination); - - // Set the audio element's source to your external audio stream - audioElement.src = `${audioStream}?${uniqueTimestamp}`; - - - audioElement.play(); - - volumeSlider.addEventListener("input", (event) => { - event.stopPropagation(); - audioElement.volume = volumeSlider.value; - }); \ No newline at end of file diff --git a/web/themes.js b/web/themes.js deleted file mode 100644 index 5b2e8f8..0000000 --- a/web/themes.js +++ /dev/null @@ -1,57 +0,0 @@ -const themes = { - theme1: { - '--color-main': '#1d1838', - '--color-main-bright': '#8069fa', - }, - theme2: { - '--color-main': '#381818', - '--color-main-bright': '#ff7070', - }, - theme3: { - '--color-main': '#121c0c', - '--color-main-bright': '#a9ff70', - }, - theme4: { - '--color-main': '#0c1c1b', - '--color-main-bright': '#68f7ee', - }, - theme5: { - '--color-main': '#171106', - '--color-main-bright': '#f5b642', - }, - theme6: { - '--color-main': '#21091d', - '--color-main-bright': '#ed51d3', - }, - theme7: { - '--color-main': '#111', - '--color-main-bright': '#aaa', - } -}; - - -function setTheme(themeName) { - const theme = themes[themeName]; - if (theme) { - for (const [variable, value] of Object.entries(theme)) { - document.documentElement.style.setProperty(variable, value); - } - } -} - -// Get the dropdown element -const themeSelector = document.getElementById('theme-selector'); - -const savedTheme = localStorage.getItem("theme"); -if(savedTheme) { - setTheme(savedTheme); - themeSelector.value = savedTheme; -} - -// Listen for changes in the dropdown -themeSelector.addEventListener('change', (event) => { - const selectedTheme = event.target.value; - setTheme(selectedTheme); - localStorage.setItem("theme", selectedTheme); - -});