You've already forked fm-dx-webserver
mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-02-27 14:33:52 +01:00
offline optimization, new signal chart, ui optimization
This commit is contained in:
@@ -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,39 +107,60 @@ 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 };
|
||||||
if (bannedAS.some((as) => locationInfo.as?.includes(as))) {
|
callback(null, bannedAS);
|
||||||
return callback("User banned");
|
|
||||||
}
|
|
||||||
|
|
||||||
const userLocation =
|
|
||||||
locationInfo.country === undefined
|
|
||||||
? "Unknown"
|
|
||||||
: `${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.countryCode}`;
|
|
||||||
|
|
||||||
storage.connectedUsers.push({
|
|
||||||
ip: clientIp,
|
|
||||||
location: userLocation,
|
|
||||||
time: connectionTime,
|
|
||||||
instance: ws,
|
|
||||||
});
|
|
||||||
|
|
||||||
consoleCmd.logInfo(
|
|
||||||
`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${userLocation}`
|
|
||||||
);
|
|
||||||
|
|
||||||
callback("User allowed");
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error parsing banned AS list:", error);
|
console.error("Error parsing banned AS list:", error);
|
||||||
callback("User allowed");
|
callback(null, []); // Default to allowing user
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).on("error", (err) => {
|
});
|
||||||
|
|
||||||
|
// 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);
|
console.error("Error fetching banned AS list:", err);
|
||||||
callback("User allowed");
|
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))) {
|
||||||
|
return callback("User banned");
|
||||||
|
}
|
||||||
|
|
||||||
|
const userLocation =
|
||||||
|
locationInfo.country === undefined
|
||||||
|
? "Unknown"
|
||||||
|
: `${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.countryCode}`;
|
||||||
|
|
||||||
|
storage.connectedUsers.push({
|
||||||
|
ip: clientIp,
|
||||||
|
location: userLocation,
|
||||||
|
time: connectionTime,
|
||||||
|
instance: ws,
|
||||||
|
});
|
||||||
|
|
||||||
|
consoleCmd.logInfo(
|
||||||
|
`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${userLocation}`
|
||||||
|
);
|
||||||
|
|
||||||
|
callback("User allowed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function formatUptime(uptimeInSeconds) {
|
function formatUptime(uptimeInSeconds) {
|
||||||
const secondsInMinute = 60;
|
const secondsInMinute = 60;
|
||||||
|
|||||||
@@ -469,11 +469,9 @@ 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') ||
|
currentUsers--;
|
||||||
(request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
|
}
|
||||||
currentUsers--;
|
|
||||||
}
|
|
||||||
dataHandler.showOnlineUsers(currentUsers);
|
dataHandler.showOnlineUsers(currentUsers);
|
||||||
|
|
||||||
const index = storage.connectedUsers.findIndex(user => user.ip === clientIp);
|
const index = storage.connectedUsers.findIndex(user => user.ip === clientIp);
|
||||||
|
|||||||
@@ -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) {
|
||||||
const response = await fetch(usStatesGeoJsonUrl);
|
try {
|
||||||
usStatesGeoJson = await response.json();
|
const response = await fetch(usStatesGeoJsonUrl);
|
||||||
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
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) {
|
||||||
})
|
console.error("Error fetching data:", error);
|
||||||
.catch(error => {
|
return null; // Return null to indicate failure
|
||||||
console.error("Error fetching data:", error);
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|
||||||
esSwitchCache.lastCheck = now;
|
|
||||||
fetch(url)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (serverConfig.identification.lon < -32) {
|
|
||||||
if (data.north_america.max_frequency != "No data") {
|
|
||||||
esSwitch = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (data.europe.max_frequency != "No data") {
|
|
||||||
esSwitch = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
esSwitchCache.esSwitch = esSwitch;
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("Error fetching data:", error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return esSwitch;
|
if (serverConfig.identification.lat > 20) {
|
||||||
|
esSwitchCache.lastCheck = now;
|
||||||
|
fetch(url)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if ((serverConfig.identification.lon < -32 && data.north_america.max_frequency !== "No data") ||
|
||||||
|
(serverConfig.identification.lon >= -32 && data.europe.max_frequency !== "No data")) {
|
||||||
|
esSwitchCache.esSwitch = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Error fetching Es data:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return esSwitchCache.esSwitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
function haversine(lat1, lon1, lat2, lon2) {
|
function haversine(lat1, lon1, lat2, lon2) {
|
||||||
|
|||||||
@@ -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
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>
|
<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">
|
||||||
@@ -16,7 +20,7 @@
|
|||||||
<meta property="og:description" content="Server description: <%= tunerDescMeta %>.">
|
<meta property="og:description" content="Server description: <%= tunerDescMeta %>.">
|
||||||
|
|
||||||
<script src="js/init.js"></script>
|
<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>
|
||||||
@@ -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,27 +113,16 @@
|
|||||||
<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 => { %>
|
||||||
<i class="fa-solid fa-wave-square fa-lg top-10"></i><br>
|
<button class="no-bg color-4 hover-brighten" id="preset<%= preset %>" style="padding: 6px; width: 64px; min-width: 64px;">
|
||||||
<span style="font-size: 10px; color: var(--color-text);" id="preset1-text"></span>
|
<i class="fa-solid fa-wave-square fa-lg top-10"></i><br>
|
||||||
</button>
|
<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>
|
</div>
|
||||||
<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>
|
|
||||||
|
|
||||||
<div class="flex-container flex-phone" style="align-items: center; height: 64px;">
|
<div class="flex-container flex-phone" style="align-items: center; height: 64px;">
|
||||||
<div class="color-3 m-10 text-medium">
|
<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: '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">
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
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 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();
|
||||||
@@ -47,12 +47,7 @@ $(document).ready(function () {
|
|||||||
$panel.css('left', -panelWidth);
|
$panel.css('left', -panelWidth);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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) {
|
||||||
@@ -273,7 +267,9 @@ $(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
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateChartSettings(signalChart) {
|
function initCanvas() {
|
||||||
// Update colors
|
const ctx = document.getElementById("signal-canvas").getContext("2d");
|
||||||
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 updateCanvas(parsedData, signalChart) {
|
window.signalChart = new Chart(ctx, {
|
||||||
const { context, canvas, maxDataPoints, pointWidth, color2, color3, color4, offset } = signalChart;
|
type: "line",
|
||||||
|
data: {
|
||||||
if (data.length > maxDataPoints) {
|
datasets: [{
|
||||||
data = data.slice(data.length - maxDataPoints);
|
label: "Signal Strength",
|
||||||
}
|
borderColor: () => getComputedStyle(document.documentElement).getPropertyValue("--color-4").trim(),
|
||||||
|
borderWidth: 2,
|
||||||
const actualLowestValue = Math.min(...data);
|
fill: {
|
||||||
const actualHighestValue = Math.max(...data);
|
target: 'start'
|
||||||
const zoomMinValue = actualLowestValue - ((actualHighestValue - actualLowestValue) / 2);
|
},
|
||||||
const zoomMaxValue = actualHighestValue + ((actualHighestValue - actualLowestValue) / 2);
|
backgroundColor: () => getComputedStyle(document.documentElement).getPropertyValue("--color-1-transparent").trim(),
|
||||||
const zoomAvgValue = (zoomMaxValue - zoomMinValue) / 2 + zoomMinValue;
|
tension: 0.6,
|
||||||
|
data: []
|
||||||
// Clear the canvas
|
}]
|
||||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
},
|
||||||
context.beginPath();
|
options: {
|
||||||
|
layout: {
|
||||||
const startingIndex = Math.max(0, data.length - maxDataPoints);
|
padding: {
|
||||||
|
left: -10,
|
||||||
for (let i = startingIndex; i < data.length; i++) {
|
right: -10,
|
||||||
const x = canvas.width - (data.length - i) * pointWidth - 40;
|
bottom: -10
|
||||||
const y = canvas.height - (data[i] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue));
|
},
|
||||||
|
},
|
||||||
if (i === startingIndex) {
|
responsive: true,
|
||||||
context.moveTo(x, y);
|
maintainAspectRatio: false,
|
||||||
} else {
|
elements: {
|
||||||
const prevX = canvas.width - (data.length - i + 1) * pointWidth - 40;
|
point: { radius: 0 },
|
||||||
const prevY = canvas.height - (data[i - 1] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue));
|
},
|
||||||
const interpolatedX = (x + prevX) / 2;
|
scales: {
|
||||||
const interpolatedY = (y + prevY) / 2;
|
x: {
|
||||||
|
type: "realtime",
|
||||||
context.quadraticCurveTo(prevX, prevY, interpolatedX, interpolatedY);
|
ticks: { display: false },
|
||||||
}
|
border: { display: false },
|
||||||
}
|
grid: { display: false, borderWidth: 0, borderColor: "transparent" },
|
||||||
|
realtime: {
|
||||||
context.strokeStyle = color4;
|
duration: 30000,
|
||||||
context.lineWidth = 2;
|
refresh: 75,
|
||||||
context.stroke();
|
delay: 150,
|
||||||
|
onRefresh: (chart) => {
|
||||||
// Draw horizontal lines for lowest, highest, and average values
|
if (!chart?.data?.datasets || parsedData?.sig === undefined) return;
|
||||||
context.strokeStyle = color3;
|
|
||||||
context.lineWidth = 1;
|
signalBuffer.push(parsedData.sig);
|
||||||
|
if (signalBuffer.length > 8) {
|
||||||
// Draw the lowest value line
|
signalBuffer.shift();
|
||||||
const lowestY = canvas.height - (zoomMinValue - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue));
|
}
|
||||||
context.beginPath();
|
const avgSignal = signalBuffer.reduce((sum, val) => sum + val, 0) / signalBuffer.length;
|
||||||
context.moveTo(40, lowestY - 18);
|
|
||||||
context.lineTo(canvas.width - 40, lowestY - 18);
|
chart.data.datasets[0].data.push({
|
||||||
context.stroke();
|
x: Date.now(),
|
||||||
|
y: avgSignal
|
||||||
// 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);
|
y: {
|
||||||
context.stroke();
|
beginAtZero: false,
|
||||||
|
grace: 0.25,
|
||||||
const avgY = canvas.height / 2;
|
border: { display: false },
|
||||||
context.beginPath();
|
ticks: {
|
||||||
context.moveTo(40, avgY - 7);
|
maxTicksLimit: 3,
|
||||||
context.lineTo(canvas.width - 40, avgY - 7);
|
display: false // Hide default labels
|
||||||
context.stroke();
|
},
|
||||||
|
grid: {
|
||||||
// Label the lines with their values
|
display: false, // Hide default grid lines
|
||||||
context.fillStyle = color4;
|
},
|
||||||
context.font = '12px Titillium Web';
|
},
|
||||||
|
y2: {
|
||||||
context.textAlign = 'right';
|
position: 'right', // Position on the right side
|
||||||
context.fillText(`${(zoomMinValue - offset).toFixed(1)}`, 35, lowestY - 14);
|
beginAtZero: false,
|
||||||
context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, 35, highestY + 14);
|
grace: 0.25,
|
||||||
context.fillText(`${(zoomAvgValue - offset).toFixed(1)}`, 35, avgY - 3);
|
border: { display: false },
|
||||||
|
ticks: {
|
||||||
context.textAlign = 'left';
|
maxTicksLimit: 3,
|
||||||
context.fillText(`${(zoomMinValue - offset).toFixed(1)}`, canvas.width - 35, lowestY - 14);
|
display: false // Hide default labels for the right axis
|
||||||
context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, canvas.width - 35, highestY + 14);
|
},
|
||||||
context.fillText(`${(zoomAvgValue - offset).toFixed(1)}`, canvas.width - 35, avgY - 3);
|
grid: {
|
||||||
|
display: false, // No grid for right axis
|
||||||
setTimeout(() => {
|
}
|
||||||
requestAnimationFrame(() => updateCanvas(parsedData, signalChart));
|
}
|
||||||
}, 1000 / 15);
|
},
|
||||||
|
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;
|
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;
|
||||||
|
|
||||||
@@ -1114,9 +1101,12 @@ function showTunerDescription() {
|
|||||||
parentDiv.css("border-radius", "");
|
parentDiv.css("border-radius", "");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user