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
bugfixes, ui improvements
This commit is contained in:
@@ -20,7 +20,7 @@ function parseMarkdown(parsed) {
|
|||||||
parsed = parsed.replace(italicRegex, '<em>$1</em>');
|
parsed = parsed.replace(italicRegex, '<em>$1</em>');
|
||||||
|
|
||||||
var linkRegex = /\[([^\]]+)]\(([^)]+)\)/g;
|
var linkRegex = /\[([^\]]+)]\(([^)]+)\)/g;
|
||||||
parsed = parsed.replace(linkRegex, '<a href="$2">$1</a>');
|
parsed = parsed.replace(linkRegex, '<a href="$2" target="_blank">$1</a>');
|
||||||
|
|
||||||
parsed = parsed.replace(/\n/g, '<br>');
|
parsed = parsed.replace(/\n/g, '<br>');
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const exec = require('child_process').exec;
|
const exec = require('child_process').exec;
|
||||||
const fs = require('fs');
|
const fs = require('fs').promises; // Use the Promise-based fs API
|
||||||
const ffmpeg = require('ffmpeg-static');
|
const ffmpeg = require('ffmpeg-static');
|
||||||
const filePath = '/proc/asound/cards';
|
const filePath = '/proc/asound/cards';
|
||||||
const platform = process.platform;
|
const platform = process.platform;
|
||||||
@@ -16,106 +16,97 @@ function parseAudioDevice(options, callback) {
|
|||||||
options = null;
|
options = null;
|
||||||
}
|
}
|
||||||
options = options || {};
|
options = options || {};
|
||||||
const ffmpegPath = "\"" + ffmpeg.replace(/\\/g, '\\\\') + "\"";
|
const ffmpegPath = `"${ffmpeg.replace(/\\/g, '\\\\')}"`;
|
||||||
const callbackExists = typeof callback === 'function';
|
const callbackExists = typeof callback === 'function';
|
||||||
|
|
||||||
let inputDevice, prefix, audioSeparator, alternativeName, deviceParams;
|
|
||||||
switch (platform) {
|
|
||||||
case 'win32':
|
|
||||||
inputDevice = 'dshow';
|
|
||||||
prefix = /\[dshow/;
|
|
||||||
audioSeparator = /DirectShow\saudio\sdevices/;
|
|
||||||
alternativeName = /Alternative\sname\s*?\"(.*?)\"/;
|
|
||||||
deviceParams = /\"(.*?)\"/;
|
|
||||||
break;
|
|
||||||
case 'darwin':
|
|
||||||
inputDevice = 'avfoundation';
|
|
||||||
prefix = /^\[AVFoundation/;
|
|
||||||
audioSeparator = /AVFoundation\saudio\sdevices/;
|
|
||||||
deviceParams = /^\[AVFoundation.*?\]\s\[(\d*?)\]\s(.*)$/;
|
|
||||||
break;
|
|
||||||
case 'linux':
|
|
||||||
fs.readFile(filePath, 'utf8', (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(`Error reading file: ${err.message}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract values between square brackets, trim whitespace, and prefix with 'hw:'
|
|
||||||
const regex = /\[([^\]]+)\]/g;
|
|
||||||
const matches = (data.match(regex) || []).map(match => 'hw:' + match.replace(/\s+/g, '').slice(1, -1));
|
|
||||||
|
|
||||||
if (matches.length > 0) {
|
|
||||||
// Process the extracted values
|
|
||||||
matches.forEach(function(match) {
|
|
||||||
if (typeof match === 'string') {
|
|
||||||
audioDevices.push({ name: match });
|
|
||||||
} else if (typeof match === 'object' && match.name) {
|
|
||||||
audioDevices.push(match);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logWarn('No audio devices have been found.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const searchPrefix = (line) => (line.search(prefix) > -1);
|
|
||||||
const searchAudioSeparator = (line) => isVideo && (line.search(audioSeparator) > -1);
|
|
||||||
const searchAlternativeName = (line) => (platform === 'win32') && (line.search(/Alternative\sname/) > -1);
|
|
||||||
|
|
||||||
|
const execute = async (fulfill, reject) => {
|
||||||
const execute = (fulfill, reject) => {
|
try {
|
||||||
exec(`${ffmpegPath} -f ${inputDevice} -list_devices true -i ""`, (err, stdout, stderr) => {
|
if (platform === 'linux') {
|
||||||
stderr.split("\n")
|
try {
|
||||||
.filter(searchPrefix)
|
const data = await fs.readFile(filePath, 'utf8');
|
||||||
.forEach((line) => {
|
const regex = /\[([^\]]+)\]/g;
|
||||||
const deviceList = isVideo ? videoDevices : audioDevices;
|
const matches = (data.match(regex) || []).map(match => 'hw:' + match.replace(/\s+/g, '').slice(1, -1));
|
||||||
if (searchAudioSeparator(line)) {
|
|
||||||
isVideo = false;
|
matches.forEach(match => {
|
||||||
return;
|
if (typeof match === 'string') {
|
||||||
|
audioDevices.push({ name: match });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error reading file: ${err.message}`);
|
||||||
}
|
}
|
||||||
if (searchAlternativeName(line)) {
|
|
||||||
const lastDevice = deviceList[deviceList.length - 1];
|
// Linux doesn't support the `-list_devices` ffmpeg command like macOS/Windows,
|
||||||
lastDevice.alternativeName = line.match(alternativeName)[1];
|
// so skip the ffmpeg exec for Linux
|
||||||
return;
|
const result = { videoDevices: [], audioDevices };
|
||||||
}
|
if (callbackExists) return callback(result);
|
||||||
const params = line.match(deviceParams);
|
return fulfill(result);
|
||||||
if (params) {
|
|
||||||
let device;
|
|
||||||
switch (platform) {
|
|
||||||
case 'win32':
|
|
||||||
device = {
|
|
||||||
name: params[1]
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'darwin':
|
|
||||||
device = {
|
|
||||||
id: parseInt(params[1]),
|
|
||||||
name: params[2]
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'linux':
|
|
||||||
device = {
|
|
||||||
name: params[1]
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
deviceList.push(device);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
audioDevices = audioDevices.filter(device => device.name !== undefined);
|
|
||||||
const result = { videoDevices, audioDevices };
|
|
||||||
if (callbackExists) {
|
|
||||||
callback(result);
|
|
||||||
} else {
|
|
||||||
fulfill(result);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
let inputDevice, prefix, audioSeparator, alternativeName, deviceParams;
|
||||||
|
|
||||||
|
switch (platform) {
|
||||||
|
case 'win32':
|
||||||
|
inputDevice = 'dshow';
|
||||||
|
prefix = /\[dshow/;
|
||||||
|
audioSeparator = /DirectShow\saudio\sdevices/;
|
||||||
|
alternativeName = /Alternative\sname\s*?"(.*?)"/;
|
||||||
|
deviceParams = /"(.*?)"/;
|
||||||
|
break;
|
||||||
|
case 'darwin':
|
||||||
|
inputDevice = 'avfoundation';
|
||||||
|
prefix = /^\[AVFoundation/;
|
||||||
|
audioSeparator = /AVFoundation\saudio\sdevices/;
|
||||||
|
deviceParams = /^\[AVFoundation.*?]\s\[(\d+)]\s(.*)$/;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(`${ffmpegPath} -f ${inputDevice} -list_devices true -i ""`, (err, stdout, stderr) => {
|
||||||
|
stderr.split("\n")
|
||||||
|
.filter(line => line.search(prefix) > -1)
|
||||||
|
.forEach(line => {
|
||||||
|
const deviceList = isVideo ? videoDevices : audioDevices;
|
||||||
|
if (line.search(audioSeparator) > -1) {
|
||||||
|
isVideo = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platform === 'win32' && line.search(/Alternative\sname/) > -1) {
|
||||||
|
const lastDevice = deviceList[deviceList.length - 1];
|
||||||
|
const alt = line.match(alternativeName);
|
||||||
|
if (lastDevice && alt) {
|
||||||
|
lastDevice.alternativeName = alt[1];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = line.match(deviceParams);
|
||||||
|
if (params) {
|
||||||
|
let device;
|
||||||
|
switch (platform) {
|
||||||
|
case 'win32':
|
||||||
|
device = { name: params[1] };
|
||||||
|
break;
|
||||||
|
case 'darwin':
|
||||||
|
device = { id: parseInt(params[1]), name: params[2] };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
deviceList.push(device);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
audioDevices = audioDevices.filter(device => device.name !== undefined);
|
||||||
|
const result = { videoDevices, audioDevices };
|
||||||
|
if (callbackExists) return callback(result);
|
||||||
|
return fulfill(result);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Unexpected error:', err);
|
||||||
|
if (callbackExists) callback({ videoDevices: [], audioDevices: [] });
|
||||||
|
else reject(err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (callbackExists) {
|
if (callbackExists) {
|
||||||
execute();
|
execute();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -23,8 +23,6 @@
|
|||||||
<meta property="og:image" content="https://fmdx.org/img/webserver_icon.png">
|
<meta property="og:image" content="https://fmdx.org/img/webserver_icon.png">
|
||||||
<meta property="og:description" content="Server description: <%= tunerDescMeta %>.">
|
<meta property="og:description" content="Server description: <%= tunerDescMeta %>.">
|
||||||
|
|
||||||
<script src="js/init.js"></script>
|
|
||||||
|
|
||||||
<!-- 3LAS Scripts for Audio streaming -->
|
<!-- 3LAS Scripts for Audio streaming -->
|
||||||
<script src="js/3las/util/3las.helpers.js"></script>
|
<script src="js/3las/util/3las.helpers.js"></script>
|
||||||
<script src="js/3las/util/3las.logging.js"></script>
|
<script src="js/3las/util/3las.logging.js"></script>
|
||||||
@@ -267,6 +265,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-container flex-phone flex-phone-column">
|
<div class="flex-container flex-phone flex-phone-column">
|
||||||
|
<div id="data-ant-container" class="hide-desktop" style="padding-left: 18px;padding-right: 18px;margin-top: -20px;">
|
||||||
|
<% if (antennas.enabled == true) { %>
|
||||||
|
<div class="panel-100-real no-bg h-100 br-0 dropdown data-ant" id="data-ant-phone" style="max-height: 48px;width: 50%;">
|
||||||
|
<input type="text" placeholder="Ant A" readonly tabindex="0" style="border-radius: 0 0 15px 15px;">
|
||||||
|
<ul class="options open-top" tabindex="-1">
|
||||||
|
<% if(antennas.ant1.enabled == true) { %><li data-value="0" class="option" tabindex="0"><%= antennas.ant1.name %></li><% } %>
|
||||||
|
<% if(antennas.ant2.enabled == true) { %><li data-value="1" class="option" tabindex="0"><%= antennas.ant2.name %></li><% } %>
|
||||||
|
<% if(antennas.ant3.enabled == true) { %><li data-value="2" class="option" tabindex="0"><%= antennas.ant3.name %></li><% } %>
|
||||||
|
<% if(antennas.ant4.enabled == true) { %><li data-value="3" class="option" tabindex="0"><%= antennas.ant4.name %></li><% } %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
<div class="panel-75 hover-brighten no-bg-phone" id="rt-container" style="height: 100px;">
|
<div class="panel-75 hover-brighten no-bg-phone" id="rt-container" style="height: 100px;">
|
||||||
<h2 style="margin-top: 4px;">RADIOTEXT</h2>
|
<h2 style="margin-top: 4px;">RADIOTEXT</h2>
|
||||||
<div id="data-rt0">
|
<div id="data-rt0">
|
||||||
@@ -287,7 +298,7 @@
|
|||||||
<span id="data-station-city" style="font-size: 16px;"></span> <span class="text-small">[<span id="data-station-itu"></span>]</span>
|
<span id="data-station-city" style="font-size: 16px;"></span> <span class="text-small">[<span id="data-station-itu"></span>]</span>
|
||||||
</h4>
|
</h4>
|
||||||
<span class="text-small">
|
<span class="text-small">
|
||||||
<span id="data-station-erp"></span> kW [<span id="data-station-pol"></span>] <span class="text-gray">•</span> <span id="data-station-distance"></span> <span class="text-gray">•</span> <span id="data-station-azimuth"></span> <span id="data-station-others"></span>
|
<span id="data-station-erp"></span> kW [<span id="data-station-pol"></span>] <span class="text-gray">•</span> <span id="data-station-distance"></span> <span class="text-gray">•</span> <span id="data-station-azimuth"></span> <span id="data-station-others hide-phone" style="opacity: 0.7;"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -346,17 +357,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<% if (antennas.enabled == true) { %>
|
|
||||||
<div class="panel-100-real no-bg h-100 br-0 dropdown data-ant" id="data-ant-phone" style="max-height: 48px;width: 50%;margin-left: 5px;">
|
|
||||||
<input type="text" placeholder="Ant A" readonly tabindex="0">
|
|
||||||
<ul class="options" tabindex="-1">
|
|
||||||
<% if(antennas.ant1.enabled == true) { %><li data-value="0" class="option" tabindex="0"><%= antennas.ant1.name %></li><% } %>
|
|
||||||
<% if(antennas.ant2.enabled == true) { %><li data-value="1" class="option" tabindex="0"><%= antennas.ant2.name %></li><% } %>
|
|
||||||
<% if(antennas.ant3.enabled == true) { %><li data-value="2" class="option" tabindex="0"><%= antennas.ant3.name %></li><% } %>
|
|
||||||
<% if(antennas.ant4.enabled == true) { %><li data-value="3" class="option" tabindex="0"><%= antennas.ant4.name %></li><% } %>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="flex-phone flex-center">Filters</p>
|
<p class="flex-phone flex-center">Filters</p>
|
||||||
@@ -409,10 +409,10 @@
|
|||||||
<div class="popup-window" id="popup-panel-transmitters">
|
<div class="popup-window" id="popup-panel-transmitters">
|
||||||
<div class="flex-container flex-column flex-phone flex-phone-column" style="height: calc(100%);">
|
<div class="flex-container flex-column flex-phone flex-phone-column" style="height: calc(100%);">
|
||||||
<div class="popup-header hover-brighten flex-center">
|
<div class="popup-header hover-brighten flex-center">
|
||||||
<p class="color-4" style="margin: 0; padding-left: 10px;">Possible transmitters</p>
|
<p class="color-4" style="margin: 0; padding-left: 10px;">Other possible transmitters</p>
|
||||||
<button class="popup-close">✖</button>
|
<button class="popup-close">✖</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="alternative-txes" class="popup-content text-left flex-container flex-phone flex-column p-10" style="flex: 1;">
|
<div id="alternative-txes" class="popup-content text-left flex-container flex-phone flex-column" style="flex: 1;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ function tuneDown() {
|
|||||||
|
|
||||||
function tuneTo(freq) {
|
function tuneTo(freq) {
|
||||||
previousFreq = getCurrentFreq();
|
previousFreq = getCurrentFreq();
|
||||||
socket.send("T" + ((freq).toFixed(1) * 1000));
|
socket.send("T" + ((parseFloat(freq)).toFixed(1) * 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetRDS() {
|
function resetRDS() {
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ $(document).ready(function() {
|
|||||||
const chatNicknameSave = $('#chat-nickname-save');
|
const chatNicknameSave = $('#chat-nickname-save');
|
||||||
|
|
||||||
$(".chatbutton").on("click", function () {
|
$(".chatbutton").on("click", function () {
|
||||||
$("#popup-panel-chat").fadeIn(200, function () {
|
togglePopup("#popup-panel-chat");
|
||||||
chatMessages.scrollTop(chatMessages[0].scrollHeight);
|
chatMessages.scrollTop(chatMessages[0].scrollHeight);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Function to generate a random string
|
// Function to generate a random string
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
var currentDate = new Date('May 2, 2025 16:00: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.8 [' + formattedDate + ']';
|
|
||||||
|
|
||||||
getInitialSettings();
|
|
||||||
removeUrlParameters();
|
|
||||||
|
|
||||||
function getInitialSettings() {
|
|
||||||
$.ajax({
|
|
||||||
url: './static_data',
|
|
||||||
dataType: 'json',
|
|
||||||
success: function (data) {
|
|
||||||
|
|
||||||
['qthLatitude', 'qthLongitude', 'defaultTheme', 'bgImage', 'rdsMode', 'rdsTimeout'].forEach(key => {
|
|
||||||
if (data[key] !== undefined) {
|
|
||||||
localStorage.setItem(key, data[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
data.presets.forEach((preset, index) => {
|
|
||||||
localStorage.setItem(`preset${index + 1}`, preset);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
error: function (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeUrlParameters() {
|
|
||||||
if (window.location.pathname === "/") {
|
|
||||||
var urlWithoutParams = window.location.protocol + "//" + window.location.host + window.location.pathname;
|
|
||||||
window.history.replaceState({ path: urlWithoutParams }, '', urlWithoutParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -64,17 +64,29 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
// Check if device is an iPhone to prevent zoom on button press
|
// Check if device is an iPhone to prevent zoom on button press
|
||||||
if (/iPhone|iPod|iPad/.test(navigator.userAgent) && !window.MSStream) {
|
if (/iPhone|iPod|iPad/.test(navigator.userAgent) && !window.MSStream) {
|
||||||
const buttons = document.querySelectorAll('button');
|
// Handle touchstart for buttons to prevent zoom
|
||||||
buttons.forEach(button => {
|
$('.button').on('touchstart', function(e) {
|
||||||
button.addEventListener('touchstart', function(e) {
|
e.preventDefault();
|
||||||
// Prevent default zoom behavior
|
let target = this;
|
||||||
e.preventDefault();
|
setTimeout(function() {
|
||||||
// Allow default button action after short delay
|
target.click();
|
||||||
setTimeout(() => {
|
}, 0);
|
||||||
e.target.click();
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Prevent zooming on input focus by modifying the viewport
|
||||||
|
let $viewportMeta = $('meta[name=viewport]');
|
||||||
|
if ($viewportMeta.length) {
|
||||||
|
let content = $viewportMeta.attr('content');
|
||||||
|
let re = /maximum\-scale=[0-9\.]+/g;
|
||||||
|
|
||||||
|
if (re.test(content)) {
|
||||||
|
content = content.replace(re, 'maximum-scale=1.0');
|
||||||
|
} else {
|
||||||
|
content += ', maximum-scale=1.0';
|
||||||
|
}
|
||||||
|
|
||||||
|
$viewportMeta.attr('content', content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const textInput = $('#commandinput');
|
const textInput = $('#commandinput');
|
||||||
@@ -83,7 +95,7 @@ $(document).ready(function () {
|
|||||||
const inputValue = Number(textInput.val());
|
const inputValue = Number(textInput.val());
|
||||||
// Check if the user agent contains 'iPhone'
|
// Check if the user agent contains 'iPhone'
|
||||||
if (/iPhone/i.test(navigator.userAgent)) {
|
if (/iPhone/i.test(navigator.userAgent)) {
|
||||||
socket.send("T" + (Math.round(inputValue * 1000)));
|
tuneTo(inputValue);
|
||||||
// Clear the input field if needed
|
// Clear the input field if needed
|
||||||
textInput.val('');
|
textInput.val('');
|
||||||
}
|
}
|
||||||
@@ -111,9 +123,8 @@ $(document).ready(function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
const inputValue = textInput.val();
|
|
||||||
if (socket.readyState === WebSocket.OPEN) {
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
socket.send("T" + (Math.round(inputValue * 1000)));
|
tuneTo(textInput.val());
|
||||||
}
|
}
|
||||||
textInput.val('');
|
textInput.val('');
|
||||||
}
|
}
|
||||||
@@ -703,10 +714,12 @@ function checkKey(e) {
|
|||||||
if ($options.length === 0) return; // No antennas available
|
if ($options.length === 0) return; // No antennas available
|
||||||
|
|
||||||
// Find the currently selected antenna
|
// Find the currently selected antenna
|
||||||
let currentText = $input.attr("placeholder").trim();
|
let currentText = $input.val().trim();
|
||||||
let currentIndex = $options.index($options.filter(function () {
|
let currentIndex = $options.index($options.filter(function () {
|
||||||
return $(this).text().trim() === currentText;
|
return $(this).text().trim() === currentText;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
console.log(currentIndex, currentText);
|
||||||
|
|
||||||
// Cycle to the next option
|
// Cycle to the next option
|
||||||
let nextIndex = (currentIndex + 1) % $options.length;
|
let nextIndex = (currentIndex + 1) % $options.length;
|
||||||
@@ -716,7 +729,6 @@ function checkKey(e) {
|
|||||||
$input.attr("placeholder", $nextOption.text());
|
$input.attr("placeholder", $nextOption.text());
|
||||||
$input.data("value", $nextOption.data("value"));
|
$input.data("value", $nextOption.data("value"));
|
||||||
|
|
||||||
// Send socket message (e.g., "Z0", "Z1", ...)
|
|
||||||
let socketMessage = "Z" + $nextOption.data("value");
|
let socketMessage = "Z" + $nextOption.data("value");
|
||||||
socket.send(socketMessage);
|
socket.send(socketMessage);
|
||||||
break;
|
break;
|
||||||
@@ -906,20 +918,19 @@ function throttle(fn, wait) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildAltTxList(txList) {
|
function buildAltTxList(txList) {
|
||||||
const wrapper = '<div class="panel-100 flex-container flex-phone" style="background:none;backdrop-filter:none;">';
|
const wrapper = '<div class="panel-100-real m-0 flex-container flex-phone" style="background:none;backdrop-filter:none;">';
|
||||||
let outString = '';
|
let outString = '';
|
||||||
outString += wrapper;
|
outString += wrapper;
|
||||||
for (let i = 0; i < txList.length; i++) {
|
for (let i = 0; i < txList.length; i++) {
|
||||||
const tx = txList[i];
|
const tx = txList[i];
|
||||||
outString += `<div class="panel-50 hover-brighten no-bg-phone" style="min-height: 91px;">
|
outString += `<div class="panel-100-real m-0 hover-brighten no-bg-phone m-0 br-0 p-10" style="min-height: 72px;padding-left: 20px;">
|
||||||
<div id="data-station-container-${i}" style="display: block;">
|
<div id="data-station-container-${i}" style="display: block;" class="text-left">
|
||||||
<h2 style="margin-top: 0;" class="mb-0">
|
<h2 style="margin-top: 0;" class="mb-0">
|
||||||
<span id="data-station-name-${i}">${tx.station.replace("R.", "Radio ").replace(/%/g, '%25')}</span>
|
<span id="data-station-name-${i}">${tx.station.replace("R.", "Radio ").replace(/%/g, '%25')}</span>
|
||||||
</h2>
|
</h2>
|
||||||
<h4 class="m-0">
|
<span id="data-station-city-${i}" style="font-size: 16px;">${tx.name}</span> <span class="text-small">[<span id="data-station-itu-${i}">${tx.itu}</span>]</span>
|
||||||
<span id="data-station-city-${i}" style="font-size: 16px;">${tx.name}</span> <span class="text-small">[<span id="data-station-itu">G</span>]</span>
|
<span class="text-small" style="opacity: 0.8;">
|
||||||
</h4>
|
<span style="margin-left: 20px;"> </span>
|
||||||
<span class="text-small">
|
|
||||||
<span id="data-station-erp">${tx.erp}</span> kW [<span id="data-station-pol">${tx.pol.toUpperCase()}</span>] <span class="text-gray">•</span> <span id="data-station-distance">${tx.distanceKm.toFixed(0)} km</span> <span class="text-gray">•</span> <span id="data-station-azimuth">${tx.azimuth.toFixed(0)}°</span>
|
<span id="data-station-erp">${tx.erp}</span> kW [<span id="data-station-pol">${tx.pol.toUpperCase()}</span>] <span class="text-gray">•</span> <span id="data-station-distance">${tx.distanceKm.toFixed(0)} km</span> <span class="text-gray">•</span> <span id="data-station-azimuth">${tx.azimuth.toFixed(0)}°</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,13 +43,11 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$(".tuner-mobile-settings").on("click", function () {
|
$(".tuner-mobile-settings").on("click", function () {
|
||||||
$(".popup-window").fadeOut(200);
|
togglePopup("#popup-panel-mobile-settings");
|
||||||
$("#popup-panel-mobile-settings").fadeIn(200);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#data-station-others").on("click", function () {
|
$("#data-station-others").on("click", function () {
|
||||||
$(".popup-window").fadeOut(200);
|
togglePopup("#popup-panel-transmitters");
|
||||||
$("#popup-panel-transmitters").fadeIn(200);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,4 +64,15 @@ function initPopups() {
|
|||||||
$(".popup-close").on("click", function () {
|
$(".popup-close").on("click", function () {
|
||||||
$(".popup-window").fadeOut(200);
|
$(".popup-window").fadeOut(200);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePopup(targetSelector) {
|
||||||
|
const $target = $(targetSelector);
|
||||||
|
|
||||||
|
if ($target.is(":visible")) {
|
||||||
|
$target.fadeOut(200);
|
||||||
|
} else {
|
||||||
|
$(".popup-window").fadeOut(200);
|
||||||
|
$target.fadeIn(200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,112 @@ const signalUnits = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
|
|
||||||
|
getInitialSettings();
|
||||||
|
|
||||||
|
$('#login-form').submit(function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: './login',
|
||||||
|
data: $(this).serialize(),
|
||||||
|
success: function (data) {
|
||||||
|
sendToast('success', 'Login success!', data.message, false, true);
|
||||||
|
setTimeout(function () {
|
||||||
|
location.reload(true);
|
||||||
|
}, 1750);
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
if (xhr.status === 403) {
|
||||||
|
sendToast('error', 'Login failed!', xhr.responseJSON.message, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.logout-link').click(function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET', // Assuming the logout is a GET request, adjust accordingly
|
||||||
|
url: './logout',
|
||||||
|
success: function (data) {
|
||||||
|
sendToast('success', 'Logout success!', data.message, false, true);
|
||||||
|
setTimeout(function () {
|
||||||
|
location.reload(true);
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
if (xhr.status === 403) {
|
||||||
|
sendToast('error', 'Logout failed!', xhr.responseJSON.message, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function getQueryParameter(name) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
return urlParams.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTheme(themeName) {
|
||||||
|
const themeColors = themes[themeName];
|
||||||
|
if (themeColors) {
|
||||||
|
// Extracting the RGBA components from themeColors[2] for --color-text-2
|
||||||
|
const rgbaComponentsText = themeColors[2].match(/(\d+(\.\d+)?)/g);
|
||||||
|
const opacityText = parseFloat(rgbaComponentsText[3]);
|
||||||
|
const newOpacityText = opacityText * 0.75;
|
||||||
|
const textColor2 = `rgba(${rgbaComponentsText[0]}, ${rgbaComponentsText[1]}, ${rgbaComponentsText[2]})`;
|
||||||
|
|
||||||
|
// Extracting the RGBA components from themeColors[0] for background color
|
||||||
|
const rgbaComponentsBackground = themeColors[3].match(/(\d+(\.\d+)?)/g);
|
||||||
|
const backgroundOpacity = 0.75;
|
||||||
|
const backgroundColorWithOpacity = `rgba(${rgbaComponentsBackground[0]}, ${rgbaComponentsBackground[1]}, ${rgbaComponentsBackground[2]}, ${backgroundOpacity})`;
|
||||||
|
|
||||||
|
$(':root').css('--color-main', themeColors[0]);
|
||||||
|
$(':root').css('--color-main-bright', themeColors[1]);
|
||||||
|
$(':root').css('--color-text', themeColors[2]);
|
||||||
|
$(':root').css('--color-text-2', textColor2);
|
||||||
|
$('.wrapper-outer').css('background-color', backgroundColorWithOpacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBg() {
|
||||||
|
const disableBackgroundParameter = getQueryParameter('disableBackground');
|
||||||
|
if(localStorage.getItem('bgImage').length > 5 && localStorage.getItem('theme') != 'theme9' && disableBackgroundParameter != 'true') {
|
||||||
|
$('body').css('background', 'url(' + localStorage.getItem('bgImage') + ') top center / cover fixed no-repeat var(--color-main)');
|
||||||
|
} else {
|
||||||
|
$('body').css('background', 'var(--color-main)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInitialSettings() {
|
||||||
|
$.ajax({
|
||||||
|
url: './static_data',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (data) {
|
||||||
|
|
||||||
|
['qthLatitude', 'qthLongitude', 'defaultTheme', 'bgImage', 'rdsMode', 'rdsTimeout'].forEach(key => {
|
||||||
|
if (data[key] !== undefined) {
|
||||||
|
localStorage.setItem(key, data[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
data.presets.forEach((preset, index) => {
|
||||||
|
localStorage.setItem(`preset${index + 1}`, preset);
|
||||||
|
});
|
||||||
|
|
||||||
|
loadInitialSettings();
|
||||||
|
},
|
||||||
|
error: function (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadInitialSettings() {
|
||||||
const themeSelector = $('#theme-selector');
|
const themeSelector = $('#theme-selector');
|
||||||
const savedTheme = localStorage.getItem('theme');
|
const savedTheme = localStorage.getItem('theme');
|
||||||
const defaultTheme = localStorage.getItem('defaultTheme');
|
const defaultTheme = localStorage.getItem('defaultTheme');
|
||||||
@@ -70,62 +176,7 @@ $(document).ready(() => {
|
|||||||
signalSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
|
signalSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
|
||||||
localStorage.setItem('signalUnit', selectedSignalUnit);
|
localStorage.setItem('signalUnit', selectedSignalUnit);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#login-form').submit(function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// Perform an AJAX request to the /login endpoint
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: './login',
|
|
||||||
data: $(this).serialize(),
|
|
||||||
success: function (data) {
|
|
||||||
// Update the content on the page with the message from the response
|
|
||||||
sendToast('success', 'Login success!', data.message, false, true);
|
|
||||||
|
|
||||||
//$('#login-message').text(data.message);
|
|
||||||
setTimeout(function () {
|
|
||||||
location.reload(true);
|
|
||||||
}, 1750);
|
|
||||||
},
|
|
||||||
error: function (xhr, status, error) {
|
|
||||||
// Handle error response
|
|
||||||
if (xhr.status === 403) {
|
|
||||||
// Update the content on the page with the message from the error response
|
|
||||||
sendToast('error', 'Login failed!', xhr.responseJSON.message, false, true);
|
|
||||||
} else {
|
|
||||||
// Handle other types of errors if needed
|
|
||||||
console.error('Error:', status, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.logout-link').click(function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: 'GET', // Assuming the logout is a GET request, adjust accordingly
|
|
||||||
url: './logout',
|
|
||||||
success: function (data) {
|
|
||||||
sendToast('success', 'Logout success!', data.message, false, true);
|
|
||||||
setTimeout(function () {
|
|
||||||
location.reload(true);
|
|
||||||
}, 1000);
|
|
||||||
},
|
|
||||||
error: function (xhr, status, error) {
|
|
||||||
// Handle error response
|
|
||||||
if (xhr.status === 403) {
|
|
||||||
// Update the content on the page with the message from the error response
|
|
||||||
sendToast('error', 'Logout failed!', xhr.responseJSON.message, false, true);
|
|
||||||
} else {
|
|
||||||
// Handle other types of errors if needed
|
|
||||||
console.error('Error:', status, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var extendedFreqRange = localStorage.getItem("extendedFreqRange");
|
var extendedFreqRange = localStorage.getItem("extendedFreqRange");
|
||||||
if (extendedFreqRange === "true") {
|
if (extendedFreqRange === "true") {
|
||||||
$("#extended-frequency-range").prop("checked", true);
|
$("#extended-frequency-range").prop("checked", true);
|
||||||
@@ -166,41 +217,4 @@ $(document).ready(() => {
|
|||||||
$('.version-string').text(currentVersion);
|
$('.version-string').text(currentVersion);
|
||||||
|
|
||||||
setBg();
|
setBg();
|
||||||
});
|
}
|
||||||
|
|
||||||
function getQueryParameter(name) {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
return urlParams.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTheme(themeName) {
|
|
||||||
const themeColors = themes[themeName];
|
|
||||||
if (themeColors) {
|
|
||||||
// Extracting the RGBA components from themeColors[2] for --color-text-2
|
|
||||||
const rgbaComponentsText = themeColors[2].match(/(\d+(\.\d+)?)/g);
|
|
||||||
const opacityText = parseFloat(rgbaComponentsText[3]);
|
|
||||||
const newOpacityText = opacityText * 0.75;
|
|
||||||
const textColor2 = `rgba(${rgbaComponentsText[0]}, ${rgbaComponentsText[1]}, ${rgbaComponentsText[2]})`;
|
|
||||||
|
|
||||||
// Extracting the RGBA components from themeColors[0] for background color
|
|
||||||
const rgbaComponentsBackground = themeColors[3].match(/(\d+(\.\d+)?)/g);
|
|
||||||
const backgroundOpacity = 0.75;
|
|
||||||
const backgroundColorWithOpacity = `rgba(${rgbaComponentsBackground[0]}, ${rgbaComponentsBackground[1]}, ${rgbaComponentsBackground[2]}, ${backgroundOpacity})`;
|
|
||||||
|
|
||||||
$(':root').css('--color-main', themeColors[0]);
|
|
||||||
$(':root').css('--color-main-bright', themeColors[1]);
|
|
||||||
$(':root').css('--color-text', themeColors[2]);
|
|
||||||
$(':root').css('--color-text-2', textColor2);
|
|
||||||
$('.wrapper-outer').css('background-color', backgroundColorWithOpacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setBg() {
|
|
||||||
const disableBackgroundParameter = getQueryParameter('disableBackground');
|
|
||||||
if(localStorage.getItem('bgImage').length > 5 && localStorage.getItem('theme') != 'theme9' && disableBackgroundParameter != 'true') {
|
|
||||||
$('body').css('background', 'url(' + localStorage.getItem('bgImage') + ') top center / cover fixed no-repeat var(--color-main)');
|
|
||||||
} else {
|
|
||||||
$('body').css('background', 'var(--color-main)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
const versionDate = new Date('May 30, 2025 21:00:00');
|
||||||
|
const currentVersion = `v1.3.9 [${versionDate.getDate()}/${versionDate.getMonth() + 1}/${versionDate.getFullYear()}]`;
|
||||||
|
|
||||||
|
|
||||||
function loadScript(src) {
|
function loadScript(src) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
|
|||||||
Reference in New Issue
Block a user