mirror of
https://github.com/wavelog/wavelog.git
synced 2026-03-22 10:24:14 +00:00
Fix for another race condition
This commit is contained in:
@@ -1492,7 +1492,12 @@ mymap.on('mousemove', onQsoMapMove);
|
||||
}
|
||||
|
||||
function format_frequency(freq) {
|
||||
const qrgunit = localStorage.getItem('qrgunit_' + $('#band').val());
|
||||
// Return null if frequency is invalid
|
||||
if (freq == null || freq == 0 || freq == '' || isNaN(freq)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const qrgunit = localStorage.getItem('qrgunit_' + $('#band').val()) || 'kHz'; // Default to kHz if not set
|
||||
let frequency_formatted=null;
|
||||
if (qrgunit == 'Hz') {
|
||||
frequency_formatted=freq;
|
||||
@@ -1730,11 +1735,17 @@ mymap.on('mousemove', onQsoMapMove);
|
||||
$(".radio_timeout_error" ).remove();
|
||||
separator = '<span style="margin-left:10px"></span>';
|
||||
|
||||
if (!(data.frequency_formatted)) {
|
||||
data.frequency_formatted=format_frequency(data.frequency);
|
||||
// Format frequency - always recalculate if it contains 'null' (from previous invalid formatting)
|
||||
if (!(data.frequency_formatted) || (typeof data.frequency_formatted === 'string' && data.frequency_formatted.includes('null'))) {
|
||||
console.log('[CAT UI] Formatting frequency:', data.frequency, 'previous formatted:', data.frequency_formatted);
|
||||
data.frequency_formatted=format_frequency(data.frequency);
|
||||
console.log('[CAT UI] New formatted:', data.frequency_formatted);
|
||||
}
|
||||
|
||||
if (data.frequency_formatted) {
|
||||
console.log('[CAT UI] Checking frequency_formatted:', data.frequency_formatted, 'type:', typeof data.frequency_formatted);
|
||||
|
||||
// Only display radio info if we have valid frequency (not null and doesn't contain 'null' string)
|
||||
if (data.frequency_formatted && (typeof data.frequency_formatted !== 'string' || !data.frequency_formatted.includes('null'))) {
|
||||
text = '<i class="fas fa-radio"></i>' + separator + '<b>';
|
||||
if (data.radio != null && data.radio != '') {
|
||||
text = text + data.radio;
|
||||
@@ -1750,35 +1761,46 @@ mymap.on('mousemove', onQsoMapMove);
|
||||
// Split operation: show TX and RX separately
|
||||
text = text + 'TX:</b> ' + data.frequency_formatted;
|
||||
data.frequency_rx_formatted = format_frequency(data.frequency_rx);
|
||||
text = text + separator + '<b>RX:</b> ' + data.frequency_rx_formatted;
|
||||
if (data.frequency_rx_formatted) {
|
||||
text = text + separator + '<b>RX:</b> ' + data.frequency_rx_formatted;
|
||||
}
|
||||
} else {
|
||||
// Simplex operation: show TX/RX combined
|
||||
text = text + 'TX/RX:</b> ' + data.frequency_formatted;
|
||||
}
|
||||
}
|
||||
if(data.mode != null) {
|
||||
|
||||
// Add mode and power (only if we have valid frequency)
|
||||
if(data.mode != null) {
|
||||
text = text + separator + data.mode;
|
||||
}
|
||||
if(data.power != null && data.power != 0) {
|
||||
text = text + separator + data.power+'W';
|
||||
}
|
||||
|
||||
// Add complementary info
|
||||
complementary_info = []
|
||||
if(data.prop_mode != null && data.prop_mode != '') {
|
||||
if (data.prop_mode == 'SAT') {
|
||||
complementary_info.push(data.prop_mode + ' ' + data.satname);
|
||||
} else {
|
||||
complementary_info.push(data.prop_mode);
|
||||
}
|
||||
if(data.prop_mode != null && data.prop_mode != '') {
|
||||
if (data.prop_mode == 'SAT') {
|
||||
complementary_info.push(data.prop_mode + ' ' + data.satname);
|
||||
} else {
|
||||
complementary_info.push(data.prop_mode);
|
||||
}
|
||||
}
|
||||
// Note: RX frequency is now displayed inline, not in complementary_info
|
||||
if( complementary_info.length > 0) {
|
||||
text = text + separator + '(' + complementary_info.join(separator) + ')';
|
||||
}
|
||||
|
||||
// Update the DOM
|
||||
if (! $('#radio_cat_state').length) {
|
||||
$('#radio_status').prepend('<div aria-hidden="true"><div id="radio_cat_state" class="alert alert-success radio_cat_state" role="alert">'+text+'</div></div>');
|
||||
} else {
|
||||
$('#radio_cat_state').html(text);
|
||||
}
|
||||
} else {
|
||||
// No valid frequency - remove radio panel if it exists
|
||||
$('#radio_cat_state').remove();
|
||||
}
|
||||
}
|
||||
|
||||
var updateFromCAT = function() {
|
||||
|
||||
@@ -84,14 +84,14 @@ var DX_WATERFALL_CONSTANTS = {
|
||||
BAND_CHANGE_THRESHOLD: 1000, // kHz outside band before recalculation
|
||||
MAJOR_TICK_TOLERANCE: 0.05, // Floating point precision for major tick detection
|
||||
SPOT_FREQUENCY_MATCH: 0.01, // Frequency match tolerance for spot navigation (kHz)
|
||||
CAT_FREQUENCY_HZ: 1000, // CAT frequency confirmation tolerance (Hz)
|
||||
CAT_FREQUENCY_HZ: 1, // CAT frequency confirmation tolerance (1 Hz for exact tuning)
|
||||
FREQUENCY_MATCH_KHZ: 0.1, // General frequency matching tolerance (kHz)
|
||||
CENTER_SPOT_TOLERANCE_KHZ: 0.1 // Tolerance for center spot frequency matching (kHz)
|
||||
},
|
||||
|
||||
// Zoom levels configuration
|
||||
ZOOM: {
|
||||
DEFAULT_LEVEL: 3, // Default zoom level
|
||||
DEFAULT_LEVEL: 3, // Default zoom level
|
||||
MAX_LEVEL: 5, // Maximum zoom level
|
||||
MIN_LEVEL: 0, // Minimum zoom level
|
||||
// Pixels per kHz for each zoom level
|
||||
@@ -311,8 +311,65 @@ function handleCATFrequencyUpdate(radioFrequency, updateCallback) {
|
||||
console.log('[DX Waterfall] CAT CHECK: First time, isInitialLoad=' + isInitialLoad);
|
||||
}
|
||||
|
||||
// Always update UI
|
||||
if (updateCallback) updateCallback();
|
||||
// Check if we're waiting for a specific frequency to be confirmed BEFORE updating UI
|
||||
var shouldSkipStaleUpdate = false;
|
||||
|
||||
// If we're waiting for radio to tune to a target frequency, check if this CAT update is stale
|
||||
if (typeof dxWaterfall !== 'undefined' && dxWaterfall.targetFrequencyHz) {
|
||||
var incomingHz = parseFloat(radioFrequency);
|
||||
var targetHz = dxWaterfall.targetFrequencyHz;
|
||||
var toleranceHz = DX_WATERFALL_CONSTANTS.THRESHOLDS.CAT_FREQUENCY_HZ; // 1 Hz tolerance
|
||||
var diff = Math.abs(incomingHz - targetHz);
|
||||
|
||||
dxWaterfall.targetFrequencyConfirmAttempts = (dxWaterfall.targetFrequencyConfirmAttempts || 0) + 1;
|
||||
|
||||
if (diff <= toleranceHz) {
|
||||
// Frequency matches! Radio has tuned to target
|
||||
console.log('[DX Waterfall] CAT CONFIRM: Frequency matched on attempt ' + dxWaterfall.targetFrequencyConfirmAttempts +
|
||||
' - target=' + targetHz + 'Hz, received=' + incomingHz + 'Hz, diff=' + diff + 'Hz');
|
||||
|
||||
// Clear target AFTER a short delay to ensure waterfall updates first
|
||||
// This prevents the overlay from disappearing before the marker moves
|
||||
dxWaterfall.targetFrequencyConfirmAttempts = 0;
|
||||
|
||||
// Use setTimeout to clear the target after the waterfall has updated
|
||||
setTimeout(function() {
|
||||
if (typeof dxWaterfall !== 'undefined') {
|
||||
dxWaterfall.targetFrequencyHz = null;
|
||||
dxWaterfall.frequencyChanging = false; // Also clear frequencyChanging flag
|
||||
dxWaterfall.catTuning = false; // Clear CAT tuning flag - radio is now at target
|
||||
dxWaterfall.catTuningStartTime = null;
|
||||
console.log('[DX Waterfall] CAT CONFIRM: Cleared all tuning flags, overlay will disappear');
|
||||
}
|
||||
}, 100); // 100ms delay ensures waterfall renders at new position before overlay clears
|
||||
|
||||
shouldSkipStaleUpdate = false; // Proceed normally - radio is at correct frequency
|
||||
} else {
|
||||
// Frequency doesn't match - this is a stale update from before radio finished tuning
|
||||
console.log('[DX Waterfall] CAT CONFIRM: Ignoring stale update (attempt ' + dxWaterfall.targetFrequencyConfirmAttempts + '/2) - ' +
|
||||
'target=' + targetHz + 'Hz, received=' + incomingHz + 'Hz, diff=' + diff + 'Hz');
|
||||
|
||||
// If we've tried twice and still no match, give up and accept current frequency
|
||||
if (dxWaterfall.targetFrequencyConfirmAttempts >= 2) {
|
||||
console.warn('[DX Waterfall] CAT CONFIRM: Giving up after 2 attempts - ' +
|
||||
'target=' + targetHz + 'Hz (' + (targetHz/1000) + 'kHz), ' +
|
||||
'received=' + incomingHz + 'Hz (' + (incomingHz/1000) + 'kHz), ' +
|
||||
'diff=' + diff + 'Hz (' + (diff/1000) + 'kHz)');
|
||||
dxWaterfall.targetFrequencyHz = null;
|
||||
dxWaterfall.targetFrequencyConfirmAttempts = 0;
|
||||
shouldSkipStaleUpdate = false; // Give up, accept current frequency
|
||||
} else {
|
||||
// Skip this stale update - waterfall already showing target frequency
|
||||
shouldSkipStaleUpdate = true;
|
||||
return true; // Exit early - don't process this stale CAT update
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI with new frequency
|
||||
if (updateCallback) {
|
||||
updateCallback();
|
||||
}
|
||||
|
||||
// Only invalidate cache and commit if frequency actually changed
|
||||
if (typeof dxWaterfall !== 'undefined' && (frequencyChanged || isInitialLoad)) {
|
||||
@@ -1375,6 +1432,10 @@ var dxWaterfall = {
|
||||
catFrequencyWaitTimer: null, // Timer for CAT frequency wait
|
||||
waitingForFrequencyUpdate: false, // Track if we're waiting for frequency to be set after band change
|
||||
|
||||
// Refresh throttling
|
||||
lastRefreshTime: 0, // Track last refresh timestamp for throttling
|
||||
refreshPending: false, // Track if a refresh is already scheduled
|
||||
|
||||
// ========================================
|
||||
// USER INTERFACE STATE
|
||||
// ========================================
|
||||
@@ -1473,10 +1534,11 @@ var dxWaterfall = {
|
||||
lastWaterfallFrequencyCommandTime: 0, // Track when waterfall sent a CAT command
|
||||
lastFrequencyRefreshTime: 0, // Throttle frequency cache refresh
|
||||
catTuning: false, // Flag to show "Tuning radio..." message during CAT operations
|
||||
targetFrequencyHz: null, // Target frequency for confirmation (null = not waiting, prevents stale CAT updates)
|
||||
targetFrequencyConfirmAttempts: 0, // Counter for CAT frequency confirmation attempts
|
||||
|
||||
// Spot fetch state management
|
||||
userInitiatedFetch: false, // Flag to distinguish user-initiated fetches from background refreshes
|
||||
|
||||
// Spot collection throttling
|
||||
lastSpotCollectionTime: 0,
|
||||
spotCollectionThrottleMs: DX_WATERFALL_CONSTANTS.DEBOUNCE.SPOT_COLLECTION_MS,
|
||||
@@ -1504,6 +1566,10 @@ var dxWaterfall = {
|
||||
bandLimitsCache: null, // Cached band limits for current band+region
|
||||
cachedBandForEdges: null, // The band for which band edges are currently cached
|
||||
|
||||
// Refresh throttling to prevent excessive rendering
|
||||
refreshPending: false, // Track if a refresh is already scheduled
|
||||
lastRefreshTime: 0, // Track when last refresh actually executed
|
||||
|
||||
// ========================================
|
||||
// INITIALIZATION AND SETUP FUNCTIONS
|
||||
// ========================================
|
||||
@@ -1594,7 +1660,9 @@ var dxWaterfall = {
|
||||
|
||||
// Clear any stuck CAT tuning or frequency changing flags when user manually interacts
|
||||
// This ensures that even if CAT is in an error state, user can still change frequency
|
||||
if (self.catTuning || self.frequencyChanging) {
|
||||
// BUT: Don't clear if we're waiting for CAT confirmation (targetFrequencyHz is set)
|
||||
if ((self.catTuning || self.frequencyChanging) && !self.targetFrequencyHz) {
|
||||
console.log('[DX Waterfall] FOCUS: Clearing catTuning flags (user editing frequency)');
|
||||
self.catTuning = false;
|
||||
self.frequencyChanging = false;
|
||||
self.catTuningStartTime = null;
|
||||
@@ -1657,6 +1725,9 @@ var dxWaterfall = {
|
||||
this.lastBand = this.getCurrentBand();
|
||||
this.lastMode = this.getCurrentMode();
|
||||
|
||||
// Initialize cachedBandForEdges to current band so band edges can render immediately
|
||||
this.cachedBandForEdges = this.getCurrentBand();
|
||||
|
||||
// Set up adaptive timeout for CAT frequency to arrive
|
||||
// WebSocket connections are much faster, so use shorter timeout
|
||||
var timings = getCATTimings();
|
||||
@@ -1737,6 +1808,9 @@ var dxWaterfall = {
|
||||
// This ensures programmaticBandUpdate flag is set before hasParametersChanged() runs
|
||||
// BUT only do this if the band actually needs to change (frequency is outside current band)
|
||||
var currentFreqKhz = DX_WATERFALL_UTILS.frequency.convertToKhz(freqValue, currentUnit);
|
||||
|
||||
// Store the committed frequency in kHz for comparison checks
|
||||
this.committedFrequencyKHz = currentFreqKhz;
|
||||
if (currentFreqKhz > 0) {
|
||||
var expectedBand = this.getFrequencyBand(currentFreqKhz);
|
||||
var currentBand = this.getCurrentBand();
|
||||
@@ -1854,6 +1928,9 @@ var dxWaterfall = {
|
||||
// Band changed after initial load, reset waiting state
|
||||
this.waitingForData = true;
|
||||
this.dataReceived = false;
|
||||
// Clear old spots immediately to prevent rendering spots from wrong band
|
||||
this.dxSpots = [];
|
||||
this.totalSpotsCount = 0;
|
||||
// Reset relevant spots array and index
|
||||
this.relevantSpots = [];
|
||||
this.currentSpotIndex = 0;
|
||||
@@ -1985,7 +2062,7 @@ var dxWaterfall = {
|
||||
},
|
||||
|
||||
// Force invalidate frequency cache - called when CAT updates frequency
|
||||
invalidateFrequencyCache: function() {
|
||||
invalidateFrequencyCache: function(frequencyKHz, isImmediateUpdate) {
|
||||
// Safety check: Don't run if waterfall is not initialized
|
||||
if (!this.canvas) {
|
||||
return;
|
||||
@@ -1996,6 +2073,16 @@ var dxWaterfall = {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is an immediate update from clicking a spot, update frequency NOW
|
||||
if (isImmediateUpdate && frequencyKHz) {
|
||||
this.cache.middleFreq = frequencyKHz;
|
||||
console.log('[DX Waterfall] FREQ COMMIT: ' + frequencyKHz + ' kHz (immediate update)');
|
||||
// Don't call refresh() here - let the animation loop handle it
|
||||
// This prevents race conditions with the 60 FPS animation frame
|
||||
// The overlay flags are already set, so next frame will show overlay
|
||||
return; // Done - CAT will confirm later
|
||||
}
|
||||
|
||||
var oldFreq = this.cache.middleFreq;
|
||||
|
||||
// Track if this was clearing the initial CAT wait
|
||||
@@ -2011,14 +2098,22 @@ var dxWaterfall = {
|
||||
}
|
||||
|
||||
// Clear CAT tuning flags since frequency is now confirmed by CAT system
|
||||
this.catTuning = false;
|
||||
this.frequencyChanging = false; // Also clear frequency changing flag
|
||||
this.catTuningStartTime = null; // Clear timeout tracking
|
||||
this.spotNavigating = false; // Clear spot navigation flag on successful CAT completion
|
||||
// Only clear if we're not waiting for a specific target frequency
|
||||
if (!this.targetFrequencyHz) {
|
||||
console.log('[DX Waterfall] INVALIDATE CACHE: Clearing catTuning flags (targetFrequencyHz not set)');
|
||||
this.catTuning = false;
|
||||
this.frequencyChanging = false; // Also clear frequency changing flag
|
||||
this.catTuningStartTime = null; // Clear timeout tracking
|
||||
this.spotNavigating = false; // Clear spot navigation flag on successful CAT completion
|
||||
|
||||
// Update zoom menu immediately after clearing flags
|
||||
if (this.zoomMenuDiv) {
|
||||
this.updateZoomMenu();
|
||||
// Update zoom menu immediately after clearing flags
|
||||
if (this.zoomMenuDiv) {
|
||||
this.updateZoomMenu();
|
||||
}
|
||||
} else {
|
||||
// Waiting for target frequency - skip normal processing, CAT will confirm later
|
||||
console.log('[DX Waterfall] INVALIDATE CACHE: Skipping (waiting for targetFrequencyHz=' + this.targetFrequencyHz + 'Hz)');
|
||||
return; // Exit early
|
||||
}
|
||||
|
||||
// Only set completion overlay if:
|
||||
@@ -2540,15 +2635,21 @@ var dxWaterfall = {
|
||||
params.phoneFilter === cacheKey.phoneFilter &&
|
||||
params.cwFilter === cacheKey.cwFilter &&
|
||||
params.digiFilter === cacheKey.digiFilter) {
|
||||
console.log('[DX Waterfall] VISIBLE SPOTS: Using cache - ' + this.cache.visibleSpots.left.length + ' left, ' + this.cache.visibleSpots.right.length + ' right');
|
||||
return this.cache.visibleSpots;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss - rebuild visible spots
|
||||
console.log('[DX Waterfall] VISIBLE SPOTS: Cache miss, rebuilding from ' + (this.dxSpots ? this.dxSpots.length : 0) + ' total spots');
|
||||
console.log('[DX Waterfall] VISIBLE SPOTS: middleFreq=' + middleFreq + ' kHz, pixelsPerKHz=' + pixelsPerKHz + ', canvasWidth=' + this.canvas.width);
|
||||
var leftSpots = [];
|
||||
var rightSpots = [];
|
||||
var centerFrequency = middleFreq;
|
||||
var centerFrequencyTolerance = DX_WATERFALL_CONSTANTS.THRESHOLDS.CENTER_SPOT_TOLERANCE_KHZ;
|
||||
var filteredCount = 0;
|
||||
var outOfBoundsCount = 0;
|
||||
var centerSkipCount = 0;
|
||||
|
||||
for (var i = 0; i < this.dxSpots.length; i++) {
|
||||
var spot = this.dxSpots[i];
|
||||
@@ -2557,6 +2658,7 @@ var dxWaterfall = {
|
||||
if (spotFreq && spot.spotted && spot.mode) {
|
||||
// Apply mode filter
|
||||
if (!this.spotMatchesModeFilter(spot)) {
|
||||
filteredCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2567,6 +2669,7 @@ var dxWaterfall = {
|
||||
if (x >= 0 && x <= this.canvas.width) {
|
||||
// Skip spots at center frequency (within tolerance)
|
||||
if (centerFrequency && Math.abs(spotFreq - centerFrequency) <= centerFrequencyTolerance) {
|
||||
centerSkipCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2586,10 +2689,14 @@ var dxWaterfall = {
|
||||
} else if (freqOffset > 0) {
|
||||
rightSpots.push(spotData);
|
||||
}
|
||||
} else {
|
||||
outOfBoundsCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[DX Waterfall] VISIBLE SPOTS: Results - ' + leftSpots.length + ' left, ' + rightSpots.length + ' right (filtered=' + filteredCount + ', outOfBounds=' + outOfBoundsCount + ', centerSkip=' + centerSkipCount + ')');
|
||||
|
||||
// Pre-calculate label widths for all spots (cached with visible spots)
|
||||
if (this.ctx) {
|
||||
var currentLabelFont = this.getCurrentLabelFont();
|
||||
@@ -3192,16 +3299,12 @@ var dxWaterfall = {
|
||||
// Store current settings
|
||||
this.currentMaxAge = age;
|
||||
|
||||
// Check if a fetch is already in progress for a DIFFERENT band
|
||||
// If band changed, we need to allow the new fetch (cancel the old one by letting it complete)
|
||||
// Check if a fetch is already in progress
|
||||
// ALWAYS block concurrent fetches to prevent race conditions and timeout issues
|
||||
// The safety timeout will force-clear the stuck state if the fetch hangs
|
||||
if (this.fetchInProgress) {
|
||||
// If the band is different from what's being fetched, allow this fetch to proceed
|
||||
// The old fetch will complete but we'll overwrite with new data
|
||||
if (this.lastFetchBand && this.lastFetchBand !== band) {
|
||||
// Don't return - let the new fetch proceed
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
console.log('[DX Waterfall] FETCH SPOTS: Blocked - fetch already in progress (waiting for timeout or completion)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we recently fetched the same data (band, continent, age)
|
||||
@@ -3239,6 +3342,30 @@ var dxWaterfall = {
|
||||
this.updateZoomMenu(); // Immediately show timer/hourglass
|
||||
}
|
||||
|
||||
// Clear any existing safety timeout before setting a new one
|
||||
if (this.safetyTimeoutId) {
|
||||
clearTimeout(this.safetyTimeoutId);
|
||||
this.safetyTimeoutId = null;
|
||||
}
|
||||
|
||||
// Set a safety timeout to force-clear stuck state after AJAX timeout + buffer
|
||||
// This ensures UI doesn't stay locked if AJAX callbacks fail to trigger
|
||||
console.log('[DX Waterfall] FETCH SPOTS: Safety timeout armed - will trigger in 32s if no response');
|
||||
this.safetyTimeoutId = setTimeout(function() {
|
||||
console.log('[DX Waterfall] FETCH SPOTS: *** SAFETY TIMEOUT TRIGGERED *** - AJAX hung for 32+ seconds');
|
||||
if (self.fetchInProgress) {
|
||||
console.log('[DX Waterfall] FETCH SPOTS: Safety timeout - forcing state clear');
|
||||
self.fetchInProgress = false;
|
||||
self.userInitiatedFetch = false;
|
||||
self.waitingForData = false;
|
||||
self.dataReceived = true;
|
||||
self.operationStartTime = null;
|
||||
self.safetyTimeoutId = null;
|
||||
self.updateZoomMenu(true); // Force menu update
|
||||
self.refresh(); // Clear any waiting overlays
|
||||
}
|
||||
}, DX_WATERFALL_CONSTANTS.AJAX.TIMEOUT_MS + 2000); // AJAX timeout + 2s buffer
|
||||
|
||||
$.ajax({
|
||||
url: ajaxUrl,
|
||||
type: 'GET',
|
||||
@@ -3246,10 +3373,39 @@ var dxWaterfall = {
|
||||
timeout: DX_WATERFALL_CONSTANTS.AJAX.TIMEOUT_MS,
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
console.log('[DX Waterfall] FETCH SPOTS: Success - received ' + (data ? data.length || 0 : 0) + ' spots');
|
||||
|
||||
// Check if band has changed since this fetch was initiated
|
||||
// If it has, discard this data (it's stale)
|
||||
var currentBand = self.getCurrentBand();
|
||||
if (band !== currentBand) {
|
||||
console.log('[DX Waterfall] FETCH SPOTS: Ignoring stale data (fetched for ' + band + ', now on ' + currentBand + ')');
|
||||
// Clear safety timeout even for stale data
|
||||
if (self.safetyTimeoutId) {
|
||||
clearTimeout(self.safetyTimeoutId);
|
||||
self.safetyTimeoutId = null;
|
||||
}
|
||||
// Clear fetch in progress flag to allow new fetches
|
||||
self.fetchInProgress = false;
|
||||
// Keep userInitiatedFetch flag - we still need data for the new band
|
||||
self.operationStartTime = null;
|
||||
|
||||
// Trigger immediate fetch for the correct (current) band
|
||||
console.log('[DX Waterfall] FETCH SPOTS: Triggering new fetch for current band ' + currentBand);
|
||||
self.fetchDxSpots(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear safety timeout for valid data
|
||||
if (self.safetyTimeoutId) {
|
||||
console.log('[DX Waterfall] FETCH SPOTS: Success - clearing safety timeout');
|
||||
clearTimeout(self.safetyTimeoutId);
|
||||
self.safetyTimeoutId = null;
|
||||
}
|
||||
// Clear fetch in progress flag
|
||||
self.fetchInProgress = false;
|
||||
|
||||
console.log('[DX Waterfall] FETCH SPOTS: Success - received ' + (data ? data.length || 0 : 0) + ' spots');
|
||||
self.userInitiatedFetch = false; // Clear user-initiated flag
|
||||
self.operationStartTime = null; // Clear timer
|
||||
|
||||
if (data && !data.error) {
|
||||
// Enrich spots with park references once during fetch
|
||||
@@ -3278,7 +3434,6 @@ var dxWaterfall = {
|
||||
} else {
|
||||
console.log('[DX Waterfall] FETCH SPOTS: Keeping waitingForData=true (waiting for frequency update)');
|
||||
}
|
||||
self.userInitiatedFetch = false; // Clear user-initiated flag
|
||||
self.lastUpdateTime = new Date(); // Record update time
|
||||
// Track fetch parameters to prevent duplicate fetches
|
||||
self.lastFetchBand = band;
|
||||
@@ -3304,7 +3459,7 @@ var dxWaterfall = {
|
||||
self.totalSpotsCount = 0;
|
||||
self.dataReceived = true; // Mark as received even if empty
|
||||
self.waitingForData = false; // Stop waiting
|
||||
self.userInitiatedFetch = false; // Clear user-initiated flag
|
||||
self.operationStartTime = null; // Clear timer
|
||||
self.lastUpdateTime = new Date(); // Record update time even on error
|
||||
|
||||
// Track fetch parameters to prevent duplicate fetches
|
||||
@@ -3328,16 +3483,23 @@ var dxWaterfall = {
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// Clear safety timeout
|
||||
if (self.safetyTimeoutId) {
|
||||
console.log('[DX Waterfall] FETCH SPOTS: Error callback - clearing safety timeout');
|
||||
clearTimeout(self.safetyTimeoutId);
|
||||
self.safetyTimeoutId = null;
|
||||
}
|
||||
// Clear fetch in progress flag
|
||||
self.fetchInProgress = false;
|
||||
self.userInitiatedFetch = false; // Clear user-initiated flag
|
||||
|
||||
console.log('[DX Waterfall] FETCH SPOTS: AJAX error - ' + status + ', ' + error);
|
||||
console.log('[DX Waterfall] FETCH SPOTS: AJAX error - status=' + status + ', error=' + error + ', readyState=' + xhr.readyState);
|
||||
|
||||
self.dxSpots = [];
|
||||
self.totalSpotsCount = 0;
|
||||
self.dataReceived = true; // Mark as received to stop waiting state
|
||||
self.waitingForData = false; // Stop waiting
|
||||
self.operationStartTime = null; // Clear timer
|
||||
|
||||
// Invalidate caches on error
|
||||
self.cache.visibleSpots = null;
|
||||
@@ -3510,6 +3672,8 @@ var dxWaterfall = {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[DX Waterfall] WAITING MSG: Displaying overlay - waitingForFrequencyUpdate=' + this.waitingForFrequencyUpdate + ', waitingForData=' + this.waitingForData);
|
||||
|
||||
// Don't clear zoom menu here - let updateZoomMenu() handle it
|
||||
// This prevents brief empty states when displayWaitingMessage() is called
|
||||
// followed immediately by updateZoomMenu()
|
||||
@@ -3533,8 +3697,16 @@ var dxWaterfall = {
|
||||
// Text position (moved down lower for more space)
|
||||
var textY = centerY + DX_WATERFALL_CONSTANTS.CANVAS.TEXT_OFFSET_Y;
|
||||
|
||||
// Draw "Downloading DX Cluster data" message
|
||||
DX_WATERFALL_UTILS.drawing.drawCenteredText(this.ctx, lang_dxwaterfall_downloading_data, centerX, textY, 'WAITING_MESSAGE', 'MESSAGE_TEXT_WHITE');
|
||||
// Choose message based on what we're waiting for
|
||||
// If waiting for frequency update (band change), show different message
|
||||
var message = this.waitingForFrequencyUpdate ?
|
||||
lang_dxwaterfall_downloading_data : // Will show as primary waiting message during band changes
|
||||
lang_dxwaterfall_downloading_data;
|
||||
|
||||
console.log('[DX Waterfall] WAITING MSG: Drawing text message at Y=' + textY);
|
||||
|
||||
// Draw waiting message
|
||||
DX_WATERFALL_UTILS.drawing.drawCenteredText(this.ctx, message, centerX, textY, 'WAITING_MESSAGE', 'MESSAGE_TEXT_WHITE');
|
||||
|
||||
// Reset opacity
|
||||
this.ctx.globalAlpha = 1.0;
|
||||
@@ -4135,11 +4307,10 @@ var dxWaterfall = {
|
||||
var labelHeight = this.getCurrentLabelHeight();
|
||||
var minSpacing = 3; // Minimum spacing between labels in pixels
|
||||
|
||||
// Function to distribute spots vertically with anti-overlap algorithm
|
||||
var drawSpotsSide = function(spots, ctx) {
|
||||
if (spots.length === 0) return;
|
||||
|
||||
// Note: Label widths are already pre-calculated in getVisibleSpots() and cached
|
||||
// Function to distribute spots vertically with anti-overlap algorithm
|
||||
var drawSpotsSide = function(spots, ctx) {
|
||||
console.log('[DX Waterfall] RENDER SPOTS: Drawing ' + spots.length + ' spots');
|
||||
if (spots.length === 0) return; // Note: Label widths are already pre-calculated in getVisibleSpots() and cached
|
||||
// Set font for drawing (not for measuring)
|
||||
ctx.font = currentLabelFont;
|
||||
|
||||
@@ -4514,6 +4685,46 @@ var dxWaterfall = {
|
||||
* @returns {void}
|
||||
*/
|
||||
refresh: function() {
|
||||
// Throttle rapid refresh calls to prevent excessive rendering
|
||||
// This prevents excessive rendering when multiple events fire simultaneously
|
||||
var currentTime = Date.now();
|
||||
var minRefreshInterval = 50; // Minimum 50ms between actual refreshes (~20 FPS max)
|
||||
|
||||
// If we already have a pending refresh, skip this call
|
||||
if (this.refreshPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If not enough time has passed since last refresh, schedule for next frame
|
||||
var timeSinceLastRefresh = currentTime - this.lastRefreshTime;
|
||||
if (timeSinceLastRefresh < minRefreshInterval) {
|
||||
this.refreshPending = true;
|
||||
var self = this;
|
||||
var remainingTime = minRefreshInterval - timeSinceLastRefresh;
|
||||
setTimeout(function() {
|
||||
self.refreshPending = false;
|
||||
self._performRefresh();
|
||||
}, remainingTime);
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute refresh immediately
|
||||
this._performRefresh();
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal refresh implementation (called by throttled refresh())
|
||||
* @private
|
||||
*/
|
||||
_performRefresh: function() {
|
||||
// Update last refresh time
|
||||
this.lastRefreshTime = Date.now();
|
||||
|
||||
// Debug: Log flag states at start of refresh
|
||||
if (this.targetFrequencyHz || this.frequencyChanging || this.catTuning) {
|
||||
console.log('[DX Waterfall] REFRESH START: catTuning=' + this.catTuning + ', frequencyChanging=' + this.frequencyChanging + ', targetFrequencyHz=' + this.targetFrequencyHz);
|
||||
}
|
||||
|
||||
if (!this.canvas) {
|
||||
this.init();
|
||||
// If init still couldn't find the canvas, exit
|
||||
@@ -4528,6 +4739,9 @@ var dxWaterfall = {
|
||||
return; // Canvas not visible or removed from DOM
|
||||
}
|
||||
|
||||
// NOTE: Removed targetFrequencyHz blocking - waterfall updates immediately on click
|
||||
// Stale CAT updates are ignored in handleCATFrequencyUpdate() instead
|
||||
|
||||
// Check if band or mode has changed and fetch new spots if needed
|
||||
// Skip during CAT operations to prevent interference
|
||||
if (!this.catTuning && !this.frequencyChanging) {
|
||||
@@ -4568,55 +4782,89 @@ var dxWaterfall = {
|
||||
return; // Don't fetch spots or draw normal display until CAT frequency arrives
|
||||
}
|
||||
|
||||
// Check if we should show waiting message
|
||||
var currentTime = Date.now();
|
||||
var timeSincePageLoad = currentTime - this.pageLoadTime;
|
||||
var isInitialLoad = timeSincePageLoad < this.minWaitTime;
|
||||
// Check if we should show waiting message
|
||||
var currentTime = Date.now();
|
||||
var timeSincePageLoad = currentTime - this.pageLoadTime;
|
||||
var isInitialLoad = timeSincePageLoad < this.minWaitTime;
|
||||
|
||||
// Show waiting if:
|
||||
// 1. We're waiting for data AND either:
|
||||
// a) It's the initial load and we haven't received data OR haven't waited 5 seconds yet
|
||||
// b) It's a parameter change and we haven't received new data yet (no time wait)
|
||||
// BUT if we've already received data (including error responses), don't show waiting
|
||||
// OR if we're specifically waiting for frequency update (band change) - always wait
|
||||
var shouldShowWaiting = (this.waitingForData && !this.dataReceived &&
|
||||
(isInitialLoad ? timeSincePageLoad < this.minWaitTime : true)) ||
|
||||
this.waitingForFrequencyUpdate;
|
||||
// Show waiting if:
|
||||
// 1. We're waiting for frequency update (band change in progress) - always wait
|
||||
// 2. OR we're waiting for data AND either:
|
||||
// a) We've never received data yet (initial load) - always wait until first data arrives
|
||||
// b) We're fetching new data after a parameter change (userInitiatedFetch)
|
||||
// c) The spots array is empty (cleared during band change)
|
||||
// 3. OR we're in a user-initiated fetch with empty spots AND still waiting for response
|
||||
// (handles gap when fetch completes but waiting flags not yet cleared)
|
||||
var shouldShowWaiting = this.waitingForFrequencyUpdate ||
|
||||
(this.waitingForData && (
|
||||
!this.dataReceived ||
|
||||
this.userInitiatedFetch ||
|
||||
this.dxSpots.length === 0
|
||||
)) ||
|
||||
(this.userInitiatedFetch && this.dxSpots.length === 0 && this.fetchInProgress);
|
||||
|
||||
if (shouldShowWaiting) {
|
||||
if (this.waitingForFrequencyUpdate) {
|
||||
console.log('[DX Waterfall] REFRESH: Blocking render - waiting for frequency update');
|
||||
}
|
||||
this.displayWaitingMessage();
|
||||
this.updateZoomMenu(); // Update menu to show loading indicator
|
||||
return; // Don't draw the normal display
|
||||
// Debug logging for waiting state
|
||||
if (!shouldShowWaiting && (this.waitingForData || this.dxSpots.length === 0)) {
|
||||
console.log('[DX Waterfall] REFRESH: NOT blocking - waitingForFreqUpdate=' + this.waitingForFrequencyUpdate +
|
||||
', waitingForData=' + this.waitingForData +
|
||||
', dataReceived=' + this.dataReceived +
|
||||
', userInitiatedFetch=' + this.userInitiatedFetch +
|
||||
', dxSpots.length=' + this.dxSpots.length);
|
||||
}
|
||||
|
||||
if (shouldShowWaiting) {
|
||||
if (this.waitingForFrequencyUpdate) {
|
||||
console.log('[DX Waterfall] REFRESH: Blocking render - waiting for frequency update');
|
||||
} else if (this.waitingForData && this.userInitiatedFetch) {
|
||||
console.log('[DX Waterfall] REFRESH: Blocking render - waiting for data (user-initiated fetch)');
|
||||
} else if (this.waitingForData && !this.dataReceived) {
|
||||
console.log('[DX Waterfall] REFRESH: Blocking render - waiting for initial data');
|
||||
} else if (this.waitingForData && this.dxSpots.length === 0) {
|
||||
console.log('[DX Waterfall] REFRESH: Blocking render - spots array empty (band change in progress)');
|
||||
} else if (this.userInitiatedFetch && this.dxSpots.length === 0) {
|
||||
console.log('[DX Waterfall] REFRESH: Blocking render - user-initiated fetch with empty spots array');
|
||||
}
|
||||
|
||||
// Check if CAT is tuning the radio with safety timeout
|
||||
this.displayWaitingMessage();
|
||||
this.updateZoomMenu(); // Update menu to show loading indicator
|
||||
return; // Don't draw the normal display
|
||||
} // Check if CAT is tuning the radio with safety timeout
|
||||
if (this.catTuning) {
|
||||
// Safety check: if CAT tuning has been true for more than 2 seconds, force clear it
|
||||
// Safety check: if CAT tuning has been true for more than fallback time, force clear it
|
||||
// BUT: Don't clear if we're waiting for CAT confirmation (targetFrequencyHz is set)
|
||||
if (!this.catTuningStartTime) {
|
||||
this.catTuningStartTime = currentTime;
|
||||
}
|
||||
|
||||
var catTuningDuration = currentTime - this.catTuningStartTime;
|
||||
if (catTuningDuration > DX_WATERFALL_CONSTANTS.CAT.TUNING_FLAG_FALLBACK_MS) {
|
||||
if (catTuningDuration > DX_WATERFALL_CONSTANTS.CAT.TUNING_FLAG_FALLBACK_MS && !this.targetFrequencyHz) {
|
||||
console.log('[DX Waterfall] TIMEOUT: Clearing catTuning flags after ' + catTuningDuration + 'ms');
|
||||
this.catTuning = false;
|
||||
this.frequencyChanging = false;
|
||||
this.catTuningStartTime = null;
|
||||
// Update menu to show normal state after timeout
|
||||
this.updateZoomMenu();
|
||||
} else {
|
||||
console.log('[DX Waterfall] CAT TUNING: Displaying overlay - catTuning=true, duration=' + catTuningDuration + 'ms');
|
||||
this.displayChangingFrequencyMessage(lang_dxwaterfall_changing_frequency, 'MESSAGE_TEXT_WHITE');
|
||||
return; // Don't draw normal display during CAT tuning
|
||||
}
|
||||
} else {
|
||||
// If targetFrequencyHz is set but catTuning is false, restore catTuning
|
||||
// This prevents the brief flash when transitioning between overlay code paths
|
||||
if (this.targetFrequencyHz) {
|
||||
console.log('[DX Waterfall] RESTORE: Setting catTuning=true because targetFrequencyHz is still set');
|
||||
this.catTuning = true;
|
||||
this.catTuningStartTime = currentTime;
|
||||
this.displayChangingFrequencyMessage(lang_dxwaterfall_changing_frequency, 'MESSAGE_TEXT_WHITE');
|
||||
return;
|
||||
}
|
||||
// Clear the start time when not tuning
|
||||
this.catTuningStartTime = null;
|
||||
}
|
||||
|
||||
// Check if frequency is changing (CAT command in progress)
|
||||
if (this.frequencyChanging) {
|
||||
if (this.frequencyChanging || this.targetFrequencyHz) {
|
||||
console.log('[DX Waterfall] OVERLAY: Displaying overlay - frequencyChanging=' + this.frequencyChanging + ', targetFrequencyHz=' + this.targetFrequencyHz);
|
||||
this.displayChangingFrequencyMessage(lang_dxwaterfall_changing_frequency, 'MESSAGE_TEXT_WHITE');
|
||||
this.updateZoomMenu(); // Update menu to show loading indicator
|
||||
return; // Don't draw normal display or process inputs
|
||||
@@ -5838,6 +6086,7 @@ var dxWaterfall = {
|
||||
this.catTuning = false;
|
||||
this.userInitiatedFetch = false;
|
||||
this.fetchInProgress = false;
|
||||
this.safetyTimeoutId = null;
|
||||
|
||||
// Reset indices
|
||||
this.currentSpotIndex = 0;
|
||||
@@ -6067,7 +6316,25 @@ function setFrequency(frequencyInKHz, fromWaterfall) {
|
||||
if (typeof tuneRadioToFrequency === 'function') {
|
||||
// Set frequency changing flag and show visual feedback
|
||||
if (typeof dxWaterfall !== 'undefined') {
|
||||
// Check if we're already at the target frequency (within 1Hz tolerance)
|
||||
var currentFreqHz = Math.round(dxWaterfall.committedFrequencyKHz * 1000);
|
||||
var diff = Math.abs(currentFreqHz - formattedFreq);
|
||||
console.log('[DX Waterfall] SPOT CLICK: Frequency check - currentFreqHz=' + currentFreqHz + 'Hz, targetFreqHz=' + formattedFreq + 'Hz, diff=' + diff + 'Hz');
|
||||
if (diff <= 1) {
|
||||
console.log('[DX Waterfall] SPOT CLICK: Already at target frequency, skipping CAT confirmation');
|
||||
// Just update the waterfall display, don't set any flags
|
||||
dxWaterfall.invalidateFrequencyCache(formattedFreq / 1000, true);
|
||||
return; // Skip the entire CAT process
|
||||
}
|
||||
|
||||
// Set target frequency FIRST before any refresh/update
|
||||
// This ensures the overlay is displayed immediately with no gap
|
||||
dxWaterfall.targetFrequencyHz = formattedFreq;
|
||||
dxWaterfall.targetFrequencyConfirmAttempts = 0; // Reset confirmation counter
|
||||
dxWaterfall.frequencyChanging = true;
|
||||
|
||||
console.log('[DX Waterfall] SPOT CLICK: Set flags - targetFrequencyHz=' + formattedFreq + 'Hz, frequencyChanging=true');
|
||||
|
||||
// Only set catTuning flag if this is a waterfall-initiated change (not external CAT updates)
|
||||
if (fromWaterfall) {
|
||||
dxWaterfall.catTuning = true; // Set CAT tuning flag
|
||||
@@ -6076,10 +6343,20 @@ function setFrequency(frequencyInKHz, fromWaterfall) {
|
||||
dxWaterfall.operationStartTime = Date.now(); // Reset operation timer for display
|
||||
dxWaterfall.lastWaterfallFrequencyCommandTime = Date.now(); // Track waterfall command time
|
||||
|
||||
// Only show changing message if this is a waterfall-initiated change
|
||||
if (fromWaterfall) {
|
||||
dxWaterfall.displayChangingFrequencyMessage(lang_dxwaterfall_changing_frequency, 'MESSAGE_TEXT_WHITE');
|
||||
}
|
||||
console.log('[DX Waterfall] SPOT CLICK: About to call refresh() - flags should trigger overlay');
|
||||
|
||||
// Force immediate refresh to show overlay (refresh() will see the flags and display overlay)
|
||||
// This ensures overlay is visible in the SAME execution context (no frame gap)
|
||||
dxWaterfall.refresh();
|
||||
|
||||
console.log('[DX Waterfall] SPOT CLICK: Called refresh(), about to update frequency position');
|
||||
|
||||
// IMMEDIATELY update waterfall to show new frequency (don't wait for CAT)
|
||||
// This prevents the visual "jump" when the old frequency comes back from CAT
|
||||
// The overlay is already visible, so this just updates the position underneath
|
||||
dxWaterfall.invalidateFrequencyCache(formattedFreq / 1000, true); // true = immediate update
|
||||
|
||||
console.log('[DX Waterfall] SPOT CLICK: Complete - overlay should be visible until CAT confirms');
|
||||
}
|
||||
|
||||
// Set debounce lock to prevent CAT feedback
|
||||
@@ -6099,11 +6376,13 @@ function setFrequency(frequencyInKHz, fromWaterfall) {
|
||||
var timings = getCATTimings();
|
||||
|
||||
// Clear frequency changing flag on successful command
|
||||
// BUT: Don't clear if we're waiting for CAT confirmation (targetFrequencyHz is set)
|
||||
if (typeof dxWaterfall !== 'undefined') {
|
||||
// Clear frequency changing flag immediately since CAT command succeeded
|
||||
// The catTuning flag will be cleared by invalidateFrequencyCache() when frequency is confirmed
|
||||
setTimeout(function() {
|
||||
dxWaterfall.frequencyChanging = false;
|
||||
if (!dxWaterfall.targetFrequencyHz) {
|
||||
console.log('[DX Waterfall] SETFREQ COMMIT: Clearing frequencyChanging after commitDelay');
|
||||
dxWaterfall.frequencyChanging = false;
|
||||
}
|
||||
}, timings.commitDelay); // WebSocket: 20ms, Polling: 50ms
|
||||
}
|
||||
|
||||
@@ -6113,7 +6392,9 @@ function setFrequency(frequencyInKHz, fromWaterfall) {
|
||||
window.dxwaterfall_cat_debounce_lock = 0;
|
||||
window.dxwaterfall_expected_frequency = null;
|
||||
// Also clear CAT tuning flag on timeout and force cache refresh
|
||||
if (typeof dxWaterfall !== 'undefined') {
|
||||
// BUT: Don't clear if we're waiting for CAT confirmation (targetFrequencyHz is set)
|
||||
if (typeof dxWaterfall !== 'undefined' && !dxWaterfall.targetFrequencyHz) {
|
||||
console.log('[DX Waterfall] SETFREQ TIMEOUT: Clearing catTuning flags after debounce timeout');
|
||||
dxWaterfall.catTuning = false;
|
||||
dxWaterfall.frequencyChanging = false;
|
||||
dxWaterfall.catTuningStartTime = null;
|
||||
@@ -6132,13 +6413,19 @@ function setFrequency(frequencyInKHz, fromWaterfall) {
|
||||
// Define error callback
|
||||
var onError = function(jqXHR, textStatus, errorThrown) {
|
||||
// Clear frequency changing flag on error
|
||||
// BUT: Don't clear if we're waiting for CAT confirmation (targetFrequencyHz is set)
|
||||
if (typeof dxWaterfall !== 'undefined') {
|
||||
dxWaterfall.frequencyChanging = false;
|
||||
dxWaterfall.catTuning = false; // Clear CAT tuning flag on error
|
||||
dxWaterfall.spotNavigating = false; // Clear navigation flag on error
|
||||
// Force clear canvas on error too
|
||||
if (dxWaterfall.canvas && dxWaterfall.ctx) {
|
||||
dxWaterfall.ctx.clearRect(0, 0, dxWaterfall.canvas.width, dxWaterfall.canvas.height);
|
||||
if (!dxWaterfall.targetFrequencyHz) {
|
||||
console.log('[DX Waterfall] ERROR CALLBACK: Clearing flags due to error - textStatus=' + textStatus + ', error=' + errorThrown);
|
||||
dxWaterfall.frequencyChanging = false;
|
||||
dxWaterfall.catTuning = false; // Clear CAT tuning flag on error
|
||||
dxWaterfall.spotNavigating = false; // Clear navigation flag on error
|
||||
// Force clear canvas on error too
|
||||
if (dxWaterfall.canvas && dxWaterfall.ctx) {
|
||||
dxWaterfall.ctx.clearRect(0, 0, dxWaterfall.canvas.width, dxWaterfall.canvas.height);
|
||||
}
|
||||
} else {
|
||||
console.log('[DX Waterfall] ERROR CALLBACK: Skipping flag clear (waiting for CAT confirmation targetFrequencyHz=' + dxWaterfall.targetFrequencyHz + 'Hz) - textStatus=' + textStatus + ', error=' + errorThrown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6149,7 +6436,7 @@ function setFrequency(frequencyInKHz, fromWaterfall) {
|
||||
}
|
||||
|
||||
// Only log if it's not a simple timeout or network issue
|
||||
if (textStatus !== 'timeout' && jqXHR.status !== 0) {
|
||||
if (textStatus !== 'timeout' && jqXHR && jqXHR.status !== 0) {
|
||||
if (jqXHR.responseText) {
|
||||
console.warn('DX Waterfall: CAT command failed: Response text:', jqXHR.responseText);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user