mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-07-29 16:29:19 +02:00
decode lps for some reason, and also intregrate enchanced tuning
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
// Plugin configuration, this is used in the administration when plugins are loaded
|
||||
var pluginConfig = {
|
||||
name: 'Example plugin',
|
||||
version: '1.0',
|
||||
author: 'OpenRadio',
|
||||
frontEndPath: 'example/frontend.js'
|
||||
}
|
||||
|
||||
// Backend (server) changes can go here...
|
||||
|
||||
// Don't change anything below here if you are making your own plugin
|
||||
module.exports = {
|
||||
pluginConfig
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
console.log('Webserver plugins loaded successfully.');
|
||||
@@ -27,6 +27,7 @@ var dataToSend = {
|
||||
rt0: '',
|
||||
rt1: '',
|
||||
rt_flag: '',
|
||||
lps: '',
|
||||
ims: 0,
|
||||
eq: 0,
|
||||
agc: 0,
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
const endpointsRouter = require('./endpoints');
|
||||
|
||||
// --- STØTTE FOR FLERE INSTANSER (--config) ---
|
||||
let configSuffix = '';
|
||||
const configArgIndex = process.argv.indexOf('--config');
|
||||
if (configArgIndex !== -1 && process.argv.length > configArgIndex + 1) {
|
||||
configSuffix = '_' + process.argv[configArgIndex + 1];
|
||||
} else if (process.env.CONFIG) {
|
||||
configSuffix = '_' + process.env.CONFIG;
|
||||
}
|
||||
const CONFIG_FILE = path.join(__dirname, `enhanced_tuning_config${configSuffix}.json`);
|
||||
// ---------------------------------------------
|
||||
|
||||
// Default Configuration
|
||||
let pluginConfig = {
|
||||
// Layout & UI
|
||||
LAYOUT_STYLE: 'modern',
|
||||
HIDE_ALL_BUTTONS: false,
|
||||
SHOW_LOOP_BUTTON: true,
|
||||
SHOW_BAND_RANGE: true,
|
||||
HIDE_DECIMAL_FOR_HF: false,
|
||||
HIDE_DECIMAL_HF_THRESHOLD: 30,
|
||||
ENABLE_TUNE_STEP_FEATURE: true,
|
||||
TUNE_STEP_TIMEOUT_SECONDS: 20,
|
||||
ENABLED_BANDS: ['FM', 'OIRT', 'SW', 'MW', 'LW'],
|
||||
|
||||
// Tuning & Hardware
|
||||
TUNING_STANDARD: 'international',
|
||||
ENABLE_MW_STEP_TOGGLE: true,
|
||||
ENABLE_FREQUENCY_MEMORY: true,
|
||||
ENABLE_SMART_KHZ_INPUT: true,
|
||||
ENABLE_AM_BW: true,
|
||||
FIRMWARE_TYPE: 'FM-DX-Tuner',
|
||||
ENABLE_DEFAULT_AM_BW: false,
|
||||
DEFAULT_AM_BW_VALUE: '56000',
|
||||
|
||||
// Limits
|
||||
overrideServerTuningLimit: true,
|
||||
fmLower: 64.0, fmUpper: 108.0,
|
||||
amLower: 0.144, amUpper: 30.0,
|
||||
|
||||
// Egendefinerte Bånd & Navn
|
||||
customMainBands: {
|
||||
'AM_SUPER': { name: 'AM', tune: 1.000, start: 0.144, end: 27.0 },
|
||||
'FM': { name: 'FM', tune: 87.500, start: 87.5, end: 108.0 },
|
||||
'OIRT': { name: 'OIRT', tune: 65.900, start: 65.9, end: 74.0 },
|
||||
'SW': { name: 'SW', tune: 9.400, start: 1.710, end: 27.0 },
|
||||
'MW': { name: 'MW', tune: 0.504, start: 0.504, end: 1.701 },
|
||||
'LW': { name: 'LW', tune: 0.144, start: 0.144, end: 0.351 }
|
||||
},
|
||||
customSwBands: {
|
||||
'160m': { tune: 1.8, start: 1.8, end: 2.0 }, '120m': { tune: 2.3, start: 2.3, end: 2.5 },
|
||||
'90m': { tune: 3.2, start: 3.2, end: 3.4 }, '75m': { tune: 3.9, start: 3.9, end: 4.0 },
|
||||
'60m': { tune: 4.75, start: 4.75, end: 5.06 }, '49m': { tune: 5.9, start: 5.9, end: 6.2 },
|
||||
'41m': { tune: 7.2, start: 7.2, end: 7.6 }, '31m': { tune: 9.4, start: 9.4, end: 9.9 },
|
||||
'25m': { tune: 11.6, start: 11.6, end: 12.1 }, '22m': { tune: 13.57, start: 13.57, end: 13.87 },
|
||||
'19m': { tune: 15.1, start: 15.1, end: 15.83 }, '16m': { tune: 17.48, start: 17.48, end: 17.9 },
|
||||
'15m': { tune: 18.9, start: 18.9, end: 19.02 }, '13m': { tune: 21.45, start: 21.45, end: 21.85 },
|
||||
'11m': { tune: 25.67, start: 25.67, end: 26.1 }
|
||||
},
|
||||
|
||||
// Plugins Integration
|
||||
ENABLE_AM_SCANNER: false,
|
||||
ENABLE_ANALOG_SCALE: true,
|
||||
ANALOG_SCALE_AUTOSTART: true,
|
||||
SHOW_TUNING_KNOB: true,
|
||||
ANALOG_SCALE_ENABLE_FM: true,
|
||||
ANALOG_SCALE_BRIGHTNESS: 1.5,
|
||||
ENABLE_VU_METER: true,
|
||||
VU_METER_GAIN_FM: 1.5,
|
||||
VU_METER_GAIN_AM: 1.5,
|
||||
VU_METER_MODE: 'RMS',
|
||||
ENABLE_MAGIC_EYE: true,
|
||||
ENABLE_PS_SCALE: true,
|
||||
ENABLE_SPECTRUM_OVERLAY: true,
|
||||
ENABLE_SW_STATIONS_SCALE: false,
|
||||
|
||||
// AM Scanner Thresholds
|
||||
thr_LW: 35, thr_MW: 35, thr_160m: 35, thr_120m: 35, thr_90m: 35,
|
||||
thr_75m: 35, thr_60m: 35, thr_49m: 35, thr_41m: 35, thr_31m: 35,
|
||||
thr_25m: 35, thr_22m: 35, thr_19m: 35, thr_16m: 35, thr_15m: 35,
|
||||
thr_13m: 35, thr_11m: 35
|
||||
};
|
||||
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
try {
|
||||
const data = fs.readFileSync(CONFIG_FILE, 'utf8');
|
||||
pluginConfig = { ...pluginConfig, ...JSON.parse(data) };
|
||||
} catch (err) {
|
||||
console.error('[Enhanced Tuning] Feil ved lesing av config:', err);
|
||||
}
|
||||
}
|
||||
|
||||
const checkStrictAdmin = (req, res, next) => {
|
||||
if (req.session && req.session.isAdminAuthenticated) return next();
|
||||
return res.status(401).send('Unauthorized. You must be an administrator.');
|
||||
};
|
||||
|
||||
endpointsRouter.get('/enhanced_tuning/api/auth-check', (req, res) => {
|
||||
res.json({
|
||||
isAdmin: (req.session && req.session.isAdminAuthenticated) || false,
|
||||
isTune: true,
|
||||
config: pluginConfig
|
||||
});
|
||||
});
|
||||
|
||||
endpointsRouter.post('/enhanced_tuning/api/config', checkStrictAdmin, express.json(), (req, res) => {
|
||||
try {
|
||||
pluginConfig = { ...pluginConfig, ...req.body };
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(pluginConfig, null, 2));
|
||||
res.json({ success: true, message: 'Settings saved successfully' });
|
||||
} catch (err) {
|
||||
console.error('[Enhanced Tuning] Feil ved lagring av config:', err);
|
||||
res.status(500).json({ success: false });
|
||||
}
|
||||
});
|
||||
|
||||
// Admin Panel Side
|
||||
endpointsRouter.get('/enhanced_tuning/AP', checkStrictAdmin, (req, res) => {
|
||||
const createToggleRow = (id, title, desc, isChecked) => `
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-title">${title}</div>
|
||||
<div class="setting-desc">${desc}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="${id}" ${isChecked ? 'checked' : ''}>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const createInputRow = (id, title, desc, value, type = "text", step = "1") => `
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-title">${title}</div>
|
||||
<div class="setting-desc">${desc}</div>
|
||||
</div>
|
||||
<input type="${type}" id="${id}" value="${value}" step="${step}" class="dark-input">
|
||||
</div>
|
||||
`;
|
||||
|
||||
const createSelectRow = (id, title, desc, options, selectedValue) => {
|
||||
let opts = options.map(opt => `<option value="${opt.val}" ${opt.val === selectedValue ? 'selected' : ''}>${opt.label}</option>`).join('');
|
||||
return `
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-title">${title}</div>
|
||||
<div class="setting-desc">${desc}</div>
|
||||
</div>
|
||||
<select id="${id}" class="dark-input">${opts}</select>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
const createSliderRow = (id, title, desc, min, max, step, value) => `
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-title">${title}</div>
|
||||
<div class="setting-desc">${desc}</div>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:15px;">
|
||||
<input type="range" id="${id}" min="${min}" max="${max}" step="${step}" value="${value}" style="cursor: pointer;">
|
||||
<span id="${id}_display" style="color:var(--accent); font-weight:bold; width: 35px; text-align:right;">${value}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Enhanced Tuning Settings</title>
|
||||
<style>
|
||||
:root { --bg: #121212; --panel-bg: #1e1e1e; --row-bg: #181818; --border: #333; --text: #f0f0f0; --subtext: #999; --accent: #3498db; --accent-hover: #2980b9; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background-color: var(--bg); color: var(--text); margin: 0; display: flex; height: 100vh; overflow: hidden; }
|
||||
.sidebar { width: 260px; background-color: var(--panel-bg); border-right: 1px solid var(--border); display: flex; flex-direction: column; }
|
||||
.sidebar-header { padding: 20px; font-size: 18px; font-weight: bold; border-bottom: 1px solid var(--border); color: var(--accent); }
|
||||
.nav-btn { padding: 15px 20px; background: none; border: none; color: var(--subtext); text-align: left; cursor: pointer; font-size: 15px; border-left: 3px solid transparent; transition: all 0.2s; }
|
||||
.nav-btn:hover { background-color: var(--row-bg); color: var(--text); }
|
||||
.nav-btn.active { border-left-color: var(--accent); color: var(--accent); background-color: rgba(52, 152, 219, 0.1); font-weight: bold; }
|
||||
.main-content { flex: 1; padding: 40px; overflow-y: auto; position: relative; }
|
||||
.tab-content { display: none; max-width: 900px; margin: 0 auto; }
|
||||
.tab-content.active { display: block; animation: fadeIn 0.3s; }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
|
||||
h2 { color: var(--accent); margin-top: 0; margin-bottom: 25px; font-weight: 500; font-size: 24px; border-bottom: 1px solid var(--border); padding-bottom: 10px; }
|
||||
.setting-row { display: flex; justify-content: space-between; align-items: center; background-color: var(--row-bg); padding: 15px 20px; border: 1px solid var(--border); border-radius: 8px; margin-bottom: 12px; transition: border-color 0.2s; }
|
||||
.setting-row:hover { border-color: #555; }
|
||||
.setting-info { flex: 1; padding-right: 20px; }
|
||||
.setting-title { font-size: 16px; font-weight: 600; margin-bottom: 4px; }
|
||||
.setting-desc { font-size: 13px; color: var(--subtext); line-height: 1.4; }
|
||||
.dark-input { background-color: var(--bg); border: 1px solid var(--border); color: var(--text); padding: 10px 15px; border-radius: 6px; font-size: 14px; min-width: 150px; outline: none; transition: border-color 0.2s; }
|
||||
.dark-input:focus { border-color: var(--accent); }
|
||||
.switch { position: relative; display: inline-block; width: 44px; height: 24px; flex-shrink: 0; }
|
||||
.switch input { opacity: 0; width: 0; height: 0; }
|
||||
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #444; transition: .3s; border-radius: 24px; }
|
||||
.slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; }
|
||||
input:checked + .slider { background-color: var(--accent); }
|
||||
input:checked + .slider:before { transform: translateX(20px); }
|
||||
.threshold-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }
|
||||
.threshold-card { background: var(--row-bg); border: 1px solid var(--border); padding: 15px; border-radius: 8px; display: flex; flex-direction: column; align-items: center; gap: 10px; }
|
||||
.threshold-card span { font-weight: bold; color: var(--accent); }
|
||||
.threshold-card input { width: 80px; text-align: center; }
|
||||
.btn-save { background-color: var(--accent); color: white; border: none; padding: 12px 25px; border-radius: 6px; font-size: 16px; font-weight: bold; cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.3); transition: background 0.2s; }
|
||||
.btn-save:hover { background-color: var(--accent-hover); }
|
||||
#toast { visibility: hidden; min-width: 250px; background-color: #27ae60; color: #fff; text-align: center; border-radius: 6px; padding: 16px; position: fixed; z-index: 1000; bottom: 40px; left: 50%; transform: translateX(-50%); font-size: 16px; font-weight: bold; opacity: 0; transition: opacity 0.3s; }
|
||||
#toast.show { visibility: visible; opacity: 1; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-header">Enhanced Tuning</div>
|
||||
<button class="nav-btn active" data-target="tab-ui">1. General UI & Layout</button>
|
||||
<button class="nav-btn" data-target="tab-hardware">2. Tuning & Hardware</button>
|
||||
<button class="nav-btn" data-target="tab-limits">3. Band Limits</button>
|
||||
<button class="nav-btn" data-target="tab-plugins">4. Plugin Integration</button>
|
||||
|
||||
<div style="margin-top: auto; padding: 20px; border-top: 1px solid var(--border);">
|
||||
<button class="btn-save" id="saveBtn" style="width: 100%;">Save Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div id="tab-ui" class="tab-content active">
|
||||
<h2>General UI & Layout</h2>
|
||||
${createSelectRow('LAYOUT_STYLE', 'Visual Layout Style', 'Choose the layout design for the plugin controls.', [{val: 'modern', label:'Modern (Side Panel)'}, {val:'classic', label:'Classic (Compact)'}], pluginConfig.LAYOUT_STYLE)}
|
||||
${createToggleRow('HIDE_ALL_BUTTONS', 'Hide All Buttons', 'Hides new UI elements. Only effective if Layout is Modern.', pluginConfig.HIDE_ALL_BUTTONS)}
|
||||
${createToggleRow('SHOW_LOOP_BUTTON', 'Show Band Loop Button', 'Enable or disable the frequency loop feature entirely.', pluginConfig.SHOW_LOOP_BUTTON)}
|
||||
${createToggleRow('SHOW_BAND_RANGE', 'Show Band Range', 'Show the start ↔ end frequency text under the main display.', pluginConfig.SHOW_BAND_RANGE)}
|
||||
${createToggleRow('HIDE_DECIMAL_FOR_HF', 'Hide Decimal Point For HF Band', 'For AM/LW/MW/SW frequencies, hide the decimal point in the main frequency display.', pluginConfig.HIDE_DECIMAL_FOR_HF)}
|
||||
${createInputRow('HIDE_DECIMAL_HF_THRESHOLD', 'Hide Decimal Threshold (MHz)', 'Frequencies at or below this value (in MHz) will have the decimal point hidden.', pluginConfig.HIDE_DECIMAL_HF_THRESHOLD, 'number', '0.001')}
|
||||
${createToggleRow('ENABLE_TUNE_STEP_FEATURE', 'Tune Step Feature', 'Click frequency display to change tuning steps.', pluginConfig.ENABLE_TUNE_STEP_FEATURE)}
|
||||
${createInputRow('TUNE_STEP_TIMEOUT_SECONDS', 'Tune Step Timeout', 'Seconds of inactivity before step resets (0 to disable).', pluginConfig.TUNE_STEP_TIMEOUT_SECONDS, 'number')}
|
||||
|
||||
<h3 style="margin-top:30px; color:#aaa; font-size:16px;">Enabled Bands</h3>
|
||||
${createToggleRow('band_FM', 'FM Band', '', pluginConfig.ENABLED_BANDS.includes('FM'))}
|
||||
${createToggleRow('band_OIRT', 'OIRT Band', '', pluginConfig.ENABLED_BANDS.includes('OIRT'))}
|
||||
${createToggleRow('band_SW', 'Shortwave (SW) Band', '', pluginConfig.ENABLED_BANDS.includes('SW'))}
|
||||
${createToggleRow('band_MW', 'Mediumwave (MW) Band', '', pluginConfig.ENABLED_BANDS.includes('MW'))}
|
||||
${createToggleRow('band_LW', 'Longwave (LW) Band', '', pluginConfig.ENABLED_BANDS.includes('LW'))}
|
||||
</div>
|
||||
|
||||
<div id="tab-hardware" class="tab-content">
|
||||
<h2>Tuning & Hardware Options</h2>
|
||||
${createSelectRow('TUNING_STANDARD', 'Regional Tuning Standard', 'Controls AM/FM steps and ranges.', [{val:'international', label:'International'}, {val:'americas', label:'Americas'}, {val:'japan', label:'Japan'}], pluginConfig.TUNING_STANDARD)}
|
||||
${createToggleRow('ENABLE_SMART_KHZ_INPUT', 'Smart kHz Input', 'Type e.g. "693" instead of "0.693". Auto-converts numbers on AM bands to avoid plugin conflicts.', pluginConfig.ENABLE_SMART_KHZ_INPUT)}
|
||||
${createToggleRow('ENABLE_MW_STEP_TOGGLE', 'MW 9/10kHz Toggle Button', 'Shows a button when in MW to switch step size.', pluginConfig.ENABLE_MW_STEP_TOGGLE)}
|
||||
${createToggleRow('ENABLE_FREQUENCY_MEMORY', 'Frequency Memory', 'Remember last tuned frequency per band.', pluginConfig.ENABLE_FREQUENCY_MEMORY)}
|
||||
|
||||
<h3 style="margin-top:30px; color:#aaa; font-size:16px;">AM Bandwidth Injection</h3>
|
||||
${createToggleRow('ENABLE_AM_BW', 'Enable Custom AM Bandwidth', 'Requires TEF6686_ESP32 firmware.', pluginConfig.ENABLE_AM_BW)}
|
||||
${createSelectRow('FIRMWARE_TYPE', 'Firmware Type', 'Select compatibility mode for bandwidth.', [{val:'TEF6686_ESP32', label:'TEF6686_ESP32 (PE5PVB)'}, {val:'FM-DX-Tuner', label:'FM-DX-Tuner (kkonrad)'}], pluginConfig.FIRMWARE_TYPE)}
|
||||
${createToggleRow('ENABLE_DEFAULT_AM_BW', 'Force Default AM BW', 'Auto-select a specific BW when entering AM.', pluginConfig.ENABLE_DEFAULT_AM_BW)}
|
||||
${createSelectRow('DEFAULT_AM_BW_VALUE', 'Default AM BW Value', 'Applied if forced default is enabled.', [{val:'56000', label:'3 kHz'}, {val:'64000', label:'4 kHz'}, {val:'72000', label:'6 kHz'}, {val:'84000', label:'8 kHz'}], pluginConfig.DEFAULT_AM_BW_VALUE)}
|
||||
</div>
|
||||
|
||||
<div id="tab-limits" class="tab-content">
|
||||
<h2>Tuning Limits & Bands Configuration</h2>
|
||||
|
||||
${createToggleRow('overrideServerTuningLimit', 'Override Server Limits', 'Allow plugin to manage its own separate hardware limits for AM and FM. Set the actual values in the core "Webserver -> Tuning Options" panel.', pluginConfig.overrideServerTuningLimit)}
|
||||
|
||||
<h3 style="margin-top:30px; color:#aaa; font-size:16px;">Main Bands Configuration</h3>
|
||||
<p style="color:var(--subtext); font-size:13px; margin-bottom:15px;">Rename bands (max 5 chars to fit buttons), set default start frequency, and adjust limits.</p>
|
||||
|
||||
${['AM_SUPER', 'FM', 'OIRT', 'SW', 'MW', 'LW'].map(key => `
|
||||
<div class="setting-row" style="padding: 10px 20px;">
|
||||
<div class="setting-info"><div class="setting-title" style="font-size:14px;">${key}</div></div>
|
||||
<div style="display:flex; gap:8px; align-items:center;">
|
||||
<span style="color:var(--subtext); font-size:11px;">Name</span>
|
||||
<input type="text" id="name_${key}" maxlength="5" value="${pluginConfig.customMainBands[key].name}" class="dark-input" style="width:70px; min-width:auto; padding:6px 10px; text-align:center;">
|
||||
<span style="color:var(--subtext); font-size:11px;">Default Tune</span>
|
||||
<input type="number" id="tune_${key}" value="${pluginConfig.customMainBands[key].tune}" step="0.001" class="dark-input" style="width:65px; min-width:auto; padding:6px 10px;">
|
||||
<span style="color:var(--subtext); font-size:11px;">Low</span>
|
||||
<input type="number" id="start_${key}" value="${pluginConfig.customMainBands[key].start}" step="0.001" class="dark-input" style="width:65px; min-width:auto; padding:6px 10px;">
|
||||
<span style="color:var(--subtext); font-size:11px;">High</span>
|
||||
<input type="number" id="end_${key}" value="${pluginConfig.customMainBands[key].end}" step="0.001" class="dark-input" style="width:65px; min-width:auto; padding:6px 10px;">
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
|
||||
<h3 style="margin-top:30px; color:#aaa; font-size:16px;">Shortwave Sub-Bands</h3>
|
||||
<div style="display: grid; grid-template-columns: 1fr; gap: 10px;">
|
||||
${Object.keys(pluginConfig.customSwBands).map(key => `
|
||||
<div class="setting-row" style="padding: 10px; margin-bottom:0;">
|
||||
<div class="setting-info"><div class="setting-title" style="font-size:13px;">${key}</div></div>
|
||||
<div style="display:flex; gap:5px; align-items:center;">
|
||||
<span style="color:var(--subtext); font-size:11px;">Default Tune</span>
|
||||
<input type="number" id="sw_tune_${key}" value="${pluginConfig.customSwBands[key].tune}" step="0.001" class="dark-input" style="width:65px; min-width:auto; padding:4px 8px; font-size:12px;">
|
||||
<span style="color:var(--subtext); font-size:11px; margin-left:10px;">Range</span>
|
||||
<input type="number" id="sw_start_${key}" value="${pluginConfig.customSwBands[key].start}" step="0.001" class="dark-input" style="width:65px; min-width:auto; padding:4px 8px; font-size:12px;">
|
||||
<span style="color:var(--subtext); font-size:12px;">-</span>
|
||||
<input type="number" id="sw_end_${key}" value="${pluginConfig.customSwBands[key].end}" step="0.001" class="dark-input" style="width:65px; min-width:auto; padding:4px 8px; font-size:12px;">
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Den nye Tab 4: Plugin Integration -->
|
||||
<div id="tab-plugins" class="tab-content">
|
||||
<h2>Plugin Integration</h2>
|
||||
|
||||
<h3 style="margin-top:20px; color:#aaa; font-size:16px;">Highpoint Scanner Integration</h3>
|
||||
${createToggleRow('ENABLE_AM_SCANNER', 'Enable Scanner plugin override', 'Intercepts the Highpoint scanner to respect AM/SW band limits and sub-bands.', pluginConfig.ENABLE_AM_SCANNER)}
|
||||
|
||||
<div id="scanner-thresholds-block" style="display: ${pluginConfig.ENABLE_AM_SCANNER ? 'block' : 'none'}; border-left: 2px solid #3498db; padding-left: 15px; margin-bottom: 30px; margin-top: 15px;">
|
||||
<p style="color:var(--subtext); margin-bottom: 15px; font-size: 13px;">Define the minimum signal strength (0-100) required to pause the scanner on a specific AM/SW band.</p>
|
||||
<div class="threshold-grid">
|
||||
${['LW', 'MW', '160m', '120m', '90m', '75m', '60m', '49m', '41m', '31m', '25m', '22m', '19m', '16m', '15m', '13m', '11m'].map(t => `
|
||||
<div class="threshold-card" style="padding: 10px;">
|
||||
<span>${t} Band</span>
|
||||
<input type="number" id="thr_${t}" value="${pluginConfig['thr_'+t]}" class="dark-input" style="width:60px;">
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top:40px; color:#aaa; font-size:16px;">Highpoint Retro Design Elements Plugin</h3>
|
||||
${createToggleRow('ENABLE_ANALOG_SCALE', 'Enable Analog Scale Integration', 'Injects a dynamic retro analog scale for all bands.', pluginConfig.ENABLE_ANALOG_SCALE)}
|
||||
|
||||
<div id="scale-settings-block" style="display: ${pluginConfig.ENABLE_ANALOG_SCALE ? 'block' : 'none'}; border-left: 2px solid #3498db; padding-left: 15px; margin-bottom: 30px; margin-top: 15px;">
|
||||
${createToggleRow('ANALOG_SCALE_AUTOSTART', 'Autostart Scale', 'Automatically open the scale when tuning to a supported band.', pluginConfig.ANALOG_SCALE_AUTOSTART)}
|
||||
${createToggleRow('SHOW_TUNING_KNOB', 'Show Tuning Knob', 'Displays the dual rotary encoder for manual tuning.', pluginConfig.SHOW_TUNING_KNOB)}
|
||||
${createSliderRow('ANALOG_SCALE_BRIGHTNESS', 'Scale Brightness', 'Adjust the backlight brightness of the analog scale.', '0.2', '2.0', '0.05', pluginConfig.ANALOG_SCALE_BRIGHTNESS)}
|
||||
|
||||
<h4 style="margin-top:20px; color:#aaa; font-size:14px; margin-bottom:10px;">VU Meter Configuration</h4>
|
||||
${createToggleRow('ENABLE_VU_METER', 'Enable VU Meter', 'Displays a stereo VU meter next to the analog scale.', pluginConfig.ENABLE_VU_METER)}
|
||||
${createSelectRow('VU_METER_MODE', 'VU Meter Mode', 'RMS gives smooth realistic pumping. Peak shows raw loudness.',[{val: 'RMS', label: 'RMS (Smooth & Realistic)'}, {val: 'Peak', label: 'Peak (Raw Max Levels)'}], pluginConfig.VU_METER_MODE)}
|
||||
${createSliderRow('VU_METER_GAIN_FM', 'VU Meter Gain (FM)', 'Calibration multiplier for the VU meter on FM/OIRT.', '0.1', '5.0', '0.1', pluginConfig.VU_METER_GAIN_FM)}
|
||||
${createSliderRow('VU_METER_GAIN_AM', 'VU Meter Gain (AM/SW)', 'Calibration multiplier for the VU meter on AM/SW/MW/LW.', '0.1', '5.0', '0.1', pluginConfig.VU_METER_GAIN_AM)}
|
||||
<h4 style="margin-top:20px; color:#aaa; font-size:14px; margin-bottom:10px;">Retro Magic Eye</h4>
|
||||
${createToggleRow('ENABLE_MAGIC_EYE', 'Enable Magic Eye Indicator', 'Converts the signal panel into a glowing vacuum tube indicator.', pluginConfig.ENABLE_MAGIC_EYE)}
|
||||
<h4 style="margin-top:20px; color:#aaa; font-size:14px; margin-bottom:10px;">Scale Overlays & Data</h4>
|
||||
${createToggleRow('ENABLE_PS_SCALE', 'Show FM PS Stations', 'Display RDS PS names directly on the analog FM scale.', pluginConfig.ENABLE_PS_SCALE !== false)}
|
||||
${createToggleRow('ENABLE_SPECTRUM_OVERLAY', 'Show Spectrum Overlay', 'Project the SDR spectrum waterfall onto the analog dial glass.', pluginConfig.ENABLE_SPECTRUM_OVERLAY !== false)}
|
||||
${createToggleRow('ENABLE_SW_STATIONS_SCALE', 'Show SW Stations', 'Display active shortwave stations dynamically on the analog dial.<br><span style="color:#e74c3c; font-weight:bold; font-size:12px;">⚠️ Requires AM Station Info Plugin V1.4 or newer!</span>', pluginConfig.ENABLE_SW_STATIONS_SCALE !== false)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toast">Settings Saved Successfully!</div>
|
||||
|
||||
<script>
|
||||
const navBtns = document.querySelectorAll('.nav-btn');
|
||||
const tabs = document.querySelectorAll('.tab-content');
|
||||
|
||||
navBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
navBtns.forEach(b => b.classList.remove('active'));
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
document.getElementById(btn.dataset.target).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Dynamisk visning av Scanner Thresholds
|
||||
document.getElementById('ENABLE_AM_SCANNER').addEventListener('change', function() {
|
||||
document.getElementById('scanner-thresholds-block').style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
|
||||
document.getElementById('ENABLE_ANALOG_SCALE').addEventListener('change', function() {
|
||||
document.getElementById('scale-settings-block').style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Oppdater teksten vedsidenav sliderne i sanntid
|
||||
document.querySelectorAll('input[type="range"]').forEach(slider => {
|
||||
slider.addEventListener('input', (e) => {
|
||||
const display = document.getElementById(e.target.id + '_display');
|
||||
if (display) display.innerText = e.target.value;
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('saveBtn').addEventListener('click', async () => {
|
||||
const bands = [];
|
||||
if(document.getElementById('band_FM').checked) bands.push('FM');
|
||||
if(document.getElementById('band_OIRT').checked) bands.push('OIRT');
|
||||
if(document.getElementById('band_SW').checked) bands.push('SW');
|
||||
if(document.getElementById('band_MW').checked) bands.push('MW');
|
||||
if(document.getElementById('band_LW').checked) bands.push('LW');
|
||||
|
||||
const config = {
|
||||
LAYOUT_STYLE: document.getElementById('LAYOUT_STYLE').value,
|
||||
HIDE_ALL_BUTTONS: document.getElementById('HIDE_ALL_BUTTONS').checked,
|
||||
SHOW_LOOP_BUTTON: document.getElementById('SHOW_LOOP_BUTTON').checked,
|
||||
SHOW_BAND_RANGE: document.getElementById('SHOW_BAND_RANGE').checked,
|
||||
HIDE_DECIMAL_FOR_HF: document.getElementById('HIDE_DECIMAL_FOR_HF').checked,
|
||||
HIDE_DECIMAL_HF_THRESHOLD: parseFloat(document.getElementById('HIDE_DECIMAL_HF_THRESHOLD').value),
|
||||
ENABLE_TUNE_STEP_FEATURE: document.getElementById('ENABLE_TUNE_STEP_FEATURE').checked,
|
||||
TUNE_STEP_TIMEOUT_SECONDS: parseInt(document.getElementById('TUNE_STEP_TIMEOUT_SECONDS').value),
|
||||
ENABLED_BANDS: bands,
|
||||
|
||||
TUNING_STANDARD: document.getElementById('TUNING_STANDARD').value,
|
||||
ENABLE_SMART_KHZ_INPUT: document.getElementById('ENABLE_SMART_KHZ_INPUT').checked,
|
||||
ENABLE_MW_STEP_TOGGLE: document.getElementById('ENABLE_MW_STEP_TOGGLE').checked,
|
||||
ENABLE_FREQUENCY_MEMORY: document.getElementById('ENABLE_FREQUENCY_MEMORY').checked,
|
||||
ENABLE_AM_BW: document.getElementById('ENABLE_AM_BW').checked,
|
||||
FIRMWARE_TYPE: document.getElementById('FIRMWARE_TYPE').value,
|
||||
ENABLE_DEFAULT_AM_BW: document.getElementById('ENABLE_DEFAULT_AM_BW').checked,
|
||||
DEFAULT_AM_BW_VALUE: document.getElementById('DEFAULT_AM_BW_VALUE').value,
|
||||
|
||||
overrideServerTuningLimit: document.getElementById('overrideServerTuningLimit').checked,
|
||||
|
||||
ENABLE_AM_SCANNER: document.getElementById('ENABLE_AM_SCANNER').checked,
|
||||
ENABLE_ANALOG_SCALE: document.getElementById('ENABLE_ANALOG_SCALE').checked,
|
||||
ANALOG_SCALE_AUTOSTART: document.getElementById('ANALOG_SCALE_AUTOSTART').checked,
|
||||
SHOW_TUNING_KNOB: document.getElementById('SHOW_TUNING_KNOB').checked,
|
||||
ANALOG_SCALE_BRIGHTNESS: parseFloat(document.getElementById('ANALOG_SCALE_BRIGHTNESS').value),
|
||||
ENABLE_VU_METER: document.getElementById('ENABLE_VU_METER').checked,
|
||||
VU_METER_MODE: document.getElementById('VU_METER_MODE').value,
|
||||
VU_METER_GAIN_FM: parseFloat(document.getElementById('VU_METER_GAIN_FM').value),
|
||||
VU_METER_GAIN_AM: parseFloat(document.getElementById('VU_METER_GAIN_AM').value),
|
||||
ENABLE_MAGIC_EYE: document.getElementById('ENABLE_MAGIC_EYE').checked,
|
||||
ENABLE_PS_SCALE: document.getElementById('ENABLE_PS_SCALE').checked,
|
||||
ENABLE_SPECTRUM_OVERLAY: document.getElementById('ENABLE_SPECTRUM_OVERLAY').checked,
|
||||
ENABLE_SW_STATIONS_SCALE: document.getElementById('ENABLE_SW_STATIONS_SCALE').checked
|
||||
};
|
||||
|
||||
const mainKeys = ['AM_SUPER', 'FM', 'OIRT', 'SW', 'MW', 'LW'];
|
||||
config.customMainBands = {};
|
||||
mainKeys.forEach(k => {
|
||||
config.customMainBands[k] = {
|
||||
name: document.getElementById('name_' + k).value,
|
||||
tune: parseFloat(document.getElementById('tune_' + k).value),
|
||||
start: parseFloat(document.getElementById('start_' + k).value),
|
||||
end: parseFloat(document.getElementById('end_' + k).value)
|
||||
};
|
||||
});
|
||||
|
||||
const swKeys = ['160m', '120m', '90m', '75m', '60m', '49m', '41m', '31m', '25m', '22m', '19m', '16m', '15m', '13m', '11m'];
|
||||
config.customSwBands = {};
|
||||
swKeys.forEach(k => {
|
||||
config.customSwBands[k] = {
|
||||
tune: parseFloat(document.getElementById('sw_tune_' + k).value),
|
||||
start: parseFloat(document.getElementById('sw_start_' + k).value),
|
||||
end: parseFloat(document.getElementById('sw_end_' + k).value)
|
||||
};
|
||||
});
|
||||
|
||||
const thresholds = ['LW', 'MW', '160m', '120m', '90m', '75m', '60m', '49m', '41m', '31m', '25m', '22m', '19m', '16m', '15m', '13m', '11m'];
|
||||
thresholds.forEach(t => {
|
||||
config['thr_' + t] = parseInt(document.getElementById('thr_' + t).value);
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await fetch('/enhanced_tuning/api/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
|
||||
const toast = document.getElementById('toast');
|
||||
|
||||
if (res.ok) {
|
||||
toast.innerText = 'Settings Saved Successfully!';
|
||||
toast.style.backgroundColor = '#27ae60';
|
||||
toast.classList.add('show');
|
||||
setTimeout(() => toast.classList.remove('show'), 3000);
|
||||
} else if (res.status === 401) {
|
||||
alert('Session expired! You are no longer logged in as Admin. Please go back to the main page, log in again, and refresh this panel.');
|
||||
} else {
|
||||
throw new Error('Server returned ' + res.status);
|
||||
}
|
||||
} catch(err) {
|
||||
const toast = document.getElementById('toast');
|
||||
toast.innerText = 'Error saving settings!';
|
||||
toast.style.backgroundColor = '#e74c3c';
|
||||
toast.classList.add('show');
|
||||
setTimeout(() => toast.classList.remove('show'), 3000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
res.send(html);
|
||||
});
|
||||
+3
-1
@@ -9,6 +9,8 @@ const { serverConfig } = require('./server_config');
|
||||
}
|
||||
|
||||
require('./stream/index');
|
||||
require("./web")(serverConfig.webserver.webserverIp);
|
||||
const start = require("./web");
|
||||
require("./enchanced_tuning");
|
||||
start(serverConfig.webserver.webserverIp);
|
||||
|
||||
require('./server_list')();
|
||||
+36
-1
@@ -157,9 +157,12 @@ class RDSDecoder {
|
||||
this.rt0_errors = Array(64).fill("10");
|
||||
this.rt1 = Array(64).fill(' ');
|
||||
this.rt1_errors = Array(64).fill("10");
|
||||
this.lps = Array(64).fill(' ');
|
||||
this.lps_errors = Array(64).fill("10");
|
||||
this.data.ps = '';
|
||||
this.data.rt1 = '';
|
||||
this.data.rt0 = '';
|
||||
this.data.rt1 = '';
|
||||
this.data.lps = '';
|
||||
this.data.pty = 0;
|
||||
this.data.tp = 0;
|
||||
this.data.ta = 0;
|
||||
@@ -350,6 +353,38 @@ class RDSDecoder {
|
||||
this.data.rt_flag = 0;
|
||||
this.rt1_to_clear = true;
|
||||
}
|
||||
} else if (group === 15 && version == 0) {
|
||||
const idx = blockB & 7;
|
||||
|
||||
if(c_error < 2) {
|
||||
const err = Math.ceil(c_error * (10/3));
|
||||
const old_err = this.lps_errors[idx * 4];
|
||||
if(err < old_err) {
|
||||
this.lps[idx * 4] = decode_charset(blockC >> 8);
|
||||
this.lps[idx * 4 + 1] = decode_charset(blockC & 0xFF);
|
||||
this.lps_errors[idx * 4] = err;
|
||||
this.lps_errors[idx * 4 + 1] = err;
|
||||
}
|
||||
}
|
||||
if(d_error < 2) {
|
||||
const err = Math.ceil(d_error * (10/3));
|
||||
const old_err = this.lps_errors[idx * 4 + 2];
|
||||
if(err < old_err) {
|
||||
this.lps[idx * 4 + 2] = decode_charset(blockD >> 8);
|
||||
this.lps[idx * 4 + 3] = decode_charset(blockD & 0xFF);
|
||||
this.lps_errors[idx * 4 + 2] = err;
|
||||
this.lps_errors[idx * 4 + 3] = err;
|
||||
}
|
||||
}
|
||||
|
||||
var i = this.lps.indexOf("\r")
|
||||
while(i != -1) {
|
||||
this.lps[i] = " ";
|
||||
i = this.lps.indexOf("\r");
|
||||
}
|
||||
|
||||
this.data.lps = this.lps.join('');
|
||||
this.data.lps_errors = this.lps_errors.join(',');
|
||||
} else {
|
||||
// console.log(group, version)
|
||||
}
|
||||
|
||||
+4
-2
@@ -6,6 +6,7 @@
|
||||
<link href="css/flags.min.css" type="text/css" rel="stylesheet">
|
||||
<link href="css/libs/fontawesome.css" type="text/css" rel="stylesheet">
|
||||
<link href="css/libs/jquery-ui.min.css" type="text/css" rel="stylesheet">
|
||||
<link href="css/enchanced_tuning.css" type="text/css" rel="stylesheet">
|
||||
<!--<link href="css/libs/jquery-ui.theme.min.css" type="text/css" rel="stylesheet">-->
|
||||
<script src="js/libs/jquery.min.js"></script>
|
||||
<script src="js/libs/jquery-ui.min.js"></script>
|
||||
@@ -25,12 +26,12 @@
|
||||
<!-- Audio streaming -->
|
||||
<script src="js/audio/mp3_player.js"></script>
|
||||
<script src="js/audio/main.js"></script>
|
||||
<script src="js/rtc-audio.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', Init, false);
|
||||
document.ontouchmove = function(e) {
|
||||
document.ontouchmove = function(e) { // Why?
|
||||
e.preventDefault();
|
||||
}
|
||||
<script src="js/rtc-audio.js"></script>
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@@ -559,6 +560,7 @@
|
||||
<div id="toast-container" aria-live="polite"></div>
|
||||
<script src="js/websocket.js"></script>
|
||||
<script src="js/webserver.js"></script>
|
||||
<script src="js/enchanced_tuning.js"></script>
|
||||
<% if (!noPlugins) { %>
|
||||
<% plugins?.forEach(function(plugin) { %>
|
||||
<script src="js/plugins/<%= plugin %>"></script>
|
||||
|
||||
@@ -0,0 +1,563 @@
|
||||
/* A1: Styles for Tune Step Feature */
|
||||
body.tune-step-enabled #freq-container {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
body.tune-step-enabled .freq-digit-marker {
|
||||
color: #00FF00;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
.et-dimmed-zero {
|
||||
opacity: 0.1;
|
||||
}
|
||||
.et-dimmed-zero.freq-digit-marker {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* A2: Styles for disabled bands (Tune Limit) */
|
||||
.disabled-band {
|
||||
background-color: var(--color-1) !important;
|
||||
color: var(--color-3) !important;
|
||||
cursor: not-allowed !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* MW 9 kHz/10 kHz button */
|
||||
#mw-step-toggle-button {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 6px;
|
||||
z-index: 11;
|
||||
display: none;
|
||||
max-width: 50px;
|
||||
text-align: center;
|
||||
height: auto;
|
||||
padding: 1px 6px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
color: var(--color-main);
|
||||
background-color: var(--color-4);
|
||||
border: 1px solid var(--color-2);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease, background-color 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
#mw-step-toggle-button:hover {
|
||||
background-color: var(--color-5);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#mw-step-toggle-button.active {
|
||||
opacity: 1;
|
||||
border-color: var(--color-4);
|
||||
}
|
||||
|
||||
|
||||
/* ========================================================================== */
|
||||
/* B: MODERN LAYOUT */
|
||||
/* ========================================================================== */
|
||||
|
||||
/* B1: Basic structure for Modern Layout */
|
||||
body.layout-modern #freq-container {
|
||||
position: relative !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
/* B2: Main wrapper and buttons (Shown only if HIDE_ALL_BUTTONS is false) */
|
||||
body.layout-modern.modern-buttons-visible .band-selector-layout-wrapper {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin: 20px 10px 0 10px;
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
backdrop-filter: none !important;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .side-band-button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 60px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .band-selector-button {
|
||||
height: 28px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
background-color: color-mix(in srgb, var(--color-4) 60%, transparent);
|
||||
color: var(--color-main);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .band-selector-button:hover {
|
||||
background-color: var(--color-5);
|
||||
color: var(--color-main);
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .side-band-button-container .band-selector-button.active-band {
|
||||
background-color: var(--color-4);
|
||||
color: var(--color-main);
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible #rt-container,
|
||||
body.layout-modern.modern-buttons-visible .am-bands-view-container {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
background-color: var(--color-1-transparent);
|
||||
backdrop-filter: blur(5px);
|
||||
border-radius: 15px;
|
||||
margin: 0 !important;
|
||||
height: auto !important;
|
||||
align-self: stretch;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .am-bands-view-container {
|
||||
display: grid;
|
||||
grid-template-columns: 85px 1fr;
|
||||
gap: 5px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .sw-grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
gap: 5px;
|
||||
height: 100%;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .am-view-button,
|
||||
body.layout-modern.modern-buttons-visible .sw-grid-button {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background-color: var(--color-3);
|
||||
color: var(--color-main);
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .am-view-button:hover,
|
||||
body.layout-modern.modern-buttons-visible .sw-grid-button:hover {
|
||||
background-color: var(--color-4);
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .am-bands-view-container .am-view-button.active-band,
|
||||
body.layout-modern.modern-buttons-visible .am-bands-view-container .sw-grid-button.active-band {
|
||||
background-color: var(--color-5);
|
||||
color: var(--color-main);
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .am-view-button {
|
||||
font-size: 14px;
|
||||
height: 22px;
|
||||
width: 60px;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .sw-bands-fieldset,
|
||||
body.layout-modern.modern-buttons-visible .band-fieldset {
|
||||
border: 1px solid var(--color-3);
|
||||
border-bottom: none;
|
||||
border-radius: 8px;
|
||||
padding: 0px 5px 5px 5px;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .sw-bands-fieldset legend,
|
||||
body.layout-modern.modern-buttons-visible .band-fieldset legend {
|
||||
color: var(--color-4);
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
width: auto;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .band-button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
padding-top: 3px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .band-selector-layout-wrapper .tooltip,
|
||||
body.layout-modern.modern-buttons-visible .band-selector-layout-wrapper .bs-tooltip {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .band-selector-layout-wrapper .bs-tooltip {
|
||||
line-height: 0;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .band-selector-layout-wrapper .bs-tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 180px;
|
||||
position: absolute;
|
||||
background-color: var(--color-2);
|
||||
border: 2px solid var(--color-3);
|
||||
color: var(--color-text);
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
border-radius: 15px;
|
||||
padding: 8px;
|
||||
z-index: 1000;
|
||||
bottom: 110%;
|
||||
left: 50%;
|
||||
margin-left: -90px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
line-height: normal;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .band-selector-layout-wrapper .bs-tooltip:hover .bs-tooltiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* B3: Loop button and frequency range for Modern Layout */
|
||||
body.layout-modern.loop-button-visible .loop-toggle-button {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
bottom: 6px;
|
||||
z-index: 5;
|
||||
width: 34px;
|
||||
height: auto;
|
||||
min-height: 22px;
|
||||
line-height: 1.2;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background-color: var(--color-3);
|
||||
color: var(--color-main);
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
}
|
||||
body.layout-modern.loop-button-visible .loop-toggle-button:hover {
|
||||
background-color: var(--color-4);
|
||||
}
|
||||
body.layout-modern.loop-button-visible .loop-toggle-button.active {
|
||||
background-color: var(--color-5) !important;
|
||||
color: var(--color-main);
|
||||
}
|
||||
body.layout-modern.band-range-visible #band-range-container {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 12px;
|
||||
color: var(--color-text);
|
||||
opacity: 0.7;
|
||||
}
|
||||
body.layout-modern.band-range-visible .band-range-part {
|
||||
cursor: pointer;
|
||||
}
|
||||
body.layout-modern.band-range-visible .band-range-part:hover {
|
||||
opacity: 1;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* B4: Mobile View for Modern Layout */
|
||||
@media (max-width: 768px) {
|
||||
body.layout-modern.modern-buttons-visible .band-selector-layout-wrapper {
|
||||
display: block !important;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible .side-band-button-container,
|
||||
body.layout-modern.modern-buttons-visible .am-bands-view-container {
|
||||
display: none !important;
|
||||
}
|
||||
body.layout-modern #band-range-container,
|
||||
body.layout-modern .loop-toggle-button {
|
||||
display: none !important;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible #data-ant-container {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
padding: 0 18px 10px 18px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
align-items: baseline;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible #data-ant-container::before,
|
||||
body.layout-modern.modern-buttons-visible #data-ant-container::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible #data-ant-container > .dropdown,
|
||||
body.layout-modern.modern-buttons-visible #mobile-band-selector-wrapper,
|
||||
body.layout-modern.modern-buttons-visible #mobile-sw-band-selector-wrapper {
|
||||
width: 45% !important;
|
||||
flex: 0 1 auto !important;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible #data-ant-container.sw-mode-active > .dropdown,
|
||||
body.layout-modern.modern-buttons-visible #data-ant-container.sw-mode-active > #mobile-band-selector-wrapper,
|
||||
body.layout-modern.modern-buttons-visible #data-ant-container.sw-mode-active > #mobile-sw-band-selector-wrapper {
|
||||
width: 30% !important;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible #mobile-sw-band-selector-wrapper {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible #data-ant-container:has(#mobile-band-selector-wrapper:only-child)::before,
|
||||
body.layout-modern.modern-buttons-visible #data-ant-container:has(#mobile-band-selector-wrapper:only-child)::after {
|
||||
display: none;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible #mobile-band-selector-wrapper:only-child {
|
||||
width: 50% !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
body.layout-modern.modern-buttons-visible #mobile-band-selector,
|
||||
body.layout-modern.modern-buttons-visible #mobile-sw-band-selector {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background-color: var(--color-4);
|
||||
color: var(--color-main);
|
||||
border: none;
|
||||
border-radius: 0 0 15px 15px;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
padding: 0 10px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-image: url("data-image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23333333' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
background-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ========================================================================== */
|
||||
/* C: CLASSIC LAYOUT */
|
||||
/* ========================================================================== */
|
||||
|
||||
/* C1: Basic structure for Classic Layout */
|
||||
body.layout-classic .band-selector-button {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-2);
|
||||
border-radius: 4px;
|
||||
padding: 1px 6px;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 1.4;
|
||||
}
|
||||
body.layout-classic #freq-container {
|
||||
position: relative !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
body.layout-classic .plugin-top-container {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 6px;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
}
|
||||
body.layout-classic .main-bands-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
body.layout-classic .main-band-button.active-band,
|
||||
body.layout-classic .sw-band-button.active-band,
|
||||
body.layout-classic #loop-toggle-button.active {
|
||||
background-color: var(--color-4);
|
||||
color: var(--color-main);
|
||||
opacity: 1;
|
||||
}
|
||||
body.layout-classic .sw-bands-container {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 6px;
|
||||
z-index: 9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
body.layout-classic .sw-bands-grid {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
body.layout-classic .sw-bands-top-wrapper {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
body.layout-classic .sw-bands-bottom-wrapper {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
body.layout-classic .sw-band-button {
|
||||
padding: 1px 2px;
|
||||
text-align: center;
|
||||
}
|
||||
body.layout-classic #band-range-container {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 11px;
|
||||
color: var(--color-text);
|
||||
opacity: 0.6;
|
||||
white-space: nowrap;
|
||||
}
|
||||
body.layout-classic .band-range-part {
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
body.layout-classic .band-range-part:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
body.layout-classic .range-separator {
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* C2: Mobile view for Classic Layout */
|
||||
@media (max-width: 768px) {
|
||||
body.layout-classic .plugin-top-container,
|
||||
body.layout-classic .sw-bands-container,
|
||||
body.layout-classic #band-range-container {
|
||||
display: none !important;
|
||||
}
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls {
|
||||
display: flex !important;
|
||||
gap: 5px;
|
||||
padding: 0 18px 10px 18px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
align-items: baseline;
|
||||
}
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls::before,
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
}
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls > .dropdown,
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls #mobile-band-selector-wrapper,
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls #mobile-sw-band-selector-wrapper {
|
||||
width: 45% !important;
|
||||
flex: 0 1 auto !important;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls.sw-mode-active > .dropdown,
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls.sw-mode-active > #mobile-band-selector-wrapper,
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls.sw-mode-active > #mobile-sw-band-selector-wrapper {
|
||||
width: 30% !important;
|
||||
}
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls #mobile-sw-band-selector-wrapper {
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls:has(#mobile-band-selector-wrapper:only-child)::before,
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls:has(#mobile-band-selector-wrapper:only-child)::after {
|
||||
display: none;
|
||||
}
|
||||
body.layout-classic #data-ant-container.classic-mobile-controls #mobile-band-selector-wrapper:only-child {
|
||||
width: 50% !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
body.layout-classic #mobile-band-selector,
|
||||
body.layout-classic #mobile-sw-band-selector {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background-color: var(--color-4);
|
||||
color: var(--color-main);
|
||||
border: none;
|
||||
border-radius: 0 0 15px 15px;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
padding: 0 10px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-image: url("data-image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23333333' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
background-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================================================== */
|
||||
/* FIX FOR BAND RANGE (Start <-> End) OVERLAP PÅ MOBIL / SMÅ SKJERMER */
|
||||
/* ========================================================================== */
|
||||
|
||||
#band-range-container {
|
||||
display: flex !important;
|
||||
flex-wrap: nowrap !important;
|
||||
white-space: nowrap !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.band-range-part {
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
#band-range-container {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.band-range-unit {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
#magic-eye-wrapper { display: none !important; }
|
||||
.magic-eye-panel-override { display: block !important; }
|
||||
.magic-eye-text-wrapper { display: contents !important; }
|
||||
}
|
||||
.magic-eye-hidden { display: none !important; }
|
||||
|
||||
.bw-option-selected {
|
||||
background-color: var(--color-4) !important;
|
||||
color: #000 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* ========================================================================== */
|
||||
/* D: ANALOG SCALE HIDE FIX (THE 1x1 PIXEL HACK) */
|
||||
/* ========================================================================== */
|
||||
body.et-analog-active #sdr-graph {
|
||||
display: block !important;
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
z-index: 1 !important; /* Ligger skjult rett bak skalaen */
|
||||
opacity: 1 !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
/* Skjuler ALT ANNET som krever ytelse, inkludert det originale runde signal-canvaset! */
|
||||
body.et-analog-active #signal-canvas,
|
||||
body.et-analog-active #Antenna,
|
||||
body.et-analog-active #containerRotator,
|
||||
body.et-analog-active #sdr-graph-button-container,
|
||||
body.et-analog-active .spectrum-graph-update-text,
|
||||
body.et-analog-active #mm-mpx-combo-flex,
|
||||
body.et-analog-active #mm-signal-analyzer-flex,
|
||||
body.et-analog-active #mm-scope-flex {
|
||||
display: none !important;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user