diff --git a/assets/js/dxwaterfall.js b/assets/js/dxwaterfall.js index f54bdc9e7..45c088887 100644 --- a/assets/js/dxwaterfall.js +++ b/assets/js/dxwaterfall.js @@ -1,7 +1,7 @@ // @ts-nocheck /** * @fileoverview DX WATERFALL for WaveLog - * @version 0.9.4 // also change line 38 + * @version 0.9.5 // also change line 38 * @author Wavelog Team * * @description @@ -29,10 +29,10 @@ var DX_WATERFALL_CONSTANTS = { // Version - VERSION: '0.9.4', // DX Waterfall version (keep in sync with @version in file header) + VERSION: '0.9.5', // DX Waterfall version (keep in sync with @version in file header) // Debug and logging - DEBUG_MODE: false, // Set to true for verbose logging, false for production + DEBUG_MODE: true, // Set to true for verbose logging, false for production // Timing and debouncing DEBOUNCE: { @@ -445,18 +445,13 @@ function handleCATFrequencyUpdate(radioFrequency, updateCallback) { var frequencyChanged = false; var isInitialLoad = false; - if (typeof dxWaterfall !== 'undefined' && dxWaterfall.lastValidCommittedFreq !== null && dxWaterfall.lastValidCommittedUnit) { + if (typeof dxWaterfall !== 'undefined' && dxWaterfall.lastValidCommittedFreqHz !== null) { // Compare incoming CAT frequency with last committed value - // CAT sends frequency in Hz, convert to kHz for comparison - var lastKhz = convertFrequency( - dxWaterfall.lastValidCommittedFreq, - dxWaterfall.lastValidCommittedUnit, - 'kHz' - ); + // CAT sends frequency in Hz, convert for comparison + var lastHz = dxWaterfall.lastValidCommittedFreqHz; var incomingHz = parseFloat(radioFrequency); - var incomingKhz = incomingHz / 1000; // Convert Hz to kHz - var tolerance = 0.001; // 1 Hz - var diff = Math.abs(incomingKhz - lastKhz); + var tolerance = 1; // 1 Hz + var diff = Math.abs(incomingHz - lastHz); frequencyChanged = diff > tolerance; } else if (typeof dxWaterfall !== 'undefined') { // First time receiving CAT frequency - always consider it changed @@ -1113,15 +1108,14 @@ var DX_WATERFALL_UTILS = { }, DX_WATERFALL_CONSTANTS.DEBOUNCE.MODE_CHANGE_SETTLE_MS); // Manually set the frequency in the input field immediately - var formattedFreq = Math.round(targetSpot.frequency * 1000); // Convert to Hz - $('#frequency').val(formattedFreq); + var formattedFreqHz = Math.round(targetSpot.frequency * 1000); // Convert kHz to Hz + $('#frequency').val(formattedFreqHz); // CRITICAL: Directly update the cache to the target frequency - // getCachedMiddleFreq() uses lastValidCommittedFreq which isn't updated by just setting the input value + // getCachedMiddleFreq() uses lastValidCommittedFreqHz which isn't updated by just setting the input value // So we bypass the cache and set it directly to ensure getSpotInfo() uses the correct frequency waterfallContext.cache.middleFreq = targetSpot.frequency; // Already in kHz - waterfallContext.lastValidCommittedFreq = formattedFreq; - waterfallContext.lastValidCommittedUnit = 'kHz'; + waterfallContext.lastValidCommittedFreqHz = formattedFreqHz; // Store in Hz var cachedFreq = waterfallContext.getCachedMiddleFreq(); @@ -1276,9 +1270,6 @@ var dxWaterfall = { noiseWidth: 0, noiseHeight: 0, middleFreq: null, - lastQrgUnit: null, - lastValidCommittedFreq: null, - lastValidCommittedUnit: null, visibleSpots: null, visibleSpotsParams: null }, @@ -1324,6 +1315,12 @@ var dxWaterfall = { lastWaterfallFrequencyCommandTime: 0, lastFrequencyRefreshTime: 0, + // Frequency commit tracking (single source of truth in Hz) + lastValidCommittedFreqHz: null, + committedFrequencyKHz: null, + lastModeForCache: null, + lastMarkerFreq: undefined, + // Spot fetch debouncing userInitiatedFetch: false, lastSpotCollectionTime: 0, @@ -1777,7 +1774,8 @@ var dxWaterfall = { this.zoomMenuDiv = document.getElementById('dxWaterfallMenu'); // Cache frequently accessed DOM elements for performance - this.$freqCalculated = $('#freq_calculated'); + this.$frequency = $('#frequency'); // Single source of truth (Hz) + this.$freqCalculated = $('#freq_calculated'); // Display field (computed from frequency) this.$qrgUnit = $('#qrg_unit'); this.$bandSelect = $('#band'); this.$modeSelect = $('#mode'); @@ -1855,9 +1853,10 @@ var dxWaterfall = { DXWaterfallStateMachine.setState(DX_WATERFALL_CONSTANTS.STATES.READY); self.updateZoomMenu(); } - if (self.lastValidCommittedFreq === null) { - var currentFreq = parseFloat($(this).val()) || 0; - if (currentFreq > 0) { + // On first focus before any commit, commit the initial frequency + if (self.lastValidCommittedFreqHz === null) { + var currentFreqHz = parseFloat(self.$frequency.val()) || 0; + if (currentFreqHz > 0) { self.commitFrequency(); } } @@ -1882,6 +1881,16 @@ var dxWaterfall = { } }); + // Listen to frequency field changes (single source of truth) + // This catches updates from qrg_handler.js when user edits freq_calculated + // or from CAT updates, ensuring waterfall stays in sync + this.$frequency.on('change', function() { + // Don't commit during user typing - wait for blur/Enter + if (!self.userEditingFrequency) { + self.commitFrequency(); + } + }); + // Set up band dropdown change handler for offline mode // When user changes band in offline mode, update frequency and fetch spots this.$bandSelect.on('change', function() { @@ -1943,12 +1952,10 @@ var dxWaterfall = { // In offline mode, also preserve frequency if (typeof isCATAvailable === 'function' && !isCATAvailable()) { - // Preserve existing frequency if available - if (!window.catState.frequency && self.$freqCalculated.val()) { - var freqVal = parseFloat(self.$freqCalculated.val()); - var unit = self.$qrgUnit.text() || 'kHz'; - var freqKhz = convertFrequency(freqVal, unit, 'kHz'); - window.catState.frequency = freqKhz * 1000; // Convert to Hz + // Preserve existing frequency if available - read from single source of truth + if (!window.catState.frequency && self.$frequency.val()) { + var freqHz = parseFloat(self.$frequency.val()); + window.catState.frequency = freqHz; // Already in Hz } DX_WATERFALL_UTILS.log.debug('[DX Waterfall] Offline mode - mode change to ' + newMode + ': virtual CAT updated'); } else { @@ -1988,8 +1995,9 @@ var dxWaterfall = { _setupInitialFrequencyCommit: function() { var self = this; var attemptCommit = function(attemptsLeft) { - var freq = parseFloat(self.$freqCalculated.val()) || 0; - if (freq > 0) { + // Read from single source of truth (Hz), convert to kHz for waterfall + var freqHz = parseFloat(self.$frequency.val()) || 0; + if (freqHz > 0) { self.commitFrequency(); } else if (attemptsLeft > 0) { setTimeout(function() { @@ -2008,25 +2016,20 @@ var dxWaterfall = { // Returns true if frequency has changed, false if same hasFrequencyChanged: function() { // Safety check: return false if waterfall is not initialized - if (!this.$freqCalculated || !this.$qrgUnit) { + if (!this.$frequency) { return false; } - var currentInput = this.$freqCalculated.val(); - var currentUnit = this.$qrgUnit.text() || 'kHz'; + var currentHz = parseFloat(this.$frequency.val()) || 0; // If we don't have a last committed value, consider it changed - if (this.lastValidCommittedFreq === null) { + if (this.lastValidCommittedFreqHz === null) { return true; } - // Convert both frequencies to kHz for comparison (normalize units) - var currentKhz = convertFrequency(currentInput, currentUnit, 'kHz'); - var lastKhz = convertFrequency(this.lastValidCommittedFreq, this.lastValidCommittedUnit, 'kHz'); - - // Compare frequencies with 1 Hz tolerance (0.001 kHz) to account for floating point errors - var tolerance = 0.001; // 1 Hz - return Math.abs(currentKhz - lastKhz) > tolerance; + // Compare frequencies with 1 Hz tolerance to account for floating point errors + var tolerance = 1; // 1 Hz + return Math.abs(currentHz - this.lastValidCommittedFreqHz) > tolerance; }, // Commit the current frequency value (called on blur or Enter key) @@ -2036,34 +2039,27 @@ var dxWaterfall = { // When CAT is active, the waterfall reads from window.catState.frequency // Safety check: return early if waterfall is not initialized (destroyed or not yet ready) - if (!this.$freqCalculated || !this.$qrgUnit) { + if (!this.$frequency) { return; } - var currentInput = this.$freqCalculated.val(); - var currentUnit = this.$qrgUnit.text() || 'kHz'; + // Read from single source of truth (Hz) + var freqHz = parseFloat(this.$frequency.val()) || 0; // If this is a valid frequency, save it as the last valid committed frequency // (used as fallback when CAT not available) - var freqValue = parseFloat(currentInput) || 0; - if (freqValue > 0) { - this.lastValidCommittedFreq = currentInput; - this.lastValidCommittedUnit = currentUnit; - - // Store the committed frequency in kHz for comparison checks - var currentFreqKhz = convertFrequency(freqValue, currentUnit, 'kHz'); - this.committedFrequencyKHz = currentFreqKhz; + if (freqHz > 0) { + this.lastValidCommittedFreqHz = freqHz; + this.committedFrequencyKHz = freqHz / 1000; // Convert to kHz for waterfall display // In offline mode, populate catState with form values to act as "virtual CAT" if (typeof isCATAvailable === 'function' && !isCATAvailable()) { - var freqHz = currentFreqKhz * 1000; // Convert kHz to Hz - // Initialize catState if it doesn't exist if (typeof window.catState === 'undefined' || window.catState === null) { window.catState = {}; } - // Update frequency in catState + // Update frequency in catState (already in Hz) window.catState.frequency = freqHz; window.catState.lastUpdate = Date.now(); @@ -2118,31 +2114,28 @@ var dxWaterfall = { // FALLBACK: Use committed frequency values (only updated on blur/Enter) to prevent shifting while typing // This is used when CAT is not available (no radio connected) // Strategy: - // 1. If we have a valid committed frequency from this session, always use last VALID commit + // 1. If we have a valid committed frequency from this session, use last VALID commit // 2. Otherwise use real-time values (initial load before any commits) - var hasValidCommit = this.lastValidCommittedFreq !== null; + var hasValidCommit = this.lastValidCommittedFreqHz !== null; - var currentInput, currentUnit; + var currentFreqHz; if (hasValidCommit) { // After first valid commit, always use the LAST VALID committed values // This keeps the waterfall stable even when user deletes and starts typing - currentInput = this.lastValidCommittedFreq; - currentUnit = this.lastValidCommittedUnit || 'kHz'; + currentFreqHz = this.lastValidCommittedFreqHz; } else { - // Before first valid commit (initial load), use real-time values - currentInput = this.$freqCalculated.val(); - currentUnit = this.$qrgUnit.text() || 'kHz'; + // Before first valid commit (initial load), use real-time values from single source + currentFreqHz = parseFloat(this.$frequency.val()) || 0; } - // Invalidate cache if input OR unit changes - if (this.lastValidCommittedFreq !== currentInput || this.lastQrgUnit !== currentUnit) { - this.lastValidCommittedFreq = currentInput; - this.lastQrgUnit = currentUnit; + // Invalidate cache if frequency changes + if (this.lastValidCommittedFreqHz !== currentFreqHz) { + this.lastValidCommittedFreqHz = currentFreqHz; - // Convert to kHz using utility function - this.cache.middleFreq = convertFrequency(currentInput, currentUnit, 'kHz'); + // Convert to kHz for waterfall display + this.cache.middleFreq = currentFreqHz / 1000; } // Update split operation state and get display configuration @@ -2265,7 +2258,7 @@ var dxWaterfall = { // Periodically refresh frequency cache to ensure display stays current refreshFrequencyCache: function() { // Safety check: Don't run if waterfall is not initialized - if (!this.$freqCalculated || !this.$qrgUnit) { + if (!this.$frequency) { return; } @@ -2282,36 +2275,32 @@ var dxWaterfall = { } this.lastFrequencyRefreshTime = currentTime; - // Get current DOM frequency - var currentInput = this.$freqCalculated.val(); + // Get current DOM frequency (single source of truth in Hz) + var currentInput = this.$frequency.val(); if (!currentInput || currentInput === '') { return; } - var freqValue = parseFloat(currentInput) || 0; - if (freqValue <= 0) { + var freqHz = parseFloat(currentInput) || 0; + if (freqHz <= 0) { return; } - var currentUnit = this.$qrgUnit.text() || 'kHz'; - - // Convert to kHz using utility function - var currentFreqFromDOM = convertFrequency(freqValue, currentUnit, 'kHz'); + // Convert to kHz for waterfall display + var currentFreqKhz = freqHz / 1000; // If cache is outdated, refresh it (but only if not during waterfall operations) - if (!this.cache.middleFreq || Math.abs(currentFreqFromDOM - this.cache.middleFreq) > 0.1) { + if (!this.cache.middleFreq || Math.abs(currentFreqKhz - this.cache.middleFreq) > 0.1) { // Clear all frequency-related cache to ensure fresh read this.cache.middleFreq = null; - this.lastQrgUnit = null; this.lastMarkerFreq = undefined; // Directly set the new frequency from DOM calculation - this.cache.middleFreq = currentFreqFromDOM; + this.cache.middleFreq = currentFreqKhz; // Also update committed frequency values to prevent getCachedMiddleFreq() conflicts // This ensures that getCachedMiddleFreq() will use the updated frequency instead of old committed values - this.lastValidCommittedFreq = currentInput; - this.lastValidCommittedUnit = currentUnit; + this.lastValidCommittedFreqHz = freqHz; } }, @@ -5855,17 +5844,12 @@ var dxWaterfall = { this.cache.noise1 = null; this.cache.noise2 = null; this.cache.middleFreq = null; - this.cache.lastQrgUnit = null; - this.cache.lastValidCommittedFreq = null; - this.cache.lastValidCommittedUnit = null; this.cache.visibleSpots = null; this.cache.visibleSpotsParams = null; // Clear frequency tracking properties (used in getCachedMiddleFreq) - this.lastQrgUnit = null; this.lastModeForCache = null; - this.lastValidCommittedFreq = null; - this.lastValidCommittedUnit = null; + this.lastValidCommittedFreqHz = null; // Clear cached pixels per kHz this.cachedPixelsPerKHz = null; @@ -6211,16 +6195,13 @@ function setFrequency(frequencyInKHz, fromWaterfall) { // Set unit button to kHz for consistency (waterfall works in kHz) $('#qrg_unit').text('kHz'); - // Update frequency field with value in kHz - $('#frequency').val(frequencyInKHz); + // Write to frequency field in Hz (single source of truth) + // The change event will trigger set_qrg() which updates freq_calculated display + $('#frequency').val(frequencyInKHz * 1000); - // Also update freq_calculated field that waterfall reads from (always in kHz) - $('#freq_calculated').val(frequencyInKHz); - - // Only trigger change if this is NOT from waterfall (external frequency change) - if (!fromWaterfall) { - $('#frequency').trigger('change'); - } + // Always trigger change to update display field via set_qrg() + // This ensures freq_calculated is kept in sync with frequency field + $('#frequency').trigger('change'); // Clear navigation flags immediately since no CAT operation is happening if (typeof dxWaterfall !== 'undefined') { @@ -6641,20 +6622,18 @@ function setFrequency(frequencyInKHz, fromWaterfall) { setFrequency(clickedFreq, true); // Update cache directly AND sync tracking variables to prevent recalculation - var formattedFreq = Math.round(clickedFreq * 1000); // Convert to Hz + var formattedFreqHz = Math.round(clickedFreq * 1000); // Convert kHz to Hz dxWaterfall.cache.middleFreq = clickedFreq; - dxWaterfall.lastValidCommittedFreq = clickedFreq; // Store in kHz - dxWaterfall.lastValidCommittedUnit = 'kHz'; - dxWaterfall.lastQrgUnit = 'kHz'; + dxWaterfall.lastValidCommittedFreqHz = formattedFreqHz; // Store in Hz // In offline mode, update catState with clicked frequency (virtual CAT) if (typeof isCATAvailable === 'function' && !isCATAvailable()) { if (typeof window.catState === 'undefined' || window.catState === null) { window.catState = {}; } - window.catState.frequency = formattedFreq; // Hz + window.catState.frequency = formattedFreqHz; // Hz window.catState.lastUpdate = Date.now(); - DX_WATERFALL_UTILS.log.debug('[DX Waterfall] Offline mode - waterfall click updated virtual CAT: freq=' + formattedFreq + ' Hz'); + DX_WATERFALL_UTILS.log.debug('[DX Waterfall] Offline mode - waterfall click updated virtual CAT: freq=' + formattedFreqHz + ' Hz'); } // Update band spot collection and zoom menu to reflect new position @@ -6665,7 +6644,7 @@ function setFrequency(frequencyInKHz, fromWaterfall) { }, 100); // Brief delay to let frequency settle // Note: No need to call commitFrequency() here since we already set - // lastValidCommittedFreq directly above + // lastValidCommittedFreqHz directly above }); // Handle keyboard shortcuts