offline optimization, new signal chart, ui optimization

This commit is contained in:
Marek Farkaš
2025-02-23 15:27:46 +01:00
parent fcfc0691fa
commit 3c91c8d06c
24 changed files with 373 additions and 287 deletions
+39 -14
View File
@@ -84,16 +84,20 @@ function handleConnect(clientIp, currentUsers, ws, callback) {
} }
}); });
}).on("error", (err) => { }).on("error", (err) => {
console.error("Error fetching location data:", err); consoleCmd.logError("Error fetching location data:", err.code);
callback("User allowed"); callback("User allowed");
}); });
} }
function processConnection(clientIp, locationInfo, currentUsers, ws, callback) { let bannedASCache = { data: null, timestamp: 0 };
const options = { year: "numeric", month: "numeric", day: "numeric", hour: "2-digit", minute: "2-digit" };
const connectionTime = new Date().toLocaleString([], options);
https.get("https://fmdx.org/banned_as.json", (banResponse) => { function fetchBannedAS(callback) {
const now = Date.now();
if (bannedASCache.data && now - bannedASCache.timestamp < 10 * 60 * 1000) {
return callback(null, bannedASCache.data);
}
const req = https.get("https://fmdx.org/banned_as.json", { family: 4 }, (banResponse) => {
let banData = ""; let banData = "";
banResponse.on("data", (chunk) => { banResponse.on("data", (chunk) => {
@@ -103,6 +107,36 @@ function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
banResponse.on("end", () => { banResponse.on("end", () => {
try { try {
const bannedAS = JSON.parse(banData).banned_as || []; const bannedAS = JSON.parse(banData).banned_as || [];
bannedASCache = { data: bannedAS, timestamp: now };
callback(null, bannedAS);
} catch (error) {
console.error("Error parsing banned AS list:", error);
callback(null, []); // Default to allowing user
}
});
});
// Set timeout for the request (5 seconds)
req.setTimeout(5000, () => {
console.error("Error: Request timed out while fetching banned AS list.");
req.abort();
callback(null, []); // Default to allowing user
});
req.on("error", (err) => {
console.error("Error fetching banned AS list:", err);
callback(null, []); // Default to allowing user
});
}
function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
const options = { year: "numeric", month: "numeric", day: "numeric", hour: "2-digit", minute: "2-digit" };
const connectionTime = new Date().toLocaleString([], options);
fetchBannedAS((error, bannedAS) => {
if (error) {
console.error("Error fetching banned AS list:", error);
}
if (bannedAS.some((as) => locationInfo.as?.includes(as))) { if (bannedAS.some((as) => locationInfo.as?.includes(as))) {
return callback("User banned"); return callback("User banned");
@@ -124,19 +158,10 @@ function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${userLocation}` `Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${userLocation}`
); );
callback("User allowed");
} catch (error) {
console.error("Error parsing banned AS list:", error);
callback("User allowed");
}
});
}).on("error", (err) => {
console.error("Error fetching banned AS list:", err);
callback("User allowed"); callback("User allowed");
}); });
} }
function formatUptime(uptimeInSeconds) { function formatUptime(uptimeInSeconds) {
const secondsInMinute = 60; const secondsInMinute = 60;
const secondsInHour = secondsInMinute * 60; const secondsInHour = secondsInMinute * 60;
+1 -3
View File
@@ -469,9 +469,7 @@ wss.on('connection', (ws, request) => {
}); });
ws.on('close', (code, reason) => { ws.on('close', (code, reason) => {
if (clientIp !== '::ffff:127.0.0.1' || if (clientIp !== '::ffff:127.0.0.1' || (request.connection && request.connection.remoteAddress && request.connection.remoteAddress !== '::ffff:127.0.0.1') || (request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
(request.connection && request.connection.remoteAddress && request.connection.remoteAddress !== '::ffff:127.0.1') ||
(request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
currentUsers--; currentUsers--;
} }
dataHandler.showOnlineUsers(currentUsers); dataHandler.showOnlineUsers(currentUsers);
+26 -22
View File
@@ -13,8 +13,14 @@ let usStatesGeoJson = null; // To cache the GeoJSON data for US states
// Load the US states GeoJSON data // Load the US states GeoJSON data
async function loadUsStatesGeoJson() { async function loadUsStatesGeoJson() {
if (!usStatesGeoJson) { if (!usStatesGeoJson) {
try {
const response = await fetch(usStatesGeoJsonUrl); const response = await fetch(usStatesGeoJsonUrl);
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
usStatesGeoJson = await response.json(); usStatesGeoJson = await response.json();
} catch (error) {
console.error("Failed to load US States GeoJSON:", error);
usStatesGeoJson = null; // Ensure it's null so it can retry later
}
} }
} }
@@ -79,18 +85,17 @@ async function fetchTx(freq, piCode, rdsPs) {
const url = "https://maps.fmdx.org/api/?freq=" + freq; const url = "https://maps.fmdx.org/api/?freq=" + freq;
return fetch(url, { try {
redirect: 'manual' const response = await fetch(url, { redirect: 'manual' });
}) if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
.then(response => response.json()) const data = await response.json();
.then(async (data) => {
cachedData[freq] = data; cachedData[freq] = data;
await loadUsStatesGeoJson(); await loadUsStatesGeoJson();
return processData(data, piCode, rdsPs); return processData(data, piCode, rdsPs);
}) } catch (error) {
.catch(error => {
console.error("Error fetching data:", error); console.error("Error fetching data:", error);
}); return null; // Return null to indicate failure
}
} }
async function processData(data, piCode, rdsPs) { async function processData(data, piCode, rdsPs) {
@@ -182,29 +187,28 @@ function checkEs() {
let esSwitch = false; let esSwitch = false;
if (now - esSwitchCache.lastCheck < esFetchInterval) { if (now - esSwitchCache.lastCheck < esFetchInterval) {
esSwitch = esSwitchCache.esSwitch; return esSwitchCache.esSwitch;
} else if (serverConfig.identification.lat > 20) { }
if (serverConfig.identification.lat > 20) {
esSwitchCache.lastCheck = now; esSwitchCache.lastCheck = now;
fetch(url) fetch(url)
.then(response => response.json()) .then(response => {
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
return response.json();
})
.then(data => { .then(data => {
if (serverConfig.identification.lon < -32) { if ((serverConfig.identification.lon < -32 && data.north_america.max_frequency !== "No data") ||
if (data.north_america.max_frequency != "No data") { (serverConfig.identification.lon >= -32 && data.europe.max_frequency !== "No data")) {
esSwitch = true; esSwitchCache.esSwitch = true;
} }
} else {
if (data.europe.max_frequency != "No data") {
esSwitch = true;
}
}
esSwitchCache.esSwitch = esSwitch;
}) })
.catch(error => { .catch(error => {
console.error("Error fetching data:", error); console.error("Error fetching Es data:", error);
}); });
} }
return esSwitch; return esSwitchCache.esSwitch;
} }
function haversine(lat1, lon1, lat2, lon2) { function haversine(lat1, lon1, lat2, lon2) {
+15 -2
View File
@@ -22,6 +22,15 @@ h1#tuner-name {
transition: 0.3s ease color; transition: 0.3s ease color;
} }
#tuner-name i {
transition: transform 0.3s ease;
}
#tuner-name i.rotated {
transform: rotate(180deg);
}
h1#tuner-name:hover { h1#tuner-name:hover {
color: var(--color-main-bright); color: var(--color-main-bright);
} }
@@ -75,9 +84,13 @@ label {
} }
.canvas-container { .canvas-container {
width: 100%; width: calc(100% - 20px);
height: 175px; height: 140px;
overflow: hidden; overflow: hidden;
border-radius: 15px;
margin-left: 10px;
margin-right: 10px;
position: relative;
} }
#data-ant { #data-ant {
+9
View File
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+13 -25
View File
@@ -4,8 +4,12 @@
<title><%= tunerName %> - FM-DX Webserver</title> <title><%= tunerName %> - FM-DX Webserver</title>
<link href="css/entry.css" type="text/css" rel="stylesheet"> <link href="css/entry.css" type="text/css" rel="stylesheet">
<link href="css/flags.min.css" type="text/css" rel="stylesheet"> <link href="css/flags.min.css" type="text/css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css" rel="stylesheet"> <link href="css/libs/fontawesome.css" type="text/css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="js/libs/jquery.min.js"></script>
<script src="js/libs/chart.umd.min.js"></script>
<script src="js/libs/luxon.min.js"></script>
<script src="js/libs/chartjs-adapter-luxon.umd.min.js"></script>
<script src="js/libs/chartjs-plugin-streaming.min.js"></script>
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/png" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -39,7 +43,7 @@
<div class="wrapper-outer dashboard-panel" style="padding-top: 20px; z-index: 10; position: relative;"> <div class="wrapper-outer dashboard-panel" style="padding-top: 20px; z-index: 10; position: relative;">
<div class="panel-100-real m-0 flex-container bg-phone flex-phone-column" style="min-height: 64px; max-width: 1160px; margin-top: 10px;align-items: center; justify-content: space-between; padding-left: 20px;padding-right: 10px;"> <div class="panel-100-real m-0 flex-container bg-phone flex-phone-column" style="min-height: 64px; max-width: 1160px; margin-top: 10px;align-items: center; justify-content: space-between; padding-left: 20px;padding-right: 10px;">
<h1 id="tuner-name" class="text-left flex-container flex-phone flex-center" style="padding-bottom: 3px; padding-right: 5px;height: 64px;"> <h1 id="tuner-name" class="text-left flex-container flex-phone flex-center" style="padding-bottom: 3px; padding-right: 5px;height: 64px;">
<span class="text-200-px" style="max-width: 450px;"><%= tunerName %></span> <i class="fa-solid fa-chevron-down p-left-10" style="font-size: 15px;"></i> <span class="text-200-px" style="max-width: 450px;padding-right: 10px;"><%= tunerName %></span> <i class="fa-solid fa-chevron-down" style="font-size: 15px;"></i>
</h1> </h1>
<% if(!publicTuner || tunerLock) { %> <% if(!publicTuner || tunerLock) { %>
<div class="tuner-status p-10 color-3"> <div class="tuner-status p-10 color-3">
@@ -109,26 +113,15 @@
<div style="width: 1px;background: var(--color-2);" class="m-10 hide-phone"></div> <div style="width: 1px;background: var(--color-2);" class="m-10 hide-phone"></div>
<div> <div>
<% const presets = [1, 2, 3, 4]; %>
<div style="height: 64px;" class="flex-center flex-phone"> <div style="height: 64px;" class="flex-center flex-phone">
<button class="no-bg color-4 hover-brighten" id="preset1" style="padding: 6px; width: 64px; min-width: 64px;"> <% presets.forEach(preset => { %>
<button class="no-bg color-4 hover-brighten" id="preset<%= preset %>" style="padding: 6px; width: 64px; min-width: 64px;">
<i class="fa-solid fa-wave-square fa-lg top-10"></i><br> <i class="fa-solid fa-wave-square fa-lg top-10"></i><br>
<span style="font-size: 10px; color: var(--color-text);" id="preset1-text"></span> <span style="font-size: 10px; color: var(--color-text);" id="preset<%= preset %>-text"></span>
</button>
<button class="no-bg color-4 hover-brighten" id="preset2" style="padding: 6px; width: 64px; min-width: 64px;">
<i class="fa-solid fa-wave-square fa-lg top-10"></i><br>
<span style="font-size: 10px; color: var(--color-text);" id="preset2-text"></span>
</button>
<button class="no-bg color-4 hover-brighten" id="preset3" style="padding: 6px; width: 64px; min-width: 64px;">
<i class="fa-solid fa-wave-square fa-lg top-10"></i><br>
<span style="font-size: 10px; color: var(--color-text);" id="preset3-text"></span>
</button>
<button class="no-bg color-4 hover-brighten" id="preset4" style="padding: 6px; width: 64px; min-width: 64px;">
<i class="fa-solid fa-wave-square fa-lg top-10"></i><br>
<span style="font-size: 10px; color: var(--color-text);" id="preset4-text"></span>
</button> </button>
<% }); %>
</div> </div>
<div class="flex-container flex-phone" style="align-items: center; height: 64px;"> <div class="flex-container flex-phone" style="align-items: center; height: 64px;">
@@ -440,11 +433,6 @@
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'RDS PS Underscores', id: 'ps-underscores'}) %> <%- include('_components', {component: 'checkbox', cssClass: '', label: 'RDS PS Underscores', id: 'ps-underscores'}) %>
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Imperial units', id: 'imperial-units'}) %> <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Imperial units', id: 'imperial-units'}) %>
<div class="form-group bottom-20 hide-desktop" style="float: none;">
<label for="users-online"><i class="fa-solid fa-user"></i> Users online</label>
<span class="users-online" name="users-online">0</span>
</div>
<% if (isAdminAuthenticated) { %> <% if (isAdminAuthenticated) { %>
<p class="color-3">You are logged in as an adminstrator.</p> <p class="color-3">You are logged in as an adminstrator.</p>
<div class="admin-quick-dashboard"> <div class="admin-quick-dashboard">
+8
View File
@@ -46,3 +46,11 @@ function tuneTo(freq) {
function resetRDS() { function resetRDS() {
socket.send("T0"); socket.send("T0");
} }
function getCurrentFreq() {
currentFreq = $('#data-frequency').text();
currentFreq = parseFloat(currentFreq).toFixed(3);
currentFreq = parseFloat(currentFreq);
return currentFreq;
}
+1 -1
View File
@@ -59,7 +59,7 @@ $(document).ready(function() {
chatNicknameSave.click(function() { chatNicknameSave.click(function() {
const currentNickname = chatNicknameInput.val().trim() || `Anonymous User ${generateRandomString(5)}`; const currentNickname = chatNicknameInput.val().trim() || `Anonymous User ${generateRandomString(5)}`;
localStorage.setItem('nickname', currentNickname); localStorage.setItem('nickname', currentNickname);
savedNickname = currentNickname; // Update the savedNickname variable savedNickname = currentNickname;
chatIdentityNickname.text(savedNickname); chatIdentityNickname.text(savedNickname);
chatNicknameInput.blur(); chatNicknameInput.blur();
}); });
+2 -2
View File
@@ -1,9 +1,9 @@
var currentDate = new Date('Feb 16, 2025 15:00:00'); var currentDate = new Date('Feb 23, 2025 15:30:00');
var day = currentDate.getDate(); var day = currentDate.getDate();
var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1 var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1
var year = currentDate.getFullYear(); var year = currentDate.getFullYear();
var formattedDate = day + '/' + month + '/' + year; var formattedDate = day + '/' + month + '/' + year;
var currentVersion = 'v1.3.5 [' + formattedDate + ']'; var currentVersion = 'v1.3.6 [' + formattedDate + ']';
getInitialSettings(); getInitialSettings();
removeUrlParameters(); removeUrlParameters();
+20
View File
File diff suppressed because one or more lines are too long
+7
View File
@@ -0,0 +1,7 @@
/*!
* chartjs-adapter-luxon v1.3.1
* https://www.chartjs.org
* (c) 2023 chartjs-adapter-luxon Contributors
* Released under the MIT license
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("chart.js"),require("luxon")):"function"==typeof define&&define.amd?define(["chart.js","luxon"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Chart,e.luxon)}(this,(function(e,t){"use strict";const n={datetime:t.DateTime.DATETIME_MED_WITH_SECONDS,millisecond:"h:mm:ss.SSS a",second:t.DateTime.TIME_WITH_SECONDS,minute:t.DateTime.TIME_SIMPLE,hour:{hour:"numeric"},day:{day:"numeric",month:"short"},week:"DD",month:{month:"short",year:"numeric"},quarter:"'Q'q - yyyy",year:{year:"numeric"}};e._adapters._date.override({_id:"luxon",_create:function(e){return t.DateTime.fromMillis(e,this.options)},init(e){this.options.locale||(this.options.locale=e.locale)},formats:function(){return n},parse:function(e,n){const i=this.options,r=typeof e;return null===e||"undefined"===r?null:("number"===r?e=this._create(e):"string"===r?e="string"==typeof n?t.DateTime.fromFormat(e,n,i):t.DateTime.fromISO(e,i):e instanceof Date?e=t.DateTime.fromJSDate(e,i):"object"!==r||e instanceof t.DateTime||(e=t.DateTime.fromObject(e,i)),e.isValid?e.valueOf():null)},format:function(e,t){const n=this._create(e);return"string"==typeof t?n.toFormat(t):n.toLocaleString(t)},add:function(e,t,n){const i={};return i[n]=t,this._create(e).plus(i).valueOf()},diff:function(e,t,n){return this._create(e).diff(this._create(t)).as(n).valueOf()},startOf:function(e,t,n){if("isoWeek"===t){n=Math.trunc(Math.min(Math.max(0,n),6));const t=this._create(e);return t.minus({days:(t.weekday-n+7)%7}).startOf("day").valueOf()}return t?this._create(e).startOf(t).valueOf():e},endOf:function(e,t){return this._create(e).endOf(t).valueOf()}})}));
File diff suppressed because one or more lines are too long
+2
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+147 -157
View File
@@ -3,8 +3,8 @@
var parsedData, signalChart, previousFreq; var parsedData, signalChart, previousFreq;
var signalData = [];
var data = []; var data = [];
var signalData = [];
let updateCounter = 0; let updateCounter = 0;
let messageCounter = 0; // Count for WebSocket data length returning 0 let messageCounter = 0; // Count for WebSocket data length returning 0
let messageData = 800; // Initial value anything above 0 let messageData = 800; // Initial value anything above 0
@@ -32,7 +32,7 @@ const usa_programmes = [
const rdsMode = localStorage.getItem('rdsMode'); const rdsMode = localStorage.getItem('rdsMode');
$(document).ready(function () { $(document).ready(function () {
var canvas = $('#signal-canvas')[0]; const signalToggle = $("#signal-units-toggle");
var $panel = $('.admin-quick-dashboard'); var $panel = $('.admin-quick-dashboard');
var panelWidth = $panel.outerWidth(); var panelWidth = $panel.outerWidth();
@@ -48,11 +48,6 @@ $(document).ready(function () {
} }
}); });
canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight;
// Start updating the canvas
initCanvas();
fillPresets(); fillPresets();
signalToggle.on("change", function () { signalToggle.on("change", function () {
@@ -205,7 +200,6 @@ $(document).ready(function () {
$(freqContainer).on("click", function () { $(freqContainer).on("click", function () {
textInput.focus(); textInput.focus();
}); });
initTooltips();
//FMLIST logging //FMLIST logging
$('.popup-content').on('click', function(event) { $('.popup-content').on('click', function(event) {
@@ -274,6 +268,8 @@ $(document).ready(function () {
} }
}); });
initCanvas();
initTooltips();
}); });
function getServerTime() { function getServerTime() {
@@ -294,29 +290,21 @@ function getServerTime() {
const serverOptions = { const serverOptions = {
...options, ...options,
timeZone: 'Etc/UTC' // Add timeZone only for server time timeZone: 'Etc/UTC'
}; };
const formattedServerTime = new Date(serverTimeUtc).toLocaleString(navigator.language ? navigator.language : 'en-US', serverOptions); const formattedServerTime = new Date(serverTimeUtc).toLocaleString(navigator.language ? navigator.language : 'en-US', serverOptions);
$("#server-time").text(formattedServerTime); $("#server-time").text(formattedServerTime);
// Get and format user's local time directly without specifying timeZone:
const localTime = new Date();
const formattedLocalTime = new Date(localTime).toLocaleString(navigator.language ? navigator.language : 'en-US', options);
// Display client time:
$("#client-time").text(formattedLocalTime);
}, },
error: function(jqXHR, textStatus, errorThrown) { error: function(jqXHR, textStatus, errorThrown) {
console.error("Error fetching server time:", errorThrown); console.error("Error fetching server time:", errorThrown);
// Handle error gracefully (e.g., display a fallback message)
} }
}); });
} }
function sendPingRequest() { function sendPingRequest() {
const timeoutDuration = 15000; // Ping response can become buggy if it exceeds 20 seconds const timeoutDuration = 5000;
const startTime = new Date().getTime(); const startTime = new Date().getTime();
const fetchWithTimeout = (url, options, timeout = timeoutDuration) => { const fetchWithTimeout = (url, options, timeout = timeoutDuration) => {
@@ -407,7 +395,6 @@ function sendPingRequest() {
} }
} }
// Automatic UI resume on WebSocket reconnect
function handleWebSocketMessage(event) { function handleWebSocketMessage(event) {
if (event.data == 'KICK') { if (event.data == 'KICK') {
console.log('Kick initiated.') console.log('Kick initiated.')
@@ -429,151 +416,161 @@ function handleWebSocketMessage(event) {
// Attach the message handler // Attach the message handler
socket.onmessage = handleWebSocketMessage; socket.onmessage = handleWebSocketMessage;
function initCanvas(parsedData) { const signalBuffer = [];
signalToggle = $("#signal-units-toggle");
// Check if signalChart is already initialized function initCanvas() {
if (!signalChart) { const ctx = document.getElementById("signal-canvas").getContext("2d");
const canvas = $('#signal-canvas')[0];
const context = canvas.getContext('2d');
const maxDataPoints = 300;
const pointWidth = (canvas.width - 80) / maxDataPoints;
window.signalChart = new Chart(ctx, {
type: "line",
data: {
datasets: [{
label: "Signal Strength",
borderColor: () => getComputedStyle(document.documentElement).getPropertyValue("--color-4").trim(),
borderWidth: 2,
fill: {
target: 'start'
},
backgroundColor: () => getComputedStyle(document.documentElement).getPropertyValue("--color-1-transparent").trim(),
tension: 0.6,
data: []
}]
},
options: {
layout: {
padding: {
left: -10,
right: -10,
bottom: -10
},
},
responsive: true,
maintainAspectRatio: false,
elements: {
point: { radius: 0 },
},
scales: {
x: {
type: "realtime",
ticks: { display: false },
border: { display: false },
grid: { display: false, borderWidth: 0, borderColor: "transparent" },
realtime: {
duration: 30000,
refresh: 75,
delay: 150,
onRefresh: (chart) => {
if (!chart?.data?.datasets || parsedData?.sig === undefined) return;
signalChart = { signalBuffer.push(parsedData.sig);
canvas, if (signalBuffer.length > 8) {
context, signalBuffer.shift();
parsedData,
maxDataPoints,
pointWidth,
color2: null,
color3: null,
color4: null,
signalUnit: localStorage.getItem('signalUnit'),
offset: 0,
};
switch(signalChart.signalUnit) {
case 'dbuv': signalChart.offset = 11.25; break;
case 'dbm': signalChart.offset = 120; break;
default: signalChart.offset = 0;
} }
// Initialize colors and signal unit const avgSignal = signalBuffer.reduce((sum, val) => sum + val, 0) / signalBuffer.length;
updateChartSettings(signalChart);
// Periodically check for color and signal unit updates chart.data.datasets[0].data.push({
setInterval(() => { x: Date.now(),
updateChartSettings(signalChart); y: avgSignal
}, 1000); // Check every 1 second });
}
updateCanvas(parsedData, signalChart);
}
function updateChartSettings(signalChart) {
// Update colors
const newColor2 = getComputedStyle(document.documentElement).getPropertyValue('--color-2').trim();
const newColor3 = getComputedStyle(document.documentElement).getPropertyValue('--color-3').trim();
const newColor4 = getComputedStyle(document.documentElement).getPropertyValue('--color-4').trim();
if (newColor2 !== signalChart.color2 || newColor4 !== signalChart.color4) {
signalChart.color2 = newColor2;
signalChart.color3 = newColor3;
signalChart.color4 = newColor4;
}
// Update signal unit
const newSignalUnit = localStorage.getItem('signalUnit');
if (newSignalUnit !== signalChart.signalUnit) {
signalChart.signalUnit = newSignalUnit;
// Adjust the offset based on the new signal unit
switch(newSignalUnit) {
case 'dbuv': signalChart.offset = 11.25; break;
case 'dbm': signalChart.offset = 120; break;
default: signalChart.offset = 0;
} }
} }
} },
y: {
function updateCanvas(parsedData, signalChart) { beginAtZero: false,
const { context, canvas, maxDataPoints, pointWidth, color2, color3, color4, offset } = signalChart; grace: 0.25,
border: { display: false },
if (data.length > maxDataPoints) { ticks: {
data = data.slice(data.length - maxDataPoints); maxTicksLimit: 3,
} display: false // Hide default labels
},
const actualLowestValue = Math.min(...data); grid: {
const actualHighestValue = Math.max(...data); display: false, // Hide default grid lines
const zoomMinValue = actualLowestValue - ((actualHighestValue - actualLowestValue) / 2); },
const zoomMaxValue = actualHighestValue + ((actualHighestValue - actualLowestValue) / 2); },
const zoomAvgValue = (zoomMaxValue - zoomMinValue) / 2 + zoomMinValue; y2: {
position: 'right', // Position on the right side
// Clear the canvas beginAtZero: false,
context.clearRect(0, 0, canvas.width, canvas.height); grace: 0.25,
context.beginPath(); border: { display: false },
ticks: {
const startingIndex = Math.max(0, data.length - maxDataPoints); maxTicksLimit: 3,
display: false // Hide default labels for the right axis
for (let i = startingIndex; i < data.length; i++) { },
const x = canvas.width - (data.length - i) * pointWidth - 40; grid: {
const y = canvas.height - (data[i] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); display: false, // No grid for right axis
if (i === startingIndex) {
context.moveTo(x, y);
} else {
const prevX = canvas.width - (data.length - i + 1) * pointWidth - 40;
const prevY = canvas.height - (data[i - 1] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue));
const interpolatedX = (x + prevX) / 2;
const interpolatedY = (y + prevY) / 2;
context.quadraticCurveTo(prevX, prevY, interpolatedX, interpolatedY);
} }
} }
},
plugins: {
legend: { display: false },
tooltip: { enabled: false }
}
},
plugins: [{
id: 'customYAxisLabels',
afterDraw: (chart) => {
const { ctx, scales, chartArea } = chart;
const yAxis = scales.y;
const y2Axis = scales.y2;
context.strokeStyle = color4; const gridLineColor = getComputedStyle(document.documentElement).getPropertyValue("--color-2-transparent").trim(); // Grid color using CSS variable
context.lineWidth = 2; const textColor = getComputedStyle(document.documentElement).getPropertyValue("--color-3-transparent").trim(); // Use the same color for text labels
context.stroke();
// Draw horizontal lines for lowest, highest, and average values ctx.save();
context.strokeStyle = color3; ctx.font = "12px Titillium Web";
context.lineWidth = 1; ctx.fillStyle = textColor;
ctx.textAlign = "center";
// Draw the lowest value line const leftX = yAxis.left + 20;
const lowestY = canvas.height - (zoomMinValue - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); const rightX = y2Axis.right - 20;
context.beginPath();
context.moveTo(40, lowestY - 18);
context.lineTo(canvas.width - 40, lowestY - 18);
context.stroke();
// Draw the highest value line const offset = 10;
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; yAxis.ticks.forEach((tick, index) => {
context.beginPath(); const y = yAxis.getPixelForValue(tick.value);
context.moveTo(40, avgY - 7); var adjustedY = Math.max(yAxis.top + 13, Math.min(y, yAxis.bottom - 6));
context.lineTo(canvas.width - 40, avgY - 7); const isMiddleTick = index === Math.floor(yAxis.ticks.length / 2);
context.stroke();
// Label the lines with their values let adjustedTickValue;
context.fillStyle = color4; switch(localStorage.getItem("signalUnit")) {
context.font = '12px Titillium Web'; case "dbuv": adjustedTickValue = tick.value - 11.25; break;
case "dbm": adjustedTickValue = tick.value - -120; break;
default: adjustedTickValue = tick.value; break;
}
context.textAlign = 'right'; if (isMiddleTick) { adjustedY += 3; }
context.fillText(`${(zoomMinValue - offset).toFixed(1)}`, 35, lowestY - 14); ctx.textAlign = 'right';
context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, 35, highestY + 14); ctx.fillText(adjustedTickValue.toFixed(1), leftX + 25, adjustedY);
context.fillText(`${(zoomAvgValue - offset).toFixed(1)}`, 35, avgY - 3);
context.textAlign = 'left'; ctx.textAlign = 'left';
context.fillText(`${(zoomMinValue - offset).toFixed(1)}`, canvas.width - 35, lowestY - 14); ctx.fillText(adjustedTickValue.toFixed(1), rightX - 25, adjustedY); // Right side
context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, canvas.width - 35, highestY + 14); });
context.fillText(`${(zoomAvgValue - offset).toFixed(1)}`, canvas.width - 35, avgY - 3);
setTimeout(() => { const gridLineWidth = 0.5; // Make the lines thinner to avoid overlapping text
requestAnimationFrame(() => updateCanvas(parsedData, signalChart)); const adjustedGridTop = chartArea.top + offset;
}, 1000 / 15); const adjustedGridBottom = chartArea.bottom - offset;
const middleY = chartArea.top + chartArea.height / 2;
const padding = 45; // 30px inward on both sides
// Helper function to draw a horizontal line
function drawGridLine(y) {
ctx.beginPath();
ctx.moveTo(chartArea.left + padding, y);
ctx.lineTo(chartArea.right - padding, y);
ctx.strokeStyle = gridLineColor;
ctx.lineWidth = gridLineWidth;
ctx.stroke();
}
// Draw the three horizontal grid lines
drawGridLine(adjustedGridTop);
drawGridLine(adjustedGridBottom);
drawGridLine(middleY);
ctx.restore();
}
}]
});
} }
let reconnectTimer = null; let reconnectTimer = null;
@@ -641,14 +638,6 @@ function processString(string, errors) {
return output; return output;
} }
function getCurrentFreq() {
currentFreq = $('#data-frequency').text();
currentFreq = parseFloat(currentFreq).toFixed(3);
currentFreq = parseFloat(currentFreq);
return currentFreq;
}
function checkKey(e) { function checkKey(e) {
e = e || window.event; e = e || window.event;
@@ -899,14 +888,12 @@ function throttle(fn, wait) {
return wrapper; return wrapper;
} }
// Utility function to update element's text if changed
function updateTextIfChanged($element, newText) { function updateTextIfChanged($element, newText) {
if ($element.text() !== newText) { if ($element.text() !== newText) {
$element.text(newText); $element.text(newText);
} }
} }
// Utility function to update element's HTML content if changed
function updateHtmlIfChanged($element, newHtml) { function updateHtmlIfChanged($element, newHtml) {
if ($element.html() !== newHtml) { if ($element.html() !== newHtml) {
$element.html(newHtml); $element.html(newHtml);
@@ -1005,7 +992,7 @@ const updateDataElements = throttle(function(parsedData) {
$dataRt1.attr('aria-label', parsedData.rt1); $dataRt1.attr('aria-label', parsedData.rt1);
$('#users-online-container').attr("aria-label", "Online users: " + parsedData.users); $('#users-online-container').attr("aria-label", "Online users: " + parsedData.users);
} }
}, 100); // Update at most once every 100 milliseconds }, 75); // Update at most once every 100 milliseconds
let isEventListenerAdded = false; let isEventListenerAdded = false;
@@ -1115,8 +1102,11 @@ function showTunerDescription() {
} }
}); });
$("#tuner-name i").toggleClass("rotated");
if ($(window).width() < 768) { if ($(window).width() < 768) {
$('.dashboard-panel-plugin-list').slideToggle(300); $('.dashboard-panel-plugin-list').slideToggle(300);
$('#users-online-container').slideToggle(300);
$('.chatbutton').slideToggle(300); $('.chatbutton').slideToggle(300);
$('#settings').slideToggle(300); $('#settings').slideToggle(300);
} }
+22 -8
View File
@@ -1,8 +1,22 @@
$.getScript('./js/api.js'); function loadScript(src) {
$.getScript('./js/main.js'); return new Promise((resolve, reject) => {
$.getScript('./js/dropdown.js'); const script = document.createElement('script');
$.getScript('./js/modal.js'); script.src = src;
$.getScript('./js/settings.js'); script.onload = resolve;
$.getScript('./js/chat.js'); script.onerror = reject;
$.getScript('./js/toast.js'); document.head.appendChild(script);
$.getScript('./js/plugins.js'); });
}
async function loadScriptsInOrder() {
await loadScript('./js/api.js');
await loadScript('./js/main.js');
await loadScript('./js/dropdown.js');
await loadScript('./js/modal.js');
await loadScript('./js/settings.js');
await loadScript('./js/chat.js');
await loadScript('./js/toast.js');
await loadScript('./js/plugins.js');
}
loadScriptsInOrder();