1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-27 06:23:53 +01:00
This commit is contained in:
NoobishSVK
2024-01-22 20:33:45 +01:00
parent c72153763e
commit 5e3a0a466c
21 changed files with 1125 additions and 1180 deletions

382
web/js/main.js Normal file
View File

@@ -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 + '<br>');
};
requestAnimationFrame(updateCanvas);
}
function compareNumbers(a, b) {
return a - b;
}
function escapeHTML(unsafeText) {
let div = document.createElement('div');
div.innerText = unsafeText;
return div.innerHTML.replace(' ', '&nbsp;');
}
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 += "<span style='opacity: " + (max_alpha - alpha) + "%'>" + escapeHTML(string[i]) + "</span>";
} 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 = $('<ul></ul>');
listContainer.append(ul);
}
ul.html('');
const listItems = scaledArray.map(element => {
return $('<li></li>').text(element.toFixed(1))[0];
});
ul.append(listItems);
listContainer.scrollTop(scrollTop);
$('#data-frequency').text(parsedData.freq);
$('#data-pi').html(parsedData.pi === '?' ? "<span class='text-gray'>?</span>" : parsedData.pi);
$('#data-ps').html(parsedData.ps === '?' ? "<span class='text-gray'>?</span>" : processString(parsedData.ps, parsedData.ps_errors));
$('#data-tp').html(parsedData.tp === false ? "<span class='text-gray'>TP</span>" : "TP");
$('#data-pty').html(europe_programmes[parsedData.pty]);
$('#data-st').html(parsedData.st === false ? "<span class='text-gray'>ST</span>" : "ST");
$('#data-rt0').html(processString(parsedData.rt0, parsedData.rt0_errors));
$('#data-rt1').html(processString(parsedData.rt1, parsedData.rt1_errors));
$('#data-flag').html('<i title="' + parsedData.country_name + '" class="flag-sm flag-sm-' + parsedData.country_iso + '"></i>');
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></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();
}
}
}
});

33
web/js/modal.js Normal file
View File

@@ -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();
}
});
});

33
web/js/themes.js Normal file
View File

@@ -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);
});
});

3
web/js/webserver.js Normal file
View File

@@ -0,0 +1,3 @@
$.getScript('/js/main.js');
$.getScript('/js/modal.js');
$.getScript('/js/themes.js');