diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index f451299..5574c36 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -3746,6 +3746,7 @@ let aprsRadioPath = null; let selectedLocatorMarker = null; let selectedLocatorPulseRaf = null; let mapFullscreenListenerBound = false; +let mapP2pRadioPathsEnabled = loadSetting("mapP2pRadioPathsEnabled", true) !== false; let mapDecodeContactPathsEnabled = loadSetting("mapDecodeContactPathsEnabled", true) !== false; const stationMarkers = new Map(); const locatorMarkers = new Map(); @@ -4169,7 +4170,7 @@ function syncDecodeContactPathVisibility() { function setMapRadioPathTo(lat, lon, className = "aprs-radio-path") { clearMapRadioPath(); - if (serverLat == null || serverLon == null || !Number.isFinite(lat) || !Number.isFinite(lon) || !aprsMap) { + if (!mapP2pRadioPathsEnabled || serverLat == null || serverLon == null || !Number.isFinite(lat) || !Number.isFinite(lon) || !aprsMap) { return; } aprsRadioPath = L.polyline( @@ -4800,6 +4801,7 @@ function initAprsMap() { const locatorPhaseEl = document.getElementById("map-locator-phase"); const locatorChoiceEl = document.getElementById("map-locator-choice-filter"); const mapSearchEl = document.getElementById("map-search-filter"); + const mapP2pPathsToggleEl = document.getElementById("map-p2p-paths-toggle"); const mapContactPathsToggleEl = document.getElementById("map-contact-paths-toggle"); const fullscreenBtn = document.getElementById("map-fullscreen-btn"); if (locatorPhaseEl) { @@ -4855,6 +4857,15 @@ function initAprsMap() { applyMapFilter(); }); } + if (mapP2pPathsToggleEl) { + updateMapP2pPathsToggle(); + mapP2pPathsToggleEl.addEventListener("click", () => { + mapP2pRadioPathsEnabled = !mapP2pRadioPathsEnabled; + saveSetting("mapP2pRadioPathsEnabled", mapP2pRadioPathsEnabled); + updateMapP2pPathsToggle(); + if (!mapP2pRadioPathsEnabled) clearMapRadioPath(); + }); + } if (mapContactPathsToggleEl) { updateMapContactPathsToggle(); mapContactPathsToggleEl.addEventListener("click", () => { @@ -5547,6 +5558,13 @@ function updateMapContactPathsToggle() { btn.classList.toggle("is-active", mapDecodeContactPathsEnabled); } +function updateMapP2pPathsToggle() { + const btn = document.getElementById("map-p2p-paths-toggle"); + if (!btn) return; + btn.textContent = mapP2pRadioPathsEnabled ? "TRX Paths On" : "TRX Paths Off"; + btn.classList.toggle("is-active", mapP2pRadioPathsEnabled); +} + function escapeMapHtml(input) { return String(input) .replaceAll("&", "&") diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index 399e89b..a66e6a7 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -259,19 +259,19 @@
VFO
-
-
Scheduler
-
-
- -
Scheduler is controlling the rig.
+
+
Channels / Scheduler
+
+
+
+
+ +
Scheduler is controlling the rig.
+
Next scheduler cycle in 30s.
+
-
Signal
@@ -687,8 +687,9 @@
Paths
+ - Directed FT8 contacts with both locators known + TRX paths on popup, directed contact paths when both locators are known
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vchan.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vchan.js index c23681d..6e9360b 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vchan.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vchan.js @@ -13,6 +13,18 @@ let vchanChannels = []; let vchanActiveId = null; let schedulerReleaseState = null; let schedulerReleasePollTimer = null; +let schedulerCycleCountdownTimer = null; + +function schedulerNextCycleSeconds() { + const periodMs = 30000; + const remMs = periodMs - (Date.now() % periodMs); + const secs = Math.ceil(remMs / 1000); + return Math.max(1, Math.min(30, secs)); +} + +function schedulerCycleSummaryText() { + return `Next scheduler cycle in ${schedulerNextCycleSeconds()}s.`; +} function vchanFmtFreq(hz) { if (!Number.isFinite(hz) || hz <= 0) return "--"; @@ -47,12 +59,14 @@ function schedulerReleaseSummaryText(state) { function vchanRenderSchedulerRelease() { const btn = document.getElementById("scheduler-release-btn"); const status = document.getElementById("scheduler-release-status"); + const cycleStatus = document.getElementById("scheduler-cycle-status"); if (!btn || !status) return; const currentReleased = !!(schedulerReleaseState && schedulerReleaseState.current_session_released); btn.disabled = !vchanSessionId || currentReleased; btn.classList.toggle("active", !currentReleased); btn.textContent = "Release to Scheduler"; status.textContent = schedulerReleaseSummaryText(schedulerReleaseState); + if (cycleStatus) cycleStatus.textContent = schedulerCycleSummaryText(); } async function vchanPollSchedulerRelease() { @@ -76,6 +90,10 @@ function vchanStartSchedulerReleasePolling() { clearInterval(schedulerReleasePollTimer); } schedulerReleasePollTimer = setInterval(vchanPollSchedulerRelease, 10000); + if (schedulerCycleCountdownTimer) { + clearInterval(schedulerCycleCountdownTimer); + } + schedulerCycleCountdownTimer = setInterval(vchanRenderSchedulerRelease, 1000); } async function vchanToggleSchedulerRelease() { @@ -288,9 +306,9 @@ function vchanReconnectAudio() { // Called by app.js from applyCapabilities(). // Shows the channel picker only for SDR rigs. function vchanApplyCapabilities(caps) { - const row = document.getElementById("vchan-row"); - if (!row) return; - row.style.display = (caps && caps.filter_controls) ? "" : "none"; + const picker = document.getElementById("vchan-picker"); + if (!picker) return; + picker.style.display = (caps && caps.filter_controls) ? "" : "none"; vchanRenderSchedulerRelease(); } diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css index 6351e06..d8032de 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css @@ -366,15 +366,24 @@ input.status-input, select.status-input { width: 100%; padding: 0.45rem 0.5rem; background: var(--btn-bg); font-weight: 600; } +.channel-scheduler-controls { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 0.6rem 0.8rem; +} .vchan-picker { display: flex; flex-wrap: wrap; gap: 4px; + flex: 1 1 18rem; + min-width: 0; } .scheduler-control-row { display: flex; align-items: flex-start; justify-content: flex-start; + flex: 0 1 22rem; } .scheduler-release-wrap { display: flex; @@ -392,6 +401,12 @@ input.status-input, select.status-input { width: 100%; padding: 0.45rem 0.5rem; font-size: 0.78rem; text-align: left; } +.scheduler-cycle-status { + color: var(--text-muted); + font-size: 0.74rem; + text-align: left; + opacity: 0.88; +} .vchan-picker button { display: inline-flex; align-items: center; @@ -3642,8 +3657,16 @@ button:focus-visible, input:focus-visible, select:focus-visible { color: var(--accent-red); } @media (max-width: 600px) { + .channel-scheduler-controls { + flex-direction: column; + align-items: stretch; + } + .vchan-picker { + flex-basis: auto; + } .scheduler-control-row { align-items: stretch; + flex-basis: auto; } .scheduler-release-wrap { align-items: stretch;