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
UI changes, accessibility features, ffmpeg adjustments
This commit is contained in:
@@ -8,12 +8,30 @@ h3 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
#data-ant {
|
||||
margin-right: 15px !important;
|
||||
}
|
||||
|
||||
#data-ps, #data-rt0, #data-rt1 {
|
||||
font-family: monospace;
|
||||
font-family: "Roboto Mono", monospace;
|
||||
}
|
||||
|
||||
#data-rt0, #data-rt1 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#data-ps {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#color-settings, #settings {
|
||||
@@ -66,6 +84,8 @@ h3 {
|
||||
#ps-container {
|
||||
background-color: var(--color-1);
|
||||
height: 100px !important;
|
||||
margin: auto !important;
|
||||
width: 100%;
|
||||
}
|
||||
h2 {
|
||||
display: none;
|
||||
@@ -80,7 +100,7 @@ h3 {
|
||||
padding: 0 !important;
|
||||
}
|
||||
#data-ps {
|
||||
font-size: 54px;
|
||||
font-size: 42px;
|
||||
}
|
||||
#data-frequency {
|
||||
font-size: 72px;
|
||||
@@ -96,7 +116,7 @@ h3 {
|
||||
ul {
|
||||
font-size: 16px;
|
||||
}
|
||||
ul li {
|
||||
#af-list ul li {
|
||||
display: inline-table;
|
||||
margin-right: 5px;
|
||||
width: 40px;
|
||||
@@ -130,3 +150,14 @@ h3 {
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 960px) and (max-height: 860px) {
|
||||
#rt-container {
|
||||
height: 90px !important;
|
||||
}
|
||||
#ps-container {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.canvas-container {
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,10 @@ button:hover {
|
||||
background-color: var(--color-main-bright);
|
||||
}
|
||||
|
||||
.btn-disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
#tune-buttons input[type="text"] {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
@@ -27,10 +31,6 @@ button:hover {
|
||||
font-family: 'Titillium Web', sans-serif;
|
||||
}
|
||||
|
||||
input[type="text"]:hover {
|
||||
border: 2px solid var(--color-main-bright);
|
||||
}
|
||||
|
||||
#tune-buttons button {
|
||||
box-sizing: border-box;
|
||||
background-color: var(--color-4);
|
||||
|
||||
80
web/css/dropdown.css
Normal file
80
web/css/dropdown.css
Normal file
@@ -0,0 +1,80 @@
|
||||
.dropdown {
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
background: var(--color-4);
|
||||
position: relative;
|
||||
margin-right: 20px;
|
||||
/*border-bottom: 4px solid var(--color-2);*/
|
||||
}
|
||||
@media (max-width: 400px) {
|
||||
.dropdown {
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
.dropdown::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 18px;
|
||||
z-index: 9999;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border: 1px solid var(--color-main);
|
||||
border-top: transparent;
|
||||
border-right: transparent;
|
||||
transform: rotate(-45deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
.dropdown ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.dropdown input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
user-select: none;
|
||||
background-color: var(--color-4);
|
||||
color: var(--color-main);
|
||||
}
|
||||
.dropdown input:focus {
|
||||
outline: none;
|
||||
}
|
||||
.dropdown input::placeholder {
|
||||
color: var(--color-main);
|
||||
opacity: 1;
|
||||
}
|
||||
.dropdown .options {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
background: var(--color-main);
|
||||
color: var(--color-4);
|
||||
border: 1px solid var(--color-4);
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
}
|
||||
.dropdown .options .option {
|
||||
color: var(--color-4);
|
||||
padding: 7px;
|
||||
}
|
||||
.dropdown .options .option:hover {
|
||||
color: var(--color-main);
|
||||
background: var(--color-4);
|
||||
}
|
||||
.dropdown.opened .options {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.dropdown.opened::before {
|
||||
transform: rotate(-225deg);
|
||||
top: 22px;
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500&display=swap');
|
||||
@import url("main.css"); /* Root stuff that affects the entire webpage (body, wrapper etc.) */
|
||||
@import url("breadcrumbs.css"); /* Stuff that applies to random elements only once/twice */
|
||||
@import url("buttons.css"); /* Buttons, inputs, select boxes, checkboxes... */
|
||||
@import url("dropdown.css"); /* Custom dropdown menus */
|
||||
@import url("panels.css"); /* Different panels and their sizes */
|
||||
@import url("modal.css"); /* Modal window */
|
||||
@import url("helpers.css"); /* Stuff that is used often such as text changers etc */
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
|
||||
.m-0 {
|
||||
margin: 0;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.m-left-20 {
|
||||
@@ -173,4 +173,17 @@
|
||||
.hide-desktop {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Laptop compact view */
|
||||
@media only screen and (min-width: 960px) and (max-height: 860px) {
|
||||
.text-big {
|
||||
font-size: 40px;
|
||||
}
|
||||
.text-medium-big {
|
||||
font-size: 34px;
|
||||
}
|
||||
.text-medium {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
@@ -52,15 +52,16 @@ body {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: auto;
|
||||
width: 1180px;
|
||||
max-width: 1180px;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
@media (max-width: 1180px) {
|
||||
#wrapper {
|
||||
position: static;
|
||||
transform: none;
|
||||
margin: 50px auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,4 +56,22 @@
|
||||
.panel-90 {
|
||||
margin-top: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Laptop compact view */
|
||||
@media only screen and (min-width: 960px) and (max-height: 860px) {
|
||||
*[class^="panel-"] {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.panel-10, .panel-90 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.panel-10 {
|
||||
margin: 0;
|
||||
padding-bottom: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.panel-10.hide-phone {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -44,13 +44,15 @@
|
||||
<body>
|
||||
<audio id="audioTag"></audio>
|
||||
<div id="wrapper">
|
||||
<canvas id="signal-canvas" width="1240" height="200"></canvas>
|
||||
<div class="canvas-container hide-phone">
|
||||
<canvas id="signal-canvas"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="flex-container">
|
||||
<div class="panel-90 bg-none">
|
||||
<div class="flex-container">
|
||||
<div class="panel-75 flex-container no-bg">
|
||||
<div class="panel-10 no-bg h-100 m-0 m-right-20 hide-phone" style="width: 100px;">
|
||||
<div class="panel-10 no-bg h-100 m-0 m-right-20 hide-phone" style="width: 100px;margin-right: 20px !important;">
|
||||
<button class="playbutton"><i class="fa-solid fa-play fa-lg"></i></button>
|
||||
</div>
|
||||
<div class="panel-100 m-0 hover-brighten flex-center" id="ps-container" style="height: 90px;">
|
||||
@@ -99,25 +101,34 @@
|
||||
<div class="panel-33 no-bg filter-controls" style="height: 48px;">
|
||||
<div class="flex-container flex-phone h-100">
|
||||
<div class="panel-75 no-bg h-100 m-0 hide-desktop m-right-20 button-play-mobile" style="margin-right: 20px;">
|
||||
<button class="playbutton"><i class="fa-solid fa-play"></i></button>
|
||||
<button class="playbutton" aria-label="Play/Stop Button"><i class="fa-solid fa-play"></i></button>
|
||||
</div>
|
||||
<div class="panel-50 no-bg h-100 m-0 dropdown" id="data-ant" style="margin-right: 25px;">
|
||||
<input type="text" placeholder="Ant A" readonly />
|
||||
<ul class="options">
|
||||
<li data-value="0" class="option">Ant A</li>
|
||||
<li data-value="1" class="option">Ant B</li>
|
||||
<li data-value="2" class="option">Ant C</li>
|
||||
<li data-value="3" class="option">Ant D</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="panel-100 no-bg h-100 m-0 button-eq">
|
||||
<button id="data-eq" style="border-radius: 30px 0px 0px 30px;"><span class="text-bold">EQ</span><br><span class="text-smaller">(RF+)</span></button>
|
||||
<button id="data-eq" style="border-radius: 30px 0px 0px 30px;" aria-label="EQ / RF+ Filter"><span class="text-bold">EQ</span><br><span class="text-smaller">(RF+)</span></button>
|
||||
</div>
|
||||
<div class="panel-100 no-bg h-100 m-0 button-ims">
|
||||
<button id="data-ims" style="border-radius: 0px 30px 30px 0px;"><span class="text-bold">iMS</span><br><span class="text-smaller">(IF+)</span></button>
|
||||
<button id="data-ims" style="border-radius: 0px 30px 30px 0px;" aria-label="iMS / IF+ Filter"><span class="text-bold">iMS</span><br><span class="text-smaller">(IF+)</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-33 flex-container flex-phone" id="tune-buttons">
|
||||
<button id="freq-down"><i class="fa-solid fa-chevron-left"></i></button>
|
||||
<button id="freq-down" aria-label="Tune down by 100 KHz"><i class="fa-solid fa-chevron-left"></i></button>
|
||||
<input type="text" id="commandinput" inputmode="numeric" placeholder="Frequency">
|
||||
<button id="freq-up"><i class="fa-solid fa-chevron-right"></i></button>
|
||||
<button id="freq-up" aria-label="Tune up by 100 KHz"><i class="fa-solid fa-chevron-right"></i></button>
|
||||
</div>
|
||||
|
||||
<div class="panel-33 hide-phone" style="height: 48px;">
|
||||
<input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="1">
|
||||
<input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="1" aria-label="Volume slider">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -167,7 +178,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="settings"><i class="fa-solid fa-gear"></i></button>
|
||||
<button id="settings" aria-label="Settings"><i class="fa-solid fa-gear"></i></button>
|
||||
|
||||
<div id="myModal" class="modal">
|
||||
<div class="modal-content">
|
||||
@@ -176,7 +187,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="themes" style="margin-top: 50px;"><i class="fa-solid fa-palette"></i> Theme:</label>
|
||||
<select name="themes" style="margin-bottom: 15px;" id="theme-selector">
|
||||
<!--<select name="themes" style="margin-bottom: 15px;" id="theme-selector">
|
||||
<option value="theme1">Monochrome</option>
|
||||
<option value="theme2">Red</option>
|
||||
<option value="theme3">Green</option>
|
||||
@@ -185,16 +196,38 @@
|
||||
<option value="theme6">Pink</option>
|
||||
<option value="theme7">Blurple</option>
|
||||
<option value="theme8">AMOLED</option>
|
||||
</select>
|
||||
</select>-->
|
||||
|
||||
<div class="dropdown" id="theme-selector">
|
||||
<input type="text" placeholder="Theme" readonly />
|
||||
<ul class="options">
|
||||
<li class="option" data-value="theme1">Monochrome</li>
|
||||
<li class="option" data-value="theme2">Red</li>
|
||||
<li class="option" data-value="theme3">Green</li>
|
||||
<li class="option" data-value="theme4">Cyan</li>
|
||||
<li class="option" data-value="theme5">Orange</li>
|
||||
<li class="option" data-value="theme6">Pink</li>
|
||||
<li class="option" data-value="theme7">Blurple</li>
|
||||
<li class="option" data-value="theme8">AMOLED</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-left: 10px;">
|
||||
<label for="signal" style="margin-top: 50px;"><i class="fa-solid fa-signal"></i> Signal units:</label>
|
||||
<select name="signal" style="margin-bottom: 15px;" id="signal-selector">
|
||||
<!--<select name="signal" style="margin-bottom: 15px;" id="signal-selector">
|
||||
<option value="dbf">dBf</option>
|
||||
<option value="dbuv">dBuV</option>
|
||||
<option value="dbm">dBm</option>
|
||||
</select>
|
||||
</select>-->
|
||||
<div class="dropdown" id="signal-selector">
|
||||
<input type="text" placeholder="Theme" readonly />
|
||||
<ul class="options">
|
||||
<li class="option" data-value="dbf">dBf</li>
|
||||
<li class="option" data-value="dbuv">dBuV</li>
|
||||
<li class="option" data-value="dbm">dBm</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-20">
|
||||
|
||||
49
web/js/dropdown.js
Normal file
49
web/js/dropdown.js
Normal file
@@ -0,0 +1,49 @@
|
||||
// Variables
|
||||
const $dropdowns = $('.dropdown');
|
||||
const $input = $('input');
|
||||
const $listOfOptions = $('.option');
|
||||
let currentDropdown = null; // Track the currently clicked dropdown
|
||||
|
||||
// Functions
|
||||
const toggleDropdown = (event) => {
|
||||
event.stopPropagation();
|
||||
const $currentDropdown = $(event.currentTarget);
|
||||
|
||||
// Close the previously opened dropdown if any
|
||||
$dropdowns.not($currentDropdown).removeClass('opened');
|
||||
|
||||
$currentDropdown.toggleClass('opened');
|
||||
currentDropdown = $currentDropdown.hasClass('opened') ? $currentDropdown : null;
|
||||
};
|
||||
|
||||
const selectOption = (event) => {
|
||||
const $currentDropdown = currentDropdown;
|
||||
$currentDropdown.find('input').val($(event.currentTarget).text());
|
||||
|
||||
if($currentDropdown.attr('id') == 'data-ant') {
|
||||
socket.send("Z" + $(event.currentTarget).attr('data-value'));
|
||||
}
|
||||
|
||||
// Use setTimeout to delay class removal
|
||||
setTimeout(() => {
|
||||
$currentDropdown.removeClass('opened');
|
||||
currentDropdown = null;
|
||||
}, 10); // Adjust the delay as needed
|
||||
};
|
||||
|
||||
const closeDropdownFromOutside = (event) => {
|
||||
const $currentDropdown = currentDropdown && $(currentDropdown);
|
||||
const isClickedInsideDropdown = $currentDropdown && $currentDropdown.has(event.target).length > 0;
|
||||
|
||||
if (!isClickedInsideDropdown && $currentDropdown && $currentDropdown.hasClass('opened')) {
|
||||
$currentDropdown.removeClass('opened');
|
||||
currentDropdown = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Event Listeners
|
||||
$(document).on('click', closeDropdownFromOutside);
|
||||
|
||||
$listOfOptions.on('click', selectOption);
|
||||
|
||||
$dropdowns.on('click', toggleDropdown);
|
||||
247
web/js/main.js
247
web/js/main.js
@@ -4,6 +4,8 @@ var port = hostParts[1] || '8080'; // Extract the port or use a default (e.g., 8
|
||||
var socketAddress = 'ws://' + hostname + ':' + port + '/text'; // Use 'wss' for secure WebSocket connections (recommended for external access)
|
||||
var socket = new WebSocket(socketAddress);
|
||||
var parsedData;
|
||||
var data = [];
|
||||
let signalChart;
|
||||
|
||||
const europe_programmes = [
|
||||
"No PTY", "News", "Current Affairs", "Info",
|
||||
@@ -16,130 +18,16 @@ const europe_programmes = [
|
||||
];
|
||||
|
||||
$(document).ready(function() {
|
||||
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 = 300;
|
||||
var pointWidth = (canvas.width - 80) / maxDataPoints;
|
||||
canvas.height = canvas.parentElement.clientHeight;
|
||||
|
||||
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) => {
|
||||
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 smooth shifting
|
||||
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));
|
||||
|
||||
// Interpolate between the current and previous points
|
||||
const interpolatedX = (x + prevX) / 2;
|
||||
const interpolatedY = (y + prevY) / 2;
|
||||
|
||||
context.quadraticCurveTo(prevX, prevY, interpolatedX, interpolatedY);
|
||||
}
|
||||
}
|
||||
|
||||
context.strokeStyle = color4;
|
||||
context.lineWidth = 1;
|
||||
context.stroke();
|
||||
|
||||
// Draw horizontal lines for lowest, highest, and average values
|
||||
context.strokeStyle = color2;
|
||||
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 signalUnit = localStorage.getItem('signalUnit');
|
||||
let offset;
|
||||
|
||||
if (signalUnit === 'dbuv') {
|
||||
offset = 11.25;
|
||||
} else if (signalUnit === 'dbm') {
|
||||
offset = 120;
|
||||
} else {
|
||||
offset = 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);
|
||||
}
|
||||
|
||||
initCanvas();
|
||||
|
||||
signalToggle.on("change", function() {
|
||||
const signalText = localStorage.getItem('signalUnit');
|
||||
@@ -250,6 +138,128 @@ function getInitialSettings() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initCanvas(parsedData) {
|
||||
signalToggle = $("#signal-units-toggle");
|
||||
|
||||
// Check if signalChart is already initialized
|
||||
if (!signalChart) {
|
||||
signalChart = {
|
||||
canvas: $('#signal-canvas')[0],
|
||||
context: $('#signal-canvas')[0].getContext('2d'),
|
||||
parsedData: parsedData,
|
||||
maxDataPoints: 300,
|
||||
}
|
||||
signalChart.pointWidth = (signalChart.canvas.width - 80) / signalChart.maxDataPoints;
|
||||
}
|
||||
|
||||
updateCanvas(parsedData, signalChart);
|
||||
}
|
||||
|
||||
function updateCanvas(parsedData, signalChart) {
|
||||
const color2 = getComputedStyle(document.documentElement).getPropertyValue('--color-2').trim();
|
||||
const color4 = getComputedStyle(document.documentElement).getPropertyValue('--color-4').trim();
|
||||
const {context, canvas, maxDataPoints, pointWidth} = signalChart;
|
||||
|
||||
while (data.length >= signalChart.maxDataPoints) {
|
||||
data.shift();
|
||||
}
|
||||
|
||||
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
|
||||
if(context) {
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Draw the signal graph with smooth shifting
|
||||
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));
|
||||
|
||||
// Interpolate between the current and previous points
|
||||
const interpolatedX = (x + prevX) / 2;
|
||||
const interpolatedY = (y + prevY) / 2;
|
||||
|
||||
context.quadraticCurveTo(prevX, prevY, interpolatedX, interpolatedY);
|
||||
}
|
||||
}
|
||||
|
||||
context.strokeStyle = color4;
|
||||
context.lineWidth = 1;
|
||||
context.stroke();
|
||||
|
||||
// Draw horizontal lines for lowest, highest, and average values
|
||||
context.strokeStyle = color2;
|
||||
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 signalUnit = localStorage.getItem('signalUnit');
|
||||
let offset;
|
||||
|
||||
if (signalUnit === 'dbuv') {
|
||||
offset = 11.25;
|
||||
} else if (signalUnit === 'dbm') {
|
||||
offset = 120;
|
||||
} else {
|
||||
offset = 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);
|
||||
|
||||
requestAnimationFrame(() => updateCanvas(parsedData, signalChart));
|
||||
}
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
parsedData = JSON.parse(event.data);
|
||||
updatePanels(parsedData);
|
||||
data.push(parsedData.signal);
|
||||
};
|
||||
|
||||
function compareNumbers(a, b) {
|
||||
return a - b;
|
||||
@@ -444,6 +454,7 @@ function updateDataElements(parsedData) {
|
||||
$('#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>`);
|
||||
$('#data-ant').find('input').val($(parsedData.ant).text());
|
||||
}
|
||||
|
||||
let isEventListenerAdded = false;
|
||||
@@ -500,9 +511,9 @@ function createListItem(element) {
|
||||
function updateButtonState(buttonId, value) {
|
||||
var button = $("#" + buttonId);
|
||||
if (value === 0) {
|
||||
button.addClass("bg-gray");
|
||||
button.addClass("btn-disabled");
|
||||
} else {
|
||||
button.removeClass("bg-gray");
|
||||
button.removeClass("btn-disabled");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +1,59 @@
|
||||
/* Themes */
|
||||
const themes = {
|
||||
theme1: ['#111', '#aaa'],
|
||||
theme2: ['#1f0c0c', '#ff7070'],
|
||||
theme3: ['#121c0c', '#a9ff70'],
|
||||
theme4: ['#0c1c1b', '#68f7ee'],
|
||||
theme5: ['#171106', '#f5b642'],
|
||||
theme6: ['#21091d', '#ed51d3'],
|
||||
theme7: ['#1d1838', '#8069fa'],
|
||||
theme8: ['#000', '#888'],
|
||||
};
|
||||
// Themes
|
||||
const themes = {
|
||||
theme1: ['#111', '#aaa'],
|
||||
theme2: ['#1f0c0c', '#ff7070'],
|
||||
theme3: ['#121c0c', '#a9ff70'],
|
||||
theme4: ['#0c1c1b', '#68f7ee'],
|
||||
theme5: ['#171106', '#f5b642'],
|
||||
theme6: ['#21091d', '#ed51d3'],
|
||||
theme7: ['#1d1838', '#8069fa'],
|
||||
theme8: ['#000', '#888'],
|
||||
};
|
||||
|
||||
/* Signal Units */
|
||||
const signalUnits = {
|
||||
dbf: ['dBf'],
|
||||
dbuv: ['dBµV'],
|
||||
dbm: ['dBm'],
|
||||
};
|
||||
// Signal Units
|
||||
const signalUnits = {
|
||||
dbf: ['dBf'],
|
||||
dbuv: ['dBµV'],
|
||||
dbm: ['dBm'],
|
||||
};
|
||||
|
||||
$(document).ready(() => {
|
||||
const themeSelector = $('#theme-selector');
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
$(document).ready(() => {
|
||||
// Theme Selector
|
||||
const themeSelector = $('#theme-selector');
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const savedUnit = localStorage.getItem('signalUnit');
|
||||
|
||||
if (savedTheme && themes[savedTheme]) {
|
||||
setTheme(savedTheme);
|
||||
themeSelector.val(savedTheme);
|
||||
}
|
||||
if (savedTheme && themes[savedTheme]) {
|
||||
setTheme(savedTheme);
|
||||
themeSelector.find('input').val(themeSelector.find('.option[data-value="' + savedTheme + '"]').text());
|
||||
}
|
||||
|
||||
themeSelector.on('change', (event) => {
|
||||
const selectedTheme = event.target.value;
|
||||
setTheme(selectedTheme);
|
||||
localStorage.setItem('theme', selectedTheme);
|
||||
});
|
||||
themeSelector.on('click', '.option', (event) => {
|
||||
const selectedTheme = $(event.target).data('value');
|
||||
setTheme(selectedTheme);
|
||||
themeSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
|
||||
localStorage.setItem('theme', selectedTheme);
|
||||
});
|
||||
|
||||
const signalSelector = $('#signal-selector');
|
||||
// Signal Selector
|
||||
const signalSelector = $('#signal-selector');
|
||||
|
||||
if (localStorage.getItem('signalUnit')) {
|
||||
signalSelector.val(localStorage.getItem('signalUnit'));
|
||||
}
|
||||
if (localStorage.getItem('signalUnit')) {
|
||||
signalSelector.find('input').val(signalSelector.find('.option[data-value="' + savedUnit + '"]').text());
|
||||
}
|
||||
|
||||
signalSelector.on('change', (event) => {
|
||||
const selectedSignalUnit = event.target.value;
|
||||
localStorage.setItem('signalUnit', selectedSignalUnit);
|
||||
});
|
||||
signalSelector.on('click', '.option', (event) => {
|
||||
const selectedSignalUnit = $(event.target).data('value');
|
||||
signalSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
|
||||
localStorage.setItem('signalUnit', selectedSignalUnit);
|
||||
});
|
||||
});
|
||||
|
||||
function setTheme(themeName) {
|
||||
const themeColors = themes[themeName];
|
||||
if (themeColors) {
|
||||
$(':root').css('--color-main', themeColors[0]);
|
||||
$(':root').css('--color-main-bright', themeColors[1]);
|
||||
}
|
||||
}
|
||||
|
||||
function setTheme(themeName) {
|
||||
const themeColors = themes[themeName];
|
||||
if (themeColors) {
|
||||
$(':root').css('--color-main', themeColors[0]);
|
||||
$(':root').css('--color-main-bright', themeColors[1]);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
$.getScript('/js/main.js');
|
||||
$.getScript('/js/dropdown.js');
|
||||
$.getScript('/js/modal.js');
|
||||
$.getScript('/js/settings.js');
|
||||
Reference in New Issue
Block a user