Initial commit of DX Waterfall for Wavelog

This commit is contained in:
Szymon Porwolik
2025-10-10 03:02:32 +02:00
parent 4e07116115
commit a0659bb31e
11 changed files with 5216 additions and 30 deletions

View File

@@ -46,6 +46,18 @@ class Band extends CI_Controller {
$this->load->view('interface_assets/footer', $footerData);
}
// API endpoint to get band edges for the logged-in user
public function get_user_bandedges()
{
$this->load->model('bands');
$data = $this->bands->get_all_bandedges_for_user();
header('Content-Type: application/json');
echo json_encode($data);
return;
}
public function create()
{
$this->load->model('bands');

View File

@@ -88,6 +88,14 @@ class QSO extends CI_Controller {
$data['user_dok_to_qso_tab'] = 0;
}
// Get status of DX Waterfall enable option
$qkey_opt=$this->user_options_model->get_options('dxwaterfall',array('option_name'=>'enable','option_key'=>'boolean'))->result();
if (count($qkey_opt)>0) {
$data['user_dxwaterfall_enable'] = $qkey_opt[0]->option_value;
} else {
$data['user_dxwaterfall_enable'] = 0;
}
$data['qso_count'] = $this->session->userdata('qso_page_last_qso_count');
$this->load->library('form_validation');

View File

@@ -208,6 +208,7 @@ class User extends CI_Controller {
$data['user_dashboard_map'] = $this->input->post('user_dashboard_map') ?? 'Y';
$data['user_dashboard_banner'] = $this->input->post('user_dashboard_banner') ?? 'Y';
$data['user_dashboard_solar'] = $this->input->post('user_dashboard_solar') ?? 'Y';
$data['user_dxwaterfall_enable'] = $this->input->post('user_dxwaterfall_enable') ?? 'N';
$data['user_stylesheet'] = $this->input->post('user_stylesheet');
$data['user_qth_lookup'] = $this->input->post('user_qth_lookup');
$data['user_sota_lookup'] = $this->input->post('user_sota_lookup');
@@ -303,6 +304,7 @@ class User extends CI_Controller {
$this->input->post('user_dashboard_banner') ?? 'Y',
$this->input->post('user_dashboard_solar') ?? 'Y',
$this->input->post('clubstation') == '1' ? true : false,
$this->input->post('user_dxwaterfall_enable') ?? 'N',
$this->input->post('global_oqrs_text') ?? '',
$this->input->post('oqrs_grouped_search') ?? 'off',
$this->input->post('oqrs_grouped_search_show_station_name') ?? 'off',
@@ -366,6 +368,7 @@ class User extends CI_Controller {
$data['oqrs_grouped_search_show_station_name'] = $this->input->post('oqrs_grouped_search_show_station_name') ?? 'off';
$data['oqrs_auto_matching'] = $this->input->post('oqrs_auto_matching') ?? 'on';
$data['oqrs_direct_auto_matching'] = $this->input->post('oqrs_direct_auto_matching') ?? 'on';
$data['user_dxwaterfall_enable'] = $this->input->post('user_dxwaterfall_enable') ?? 'N';
$this->load->view('user/edit', $data);
$this->load->view('interface_assets/footer', $footerData);
}
@@ -733,6 +736,16 @@ class User extends CI_Controller {
}
}
// DX Waterfall enable option
if($this->input->post('user_dxwaterfall_enable')) {
$data['user_dxwaterfall_enable'] = $this->input->post('user_dxwaterfall_enable', false);
} else {
$dkey_opt=$this->user_options_model->get_options('dxwaterfall',array('option_name'=>'enable','option_key'=>'boolean'), $this->uri->segment(3))->result();
if (count($dkey_opt)>0) {
$data['user_dxwaterfall_enable'] = $dkey_opt[0]->option_value;
}
}
if($this->input->post('user_hamsat_workable_only')) {
$data['user_hamsat_workable_only'] = $this->input->post('user_hamsat_workable_only', false);
} else {

View File

@@ -224,7 +224,7 @@ class User_Model extends CI_Model {
$user_wwff_to_qso_tab, $user_pota_to_qso_tab, $user_sig_to_qso_tab, $user_dok_to_qso_tab,
$user_lotw_name, $user_lotw_password, $user_eqsl_name, $user_eqsl_password, $user_clublog_name, $user_clublog_password,
$user_winkey, $on_air_widget_enabled, $on_air_widget_display_last_seen, $on_air_widget_show_only_most_recent_radio,
$qso_widget_display_qso_time, $dashboard_banner,$dashboard_solar, $clubstation = 0) {
$qso_widget_display_qso_time, $dashboard_banner,$dashboard_solar, $clubstation = 0,$user_dxwaterfall_enable) {
// Check that the user isn't already used
if(!$this->exists($username)) {
$data = array(
@@ -313,7 +313,7 @@ class User_Model extends CI_Model {
$this->db->query("insert into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $insert_id . ", 'widget','on_air','display_last_seen','".(xss_clean($on_air_widget_display_last_seen ?? 'false'))."');");
$this->db->query("insert into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $insert_id . ", 'widget','on_air','display_only_most_recent_radio','".(xss_clean($on_air_widget_show_only_most_recent_radio ?? 'true'))."');");
$this->db->query("insert into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $insert_id . ", 'widget','qso','display_qso_time','".(xss_clean($qso_widget_display_qso_time ?? 'false'))."');");
$this->db->query("insert into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $insert_id . ", 'dxwaterfall','enable','boolean','".xss_clean($user_dxwaterfall_enable ?? 'N')."');");
return OK;
} else {
return EUSERNAMEEXISTS;
@@ -387,11 +387,13 @@ class User_Model extends CI_Model {
$this->db->query("replace into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $fields['id'] . ", 'dashboard','show_map','boolean','".xss_clean($fields['user_dashboard_map'] ?? 'Y')."');");
$this->db->query("replace into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $fields['id'] . ", 'dashboard','show_dashboard_banner','boolean','".xss_clean($fields['user_dashboard_banner'] ?? 'Y')."');");
$this->db->query("replace into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $fields['id'] . ", 'dashboard','show_dashboard_solar','boolean','".xss_clean($fields['user_dashboard_solar'] ?? 'N')."');");
$this->db->query("replace into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $fields['id'] . ", 'dxwaterfall','enable','boolean','".xss_clean($fields['user_dxwaterfall_enable'] ?? 'N')."');");
$this->session->set_userdata('dashboard_last_qso_count', $dashboard_last_qso_count);
$this->session->set_userdata('qso_page_last_qso_count', $qso_page_last_qso_count);
$this->session->set_userdata('user_dashboard_map',xss_clean($fields['user_dashboard_map'] ?? 'Y'));
$this->session->set_userdata('user_dashboard_banner',xss_clean($fields['user_dashboard_banner'] ?? 'Y'));
$this->session->set_userdata('user_dashboard_solar',xss_clean($fields['user_dashboard_solar'] ?? 'N'));
$this->session->set_userdata('user_dxwaterfall_enable',xss_clean($fields['user_dxwaterfall_enable'] ?? 'N'));
// Check to see if the user is allowed to change user levels
if($this->session->userdata('user_type') == 99) {
@@ -551,6 +553,7 @@ class User_Model extends CI_Model {
'user_dashboard_map' => ((($this->session->userdata('user_dashboard_map') ?? 'Y') == 'Y') ? $this->user_options_model->get_options('dashboard', array('option_name' => 'show_map', 'option_key' => 'boolean'))->row()->option_value ?? 'Y' : $this->session->userdata('user_dashboard_map')),
'user_dashboard_banner' => ((($this->session->userdata('user_dashboard_banner') ?? 'Y') == 'Y') ? $this->user_options_model->get_options('dashboard', array('option_name' => 'show_dashboard_banner', 'option_key' => 'boolean'))->row()->option_value ?? 'Y' : $this->session->userdata('user_dashboard_banner')),
'user_dashboard_solar' => ((($this->session->userdata('user_dashboard_solar') ?? 'N') == 'Y') ? $this->session->userdata('user_dashboard_solar') : $this->user_options_model->get_options('dashboard', array('option_name' => 'show_dashboard_solar', 'option_key' => 'boolean'))->row()->option_value ?? 'N'),
'user_dxwaterfall_enable' => ((($this->session->userdata('user_dxwaterfall_enable') ?? 'N') == 'Y') ? $this->session->userdata('user_dxwaterfall_enable') : $this->user_options_model->get_options('dxwaterfall', array('option_name' => 'enable', 'option_key' => 'boolean'))->row()->option_value ?? 'N'),
'user_date_format' => $u->row()->user_date_format,
'user_stylesheet' => $u->row()->user_stylesheet,
'user_qth_lookup' => isset($u->row()->user_qth_lookup) ? $u->row()->user_qth_lookup : 0,

View File

@@ -69,6 +69,47 @@
var lang_notes_duplication_disabled_short = "<?= __("Duplication Disabled"); ?>";
var lang_notes_not_found = "<?= __("No notes were found"); ?>";
/*
DX Waterfall Language
*/
var lang_dxwaterfall_tune_to_spot = "<?= __("Tune to spot frequency [Ctrl+Shift+Space]"); ?>";
var lang_dxwaterfall_cycle_through = "<?= __("Cycle through"); ?>";
var lang_dxwaterfall_spots_currently_showing = "<?= __("spots (currently showing"); ?>";
var lang_dxwaterfall_log_qso_with = "<?= __("Log QSO with"); ?>";
var lang_dxwaterfall_new_continent = "<?= __("New Continent"); ?>";
var lang_dxwaterfall_new_dxcc = "<?= __("New DXCC"); ?>";
var lang_dxwaterfall_new_callsign = "<?= __("New Callsign"); ?>";
var lang_dxwaterfall_previous_spot = "<?= __("Previous spot [Ctrl+Left] | First spot [Ctrl+Down]"); ?>";
var lang_dxwaterfall_no_spots_lower = "<?= __("No spots at lower frequency"); ?>";
var lang_dxwaterfall_next_spot = "<?= __("Next spot [Ctrl+Right] | Last spot [Ctrl+Up]"); ?>";
var lang_dxwaterfall_no_spots_higher = "<?= __("No spots at higher frequency"); ?>";
var lang_dxwaterfall_no_spots_available = "<?= __("No spots available"); ?>";
var lang_dxwaterfall_cycle_unworked = "<?= __("Cycle through unworked continents/DXCC"); ?>";
var lang_dxwaterfall_dx_hunter = "<?= __("DX Hunter"); ?>";
var lang_dxwaterfall_no_unworked = "<?= __("No unworked continents/DXCC on this band"); ?>";
var lang_dxwaterfall_fetching_spots = "<?= __("Fetching spots..."); ?>";
var lang_dxwaterfall_click_to_cycle = "<?= __("Click to cycle or wait 1.5s to apply"); ?>";
var lang_dxwaterfall_change_continent = "<?= __("Change spotter continent"); ?>";
var lang_dxwaterfall_filter_by_mode = "<?= __("Filter by mode"); ?>";
var lang_dxwaterfall_toggle_phone = "<?= __("Toggle Phone mode filter"); ?>";
var lang_dxwaterfall_phone = "<?= __("Phone"); ?>";
var lang_dxwaterfall_toggle_cw = "<?= __("Toggle CW mode filter"); ?>";
var lang_dxwaterfall_cw = "<?= __("CW"); ?>";
var lang_dxwaterfall_toggle_digi = "<?= __("Toggle Digital mode filter"); ?>";
var lang_dxwaterfall_digi = "<?= __("Digi"); ?>";
var lang_dxwaterfall_zoom_out = "<?= __("Zoom out [Ctrl+-]"); ?>";
var lang_dxwaterfall_reset_zoom = "<?= __("Reset zoom to default (3)"); ?>";
var lang_dxwaterfall_zoom_in = "<?= __("Zoom in [Ctrl++]"); ?>";
var lang_dxwaterfall_waiting_data = "<?= __("Waiting for DX Cluster data..."); ?>";
var lang_dxwaterfall_comment = "<?= __("Comment: "); ?>";
var lang_dxwaterfall_modes_label = "<?= __("modes:"); ?>";
var lang_dxwaterfall_out_of_bandplan = "<?= __("OUT OF BANDPLAN"); ?>";
var lang_dxwaterfall_changing_frequency = "<?= __("Changing radio frequency..."); ?>";
var lang_dxwaterfall_spots_fetched = "<?= __("spot(s) de"); ?>";
var lang_dxwaterfall_fetched_for_band = "<?= __("fetched from DXCluster for band"); ?>";
var lang_dxwaterfall_displaying = "<?= __(", displaying"); ?>";
var lang_dxwaterfall_invalid = "<?= __("INVALID"); ?>";
</script>
<!-- General JS Files used across Wavelog -->
@@ -1096,6 +1137,9 @@ $($('#callsign')).on('keypress',function(e) {
<?php if ($this->uri->segment(1) == "qso") { ?>
<script src="<?php echo base_url() ;?>assets/js/sections/qso.js"></script>
<?php if ($this->session->userdata('user_dxwaterfall_enable') == 'Y') { ?>
<script src="<?php echo base_url() ;?>assets/js/dxwaterfall.js"></script>
<?php } ?>
<script src="<?php echo base_url() ;?>assets/js/sections/satellite_functions.js"></script>
<script src="<?php echo base_url() ;?>assets/js/bootstrap-multiselect.js"></script>
<?php if ($this->session->userdata('isWinkeyEnabled')) { ?>
@@ -1366,6 +1410,9 @@ mymap.on('mousemove', onQsoMapMove);
<script>
$( document ).ready(function() {
// Javascript for controlling rig frequency.
var updateFromCAT_lock =0; // This mechanism prevents multiple simultaneous calls to query the CAT interface information
var dxwaterfall_cat_debounce_lock = 0; // Lock to prevent CAT frequency updates when sending commands from waterfall
window.dxwaterfall_cat_debounce_lock = dxwaterfall_cat_debounce_lock; // Make it global for waterfall access
var updateFromCAT = function() {
var cat2UI = function(ui, cat, allow_empty, allow_zero, callback_on_update) {
// Check, if cat-data is available
@@ -1385,8 +1432,11 @@ mymap.on('mousemove', onQsoMapMove);
}
if($('select.radios option:selected').val() != '0') {
radioID = $('select.radios option:selected').val();
if ((typeof radioID !== 'undefined') && (radioID !== null) && (radioID !== "")) {
if ((typeof radioID !== 'undefined') && (radioID !== null) && (radioID !== "") && (updateFromCAT_lock == 0)) {
updateFromCAT_lock = 1;
// Debug: Log that we're polling CAT
$.getJSON( "radio/json/" + radioID, function( data ) {
/* {
"frequency": "2400210000",
@@ -1410,31 +1460,79 @@ mymap.on('mousemove', onQsoMapMove);
if($('.radio_login_error').length != 0) {
$(".radio_login_error" ).remove();
}
cat2UI($('#frequency'),data.frequency,false,true,function(d){
$('#frequency').trigger('change');
if ($("#band").val() != frequencyToBand(d)) {
$("#band").val(frequencyToBand(d)).trigger('change'); // Let's only change if we really have a different band!
}
});
cat2UI($('#frequency_rx'),data.frequency_rx,false,true,function(d){$("#band_rx").val(frequencyToBand(d))});
cat2UI($('.mode'),data.mode,false,false,function(d){setRst($(".mode").val())});
cat2UI($('#sat_name'),data.satname,false,false);
cat2UI($('#sat_mode'),data.satmode,false,false);
cat2UI($('#transmit_power'),data.power,false,false);
cat2UI($('#selectPropagation'),data.prop_mode,false,false);
// Display CAT Timeout warning based on the figure given in the config file
var minutes = Math.floor(<?php echo $this->optionslib->get_option('cat_timeout_interval'); ?> / 60);
if(data.updated_minutes_ago > minutes) {
dxwaterfall_allowcat = false;
$(".radio_cat_state" ).remove();
if($('.radio_timeout_error').length == 0) {
$('#radio_status').prepend('<div class="alert alert-danger radio_timeout_error" role="alert"><i class="fas fa-broadcast-tower"></i> Radio connection timed-out: ' + $('select.radios option:selected').text() + ' data is ' + data.updated_minutes_ago + ' minutes old.</div>');
} else {
$('.radio_timeout_error').html('Radio connection timed-out: ' + $('select.radios option:selected').text() + ' data is ' + data.updated_minutes_ago + ' minutes old.');
}
dxwaterfall_caturl = "";
} else {
// Handle frequency updates with waterfall debounce lock
// Sync local variable with global one
dxwaterfall_cat_debounce_lock = window.dxwaterfall_cat_debounce_lock || 0;
if (dxwaterfall_cat_debounce_lock == 0) {
// Normal case - no lock, update frequency as usual
cat2UI($('#frequency'),data.frequency,false,true,function(d){
$('#frequency').trigger('change');
if ($("#band").val() != frequencyToBand(d)) {
$("#band").val(frequencyToBand(d)).trigger('change'); // Let's only change if we really have a different band!
}
});
} else {
// Locked - check if radio confirms our sent frequency
if (typeof window.dxwaterfall_expected_frequency !== 'undefined' && window.dxwaterfall_expected_frequency) {
var expectedFreq = parseFloat(window.dxwaterfall_expected_frequency);
var actualFreq = parseFloat(data.frequency);
var tolerance = 1000; // 1000 Hz tolerance (1 kHz)
var diff = Math.abs(expectedFreq - actualFreq);
if (diff <= tolerance) {
// Radio confirmed the frequency change - unlock and update
dxwaterfall_cat_debounce_lock = 0;
window.dxwaterfall_cat_debounce_lock = 0;
window.dxwaterfall_expected_frequency = null;
cat2UI($('#frequency'),data.frequency,false,true,function(d){
$('#frequency').trigger('change');
if ($("#band").val() != frequencyToBand(d)) {
$("#band").val(frequencyToBand(d)).trigger('change');
}
// Invalidate waterfall frequency cache to force update from new CAT frequency
if (typeof dxWaterfall !== 'undefined' && dxWaterfall.invalidateFrequencyCache) {
dxWaterfall.invalidateFrequencyCache();
}
// Update waterfall cached frequency after CAT frequency change
if (typeof dxWaterfall !== 'undefined' && dxWaterfall.commitFrequency) {
dxWaterfall.commitFrequency();
}
});
}
// If frequency doesn't match, stay locked and don't update
} else {
}
}
cat2UI($('#frequency_rx'),data.frequency_rx,false,true,function(d){$("#band_rx").val(frequencyToBand(d))});
cat2UI($('.mode'),data.mode,false,false,function(d){setRst($(".mode").val())});
cat2UI($('#sat_name'),data.satname,false,false);
cat2UI($('#sat_mode'),data.satmode,false,false);
cat2UI($('#transmit_power'),data.power,false,false);
cat2UI($('#selectPropagation'),data.prop_mode,false,false);
dxwaterfall_allowcat = true;
dxwaterfall_caturl=data.cat_url;
$(".radio_timeout_error" ).remove();
separator = '<span style="margin-left:10px"></span>';
text = '<i class="fas fa-broadcast-tower"></i>' + separator + '<b>TX:</b> ' + data.frequency_formatted;
@@ -1465,13 +1563,19 @@ mymap.on('mousemove', onQsoMapMove);
}
}
}
updateFromCAT_lock = 0;
});
}
}
};
// Update frequency every three second
setInterval(updateFromCAT, 3000);
// Update frequency from CAT
// Add a small delay (500ms) before first poll to allow radio interface to initialize
setTimeout(function() {
updateFromCAT();
// Then poll every 500ms
setInterval(updateFromCAT, 500 * 1); // Note: this is minimum update intervals, there is a lock mechanism to prevent overlapping calls.
}, 500);
// If a radios selected from drop down select radio update.
$('.radios').change(updateFromCAT);

View File

@@ -40,6 +40,152 @@ switch ($date_format) {
var user_date_format = "<?php echo $date_format; ?>"; // Pass the user's date format to JavaScript
</script>
<!--- DX Waterfall --->
<?php if ($this->session->userdata('user_dxwaterfall_enable') == 'Y' && $manual_mode == 0) { ?>
<script language="javascript">
let dxwaterfall_decont = '<?php echo $this->optionslib->get_option('dxcluster_decont'); ?>';
let dxwaterfall_maxage = '<?php echo $this->optionslib->get_option('dxcluster_maxage'); ?>';
let dxwaterfall_allowcat = 0;
let dxwaterfall_caturl = "";
</script>
<style>
#dxWaterfall {
width: 100%;
height: 200px;
display: block;
border-left: 1px solid #000000;
border-right: 1px solid #000000;
}
#dxWaterfallSpot {
width: 100%;
background-color: #000000;
color: #FFFFFF;
padding: 5px;
font-family: "Consolas", "Courier New", monospace;
font-size: 11px;
border-left: 1px solid #000000;
border-right: 1px solid #000000;
border-top: 1px solid #000000;
min-height: 20px;
line-height: 1.4;
word-wrap: break-word;
overflow-wrap: break-word;
}
#dxWaterfallMenu {
width: 100%;
background-color: #000000;
color: #FFFFFF;
padding: 5px;
font-family: "Consolas", "Courier New", monospace;
font-size: 11px;
border-left: 1px solid #000000;
border-right: 1px solid #000000;
border-bottom: 1px solid #000000;
min-height: 20px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
}
#dxWaterfallMenu .zoom-in-icon,
#dxWaterfallMenu .zoom-out-icon,
#dxWaterfallMenu .zoom-reset-icon,
#dxWaterfallMenu .prev-spot-icon,
#dxWaterfallMenu .next-spot-icon,
#dxWaterfallMenu .smart-hunter-icon,
#dxWaterfallMenu .continent-cycle-icon,
#dxWaterfallMenu .mode-filter-icon {
cursor: pointer;
color: #FFFFFF;
margin: 0 2px;
font-size: 12px;
transition: color 0.2s;
}
#dxWaterfallMenu .zoom-in-icon:hover:not(.disabled),
#dxWaterfallMenu .zoom-out-icon:hover:not(.disabled),
#dxWaterfallMenu .zoom-reset-icon:hover:not(.disabled),
#dxWaterfallMenu .prev-spot-icon:hover:not(.disabled),
#dxWaterfallMenu .next-spot-icon:hover:not(.disabled),
#dxWaterfallMenu .smart-hunter-icon:hover:not(.disabled),
#dxWaterfallMenu .smart-hunter-text:hover:not(.disabled),
#dxWaterfallMenu .continent-cycle-icon:hover:not(.disabled),
#dxWaterfallMenu .continent-cycle-text:hover:not(.disabled),
#dxWaterfallMenu .mode-filter-icon:hover:not(.disabled) {
color: #AAAAAA;
}
#dxWaterfallMenu .smart-hunter-text,
#dxWaterfallMenu .continent-cycle-text {
cursor: pointer;
color: #FFFFFF;
margin-left: 5px;
font-size: 11px;
transition: color 0.2s;
user-select: none;
}
#dxWaterfallMenu .mode-filter-phone,
#dxWaterfallMenu .mode-filter-cw,
#dxWaterfallMenu .mode-filter-digi {
user-select: none;
}
#dxWaterfallMenu .mode-filter-phone:hover:not(.disabled),
#dxWaterfallMenu .mode-filter-cw:hover:not(.disabled),
#dxWaterfallMenu .mode-filter-digi:hover:not(.disabled) {
opacity: 0.7;
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0.3; }
}
#dxWaterfallSpot .flag-emoji {
font-family: "Twemoji Country Flags", "Helvetica", "Comic Sans", sans-serif !important;
font-style: normal !important;
font-variant: normal !important;
}
#dxWaterfallSpot .copy-icon,
#dxWaterfallSpot .tune-icon,
#dxWaterfallSpot .cycle-spot-icon {
cursor: pointer;
color: #FFFFFF;
margin-right: 5px;
font-size: 12px;
transition: color 0.2s;
}
#dxWaterfallSpot .copy-icon:hover,
#dxWaterfallSpot .tune-icon:hover,
#dxWaterfallSpot .cycle-spot-icon:hover {
color: #AAAAAA;
}
#dxWaterfallSpot .new-continent-icon {
color: #FFD700; /* Gold */
margin-left: 5px;
font-size: 12px;
}
#dxWaterfallSpot .new-dxcc-icon {
color: #C0C0C0; /* Silver */
margin-left: 5px;
font-size: 12px;
}
#dxWaterfallSpot .new-callsign-icon {
color: #CD7F32; /* Bronze */
margin-left: 5px;
font-size: 12px;
}
.dxwaterfallpane{
margin-bottom: 2px;
}
</style>
<div class="row dxwaterfallpane">
<div class="col-sm-12"><div id="dxWaterfallSpot">&nbsp;</div></div>
<div class="col-sm-12">
<canvas id="dxWaterfall"></canvas>
</div>
<div class="col-sm-12"><div id="dxWaterfallMenu">&nbsp;</div></div>
</div>
<?php } ?>
<div class="row qsopane">
<div class="col-sm-5">
@@ -818,7 +964,7 @@ switch ($date_format) {
<?php
$result = $this->optionslib->get_option('disable_refresh_past_contacts');
if($result === null) { ?>
<div id="qso-last-table" hx-get="<?php echo site_url('/qso/component_past_contacts'); ?>" hx-trigger="load, qso_event, every 5s">
<div id="qso-last-table" hx-get="<?php echo site_url('/qso/component_past_contacts'); ?>" hx-trigger="load, qso_event, every 15s">
<?php } else { ?>
<div id="qso-last-table" hx-get="<?php echo site_url('/qso/component_past_contacts'); ?>" hx-trigger="load, qso_event">
<?php } ?>

View File

@@ -430,6 +430,16 @@
} ?>
</select>
</div>
<!--- DX Waterfall -->
<div class="mb-3">
<label for="user_dxwaterfall_enable"><?= __("DX Waterfall"); ?></label>
<?php if(!isset($user_dxwaterfall_enable)) { $user_dxwaterfall_enable='N'; }?>
<select class="form-select" id="user_dxwaterfall_enable" name="user_dxwaterfall_enable" aria-describedby="user_dxwaterfall_enable_Help" required>
<option value='Y' <?php if($user_dxwaterfall_enable == "Y") { echo "selected=\"selected\""; } ?>><?= __("Enabled"); ?></option>
<option value='N' <?php if($user_dxwaterfall_enable == "N") { echo "selected=\"selected\""; } ?>><?= __("Disabled"); ?></option>
</select>
<small id="user_dxwaterfall_enable_Help" class="form-text text-muted"><?= __("Show an interactive DX Cluster 'Waterfall' on the QSO logging page."); ?></small>
</div>
</div>
</div>
</div>

4558
assets/js/dxwaterfall.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -61,8 +61,31 @@ async function set_qrg() {
}
async function set_new_qrg() {
let new_qrg = $('#freq_calculated').val().trim();
let new_qrg = $('#freq_calculated').val();
// Trim and validate input
if (new_qrg !== null && new_qrg !== undefined) {
new_qrg = new_qrg.trim();
}
let parsed_qrg = parseFloat(new_qrg);
// If field is empty or parsing failed, fetch default frequency for current band/mode
if (!new_qrg || new_qrg === '' || isNaN(parsed_qrg) || !isFinite(parsed_qrg) || parsed_qrg <= 0) {
if (typeof base_url !== 'undefined') {
try {
const result = await $.get(base_url + 'index.php/qso/band_to_freq/' + $('#band').val() + '/' + $('.mode').val());
$('#frequency').val(result);
await set_qrg();
return;
} catch (error) {
console.error('Failed to fetch default frequency:', error);
return;
}
}
return;
}
let unit = $('#qrg_unit').html();
// check if the input contains a unit and parse the qrg
@@ -131,6 +154,14 @@ $('#freq_calculated').on('change', function () {
$('#freq_calculated').on('keydown', function (e) {
if (e.which === 13) {
// Check if DX Waterfall is active - if so, let it handle Enter key
if (typeof dxWaterfall !== 'undefined' && $('#dxWaterfall').length > 0) {
// DX Waterfall is active, don't handle Enter here
// Let dxwaterfall.js handle it instead
return;
}
// DX Waterfall not active, use normal behavior
e.preventDefault();
if ($('#callsign').val() != '') {
set_new_qrg().then(() => {
@@ -140,4 +171,4 @@ $('#freq_calculated').on('keydown', function (e) {
set_new_qrg();
}
}
});
});

View File

@@ -399,7 +399,7 @@ function parseUserDate(user_provided_date) { // creates JS-Date out of user-prov
month = parseInt(parts[1], 10) - 1;
year = parseInt(parts[2], 10);
}
if (isNaN(day) || day < 1 || day > 31 || isNaN(month) || month < 0 || month > 11 || isNaN(year)) return null;
if (isNaN(day) || day < 1 || day > 31 || isNaN(month) || month < 0 || month > 11 || isNaN(year)) return null;
return new Date(year, month, day);
}
@@ -1795,9 +1795,12 @@ $('#start_date').on('change', function () {
/* on mode change */
$('.mode').on('change', function () {
if ($('#radio').val() == 0 && $('#sat_name').val() == '') {
$.get(base_url + 'index.php/qso/band_to_freq/' + $('#band').val() + '/' + $('.mode').val(), function (result) {
$('#frequency').val(result).trigger("change");
});
// Only fetch default frequency if frequency field is empty
if ($('#frequency').val() == '' || $('#frequency').val() == null) {
$.get(base_url + 'index.php/qso/band_to_freq/' + $('#band').val() + '/' + $('.mode').val(), function (result) {
$('#frequency').val(result).trigger("change");
});
}
$('#frequency_rx').val("");
}
$("#callsign").blur();
@@ -1806,17 +1809,54 @@ $('.mode').on('change', function () {
/* Calculate Frequency */
/* on band change */
$('#band').on('change', function () {
if ($('#radio').val() == 0) {
$.get(base_url + 'index.php/qso/band_to_freq/' + $(this).val() + '/' + $('.mode').val(), function (result) {
$('#frequency').val(result).trigger("change");
});
const selectedBand = $(this).val();
// Clear the QSO form when band is manually changed
$('#btn_reset').click();
// Set flag to prevent waterfall from auto-reverting the band change
if (typeof dxWaterfall !== 'undefined') {
dxWaterfall.userChangedBand = true;
if (dxWaterfall.userChangedBandTimer) {
clearTimeout(dxWaterfall.userChangedBandTimer);
}
dxWaterfall.userChangedBandTimer = setTimeout(function() {
dxWaterfall.userChangedBand = false;
}, 10000);
// Reset operation timer when band is manually changed
dxWaterfall.operationStartTime = Date.now();
}
// Always fetch default frequency for the new band when band is changed manually
// This ensures frequency field matches the selected band
$.get(base_url + 'index.php/qso/band_to_freq/' + selectedBand + '/' + $('.mode').val(), function (result) {
// Set the frequency
$('#frequency').val(result);
// Update the displayed frequency field with proper units
set_qrg();
// If DX Waterfall is active and CAT is available, tune the radio
if (typeof dxWaterfall !== 'undefined' && $('#dxWaterfall').length > 0) {
// Check if CAT is available (same check as in dxwaterfall.js)
const catAvailable = (typeof dxwaterfall_allowcat !== 'undefined' && dxwaterfall_allowcat !== null &&
typeof dxwaterfall_caturl !== 'undefined' && dxwaterfall_caturl !== null &&
dxwaterfall_allowcat && dxwaterfall_caturl !== "");
if (catAvailable && typeof setFrequency === 'function') {
// Convert Hz to kHz and tune the radio
const freqKHz = result / 1000;
setFrequency(freqKHz);
}
}
});
$('#frequency_rx').val("");
$('#band_rx').val("");
$("#selectPropagation").val("");
$("#sat_name").val("");
$("#sat_mode").val("");
set_qrg();
$("#callsign").blur();
stop_az_ele_ticker();
});

View File

@@ -0,0 +1,261 @@
{
"version": "2025-10-19",
"R1": {
"2200m": {
"start_hz": 135700,
"end_hz": 137800
},
"630m": {
"start_hz": 472000,
"end_hz": 479000
},
"160m": {
"start_hz": 1810000,
"end_hz": 2000000
},
"80m": {
"start_hz": 3500000,
"end_hz": 3800000
},
"60m": {
"start_hz": 5351500,
"end_hz": 5366500
},
"40m": {
"start_hz": 7000000,
"end_hz": 7200000
},
"30m": {
"start_hz": 10100000,
"end_hz": 10150000
},
"20m": {
"start_hz": 14000000,
"end_hz": 14350000
},
"17m": {
"start_hz": 18068000,
"end_hz": 18168000
},
"15m": {
"start_hz": 21000000,
"end_hz": 21450000
},
"12m": {
"start_hz": 24890000,
"end_hz": 24990000
},
"10m": {
"start_hz": 28000000,
"end_hz": 29700000
},
"70cm": {
"start_hz": 430000000,
"end_hz": 440000000
},
"23cm": {
"start_hz": 1240000000,
"end_hz": 1300000000
},
"13cm": {
"start_hz": 2300000000,
"end_hz": 2450000000
},
"9cm": {
"start_hz": 3400000000,
"end_hz": 3475000000
},
"6cm": {
"start_hz": 5650000000,
"end_hz": 5850000000
},
"3cm": {
"start_hz": 10000000000,
"end_hz": 10500000000
},
"1.2cm": {
"start_hz": 24000000000,
"end_hz": 24250000000
}
},
"R2": {
"2200m": {
"start_hz": 135700,
"end_hz": 137800
},
"630m": {
"start_hz": 472000,
"end_hz": 479000
},
"160m": {
"start_hz": 1800000,
"end_hz": 2000000
},
"80m": {
"start_hz": 3500000,
"end_hz": 4000000
},
"60m": {
"start_hz": 5351500,
"end_hz": 5366500
},
"40m": {
"start_hz": 7000000,
"end_hz": 7300000
},
"30m": {
"start_hz": 10100000,
"end_hz": 10150000
},
"20m": {
"start_hz": 14000000,
"end_hz": 14350000
},
"17m": {
"start_hz": 18068000,
"end_hz": 18168000
},
"15m": {
"start_hz": 21000000,
"end_hz": 21450000
},
"12m": {
"start_hz": 24890000,
"end_hz": 24990000
},
"10m": {
"start_hz": 28000000,
"end_hz": 29700000
},
"6m": {
"start_hz": 50000000,
"end_hz": 54000000
},
"2m": {
"start_hz": 144000000,
"end_hz": 148000000
},
"1.25m": {
"start_hz": 220000000,
"end_hz": 225000000
},
"70cm": {
"start_hz": 420000000,
"end_hz": 450000000
},
"33cm": {
"start_hz": 902000000,
"end_hz": 928000000
},
"23cm": {
"start_hz": 1240000000,
"end_hz": 1294000000
},
"13cm": {
"start_hz": 2303000000,
"end_hz": 2450000000
},
"9cm": {
"start_hz": 3300000000,
"end_hz": 3500000000
},
"6cm": {
"start_hz": 5650000000,
"end_hz": 5850000000
},
"3cm": {
"start_hz": 10000000000,
"end_hz": 10500000000
},
"1.2cm": {
"start_hz": 24000000000,
"end_hz": 24250000000
}
},
"R3": {
"2200m": {
"start_hz": 135700,
"end_hz": 137800
},
"630m": {
"start_hz": 472000,
"end_hz": 479000
},
"160m": {
"start_hz": 1800000,
"end_hz": 2000000
},
"80m": {
"start_hz": 3500000,
"end_hz": 3900000
},
"60m": {
"start_hz": 5351500,
"end_hz": 5366500
},
"40m": {
"start_hz": 7000000,
"end_hz": 7200000
},
"30m": {
"start_hz": 10110000,
"end_hz": 10150000
},
"20m": {
"start_hz": 14000000,
"end_hz": 14350000
},
"17m": {
"start_hz": 18068000,
"end_hz": 18168000
},
"15m": {
"start_hz": 21000000,
"end_hz": 21125000
},
"12m": {
"start_hz": 24890000,
"end_hz": 24990000
},
"10m": {
"start_hz": 28000000,
"end_hz": 29700000
},
"6m": {
"start_hz": 50000000,
"end_hz": 54000000
},
"2m": {
"start_hz": 144000000,
"end_hz": 148000000
},
"70cm": {
"start_hz": 430000000,
"end_hz": 440000000
},
"23cm": {
"start_hz": 1240000000,
"end_hz": 1300000000
},
"13cm": {
"start_hz": 2300000000,
"end_hz": 2450000000
},
"9cm": {
"start_hz": 3300000000,
"end_hz": 3500000000
},
"6cm": {
"start_hz": 5650000000,
"end_hz": 5850000000
},
"3cm": {
"start_hz": 10000000000,
"end_hz": 10500000000
},
"1.2cm": {
"start_hz": 24000000000,
"end_hz": 24250000000
}
}
}