From 5b37848e35f72610299abea911af5f293957b6b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Farka=C5=A1?= Date: Sun, 14 Jan 2024 14:36:39 +0100 Subject: [PATCH] Add files via upload --- web/index.html | 104 ++++++++++++ web/main.js | 168 +++++++++++++++++++ web/modal.js | 33 ++++ web/stream.js | 20 +++ web/styles.css | 443 +++++++++++++++++++++++++++++++++++++++++++++++++ web/themes.js | 49 ++++++ 6 files changed, 817 insertions(+) create mode 100644 web/index.html create mode 100644 web/main.js create mode 100644 web/modal.js create mode 100644 web/stream.js create mode 100644 web/styles.css create mode 100644 web/themes.js diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..f2717fa --- /dev/null +++ b/web/index.html @@ -0,0 +1,104 @@ + + + + FM-DX Webserver [Noobish's Server] + + + + + +
+ + +
+
+ +
+ +
+

+ + TP + +

+
+
+ + +
+
+

PI CODE

+ +
+ +
+

FREQUENCY

+ +
+ +
+

SIGNAL

+ + + dBf + +
+
+ +
+
+ + +
+ +
+ +
+ +
+ +
+
+ +
+

RADIOTEXT

+
+
+ +
+ +
+
+ + + + + + + + + + \ No newline at end of file diff --git a/web/main.js b/web/main.js new file mode 100644 index 0000000..2531a55 --- /dev/null +++ b/web/main.js @@ -0,0 +1,168 @@ +const hostParts = window.location.host.split(':'); +const hostname = hostParts[0]; // Extract the hostname +const port = hostParts[1] || '8080'; // Extract the port or use a default (e.g., 8080) +const socketAddress = `ws://${hostname}:${port}/text`; // Use 'wss' for secure WebSocket connections (recommended for external access) +const socket = new WebSocket(socketAddress); + +const dataContainer = document.querySelector('#data-container'); +const canvas = document.querySelector('#signal-canvas'); +const context = canvas.getContext('2d'); + +var signalToggle = document.getElementById("signal-units-toggle"); + +canvas.width = canvas.parentElement.clientWidth; + +const data = []; +const maxDataPoints = 250; +const pointWidth = (canvas.width - 80) / maxDataPoints; + +var europe_programmes = [ + "No PTY", "News", "Current Affairs", "Info", + "Sport", "Education", "Drama", "Culture", "Science", "Varied", + "Pop M", "Rock M", "Easy Listening", "Light Classical", + "Serious Classical", "Other Music", "Weather", "Finance", + "Children's Programmes", "Social Affairs", "Religion", "Phone-in", + "Travel", "Leisure", "Jazz Music", "Country Music", "National Music", + "Oldies Music", "Folk Music", "Documentary", "Alarm Test" +]; + +// Function to handle zoom in +function zoomIn() { + zoomMinValue *= 0.9; + zoomMaxValue *= 0.9; +} + +// Function to handle zoom out +function zoomOut() { + zoomMinValue *= 1.1; + zoomMaxValue *= 1.1; +} + +function updateCanvas() { + // Remove old data when it exceeds the maximum data points + + const color2 = getComputedStyle(document.documentElement).getPropertyValue('--color-2').trim(); + const color4 = getComputedStyle(document.documentElement).getPropertyValue('--color-4').trim(); + + while (data.length >= maxDataPoints) { + data.shift(); + } + // Modify the WebSocket onmessage callback + socket.onmessage = (event) => { + const parsedData = JSON.parse(event.data); + + updatePanels(parsedData); + // Push the new signal data to the array + data.push(parsedData.signal); + const actualLowestValue = Math.min(...data); + const actualHighestValue = Math.max(...data); + zoomMinValue = actualLowestValue - ((actualHighestValue - actualLowestValue) / 2); + zoomMaxValue = actualHighestValue + ((actualHighestValue - actualLowestValue) / 2); + zoomAvgValue = (zoomMaxValue - zoomMinValue) / 2 + zoomMinValue; + + // Clear the canvas + context.clearRect(0, 0, canvas.width, canvas.height); + + // Draw the signal graph with zoom + context.beginPath(); + context.moveTo(50, canvas.height - (data[0] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue))); + + for (let i = 1; i < data.length; i++) { + const x = i * pointWidth; + const y = canvas.height - (data[i] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); + context.lineTo(x + 40, y); + } + + context.strokeStyle = color4; + context.lineWidth = 1; + context.stroke(); + + // Draw horizontal lines for lowest, highest, and average values + context.strokeStyle = color2; // Set line color + context.lineWidth = 1; + + // Draw the lowest value line + const lowestY = canvas.height - (zoomMinValue - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); + context.beginPath(); + context.moveTo(40, lowestY - 18); + context.lineTo(canvas.width - 40, lowestY - 18); + context.stroke(); + + // Draw the highest value line + const highestY = canvas.height - (zoomMaxValue - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); + context.beginPath(); + context.moveTo(40, highestY + 10); + context.lineTo(canvas.width - 40, highestY + 10); + context.stroke(); + + const avgY = canvas.height / 2; + context.beginPath(); + context.moveTo(40, avgY - 7); + context.lineTo(canvas.width - 40, avgY - 7); + context.stroke(); + + // Label the lines with their values + context.fillStyle = color4; + context.font = '12px Titillium Web'; + + const offset = signalToggle.checked ? 11.75 : 0; + context.textAlign = 'right'; + context.fillText(`${(zoomMinValue - offset).toFixed(1)}`, 35, lowestY - 14); + context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, 35, highestY + 14); + context.fillText(`${(zoomAvgValue - offset).toFixed(1)}`, 35, avgY - 3); + + context.textAlign = 'left'; + context.fillText(`${(zoomMinValue - offset).toFixed(1)}`, canvas.width - 35, lowestY - 14); + context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, canvas.width - 35, highestY + 14); + context.fillText(`${(zoomAvgValue - offset).toFixed(1)}`, canvas.width - 35, avgY - 3); + + // Update the data container with the latest data + dataContainer.innerHTML = event.data + '
'; + }; + + requestAnimationFrame(updateCanvas); +} + +// Start updating the canvas +updateCanvas(); + +function compareNumbers(a, b) { + return a - b; +} + +function divideByHundred(a) { + a = a / 100; +} + +function updatePanels(parsedData) { + sortedAf = parsedData.af.sort(compareNumbers); + + sortedAf.forEach((element, index, array) => { + array[index] = element / 1000; + + // Check if it's the last element in the array + if (index === array.length - 1) { + document.querySelector('#data-af').innerHTML = array; + } + }); + + document.querySelector('#data-frequency').textContent = parsedData.freq; + document.querySelector('#data-pi').innerHTML = parsedData.pi === '?' ? "?" : parsedData.pi; + document.querySelector('#data-ps').innerHTML = parsedData.ps === '?' ? "?" : parsedData.ps; + document.querySelector('#data-tp').innerHTML = parsedData.tp === false ? "TP" : "TP"; + document.querySelector('#data-pty').innerHTML = europe_programmes[parsedData.pty]; + document.querySelector('#data-st').innerHTML = parsedData.st === false ? "ST" : "ST"; + document.querySelector('#data-rt0').innerHTML = parsedData.rt0; + document.querySelector('#data-rt1').innerHTML = parsedData.rt1; + document.querySelector('#data-signal').textContent = signalToggle.checked ? (parsedData.signal - 11.75).toFixed(1) : parsedData.signal; +} + +signalToggle.addEventListener("change", function() { + signalText = document.querySelector('#signal-units'); + if (signalToggle.checked) { + signalText.textContent = 'dBµV'; + } else { + // Checkbox is unchecked + signalText.textContent = 'dBf'; + } +}); diff --git a/web/modal.js b/web/modal.js new file mode 100644 index 0000000..33c1b80 --- /dev/null +++ b/web/modal.js @@ -0,0 +1,33 @@ +// Get the modal element and the buttons to open and close it +var modal = document.getElementById("myModal"); +var openBtn = document.getElementById("settings"); +var closeBtn = document.getElementById("closeModal"); +var closeBtnFull = document.getElementById("closeModalButton"); + +// Function to open the modal +function openModal() { + modal.style.display = "block"; + setTimeout(function() { + modal.style.opacity = 1; + }, 10); +} + +// Function to close the modal +function closeModal() { + modal.style.opacity = 0; + setTimeout(function() { + modal.style.display = "none"; + }, 300); // This delay should match the transition duration (0.3s). +} + +// Event listeners for the open and close buttons +openBtn.addEventListener("click", openModal); +closeBtn.addEventListener("click", closeModal); +closeBtnFull.addEventListener("click", closeModal); + +// Close the modal when clicking outside of it +window.addEventListener("click", function(event) { + if (event.target == modal) { + closeModal(); + } +}); \ No newline at end of file diff --git a/web/stream.js b/web/stream.js new file mode 100644 index 0000000..5143e6e --- /dev/null +++ b/web/stream.js @@ -0,0 +1,20 @@ + const audioElement = document.getElementById("myAudio"); + const volumeSlider = document.getElementById("volumeSlider"); + const audioStream = "/audio-proxy"; + const uniqueTimestamp = Date.now(); // Create a unique timestamp + + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const audioSource = audioContext.createMediaElementSource(audioElement); + + audioSource.connect(audioContext.destination); + + // Set the audio element's source to your external audio stream + audioElement.src = `${audioStream}?${uniqueTimestamp}`; + + + audioElement.play(); + + volumeSlider.addEventListener("input", (event) => { + event.stopPropagation(); + audioElement.volume = volumeSlider.value; + }); \ No newline at end of file diff --git a/web/styles.css b/web/styles.css new file mode 100644 index 0000000..632a1b2 --- /dev/null +++ b/web/styles.css @@ -0,0 +1,443 @@ +@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'); + +:root { + --color-main: #1d1838; + --color-main-bright: #8069fa; + + --color-1: color-mix(in srgb, var(--color-main) 95%, var(--color-main-bright)); + --color-2: color-mix(in srgb, var(--color-main) 75%, var(--color-main-bright)); + --color-3: color-mix(in srgb, var(--color-main) 50%, var(--color-main-bright)); + --color-4: color-mix(in srgb, var(--color-main) 20%, var(--color-main-bright)); + --color-5: color-mix(in srgb, var(--color-main) 0%, var(--color-main-bright)); +} + +::selection { + background: var(--color-main-bright); + color: inherit; +} + +body { + font-family: 'Titillium Web', sans-serif; + color: white; + background: var(--color-main); +} + +a { + text-decoration: none; + color: white; +} + +#data-pi { + text-transform: uppercase; +} + +#wrapper { + margin: auto; + width: auto; + max-width: 1024px; +} + +#color-settings, #settings { + background: transparent; + border: 0; + color: white; + position: absolute; + top: 15px; + right: 15px; + font-size: 16px; + width: 64px; + height: 64px; + line-height: 64px; + text-align: center; + border-radius: 50%; + transition: 500ms ease-in-out background; + cursor: pointer; +} + +#color-settings { + top: 96px; +} + +#settings:hover, #color-settings:hover { + background: var(--color-3); +} + +h2 { + color: var(--color-4); + margin-bottom: 0; +} + +.flex-container { + display: flex; +} + +.panel-33 { + width: 33%; + background: var(--color-1); + margin-left: 10px; + margin-right: 10px; + border-radius: 30px; + text-align: center; + margin-top: 30px; +} + +.panel-75 { + width: 68%; + background: var(--color-1); + margin-left: 10px; + margin-right: 10px; + border-radius: 30px; + text-align: center; + margin-top: 30px; +} + + +.panel-100 { + width: 98%; + background: var(--color-1); + margin-left: 10px; + margin-right: 10px; + min-height: 100px; + border-radius: 30px; + text-align: center; + margin-top: 30px; +} + +.auto { + margin: auto; +} + +.text-big { + font-size: 72px; + font-weight: 600; +} + +.text-medium-big { + font-size: 24px; + color: #aaa; + font-weight: 300; +} + +.text-small { + font-size: 13px; +} + +.text-gray { + color: #666; +} + +.bg-dark { + background: #100d1f; +} + + + +@media only screen and (max-width: 768px) { + .flex-container { + display: block; + } + .modal-content { + max-width: 90%; + margin: auto; + } + .panel-33 { + width: 90%; + margin: auto; + margin-bottom: 20px; + } + .panel-75 { + margin: 80px auto 0 auto !important; + width: 90%; + } + .panel-33 h2 { + padding: 20px; + } + .text-big { + font-size: 54px; + display: block; + margin-top: -50px; + } + .panel-100 { + width: 90%; + margin: auto; + } + +} + +input[type="range"] { + margin: 0; + /* removing default appearance */ + -webkit-appearance: none; + appearance: none; + /* creating a custom design */ + width: 100%; + cursor: pointer; + outline: none; + /* slider progress trick */ + overflow: hidden; + border-radius: 30px; + height: 100%; + background: transparent; +} + +/* Track: Mozilla Firefox */ +input[type="range"]::-moz-range-track { + height: 48px; + background: var(--color-1); + border-radius: 30px; + border: 0; +} + +/* Thumb: webkit */ +input[type="range"]::-webkit-slider-thumb { + /* removing default appearance */ + -webkit-appearance: none; + appearance: none; + /* creating a custom design */ + height: 48px; + width: 48px; + background-color: #fff; + border-radius: 10px; + border: 2px solid var(--color-4); + /* slider progress trick */ + box-shadow: -407px 0 0 400px var(--color-4); +} + +/* Thumb: Firefox */ +input[type="range"]::-moz-range-thumb { + box-sizing: border-box; + height: 48px; + width: 48px; + background-color: var(--color-4); + border-radius: 0px 30px 30px 0px; + border: 0; + outline: none; + /* slider progress trick */ + box-shadow: -420px 0 0 400px var(--color-4); +} + +/* Toggle Switch */ + +.toggleSwitch span span { + display: none; +} + +.toggleSwitch { + user-select: none; + display: inline-block; + height: 48px; + position: relative; + overflow: hidden; + padding: 0; + cursor: pointer; + width: 100%; + border-radius: 25px; + font-weight: bold; +} +.toggleSwitch * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.toggleSwitch input:focus ~ a, +.toggleSwitch input:focus + label { + outline: none; +} +.toggleSwitch label { + position: relative; + z-index: 3; + display: block; + width: 100%; +} +.toggleSwitch input { + position: absolute; + opacity: 0; + z-index: 5; +} +.toggleSwitch > span { + position: absolute; + left: 0; + width: calc(100% - 6px); + margin: 0; + text-align: left; + white-space: nowrap; + margin:0; +} +.toggleSwitch > span span { + position: absolute; + top: 0; + left: 0; + z-index: 5; + display: block; + width: 50%; + margin-left: 50px; + text-align: left; + font-size: 0.9em; + width: auto; + opacity: 1; + width: 40%; + text-align: center; + line-height:48px; +} +.toggleSwitch a { + position: absolute; + right: 50%; + z-index: 4; + display: block; + top: 0; + bottom: 0; + padding: 0; + left: 0; + width: 50%; + background-color: var(--color-4); + border-radius: 25px; + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} +.toggleSwitch > span span:first-of-type { + color: var(--color-1); + opacity: 1; + left: 0; + margin: 0; + width: 50%; +} +.toggleSwitch > span span:last-of-type { + left:auto; + right:0; + color: var(--color-4); + margin: 0; + width: 50%; +} +.toggleSwitch > span:before { + content: ''; + display: block; + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: -2px; + border-radius: 30px; + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; +} +.toggleSwitch input:checked ~ a { + left: calc(50% - 3px); +} + +.toggleSwitch input:checked ~ span span:first-of-type { + left:0; + color: var(--color-4); +} +.toggleSwitch input:checked ~ span span:last-of-type { + color: var(--color-1); +} + +/* End Toggle Switch */ + + + +/* Style for the modal container */ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); /* Semi-transparent background */ + opacity: 0; + transition: opacity 0.3s ease-in-out; /* Fade-in/out transition */ + z-index: 20; /* Ensure the modal is above other content */ + color: var(--color-4); + backdrop-filter: blur(10px); +} + +/* Style for the modal content */ +.modal-content { + box-sizing: border-box; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: var(--color-main); + padding: 30px; + border-radius: 30px; + opacity: 1; + transition: opacity 0.3s ease-in-out; /* Fade-in/out transition */ + z-index: 21; /* Ensure the modal content is above the modal background */ + min-width: 500px; +} + +.modal-content p { + margin: 0; +} + +.modal-title { + font-size: 20px; + position: absolute; + font-weight: 300; + top: 14px; + left: 30px; +} + +/* Style for the close button */ +.close { + position: absolute; + top: 17px; + right: 30px; + cursor: pointer; + transition: 0.3s ease-in-out color; +} + +.close:hover { + color: white; +} + +.modal-content .button-close { + position: absolute; + bottom: 25px; + right: 35px; + width: 100px; + height: 48px; + border-radius: 30px; + background: var(--color-4); + font-weight: bold; + border: 0; + transition: 0.35s ease-in-out background; + cursor: pointer; +} + +.modal-content .button-close:hover { + background: var(--color-5); +} + +.modal label { + font-size: 12px; + font-weight: bold; + text-transform: uppercase; + display: block; +} + +.themes-list select { + display: none; +} + +#theme-selector { + height: 42px; + width: 150px; + padding: 10px; + background: var(--color-4); + color: var(--color-1); + border: 0; + border-bottom: 4px solid var(--color-2); + cursor: pointer; + transition: 0.35s ease-in-out background; +} + +#theme-selector:hover { + background: var(--color-5); +} \ No newline at end of file diff --git a/web/themes.js b/web/themes.js new file mode 100644 index 0000000..c6bde55 --- /dev/null +++ b/web/themes.js @@ -0,0 +1,49 @@ +const themes = { + theme1: { + '--color-main': '#1d1838', + '--color-main-bright': '#8069fa', + }, + theme2: { + '--color-main': '#381818', + '--color-main-bright': '#ff7070', + }, + theme3: { + '--color-main': '#121c0c', + '--color-main-bright': '#a9ff70', + }, + theme4: { + '--color-main': '#0c1c1b', + '--color-main-bright': '#68f7ee', + }, + theme5: { + '--color-main': '#171106', + '--color-main-bright': '#f5b642', + } +}; + + +function setTheme(themeName) { + const theme = themes[themeName]; + if (theme) { + for (const [variable, value] of Object.entries(theme)) { + document.documentElement.style.setProperty(variable, value); + } + } +} + +// Get the dropdown element +const themeSelector = document.getElementById('theme-selector'); + +const savedTheme = localStorage.getItem("theme"); +if(savedTheme) { + setTheme(savedTheme); + themeSelector.value = savedTheme; +} + +// Listen for changes in the dropdown +themeSelector.addEventListener('change', (event) => { + const selectedTheme = event.target.value; + setTheme(selectedTheme); + localStorage.setItem("theme", selectedTheme); + +});