Add ability to tag callsigns as worked. Closes #41

This commit is contained in:
Ian Renton
2026-01-31 09:34:37 +00:00
parent 47b4ddb5c8
commit 7b409bcb67
13 changed files with 96 additions and 24 deletions

View File

@@ -66,7 +66,7 @@
<p>This software is dedicated to the memory of Tom G1PJB, SK, a friend and colleague who sadly passed away around the time I started writing it in Autumn 2025. I was looking forward to showing it to you when it was done.</p> <p>This software is dedicated to the memory of Tom G1PJB, SK, a friend and colleague who sadly passed away around the time I started writing it in Autumn 2025. I was looking forward to showing it to you when it was done.</p>
</div> </div>
<script src="/js/common.js?v=7"></script> <script src="/js/common.js?v=8"></script>
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script> <script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %} {% end %}

View File

@@ -69,8 +69,8 @@
</div> </div>
<script src="/js/common.js?v=7"></script> <script src="/js/common.js?v=8"></script>
<script src="/js/add-spot.js?v=7"></script> <script src="/js/add-spot.js?v=8"></script>
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script> <script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %} {% end %}

View File

@@ -56,8 +56,8 @@
</div> </div>
<script src="/js/common.js?v=7"></script> <script src="/js/common.js?v=8"></script>
<script src="/js/alerts.js?v=7"></script> <script src="/js/alerts.js?v=8"></script>
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script> <script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %} {% end %}

View File

@@ -62,9 +62,9 @@
<script> <script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %}; let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script> </script>
<script src="/js/common.js?v=7"></script> <script src="/js/common.js?v=8"></script>
<script src="/js/spotsbandsandmap.js?v=7"></script> <script src="/js/spotsbandsandmap.js?v=8"></script>
<script src="/js/bands.js?v=7"></script> <script src="/js/bands.js?v=8"></script>
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script> <script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %} {% end %}

View File

@@ -46,10 +46,10 @@
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/tinycolor2@1.6.0/cjs/tinycolor.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/tinycolor2@1.6.0/cjs/tinycolor.min.js"></script>
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=7"></script> <script src="https://misc.ianrenton.com/jsutils/utils.js?v=8"></script>
<script src="https://misc.ianrenton.com/jsutils/storage.js?v=7"></script> <script src="https://misc.ianrenton.com/jsutils/storage.js?v=8"></script>
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=7"></script> <script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=8"></script>
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=7"></script> <script src="https://misc.ianrenton.com/jsutils/geo.js?v=8"></script>
</head> </head>
<body> <body>

View File

@@ -38,6 +38,10 @@
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowDE" value="tableShowDE" oninput="columnsUpdated();" checked> <input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowDE" value="tableShowDE" oninput="columnsUpdated();" checked>
<label class="form-check-label" for="tableShowDE">DE</label> <label class="form-check-label" for="tableShowDE">DE</label>
</div> </div>
<div class="form-check form-check-inline">
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowWorkedCheckbox" value="tableShowWorkedCheckbox" oninput="columnsUpdated();" checked>
<label class="form-check-label" for="tableShowWorkedCheckbox">Worked?</label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,6 @@
<div class="card">
<div class="card-body">
<h5 class="card-title">Worked Calls</h5>
<button type="button" class="btn btn-primary" onClick="clearWorked();">Clear worked calls</button>
</div>
</div>

View File

@@ -70,9 +70,9 @@
<script> <script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %}; let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script> </script>
<script src="/js/common.js?v=7"></script> <script src="/js/common.js?v=8"></script>
<script src="/js/spotsbandsandmap.js?v=7"></script> <script src="/js/spotsbandsandmap.js?v=8"></script>
<script src="/js/map.js?v=7"></script> <script src="/js/map.js?v=8"></script>
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script> <script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %} {% end %}

View File

@@ -65,6 +65,9 @@
<div class="col"> <div class="col">
{% module Template("cards/location.html", web_ui_options=web_ui_options) %} {% module Template("cards/location.html", web_ui_options=web_ui_options) %}
</div> </div>
<div class="col">
{% module Template("cards/worked-calls.html", web_ui_options=web_ui_options) %}
</div>
<div class="col"> <div class="col">
{% module Template("cards/color-scheme-and-band-color-scheme.html", web_ui_options=web_ui_options) %} {% module Template("cards/color-scheme-and-band-color-scheme.html", web_ui_options=web_ui_options) %}
</div> </div>
@@ -84,9 +87,9 @@
<script> <script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %}; let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script> </script>
<script src="/js/common.js?v=7"></script> <script src="/js/common.js?v=8"></script>
<script src="/js/spotsbandsandmap.js?v=7"></script> <script src="/js/spotsbandsandmap.js?v=8"></script>
<script src="/js/spots.js?v=7"></script> <script src="/js/spots.js?v=8"></script>
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script> <script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %} {% end %}

View File

@@ -3,8 +3,8 @@
<div id="status-container" class="row row-cols-1 row-cols-md-4 g-4 mt-4"></div> <div id="status-container" class="row row-cols-1 row-cols-md-4 g-4 mt-4"></div>
<script src="/js/common.js?v=7"></script> <script src="/js/common.js?v=8"></script>
<script src="/js/status.js?v=7"></script> <script src="/js/status.js?v=8"></script>
<script>$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav --></script> <script>$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %} {% end %}

View File

@@ -145,7 +145,8 @@ function updateBands() {
// Now each spot is tagged with how far down the div it should go, add them to the DOM. // Now each spot is tagged with how far down the div it should go, add them to the DOM.
spotList.forEach(s => { spotList.forEach(s => {
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'])};"><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 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>`);
}); });
// Work out how tall the canvas should be. Normally this is matching the normal band column height, but if some // Work out how tall the canvas should be. Normally this is matching the normal band column height, but if some

View File

@@ -105,6 +105,7 @@ function updateTable() {
var showType = $("#tableShowType")[0].checked; var showType = $("#tableShowType")[0].checked;
var showRef = $("#tableShowRef")[0].checked; var showRef = $("#tableShowRef")[0].checked;
var showDE = $("#tableShowDE")[0].checked; var showDE = $("#tableShowDE")[0].checked;
var showWorkedCheckbox = $("#tableShowWorkedCheckbox")[0].checked;
// Populate table with headers // Populate table with headers
let table = $("#table"); let table = $("#table");
@@ -136,12 +137,18 @@ function updateTable() {
if (showDE) { if (showDE) {
table.find('thead tr').append(`<th class='hideonmobile'>DE</th>`); table.find('thead tr').append(`<th class='hideonmobile'>DE</th>`);
} }
if (showWorkedCheckbox) {
table.find('thead tr').append(`<th class='hideonmobile'></th>`);
}
table.find('tbody').empty(); table.find('tbody').empty();
if (spots.length == 0) { if (spots.length == 0) {
table.find('tbody').append('<tr class="table-danger"><td colspan="100" style="text-align:center;">No spots match your filters.</td></tr>'); table.find('tbody').append('<tr class="table-danger"><td colspan="100" style="text-align:center;">No spots match your filters.</td></tr>');
} }
// We are regenerating the entire table not just adding a new row, so reset the row counter
rowCount = 0;
let spotsNewestFirst = spots.toReversed(); let spotsNewestFirst = spots.toReversed();
spotsNewestFirst.forEach(s => addSpotToTopOfTable(s, false)); spotsNewestFirst.forEach(s => addSpotToTopOfTable(s, false));
} }
@@ -174,6 +181,7 @@ function createNewTableRowsForSpot(s, highlightNew) {
var showType = $("#tableShowType")[0].checked; var showType = $("#tableShowType")[0].checked;
var showRef = $("#tableShowRef")[0].checked; var showRef = $("#tableShowRef")[0].checked;
var showDE = $("#tableShowDE")[0].checked; var showDE = $("#tableShowDE")[0].checked;
var showWorkedCheckbox = $("#tableShowWorkedCheckbox")[0].checked;
// Create row // Create row
let $tr = $('<tr>'); let $tr = $('<tr>');
@@ -185,8 +193,9 @@ function createNewTableRowsForSpot(s, highlightNew) {
$tr.addClass("table-active"); $tr.addClass("table-active");
} }
// Show faded out if QRT // Show faded out if QRT or already worked
if (s["qrt"] == true) { let alreadyWorkedThis = alreadyWorked(s["dx_call"], s["band"], s["mode"]);
if (s["qrt"] == true || alreadyWorkedThis) {
$tr.addClass("table-faded"); $tr.addClass("table-faded");
} }
@@ -308,6 +317,9 @@ function createNewTableRowsForSpot(s, highlightNew) {
// Format band name // Format band name
var bandFullName = s['band'] ? s['band'] + " band": "Unknown band"; var bandFullName = s['band'] ? s['band'] + " band": "Unknown band";
// Format "worked" checkbox
var workedCheckbox = `<input type="checkbox" ${alreadyWorkedThis ? "checked" : ""} onClick="setWorkedState('${s['dx_call']}', '${s['band']}', '${s['mode']}', ${alreadyWorkedThis ? "false" : "true"});">`;
// Populate the row // Populate the row
if (showTime) { if (showTime) {
$tr.append(`<td class='nowrap'>${time_formatted}</td>`); $tr.append(`<td class='nowrap'>${time_formatted}</td>`);
@@ -336,6 +348,9 @@ function createNewTableRowsForSpot(s, highlightNew) {
if (showDE) { if (showDE) {
$tr.append(`<td class='nowrap hideonmobile'><span class='flag-wrapper' title='${de_country}'>${de_flag}</span>${de_call}</td>`); $tr.append(`<td class='nowrap hideonmobile'><span class='flag-wrapper' title='${de_country}'>${de_flag}</span>${de_call}</td>`);
} }
if (showWorkedCheckbox) {
$tr.append(`<td class='nowrap hideonmobile'>${workedCheckbox}</td>`);
}
// Second row for mobile view only, containing type, ref & comment // Second row for mobile view only, containing type, ref & comment
$tr2 = $("<tr class='hidenotonmobile'>"); $tr2 = $("<tr class='hidenotonmobile'>");
@@ -344,7 +359,7 @@ function createNewTableRowsForSpot(s, highlightNew) {
if (rowCount % 2 == 1) { if (rowCount % 2 == 1) {
$tr2.addClass("table-active"); $tr2.addClass("table-active");
} }
if (s["qrt"] == true) { if (s["qrt"] == true || alreadyWorkedThis) {
$tr2.addClass("table-faded"); $tr2.addClass("table-faded");
} }
if (highlightNew) { if (highlightNew) {
@@ -367,6 +382,9 @@ function createNewTableRowsForSpot(s, highlightNew) {
if (showDE) { if (showDE) {
$td2floatright.append(` de ${de_call} &nbsp;`); $td2floatright.append(` de ${de_call} &nbsp;`);
} }
if (showWorkedCheckbox) {
$td2floatright.append(` ${workedCheckbox} &nbsp;`);
}
$td2.append($td2floatright); $td2.append($td2floatright);
$td2.append(`</div><div style="clear: both;"></div>`); $td2.append(`</div><div style="clear: both;"></div>`);
if (showComment) { if (showComment) {
@@ -481,6 +499,27 @@ function displayIntroBox() {
}); });
} }
// Mark a callsign-band-mode combination as worked (or unmark it). Persist this to localStorage.
function setWorkedState(callsign, band, mode, nowWorked) {
let combo = callsign + "-" + band + "-" + mode;
if (nowWorked && !worked.includes(combo)) {
worked.push(combo);
updateTable();
localStorage.setItem("worked", JSON.stringify(worked));
} else if (!nowWorked && worked.includes(combo)) {
worked.splice(worked.indexOf(combo), 1);
updateTable();
localStorage.setItem("worked", JSON.stringify(worked));
}
}
// Clear the list of worked calls
function clearWorked() {
worked = [];
updateTable();
localStorage.setItem("worked", JSON.stringify(worked));
}
// Startup // Startup
$(document).ready(function() { $(document).ready(function() {
// Call loadOptions(), this will then trigger loading spots and setting up timers. // Call loadOptions(), this will then trigger loading spots and setting up timers.

View File

@@ -1,5 +1,10 @@
// Storage for the spot data that the server gives us. // Storage for the spot data that the server gives us.
var spots = [] var spots = []
// List of people the user has worked. Each entry has the format callsign-band-mode. These can be added to the list by
// ticking the checkbox on a row of the table, and cleared from the Display menu. Where a row would be added to the
// table and the callsign-band-mode is in this list, it is shown struck through as already worked. This is persisted
// to localStorage.
let worked = []
// Dynamically add CSS code for the band toggle buttons to be in the appropriate colour. // Dynamically add CSS code for the band toggle buttons to be in the appropriate colour.
// Some band names contain decimal points which are not allowed in CSS classes, so we text-replace them to "p". // Some band names contain decimal points which are not allowed in CSS classes, so we text-replace them to "p".
@@ -118,6 +123,11 @@ function setBandColorSchemeFromUI() {
window.location.reload(); window.location.reload();
} }
// Query if a callsign-band-mode combination as has already been worked
function alreadyWorked(callsign, band, mode) {
return worked.includes(callsign + "-" + band + "-" + mode);
}
// Reload spots on becoming visible. This forces a refresh when used as a PWA and the user switches back to the PWA // Reload spots on becoming visible. This forces a refresh when used as a PWA and the user switches back to the PWA
// after some time has passed with it in the background. // after some time has passed with it in the background.
addEventListener("visibilitychange", (event) => { addEventListener("visibilitychange", (event) => {
@@ -125,3 +135,12 @@ addEventListener("visibilitychange", (event) => {
loadSpots(); loadSpots();
} }
}); });
// Startup
$(document).ready(function() {
// Load worked list
var tmpWorked = JSON.parse(localStorage.getItem("worked"));
if (tmpWorked) {
worked = tmpWorked;
}
});