You've already forked fm-dx-webserver
mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-02-26 22:13:53 +01:00
offline optimization, new signal chart, ui optimization
This commit is contained in:
@@ -22,6 +22,15 @@ h1#tuner-name {
|
||||
transition: 0.3s ease color;
|
||||
}
|
||||
|
||||
#tuner-name i {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
#tuner-name i.rotated {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
|
||||
h1#tuner-name:hover {
|
||||
color: var(--color-main-bright);
|
||||
}
|
||||
@@ -75,9 +84,13 @@ label {
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
width: 100%;
|
||||
height: 175px;
|
||||
width: calc(100% - 20px);
|
||||
height: 140px;
|
||||
overflow: hidden;
|
||||
border-radius: 15px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#data-ant {
|
||||
|
||||
9
web/css/libs/fontawesome.css
vendored
Normal file
9
web/css/libs/fontawesome.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
web/css/webfonts/fa-brands-400.ttf
Normal file
BIN
web/css/webfonts/fa-brands-400.ttf
Normal file
Binary file not shown.
BIN
web/css/webfonts/fa-brands-400.woff2
Normal file
BIN
web/css/webfonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
web/css/webfonts/fa-regular-400.ttf
Normal file
BIN
web/css/webfonts/fa-regular-400.ttf
Normal file
Binary file not shown.
BIN
web/css/webfonts/fa-regular-400.woff2
Normal file
BIN
web/css/webfonts/fa-regular-400.woff2
Normal file
Binary file not shown.
BIN
web/css/webfonts/fa-solid-900.ttf
Normal file
BIN
web/css/webfonts/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
web/css/webfonts/fa-solid-900.woff2
Normal file
BIN
web/css/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
BIN
web/css/webfonts/fa-v4compatibility.ttf
Normal file
BIN
web/css/webfonts/fa-v4compatibility.ttf
Normal file
Binary file not shown.
BIN
web/css/webfonts/fa-v4compatibility.woff2
Normal file
BIN
web/css/webfonts/fa-v4compatibility.woff2
Normal file
Binary file not shown.
@@ -4,8 +4,12 @@
|
||||
<title><%= tunerName %> - FM-DX Webserver</title>
|
||||
<link href="css/entry.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">
|
||||
<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>
|
||||
<link href="css/libs/fontawesome.css" type="text/css" rel="stylesheet">
|
||||
<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" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -16,7 +20,7 @@
|
||||
<meta property="og:description" content="Server description: <%= tunerDescMeta %>.">
|
||||
|
||||
<script src="js/init.js"></script>
|
||||
|
||||
|
||||
<!-- 3LAS Scripts for Audio streaming -->
|
||||
<script src="js/3las/util/3las.helpers.js"></script>
|
||||
<script src="js/3las/util/3las.logging.js"></script>
|
||||
@@ -39,7 +43,7 @@
|
||||
<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;">
|
||||
<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>
|
||||
<% if(!publicTuner || tunerLock) { %>
|
||||
<div class="tuner-status p-10 color-3">
|
||||
@@ -109,27 +113,16 @@
|
||||
<div style="width: 1px;background: var(--color-2);" class="m-10 hide-phone"></div>
|
||||
|
||||
<div>
|
||||
<% const presets = [1, 2, 3, 4]; %>
|
||||
|
||||
<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;">
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
<% 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>
|
||||
<span style="font-size: 10px; color: var(--color-text);" id="preset<%= preset %>-text"></span>
|
||||
</button>
|
||||
<% }); %>
|
||||
</div>
|
||||
|
||||
<div class="flex-container flex-phone" style="align-items: center; height: 64px;">
|
||||
<div class="color-3 m-10 text-medium">
|
||||
@@ -440,11 +433,6 @@
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'RDS PS Underscores', id: 'ps-underscores'}) %>
|
||||
<%- 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) { %>
|
||||
<p class="color-3">You are logged in as an adminstrator.</p>
|
||||
<div class="admin-quick-dashboard">
|
||||
|
||||
@@ -46,3 +46,11 @@ function tuneTo(freq) {
|
||||
function resetRDS() {
|
||||
socket.send("T0");
|
||||
}
|
||||
|
||||
function getCurrentFreq() {
|
||||
currentFreq = $('#data-frequency').text();
|
||||
currentFreq = parseFloat(currentFreq).toFixed(3);
|
||||
currentFreq = parseFloat(currentFreq);
|
||||
|
||||
return currentFreq;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ $(document).ready(function() {
|
||||
chatNicknameSave.click(function() {
|
||||
const currentNickname = chatNicknameInput.val().trim() || `Anonymous User ${generateRandomString(5)}`;
|
||||
localStorage.setItem('nickname', currentNickname);
|
||||
savedNickname = currentNickname; // Update the savedNickname variable
|
||||
savedNickname = currentNickname;
|
||||
chatIdentityNickname.text(savedNickname);
|
||||
chatNicknameInput.blur();
|
||||
});
|
||||
|
||||
@@ -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 month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1
|
||||
var year = currentDate.getFullYear();
|
||||
var formattedDate = day + '/' + month + '/' + year;
|
||||
var currentVersion = 'v1.3.5 [' + formattedDate + ']';
|
||||
var currentVersion = 'v1.3.6 [' + formattedDate + ']';
|
||||
|
||||
getInitialSettings();
|
||||
removeUrlParameters();
|
||||
|
||||
20
web/js/libs/chart.umd.min.js
vendored
Normal file
20
web/js/libs/chart.umd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
web/js/libs/chartjs-adapter-luxon.umd.min.js
vendored
Normal file
7
web/js/libs/chartjs-adapter-luxon.umd.min.js
vendored
Normal 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()}})}));
|
||||
7
web/js/libs/chartjs-plugin-streaming.min.js
vendored
Normal file
7
web/js/libs/chartjs-plugin-streaming.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
web/js/libs/jquery.min.js
vendored
Normal file
2
web/js/libs/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
web/js/libs/luxon.min.js
vendored
Normal file
1
web/js/libs/luxon.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
340
web/js/main.js
340
web/js/main.js
@@ -3,8 +3,8 @@
|
||||
|
||||
|
||||
var parsedData, signalChart, previousFreq;
|
||||
var signalData = [];
|
||||
var data = [];
|
||||
var signalData = [];
|
||||
let updateCounter = 0;
|
||||
let messageCounter = 0; // Count for WebSocket data length returning 0
|
||||
let messageData = 800; // Initial value anything above 0
|
||||
@@ -32,7 +32,7 @@ const usa_programmes = [
|
||||
const rdsMode = localStorage.getItem('rdsMode');
|
||||
|
||||
$(document).ready(function () {
|
||||
var canvas = $('#signal-canvas')[0];
|
||||
const signalToggle = $("#signal-units-toggle");
|
||||
|
||||
var $panel = $('.admin-quick-dashboard');
|
||||
var panelWidth = $panel.outerWidth();
|
||||
@@ -47,12 +47,7 @@ $(document).ready(function () {
|
||||
$panel.css('left', -panelWidth);
|
||||
}
|
||||
});
|
||||
|
||||
canvas.width = canvas.parentElement.clientWidth;
|
||||
canvas.height = canvas.parentElement.clientHeight;
|
||||
|
||||
// Start updating the canvas
|
||||
initCanvas();
|
||||
|
||||
fillPresets();
|
||||
|
||||
signalToggle.on("change", function () {
|
||||
@@ -205,7 +200,6 @@ $(document).ready(function () {
|
||||
$(freqContainer).on("click", function () {
|
||||
textInput.focus();
|
||||
});
|
||||
initTooltips();
|
||||
|
||||
//FMLIST logging
|
||||
$('.popup-content').on('click', function(event) {
|
||||
@@ -273,7 +267,9 @@ $(document).ready(function () {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
initCanvas();
|
||||
initTooltips();
|
||||
});
|
||||
|
||||
function getServerTime() {
|
||||
@@ -294,29 +290,21 @@ function getServerTime() {
|
||||
|
||||
const serverOptions = {
|
||||
...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);
|
||||
|
||||
$("#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) {
|
||||
console.error("Error fetching server time:", errorThrown);
|
||||
// Handle error gracefully (e.g., display a fallback message)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendPingRequest() {
|
||||
const timeoutDuration = 15000; // Ping response can become buggy if it exceeds 20 seconds
|
||||
const timeoutDuration = 5000;
|
||||
const startTime = new Date().getTime();
|
||||
|
||||
const fetchWithTimeout = (url, options, timeout = timeoutDuration) => {
|
||||
@@ -407,7 +395,6 @@ function sendPingRequest() {
|
||||
}
|
||||
}
|
||||
|
||||
// Automatic UI resume on WebSocket reconnect
|
||||
function handleWebSocketMessage(event) {
|
||||
if (event.data == 'KICK') {
|
||||
console.log('Kick initiated.')
|
||||
@@ -429,151 +416,161 @@ function handleWebSocketMessage(event) {
|
||||
// Attach the message handler
|
||||
socket.onmessage = handleWebSocketMessage;
|
||||
|
||||
function initCanvas(parsedData) {
|
||||
signalToggle = $("#signal-units-toggle");
|
||||
|
||||
// Check if signalChart is already initialized
|
||||
if (!signalChart) {
|
||||
const canvas = $('#signal-canvas')[0];
|
||||
const context = canvas.getContext('2d');
|
||||
const maxDataPoints = 300;
|
||||
const pointWidth = (canvas.width - 80) / maxDataPoints;
|
||||
|
||||
|
||||
signalChart = {
|
||||
canvas,
|
||||
context,
|
||||
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
|
||||
updateChartSettings(signalChart);
|
||||
|
||||
// Periodically check for color and signal unit updates
|
||||
setInterval(() => {
|
||||
updateChartSettings(signalChart);
|
||||
}, 1000); // Check every 1 second
|
||||
}
|
||||
|
||||
updateCanvas(parsedData, signalChart);
|
||||
}
|
||||
const signalBuffer = [];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
function initCanvas() {
|
||||
const ctx = document.getElementById("signal-canvas").getContext("2d");
|
||||
|
||||
function updateCanvas(parsedData, signalChart) {
|
||||
const { context, canvas, maxDataPoints, pointWidth, color2, color3, color4, offset } = signalChart;
|
||||
|
||||
if (data.length > maxDataPoints) {
|
||||
data = data.slice(data.length - maxDataPoints);
|
||||
}
|
||||
|
||||
const actualLowestValue = Math.min(...data);
|
||||
const actualHighestValue = Math.max(...data);
|
||||
const zoomMinValue = actualLowestValue - ((actualHighestValue - actualLowestValue) / 2);
|
||||
const zoomMaxValue = actualHighestValue + ((actualHighestValue - actualLowestValue) / 2);
|
||||
const zoomAvgValue = (zoomMaxValue - zoomMinValue) / 2 + zoomMinValue;
|
||||
|
||||
// Clear the canvas
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
context.beginPath();
|
||||
|
||||
const startingIndex = Math.max(0, data.length - maxDataPoints);
|
||||
|
||||
for (let i = startingIndex; i < data.length; i++) {
|
||||
const x = canvas.width - (data.length - i) * pointWidth - 40;
|
||||
const y = canvas.height - (data[i] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
context.strokeStyle = color4;
|
||||
context.lineWidth = 2;
|
||||
context.stroke();
|
||||
|
||||
// Draw horizontal lines for lowest, highest, and average values
|
||||
context.strokeStyle = color3;
|
||||
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';
|
||||
|
||||
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);
|
||||
|
||||
setTimeout(() => {
|
||||
requestAnimationFrame(() => updateCanvas(parsedData, signalChart));
|
||||
}, 1000 / 15);
|
||||
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;
|
||||
|
||||
signalBuffer.push(parsedData.sig);
|
||||
if (signalBuffer.length > 8) {
|
||||
signalBuffer.shift();
|
||||
}
|
||||
const avgSignal = signalBuffer.reduce((sum, val) => sum + val, 0) / signalBuffer.length;
|
||||
|
||||
chart.data.datasets[0].data.push({
|
||||
x: Date.now(),
|
||||
y: avgSignal
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
grace: 0.25,
|
||||
border: { display: false },
|
||||
ticks: {
|
||||
maxTicksLimit: 3,
|
||||
display: false // Hide default labels
|
||||
},
|
||||
grid: {
|
||||
display: false, // Hide default grid lines
|
||||
},
|
||||
},
|
||||
y2: {
|
||||
position: 'right', // Position on the right side
|
||||
beginAtZero: false,
|
||||
grace: 0.25,
|
||||
border: { display: false },
|
||||
ticks: {
|
||||
maxTicksLimit: 3,
|
||||
display: false // Hide default labels for the right axis
|
||||
},
|
||||
grid: {
|
||||
display: false, // No grid for right axis
|
||||
}
|
||||
}
|
||||
},
|
||||
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;
|
||||
|
||||
const gridLineColor = getComputedStyle(document.documentElement).getPropertyValue("--color-2-transparent").trim(); // Grid color using CSS variable
|
||||
const textColor = getComputedStyle(document.documentElement).getPropertyValue("--color-3-transparent").trim(); // Use the same color for text labels
|
||||
|
||||
ctx.save();
|
||||
ctx.font = "12px Titillium Web";
|
||||
ctx.fillStyle = textColor;
|
||||
ctx.textAlign = "center";
|
||||
|
||||
const leftX = yAxis.left + 20;
|
||||
const rightX = y2Axis.right - 20;
|
||||
|
||||
const offset = 10;
|
||||
|
||||
yAxis.ticks.forEach((tick, index) => {
|
||||
const y = yAxis.getPixelForValue(tick.value);
|
||||
var adjustedY = Math.max(yAxis.top + 13, Math.min(y, yAxis.bottom - 6));
|
||||
const isMiddleTick = index === Math.floor(yAxis.ticks.length / 2);
|
||||
|
||||
let adjustedTickValue;
|
||||
switch(localStorage.getItem("signalUnit")) {
|
||||
case "dbuv": adjustedTickValue = tick.value - 11.25; break;
|
||||
case "dbm": adjustedTickValue = tick.value - -120; break;
|
||||
default: adjustedTickValue = tick.value; break;
|
||||
}
|
||||
|
||||
if (isMiddleTick) { adjustedY += 3; }
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillText(adjustedTickValue.toFixed(1), leftX + 25, adjustedY);
|
||||
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(adjustedTickValue.toFixed(1), rightX - 25, adjustedY); // Right side
|
||||
});
|
||||
|
||||
const gridLineWidth = 0.5; // Make the lines thinner to avoid overlapping text
|
||||
const adjustedGridTop = chartArea.top + offset;
|
||||
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;
|
||||
@@ -641,14 +638,6 @@ function processString(string, errors) {
|
||||
return output;
|
||||
}
|
||||
|
||||
function getCurrentFreq() {
|
||||
currentFreq = $('#data-frequency').text();
|
||||
currentFreq = parseFloat(currentFreq).toFixed(3);
|
||||
currentFreq = parseFloat(currentFreq);
|
||||
|
||||
return currentFreq;
|
||||
}
|
||||
|
||||
function checkKey(e) {
|
||||
e = e || window.event;
|
||||
|
||||
@@ -899,14 +888,12 @@ function throttle(fn, wait) {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
// Utility function to update element's text if changed
|
||||
function updateTextIfChanged($element, newText) {
|
||||
if ($element.text() !== newText) {
|
||||
$element.text(newText);
|
||||
}
|
||||
}
|
||||
|
||||
// Utility function to update element's HTML content if changed
|
||||
function updateHtmlIfChanged($element, newHtml) {
|
||||
if ($element.html() !== newHtml) {
|
||||
$element.html(newHtml);
|
||||
@@ -1005,7 +992,7 @@ const updateDataElements = throttle(function(parsedData) {
|
||||
$dataRt1.attr('aria-label', parsedData.rt1);
|
||||
$('#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;
|
||||
|
||||
@@ -1114,9 +1101,12 @@ function showTunerDescription() {
|
||||
parentDiv.css("border-radius", "");
|
||||
}
|
||||
});
|
||||
|
||||
$("#tuner-name i").toggleClass("rotated");
|
||||
|
||||
if ($(window).width() < 768) {
|
||||
$('.dashboard-panel-plugin-list').slideToggle(300);
|
||||
$('#users-online-container').slideToggle(300);
|
||||
$('.chatbutton').slideToggle(300);
|
||||
$('#settings').slideToggle(300);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
$.getScript('./js/api.js');
|
||||
$.getScript('./js/main.js');
|
||||
$.getScript('./js/dropdown.js');
|
||||
$.getScript('./js/modal.js');
|
||||
$.getScript('./js/settings.js');
|
||||
$.getScript('./js/chat.js');
|
||||
$.getScript('./js/toast.js');
|
||||
$.getScript('./js/plugins.js');
|
||||
function loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user