diff --git a/application/views/bandmap/list.php b/application/views/bandmap/list.php index 0fc55c061..24b5d34ba 100644 --- a/application/views/bandmap/list.php +++ b/application/views/bandmap/list.php @@ -33,10 +33,12 @@
DX Cluster - spot list
- - + +
+ +
diff --git a/assets/css/bandmap_list.css b/assets/css/bandmap_list.css index c4f89c574..ed6d8be18 100644 --- a/assets/css/bandmap_list.css +++ b/assets/css/bandmap_list.css @@ -153,6 +153,78 @@ tbody a { transition: all 0.2s ease; } +/* CAT Control - Locked Sorting */ +.spottable.cat-sorting-locked thead th { + cursor: not-allowed !important; + pointer-events: none; + opacity: 0.6; +} + +.spottable.cat-sorting-locked thead th:hover { + background-color: transparent !important; +} + +.spottable.cat-sorting-locked thead th.sorting_asc::after, +.spottable.cat-sorting-locked thead th.sorting_desc::after { + opacity: 1 !important; +} + +/* CAT Control - Frequency Gradient */ +.cat-frequency-gradient { + transition: background-color 0.3s ease, var(--bs-table-bg) 0.3s ease !important; +} + +/* Force gradient background to override Bootstrap striping AND spot lifecycle colors - MAXIMUM SPECIFICITY */ +/* Override normal rows */ +table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient, +table.dataTable.table-striped tbody tr.cat-frequency-gradient, +table.table-striped tbody tr.cat-frequency-gradient, +.table-striped > tbody > tr.cat-frequency-gradient, +table tbody tr.cat-frequency-gradient { + background-color: var(--bs-table-bg) !important; +} + +/* Override expiring spots (red) */ +table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient.spot-expiring, +table.dataTable.table-striped tbody tr.cat-frequency-gradient.spot-expiring, +table tbody tr.cat-frequency-gradient.spot-expiring { + background-color: var(--bs-table-bg) !important; +} + +/* Override very new spots (green) */ +table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient.spot-very-new, +table.dataTable.table-striped tbody tr.cat-frequency-gradient.spot-very-new, +table tbody tr.cat-frequency-gradient.spot-very-new { + background-color: var(--bs-table-bg) !important; +} + +/* Override fresh spots - force gradient color over fresh blue */ +table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient.fresh, +table.dataTable.table-striped tbody tr.cat-frequency-gradient.fresh, +table tbody tr.cat-frequency-gradient.fresh { + /* Override both the variable and background directly */ + background-color: var(--bs-table-bg) !important; + /* Prevent .fresh class from setting its own --bs-table-bg */ + transition: background-color 0.3s ease !important; +} + +/* Override even and odd striping */ +table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient:nth-of-type(odd), +table.table.table-sm.table-bordered.table-hover.table-striped.spottable tbody tr.cat-frequency-gradient:nth-of-type(even), +table.dataTable.table-striped tbody tr.cat-frequency-gradient:nth-of-type(odd), +table.dataTable.table-striped tbody tr.cat-frequency-gradient:nth-of-type(even), +table.table-striped tbody tr.cat-frequency-gradient:nth-of-type(odd), +table.table-striped tbody tr.cat-frequency-gradient:nth-of-type(even), +.table-striped > tbody > tr.cat-frequency-gradient:nth-of-type(odd), +.table-striped > tbody > tr.cat-frequency-gradient:nth-of-type(even) { + --bs-table-accent-bg: transparent !important; + --bs-table-striped-bg: transparent !important; +} + +.cat-frequency-gradient:hover { + filter: brightness(0.95); +} + /* Status bar styling */ .status-bar { font-size: 0.875rem; @@ -301,6 +373,55 @@ tbody a { vertical-align: middle; } +/* Fullscreen toggle button wrapper - large clickable area */ +#fullscreenToggleWrapper { + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + z-index: 10; + border-radius: 0.25rem; + transition: background-color 150ms ease; +} + +#fullscreenToggleWrapper:hover { + background-color: rgba(0, 0, 0, 0.08) !important; +} + +/* Fullscreen toggle button - ensure proper clickable area */ +#fullscreenToggle { + min-width: 48px; + min-height: 48px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer !important; + position: relative; + flex-shrink: 0; +} + +#fullscreenToggle:hover { + background-color: transparent !important; +} + +#fullscreenIcon { + pointer-events: none; /* Icon shouldn't intercept clicks */ + font-size: 1.4rem !important; /* Make icon slightly bigger too */ +} + +/* Ensure wavelog text doesn't overlap button */ +.fullscreen-wavelog-text { + pointer-events: auto; + flex-shrink: 1; + white-space: nowrap; + cursor: pointer; +} + +.fullscreen-wavelog-text:hover { + text-decoration: underline !important; + opacity: 0.8; +} + /* Fullscreen mode - CLEAN REBUILD */ .bandmap-logo-fullscreen { display: none; diff --git a/assets/js/sections/bandmap_list.js b/assets/js/sections/bandmap_list.js index 72c2b09a0..1e230565f 100644 --- a/assets/js/sections/bandmap_list.js +++ b/assets/js/sections/bandmap_list.js @@ -384,16 +384,17 @@ $(function() { 'orderable': false } ], - search: { smart: true }, - drawCallback: function(settings) { - // Update status bar after table is drawn (including after search) - let totalRows = cachedSpotData ? cachedSpotData.length : 0; - let displayedRows = this.api().rows({ search: 'applied' }).count(); - updateStatusBar(totalRows, displayedRows, getServerFilterText(), getClientFilterText(), false, false); - } - }); + search: { smart: true }, + drawCallback: function(settings) { + // Update status bar after table is drawn (including after search) + let totalRows = cachedSpotData ? cachedSpotData.length : 0; + let displayedRows = this.api().rows({ search: 'applied' }).count(); + updateStatusBar(totalRows, displayedRows, getServerFilterText(), getClientFilterText(), false, false); - $('.spottable tbody').off('click', 'tr').on('click', 'tr', function(e) { + // Note: CAT frequency gradient is now updated only from updateCATui (every 3s) + // to prevent recursion issues with table redraws + } + }); $('.spottable tbody').off('click', 'tr').on('click', 'tr', function(e) { // Don't trigger row click if clicking on a link (LoTW, POTA, SOTA, WWFF, QRZ, etc.) if ($(e.target).is('a') || $(e.target).closest('a').length) { return; @@ -1039,18 +1040,36 @@ $(function() { } // Add row with appropriate class + let addedRow = table.rows.add(data).draw().nodes().to$(); + if (rowClass) { - let addedRow = table.rows.add(data).draw().nodes().to$(); addedRow.addClass(rowClass); if (ttl === 0) { console.log('Added expiring class to row:', addedRow.hasClass('spot-expiring')); } - } else { - table.rows.add(data).draw(); + } + + // Apply CAT frequency gradient AFTER adding lifecycle classes to ensure it overrides + if (isCatTrackingEnabled && currentRadioFrequency) { + const spotFreqKhz = single.frequency * 1000; // Convert MHz to kHz + const gradientColor = getFrequencyGradientColor(spotFreqKhz, currentRadioFrequency); + if (gradientColor) { + // Store gradient color and frequency for later reapplication + addedRow.attr('data-spot-frequency', spotFreqKhz); + addedRow.attr('data-gradient-color', gradientColor); + // Use setProperty with priority 'important' to force override + addedRow.each(function() { + this.style.setProperty('--bs-table-bg', gradientColor, 'important'); + this.style.setProperty('--bs-table-accent-bg', gradientColor, 'important'); + this.style.setProperty('background-color', gradientColor, 'important'); + }); + addedRow.addClass('cat-frequency-gradient'); + } } }); // Remove "fresh" highlight after 10 seconds + // (CAT gradient is updated every 3s from updateCATui, no need to force here) setTimeout(function () { $(".fresh").removeClass("fresh"); }, 10000); @@ -1532,23 +1551,7 @@ $(function() { startRefreshTimer(); }); } // Highlight rows within ±20 kHz of specified frequency (for CAT integration) - function highlight_current_qrg(qrg) { - var table = get_dtable(); - table.rows().eq(0).each( function ( index ) { - let row = table.row( index ); - var d=row.data(); - var distance=Math.abs(parseInt(d[1])-qrg); - if (distance<=20) { - distance++; - alpha=(.5/distance); - $(row.node()).css('--bs-table-bg', 'rgba(0,0,255,' + alpha + ')'); - $(row.node()).css('--bs-table-accent-bg', 'rgba(0,0,255,' + alpha + ')'); - } else { - $(row.node()).css('--bs-table-bg', ''); - $(row.node()).css('--bs-table-accent-bg', ''); - } - }); - } + // Old highlight_current_qrg function removed - now using updateFrequencyGradientColors // Initialize DataTable var table=get_dtable(); @@ -2022,8 +2025,109 @@ $(function() { var isCatTrackingEnabled = false; // Track CAT Control button state window.isCatTrackingEnabled = isCatTrackingEnabled; // Expose to window for cat.js + var currentRadioFrequency = null; // Store current radio frequency in kHz + var lastGradientFrequency = null; // Track last frequency used for gradient update - // Save reference to cat.js's updateCATui if it exists + /** + * Calculate frequency gradient color based on distance from radio frequency + * @param {number} spotFreqKhz - Spot frequency in kHz + * @param {number} radioFreqKhz - Radio frequency in kHz + * @returns {string|null} - CSS background color or null if outside gradient range + */ + function getFrequencyGradientColor(spotFreqKhz, radioFreqKhz) { + if (!radioFreqKhz || !isCatTrackingEnabled) { + return null; + } + + // Determine if we're in LSB or USB mode (below/above 10 MHz) + const isLSB = radioFreqKhz < 10000; + + // Calculate frequency difference in kHz + // For LSB: lower frequencies are "closer" to tune (radio freq - spot freq) + // For USB: higher frequencies are "closer" to tune (spot freq - radio freq) + let freqDiff; + if (isLSB) { + freqDiff = Math.abs(radioFreqKhz - spotFreqKhz); + } else { + freqDiff = Math.abs(spotFreqKhz - radioFreqKhz); + } + + // Maximum gradient range: 2.5 kHz + const maxGradientKhz = 2.5; + + if (freqDiff > maxGradientKhz) { + return null; // Outside gradient range, use default color + } + + // Calculate gradient factor: 0 (perfectly tuned) to 1 (2.5 kHz away) + const gradientFactor = freqDiff / maxGradientKhz; + + // Violet color for perfectly tuned: rgb(138, 43, 226) - BlueViolet + // Fade to transparent as we move away + const alpha = 1 - gradientFactor; // 1 at perfect tune, 0 at 2.5 kHz + const intensity = 0.3 + (alpha * 0.4); // Range from 0.3 to 0.7 alpha + + return `rgba(138, 43, 226, ${intensity})`; + } + + /** + * Update frequency gradient colors for all visible table rows + * Called when radio frequency changes + */ + function updateFrequencyGradientColors(forceUpdate = false) { + if (!isCatTrackingEnabled || !currentRadioFrequency) { + return; + } + + // Skip update if frequency hasn't changed significantly (unless forced) + // Only update if frequency changed by more than 500 Hz to reduce flickering + if (!forceUpdate && lastGradientFrequency !== null) { + const freqDiff = Math.abs(currentRadioFrequency - lastGradientFrequency); + if (freqDiff < 0.5) { // 500 Hz threshold + return; + } + } + + lastGradientFrequency = currentRadioFrequency; + var table = get_dtable(); + let coloredCount = 0; + + // Iterate through all visible rows + table.rows({ search: 'applied' }).every(function() { + const row = this.node(); + const rowData = this.data(); + + if (!rowData || !rowData[2]) return; + + // Get spot frequency (column 2, in MHz) + const spotFreqMhz = parseFloat(rowData[2]); + const spotFreqKhz = spotFreqMhz * 1000; + + // Calculate gradient color + const gradientColor = getFrequencyGradientColor(spotFreqKhz, currentRadioFrequency); + + // Store gradient data for persistence + $(row).attr('data-spot-frequency', spotFreqKhz); + + if (gradientColor) { + coloredCount++; + // Store and apply gradient color directly to override Bootstrap striping + $(row).attr('data-gradient-color', gradientColor); + // Use setProperty with 'important' priority to force override .fresh, .spot-expiring, etc. + row.style.setProperty('--bs-table-bg', gradientColor, 'important'); + row.style.setProperty('--bs-table-accent-bg', gradientColor, 'important'); + row.style.setProperty('background-color', gradientColor, 'important'); + $(row).addClass('cat-frequency-gradient'); + } else { + // Remove gradient styling if outside range + $(row).removeAttr('data-gradient-color'); + $(row).removeClass('cat-frequency-gradient'); + row.style.removeProperty('--bs-table-bg'); + row.style.removeProperty('--bs-table-accent-bg'); + row.style.removeProperty('background-color'); + } + }); +} // Save reference to cat.js's updateCATui if it exists var catJsUpdateCATui = window.updateCATui; // Override updateCATui to add bandmap-specific behavior @@ -2034,6 +2138,9 @@ $(function() { console.log('Bandmap CAT Update - Frequency:', data.frequency, 'Band:', band, 'Control enabled:', isCatTrackingEnabled); + // Store current radio frequency (convert Hz to kHz) + currentRadioFrequency = data.frequency / 1000; + // Bandmap-specific: Update band filter if CAT Control is enabled if (isCatTrackingEnabled) { const currentBands = $("#band").val() || []; @@ -2067,12 +2174,10 @@ $(function() { } } } - } - // Bandmap-specific: Highlight current QRG in the spot list - if (data.frequency) { - highlight_current_qrg((parseInt(data.frequency))/1000); - } + // Update frequency gradient colors for all visible rows + updateFrequencyGradientColors(); + } // Call cat.js's original updateCATui for standard CAT UI updates if (typeof catJsUpdateCATui === 'function') { @@ -2098,7 +2203,13 @@ $(function() { let isFullscreen = false; - $('#fullscreenToggle').on('click', function() { + // Handle clicks on both the button and wrapper + $('#fullscreenToggle, #fullscreenToggleWrapper').on('click', function(e) { + // Prevent double firing if clicking directly on button + if (e.target.id === 'fullscreenToggle' && this.id === 'fullscreenToggleWrapper') { + return; + } + const container = $('#bandmapContainer'); const icon = $('#fullscreenIcon'); @@ -2106,7 +2217,7 @@ $(function() { container.addClass('bandmap-fullscreen'); $('body').addClass('fullscreen-active'); icon.removeClass('fa-expand').addClass('fa-compress'); - $(this).attr('title', 'Exit Fullscreen'); + $('#fullscreenToggle').attr('title', 'Exit Fullscreen'); isFullscreen = true; @@ -3001,6 +3112,87 @@ $(function() { $('.cat-control-info').remove(); } + // ======================================== + // CAT CONTROL - TABLE SORTING LOCK + // ======================================== + + /** + * Lock table sorting to frequency column only (descending) when CAT Control is active + */ + function lockTableSortingToFrequency() { + var table = get_dtable(); + + // Add class to table for CSS styling + $('.spottable').addClass('cat-sorting-locked'); + + // Force sort by frequency (column 2) descending + table.order([2, 'desc']).draw(); + + // Disable sorting on all columns + table.settings()[0].aoColumns.forEach(function(col, index) { + col.bSortable = false; + }); + + // Disable click events on all column headers + $('.spottable thead th').off('click.DT'); + + // Redraw column headers to update sort icons + table.columns.adjust(); + + console.log('Table sorting locked to Frequency (DESC) only'); + } + + /** + * Unlock table sorting when CAT Control is disabled + */ + function unlockTableSorting() { + var table = get_dtable(); + + // Remove class from table + $('.spottable').removeClass('cat-sorting-locked'); + + // Re-enable sorting on all columns that were originally sortable + // Based on columnDefs: columns 5, 6, 7, 11, 12, 13, 14 are not sortable + const nonSortableColumns = [5, 6, 7, 11, 12, 13, 14]; + + table.settings()[0].aoColumns.forEach(function(col, index) { + if (!nonSortableColumns.includes(index)) { + col.bSortable = true; + } + }); + + // Re-enable DataTables default click handlers + table.off('click.DT', 'thead th'); + + // Reset to default sort (frequency ascending) + table.order([2, 'asc']).draw(); + + // Redraw column headers to update sort icons + table.columns.adjust(); + + // Clear frequency gradient colors + clearFrequencyGradientColors(); + + console.log('Table sorting unlocked'); + } + + /** + * Clear all frequency gradient colors from table rows + */ + function clearFrequencyGradientColors() { + var table = get_dtable(); + + table.rows().every(function() { + const row = this.node(); + $(row).removeClass('cat-frequency-gradient'); + $(row).css({ + '--bs-table-bg': '', + '--bs-table-accent-bg': '', + 'background-color': '' + }); + }); + } + // Toggle CAT Control $('#toggleCatTracking').on('click', function() { let btn = $(this); @@ -3017,6 +3209,9 @@ $(function() { // Re-enable band filter controls enableBandFilterControls(); + + // Unlock table sorting + unlockTableSorting(); } else { // Enable CAT Control btn.removeClass('btn-secondary').addClass('btn-success'); @@ -3034,6 +3229,9 @@ $(function() { // Disable band filter controls disableBandFilterControls(); + // Lock table sorting to frequency only + lockTableSortingToFrequency(); + // Immediately apply current radio frequency if available if (window.lastCATData && window.lastCATData.frequency) { console.log('Applying current radio frequency:', window.lastCATData.frequency);