Highlight wanted calls. Closes #117

This commit is contained in:
Ian Renton
2026-08-15 11:43:44 +01:00
parent 2ffa4780e8
commit 3006ee38cb
16 changed files with 224 additions and 122 deletions
+35 -1
View File
@@ -181,12 +181,15 @@ tr.table-faded td span {
}
/* New spot styles */
tr.new td {
tr.row-new td {
animation: 2s linear newspotanim;
}
@keyframes newspotanim {
0% {
background-color: initial;
}
10% {
background-color: var(--bs-success-border-subtle);
}
100% {
@@ -194,6 +197,37 @@ tr.new td {
}
}
/* Wanted spot styles */
tr.row-wanted td {
animation: wantedanim 1.5s ease-in-out alternate infinite;
}
div.band-spot-wanted {
animation: wantedanim 1.5s ease-in-out alternate infinite;
}
.leaflet-marker-icon.marker-icon-wanted::after {
content: "";
width: 20px;
height: 20px;
left: 7px;
top: 13px;
background-color: #cf9e3d;
box-shadow: 0px 0px 10px 15px #cf9e3d;
position: absolute;
z-index: -1;
border-radius: 1000%;
}
@keyframes wantedanim {
0% {
background-color: #cf9e3d;
}
100% {
background-color: #e1b655;
}
}
/* TABLE */
#table-container {
+34 -12
View File
@@ -9,15 +9,17 @@ let alerts = [];
// Load alerts and populate the table. No need for QRZ/HamQTH credential headers as these won't add any useful data
// to alerts
function loadAlerts() {
$.ajax({url: '/api/v2/alerts' + buildQueryString(), dataType: 'json', success: function (jsonData) {
// Store last updated time
lastUpdateTime = moment.utc();
updateRefreshDisplay();
// Store data
alerts = jsonData;
// Update table
updateTable();
}});
$.ajax({
url: '/api/v2/alerts' + buildQueryString(), dataType: 'json', success: function (jsonData) {
// Store last updated time
lastUpdateTime = moment.utc();
updateRefreshDisplay();
// Store data
alerts = jsonData;
// Update table
updateTable();
}
});
}
// Build a query string for the API, based on the filters that the user has selected.
@@ -115,12 +117,28 @@ function addAlertRowsToTable(tbody, alerts) {
// Create row
let $tr = $('<tr>');
// Calculate some values
let wantThis = false;
if (a["dx_calls"] != null) {
for (i in a["dx_calls"]) {
if (callWanted(a["dx_calls"][i])) {
wantThis = true;
break;
}
}
}
// Apply striping to the table. We can't just use Bootstrap's table-striped class because we have all sorts of
// extra faff to deal with, like the mobile view having extra rows, and the On Now / Next 24h / Later banners
// which cause the table-striped colouring to go awry.
if (count % 2 === 1) {
// which cause the table-striped colouring to go awry. If the call is wanted, suppress the striping so the
// highlight colour looks right.
if (count % 2 === 1 && !wantThis) {
$tr.addClass("table-active");
}
// If this is a wanted call, highlight it
if (wantThis) {
$tr.addClass("row-wanted");
}
// Use local time instead of UTC?
const useLocalTime = $("#timeZone")[0].value === "local";
@@ -255,9 +273,13 @@ function addAlertRowsToTable(tbody, alerts) {
// Second row for mobile view only, containing source, ref, freqs/modes & comment
const $tr2 = $("<tr class='hidenotonmobile'>");
if (count % 2 === 1) {
if (count % 2 === 1 && !wantThis) {
$tr2.addClass("table-active");
}
if (wantThis) {
$tr2.addClass("row-wanted");
}
const $td2 = $("<td colspan='100'>");
if (showSource) {
$td2.append(`<span class='icon-wrapper'><i class='fa-solid ${sigToIcon(a["sig"], "fa-globe-africa")}'></i></span> `);
+4 -2
View File
@@ -19,7 +19,8 @@ function loadSpots() {
if (sseAbortController != null) {
sseAbortController.abort();
}
$.ajax({url: '/api/v2/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
$.ajax({
url: '/api/v2/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
// Store data
spots = jsonData;
// Update bands display
@@ -201,7 +202,8 @@ function updateBands() {
// Now each spot is tagged with how far down the div it should go, add them to the DOM.
spotList.forEach(s => {
let worked = alreadyWorked(s["dx_call"], s["band"], s["mode"]);
bandSpotsDiv.append(`<div class="band-spot" style="top: ${s['pxDownBandLabel']}px; border-top: 1px solid ${bandToColor(s['band'])}; border-left: 5px solid ${bandToColor(s['band'])}; border-bottom: 1px solid ${bandToColor(s['band'])}; border-right: 1px solid ${bandToColor(s['band'])}; text-decoration: ${worked ? 'line-through' : 'none'};"><span class="band-spot-call">${s.dx_call}${s.dx_ssid != null ? "-" + s.dx_ssid : ""}</span><span class="band-spot-info">${s.dx_call}${s.dx_ssid != null ? "-" + s.dx_ssid : ""} ${(s.freq / 1000000).toFixed(3)} ${s.mode}</span></div>`);
let wanted = callWanted(s["dx_call"]) && !worked;
bandSpotsDiv.append(`<div class="band-spot ${wanted ? "band-spot-wanted" : ""}" style="top: ${s['pxDownBandLabel']}px; border-top: 1px solid ${bandToColor(s['band'])}; border-left: 5px solid ${bandToColor(s['band'])}; border-bottom: 1px solid ${bandToColor(s['band'])}; border-right: 1px solid ${bandToColor(s['band'])}; text-decoration: ${worked ? 'line-through' : 'none'};"><span class="band-spot-call">${s.dx_call}${s.dx_ssid != null ? "-" + s.dx_ssid : ""}</span><span class="band-spot-info">${s.dx_call}${s.dx_ssid != null ? "-" + s.dx_ssid : ""} ${(s.freq / 1000000).toFixed(3)} ${s.mode}</span></div>`);
});
// Work out how tall the canvas should be. Normally this is matching the normal band column height, but if some
+16
View File
@@ -269,6 +269,22 @@ function getCredentialHeaders() {
return headers;
}
// Does the DX call match something in the user's "wanted" list?
function callWanted(call) {
let wantedString = $("#wantedCalls").val();
if (wantedString) {
let split = wantedString.split(/[ ,]+/);
for (const i in split) {
if (split[i].length > 0 && call.toUpperCase().includes(split[i].toUpperCase())) {
return true;
}
}
return false;
} else {
return false;
}
}
// Startup
$(document).ready(function () {
+34 -27
View File
@@ -46,34 +46,35 @@ function loadSpots() {
// data if they can.
$.ajax({
url: '/api/v2/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
// Store data
spots = jsonData;
// Update map
updateMap();
if ($("#showTerminator")[0].checked) {
terminator.setTime();
// Store data
spots = jsonData;
// Update map
updateMap();
if ($("#showTerminator")[0].checked) {
terminator.setTime();
}
// Check if we have any credentials to use
if (Object.keys(getCredentialHeaders()).length > 0) {
// OK, we have credentials and have loaded once without them so the user has a basic map. Now reload
// with the credentials and replace what's on the map, so we can improve the data.
$.ajax({
url: '/api/v2/spots' + buildQueryString(),
dataType: 'json',
headers: getCredentialHeaders(),
success: function (jsonData2) {
spots = jsonData2;
updateMap();
// Now start the ongoing SSE connection
startSSEConnection();
}
});
} else {
// We had no credentials with which to augment the data anyway, so just start the SSE connection
// now
startSSEConnection();
}
}
// Check if we have any credentials to use
if (Object.keys(getCredentialHeaders()).length > 0) {
// OK, we have credentials and have loaded once without them so the user has a basic map. Now reload
// with the credentials and replace what's on the map, so we can improve the data.
$.ajax({
url: '/api/v2/spots' + buildQueryString(),
dataType: 'json',
headers: getCredentialHeaders(),
success: function(jsonData2) {
spots = jsonData2;
updateMap();
// Now start the ongoing SSE connection
startSSEConnection();
}
});
} else {
// We had no credentials with which to augment the data anyway, so just start the SSE connection
// now
startSSEConnection();
}
}});
});
}
// Start the SSE connection to receive new spots as they arrive
@@ -143,6 +144,12 @@ function addSpotToMap(s) {
markersLayer.addLayer(m);
oms.addMarker(m);
// Add highlight for wanted spots
if (callWanted(s["dx_call"])) {
console.log(s["dx_call"]);
$(m._icon).addClass('marker-icon-wanted');
}
let geodesic = null;
if ($("#mapShowGeodesics")[0].checked && s["de_latitude"] != null && s["de_longitude"] != null) {
try {
+35 -21
View File
@@ -19,17 +19,19 @@ function loadSpots() {
}
// Make the new query. No credential headers on the first load to keep things quick
$.ajax({url: '/api/v2/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
// Store data
spots = jsonData;
// Update table
updateTable();
// Start SSE connection to fetch updates in the background, if we are in "run" mode
let run = $('#runButton:checked').val();
if (run) {
startSSEConnection();
$.ajax({
url: '/api/v2/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
// Store data
spots = jsonData;
// Update table
updateTable();
// Start SSE connection to fetch updates in the background, if we are in "run" mode
let run = $('#runButton:checked').val();
if (run) {
startSSEConnection();
}
}
}});
});
}
// Start an SSE connection (closing an existing one if it exists). This will then be used to add to the table on the
@@ -210,23 +212,32 @@ function createNewTableRowsForSpot(s, highlightNew) {
// Create row
let $tr = $('<tr>');
// Calculate some values
let alreadyWorkedThis = alreadyWorked(s["dx_call"], s["band"], s["mode"]);
let wantThis = callWanted(s["dx_call"]);
let qrt = s["qrt"]
// Apply striping to the table. We can't just use Bootstrap's table-striped class because we have all sorts of
// extra faff to deal with, like the mobile view having extra rows, and the On Now / Next 24h / Later banners
// which cause the table-striped colouring to go awry.
if (rowCount % 2 === 1) {
// which cause the table-striped colouring to go awry. If the call is wanted, suppress the striping so the highlight
// colour looks right.
if (rowCount % 2 === 1 && !(wantThis && !qrt && !alreadyWorkedThis)) {
$tr.addClass("table-active");
}
// Show faded out if QRT or already worked
let alreadyWorkedThis = alreadyWorked(s["dx_call"], s["band"], s["mode"]);
if (s["qrt"] === true || alreadyWorkedThis) {
if (qrt || alreadyWorkedThis) {
$tr.addClass("table-faded");
}
// If we are asked to highlight new rows (i.e. this row is being added "live" via the SSE client and not as a bulk
// reload of the whole table)
if (highlightNew) {
$tr.addClass("new");
// reload of the whole table), do so
if (highlightNew && !qrt && !alreadyWorkedThis) {
$tr.addClass("row-new");
}
// If this is a wanted call, highlight it
if (wantThis && !qrt && !alreadyWorkedThis) {
$tr.addClass("row-wanted");
}
// Format a UTC or local time for display
@@ -399,14 +410,17 @@ function createNewTableRowsForSpot(s, highlightNew) {
const $tr2 = $("<tr class='hidenotonmobile'>");
// Apply styles as per the first row
if (rowCount % 2 === 1) {
if (rowCount % 2 === 1 && !(wantThis && !qrt && !alreadyWorkedThis)) {
$tr2.addClass("table-active");
}
if (s["qrt"] === true || alreadyWorkedThis) {
if (qrt || alreadyWorkedThis) {
$tr2.addClass("table-faded");
}
if (highlightNew) {
$tr2.addClass("new");
if (highlightNew && !qrt && !alreadyWorkedThis) {
$tr2.addClass("row-new");
}
if (wantThis && !qrt && !alreadyWorkedThis) {
$tr2.addClass("row-wanted");
}
const $td2 = $("<td colspan='100'>");
+1 -1
View File
@@ -76,7 +76,7 @@
</div>
<script src="/static/js/add-spot.js?v=1786780346"></script>
<script src="/static/js/add-spot.js?v=1786790625"></script>
<script>$(document).ready(function () {
$("#nav-link-add-spot").addClass("active");
}); <!-- highlight active page in nav --></script>
+5 -3
View File
@@ -8,8 +8,7 @@
</div>
<div class="col-auto">
<div class="d-inline-flex gap-1">
{% module Template("widgets/filters_display_data_buttons.html", web_ui_options=web_ui_options,
show_data_button=web_ui_options["qrz_enabled"] or web_ui_options["hamqth_enabled"]) %}
{% module Template("widgets/filters_display_data_buttons.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
@@ -66,6 +65,9 @@
{% module Template("cards/hamqth.html", web_ui_options=web_ui_options) %}
</div>
{% end %}
<div class="col">
{% module Template("cards/wanted_calls.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
</div>
@@ -82,7 +84,7 @@
</div>
<script src="/static/js/alerts.js?v=1786780346"></script>
<script src="/static/js/alerts.js?v=1786790625"></script>
<script>$(document).ready(function () {
$("#nav-link-alerts").addClass("active");
}); <!-- highlight active page in nav --></script>
+11 -14
View File
@@ -6,8 +6,7 @@
<div class="col-auto me-auto pt-3"></div>
<div class="col-auto">
<div class="d-inline-flex gap-1">
{% module Template("widgets/filters_display_data_buttons.html", web_ui_options=web_ui_options,
show_data_button=web_ui_options["qrz_enabled"] or web_ui_options["hamqth_enabled"]) %}
{% module Template("widgets/filters_display_data_buttons.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
@@ -18,22 +17,17 @@
<div class="row row-cols-1 g-4 mb-4 row-cols-md-3">
<div class="col">
{% module Template("cards/bands.html", web_ui_options=web_ui_options) %}
<div class="mt-4"></div>
{% module Template("cards/dx_continent.html", web_ui_options=web_ui_options) %}
<div class="mt-4"></div>
{% module Template("cards/de_continent.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/sigs.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/sources.html", web_ui_options=web_ui_options) %}
</div>
</div>
<div class="row row-cols-1 row-cols-md-3 g-4">
<div class="col">
{% module Template("cards/dx_continent.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/de_continent.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
<div class="mt-4"></div>
{% module Template("cards/modes.html", web_ui_options=web_ui_options) %}
</div>
</div>
@@ -70,6 +64,9 @@
{% module Template("cards/hamqth.html", web_ui_options=web_ui_options) %}
</div>
{% end %}
<div class="col">
{% module Template("cards/wanted_calls.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
</div>
@@ -79,8 +76,8 @@
</div>
<script src="/static/js/spotsbandsandmap.js?v=1786780346"></script>
<script src="/static/js/bands.js?v=1786780346"></script>
<script src="/static/js/spotsbandsandmap.js?v=1786790625"></script>
<script src="/static/js/bands.js?v=1786790625"></script>
<script>$(document).ready(function () {
$("#nav-link-bands").addClass("active");
}); <!-- highlight active page in nav --></script>
+5 -5
View File
@@ -1,6 +1,6 @@
{% extends "skeleton.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/css/style.css?v=1786780346" type="text/css">
<link rel="stylesheet" href="/static/css/style.css?v=1786790625" type="text/css">
<link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
<link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet">
<link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet">
@@ -15,10 +15,10 @@
window.fetchEventSource = fetchEventSource;
</script>
<script src="/static/js/utils.js?v=1786780346"></script>
<script src="/static/js/ui-ham.js?v=1786780346"></script>
<script src="/static/js/geo.js?v=1786780346"></script>
<script src="/static/js/common.js?v=1786780346"></script>
<script src="/static/js/utils.js?v=1786790625"></script>
<script src="/static/js/ui-ham.js?v=1786790625"></script>
<script src="/static/js/geo.js?v=1786790625"></script>
<script src="/static/js/common.js?v=1786790625"></script>
{% end %}
{% block body %}
<div class="container">
+18
View File
@@ -0,0 +1,18 @@
<div class="card">
<div class="card-body">
<h5 class="card-title">Wanted Calls</h5>
<div class="card-text spothole-card-text">
<input type="text" class="storeable-text form-control" id="wantedCalls"
oninput="saveSettings();" style="width: 14em; display: inline-block;">
<div class="mt-1">
<small>Enter a space- and/or comma-separated list of callsigns or partial callsigns. Any DX callsigns
that
match will be highlighted.</small>
</div>
<div class="mt-3">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="location.reload();">Reload
</button>
</div>
</div>
</div>
</div>
+1 -1
View File
@@ -284,7 +284,7 @@
</div>
<script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script>
<script src="/static/js/conditions.js?v=1786780346"></script>
<script src="/static/js/conditions.js?v=1786790625"></script>
<script>$(document).ready(function () {
$("#nav-link-conditions").addClass("active");
}); <!-- highlight active page in nav --></script>
+11 -14
View File
@@ -20,8 +20,7 @@
<div class="col-auto me-auto pt-3"></div>
<div class="col-auto">
<div class="d-inline-flex gap-1">
{% module Template("widgets/filters_display_data_buttons.html", web_ui_options=web_ui_options,
show_data_button=web_ui_options["qrz_enabled"] or web_ui_options["hamqth_enabled"]) %}
{% module Template("widgets/filters_display_data_buttons.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
@@ -32,22 +31,17 @@
<div class="row row-cols-1 g-4 mb-4 row-cols-md-3">
<div class="col">
{% module Template("cards/bands.html", web_ui_options=web_ui_options) %}
<div class="mt-4"></div>
{% module Template("cards/dx_continent.html", web_ui_options=web_ui_options) %}
<div class="mt-4"></div>
{% module Template("cards/de_continent.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/sigs.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/sources.html", web_ui_options=web_ui_options) %}
</div>
</div>
<div class="row row-cols-1 row-cols-md-3 g-4">
<div class="col">
{% module Template("cards/dx_continent.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/de_continent.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
<div class="mt-4"></div>
{% module Template("cards/modes.html", web_ui_options=web_ui_options) %}
</div>
</div>
@@ -90,6 +84,9 @@
{% module Template("cards/hamqth.html", web_ui_options=web_ui_options) %}
</div>
{% end %}
<div class="col">
{% module Template("cards/wanted_calls.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
</div>
@@ -112,8 +109,8 @@
<script src="/static/vendor/js/leaflet-cqzones.js"></script>
<script src="/static/vendor/js/leaflet-workedallbritainireland.js" type="module"></script>
<script src="/static/js/spotsbandsandmap.js?v=1786780346"></script>
<script src="/static/js/map.js?v=1786780346"></script>
<script src="/static/js/spotsbandsandmap.js?v=1786790625"></script>
<script src="/static/js/map.js?v=1786790625"></script>
<script>$(document).ready(function () {
$("#nav-link-map").addClass("active");
}); <!-- highlight active page in nav --></script>
+13 -18
View File
@@ -25,8 +25,7 @@
<div class="col-md-8 text-end">
<div class="d-inline-flex gap-3">
{% module Template("widgets/search.html", web_ui_options=web_ui_options) %}
{% module Template("widgets/filters_display_data_buttons.html", web_ui_options=web_ui_options,
show_data_button=web_ui_options["qrz_enabled"] or web_ui_options["hamqth_enabled"]) %}
{% module Template("widgets/filters_display_data_buttons.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
@@ -37,22 +36,17 @@
<div class="row row-cols-1 g-4 mb-4 row-cols-md-3">
<div class="col">
{% module Template("cards/bands.html", web_ui_options=web_ui_options) %}
<div class="mt-4"></div>
{% module Template("cards/dx_continent.html", web_ui_options=web_ui_options) %}
<div class="mt-4"></div>
{% module Template("cards/de_continent.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/sigs.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/sources.html", web_ui_options=web_ui_options) %}
</div>
</div>
<div class="row row-cols-1 row-cols-md-3 g-4">
<div class="col">
{% module Template("cards/dx_continent.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/de_continent.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
<div class="mt-4"></div>
{% module Template("cards/modes.html", web_ui_options=web_ui_options) %}
</div>
</div>
@@ -65,6 +59,8 @@
<div id="display-container" class="row row-cols-1 row-cols-md-4 g-4">
<div class="col">
{% module Template("cards/time_zone.html", web_ui_options=web_ui_options) %}
<div class="mt-4"></div>
{% module Template("cards/audio.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/number_of_spots.html", web_ui_options=web_ui_options) %}
@@ -76,9 +72,6 @@
<div class="col">
{% module Template("cards/table_columns_spots.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/audio.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
</div>
@@ -99,9 +92,11 @@
{% end %}
<div class="col">
{% module Template("cards/location.html", web_ui_options=web_ui_options) %}
<div class="mt-4"></div>
{% module Template("cards/worked_calls.html", web_ui_options=web_ui_options) %}
</div>
<div class="col">
{% module Template("cards/worked_calls.html", web_ui_options=web_ui_options) %}
{% module Template("cards/wanted_calls.html", web_ui_options=web_ui_options) %}
</div>
</div>
</div>
@@ -118,8 +113,8 @@
</div>
<script src="/static/js/spotsbandsandmap.js?v=1786780346"></script>
<script src="/static/js/spots.js?v=1786780346"></script>
<script src="/static/js/spotsbandsandmap.js?v=1786790625"></script>
<script src="/static/js/spots.js?v=1786790625"></script>
<script>$(document).ready(function () {
$("#nav-link-spots").addClass("active");
}); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -86,7 +86,7 @@
</div>
</div>
<script src="/static/js/status.js?v=1786780346"></script>
<script src="/static/js/status.js?v=1786790625"></script>
<script>
$(document).ready(function () {
$("#nav-link-status").addClass("active");
@@ -5,9 +5,7 @@
<button id="display-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button"
onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i><span
class="hideonmobile">&nbsp;Display</span></button>
{% if show_data_button %}
<button id="data-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button"
onclick="toggleDataPanel();"><i class="fa-solid fa-database"></i><span
class="hideonmobile">&nbsp;Your data</span></button>
{% end %}
</div>