Fix some IDE warnings

This commit is contained in:
Ian Renton
2026-06-19 19:31:56 +01:00
parent 725eb619b4
commit 05ac652cee
22 changed files with 310 additions and 356 deletions

View File

@@ -1,6 +1,45 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Annotator" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="BadExpressionStatementJS" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="CssOverwrittenProperties" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssUnresolvedCustomProperty" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssUnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
<inspection_tool class="GrazieStyle" enabled="false" level="STYLE_SUGGESTION" enabled_by_default="false" />
<inspection_tool class="HtmlUnknownAttribute" enabled="false" level="WARNING" enabled_by_default="false">
<option name="myValues">
<value>
<list size="0" />
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="HtmlUnknownTag" enabled="false" level="WARNING" enabled_by_default="false">
<option name="myValues">
<value>
<list size="6">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="HtmlUnknownTarget" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpUrlsUsage" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="JSDeprecatedSymbols" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="JSIgnoredPromiseFromCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="JSJQueryEfficiency" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JSUnnecessarySemicolon" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JSUnresolvedReference" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="OutdatedRequirementInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{redoc.standalone}" />
</component>
</project>

2
.idea/spothole.iml generated
View File

@@ -3,8 +3,10 @@
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
<excludeFolder url="file://$MODULE_DIR$/webassets/vendor" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 virtualenv at ~/code/spothole/.venv" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="redoc.standalone" level="application" />
</component>
</module>

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{% extends "skeleton.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/css/style.css?v=1781809662" type="text/css">
<link rel="stylesheet" href="/css/style.css?v=1781893916" type="text/css">
<link href="/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
<link href="/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet">
<link href="/vendor/css/solid-6.7.2.min.css" rel="stylesheet">
@@ -10,10 +10,10 @@
<script src="/vendor/js/bootstrap-5.3.8.bundle.min.js"></script>
<script src="/vendor/js/tinycolor2-1.6.0.min.js"></script>
<script src="/js/utils.js?v=1781809662"></script>
<script src="/js/ui-ham.js?v=1781809662"></script>
<script src="/js/geo.js?v=1781809662"></script>
<script src="/js/common.js?v=1781809662"></script>
<script src="/js/utils.js?v=1781893916"></script>
<script src="/js/ui-ham.js?v=1781893916"></script>
<script src="/js/geo.js?v=1781893916"></script>
<script src="/js/common.js?v=1781893916"></script>
{% end %}
{% block body %}
<div class="container">

View File

@@ -284,7 +284,7 @@
</div>
<script src="/vendor/js/chart-4.4.9.umd.min.js"></script>
<script src="/js/conditions.js?v=1781809662"></script>
<script src="/js/conditions.js?v=1781893916"></script>
<script>$(document).ready(function () {
$("#nav-link-conditions").addClass("active");
}); <!-- highlight active page in nav --></script>

View File

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

View File

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

View File

@@ -59,7 +59,7 @@
</div>
</div>
<script src="/js/status.js?v=1781809662"></script>
<script src="/js/status.js?v=1781893916"></script>
<script>
$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav -->
</script>

View File

@@ -90,7 +90,7 @@ input#search {
i#searchicon {
position: absolute;
left: 0rem;
left: 0;
top: 2px;
padding: 10px;
pointer-events: none;
@@ -217,7 +217,7 @@ div#map {
}
.leaflet-container {
font-family: var(--bs-body-font-family) !important;
font-family: var(--bs-body-font-family) sans-serif !important;
}
.leaflet-control-attribution {
background: none;

View File

@@ -33,44 +33,44 @@ function addSpot() {
saveSettings();
// Unpack the user's entered values
var dx = $("#dx-call").val().toUpperCase();
var freqStr = $("#freq").val();
var mode = $("#mode")[0].value;
var sig = $("#sig")[0].value;
var sigRef = $("#sig-ref").val();
var dxGrid = $("#dx-grid").val();
var comment = $("#comment").val();
var de = $("#de-call").val().toUpperCase();
const dx = $("#dx-call").val().toUpperCase();
const freqStr = $("#freq").val();
const mode = $("#mode")[0].value;
const sig = $("#sig")[0].value;
const sigRef = $("#sig-ref").val();
const dxGrid = $("#dx-grid").val();
const comment = $("#comment").val();
const de = $("#de-call").val().toUpperCase();
var spot = {}
if (dx != "") {
const spot = {};
if (dx !== "") {
spot["dx_call"] = dx;
} else {
showAddSpotError("A DX callsign is required in order to spot.");
return;
}
if (freqStr != "") {
if (freqStr !== "") {
spot["freq"] = parseFloat(freqStr) * 1000;
} else {
showAddSpotError("A frequency is required in order to spot.");
return;
}
if (mode != "") {
if (mode !== "") {
spot["mode"] = mode;
}
if (sig != "") {
if (sig !== "") {
spot["sig"] = sig;
}
if (sigRef != "") {
if (sigRef !== "") {
spot["sig_refs"] = [{id: sigRef}];
}
if (dxGrid != "") {
if (dxGrid !== "") {
spot["dx_grid"] = dxGrid;
}
if (comment != "") {
if (comment !== "") {
spot["comment"] = comment;
}
if (de != "") {
if (de !== "") {
spot["de_call"] = de;
} else {
showAddSpotError("A spotter callsign is required in order to spot.");
@@ -83,7 +83,7 @@ function addSpot() {
contentType : 'application/json',
type : 'POST',
timeout: 10000,
success: async function (result) {
success: async function() {
$("#result-good").html("<div class='alert alert-success fade show mb-0 mt-4' role='alert'><i class='fa-solid fa-check'></i> Spot submitted. Returning you to the spots list...</div>");
$("#result-bad").html("");
setTimeout(() => {
@@ -103,7 +103,7 @@ function addSpot() {
// Show an "add spot" error.
function showAddSpotError(text) {
var div = $("<div class='alert alert-danger alert-dismissible fade show mb-0 mt-4' role='alert'></div>");
const div = $("<div class='alert alert-danger alert-dismissible fade show mb-0 mt-4' role='alert'></div>");
div.append("<i class='fa-solid fa-triangle-exclamation'></i> ");
div.append(document.createTextNode(text));
div.append("<button type='button' class='btn-close' data-bs-dismiss='alert' aria-label='Close'></button>");

View File

@@ -2,7 +2,7 @@
const REFRESH_INTERVAL_SEC = 60 * 10;
// Storage for the alert data that the server gives us.
var alerts = []
let alerts = [];
// Load alerts and populate the table.
function loadAlerts() {
@@ -19,15 +19,15 @@ function loadAlerts() {
// Build a query string for the API, based on the filters that the user has selected.
function buildQueryString(includeCredentials) {
var str = "?";
let str = "?";
["dx_continent", "source"].forEach(fn => {
if (!allFilterOptionsSelected(fn)) {
str = str + getQueryStringFor(fn) + "&";
}
});
str = str + "limit=" + $("#alerts-to-fetch option:selected").val();
var maxDur = $("#max-duration option:selected").val();
if (maxDur != "9999999999") {
const maxDur = $("#max-duration option:selected").val();
if (maxDur !== "9999999999") {
str = str + "&max_duration=" + maxDur;
}
if ($("#dxpeditions_skip_max_duration_check")[0].checked) {
@@ -42,16 +42,16 @@ function buildQueryString(includeCredentials) {
// Update the alerts table
function updateTable() {
// Use local time instead of UTC?
var useLocalTime = $("#timeZone")[0].value == "local";
const useLocalTime = $("#timeZone")[0].value === "local";
// Table data toggles
var showStartTime = $("#tableShowStartTime")[0].checked;
var showEndTime = $("#tableShowEndTime")[0].checked;
var showDX = $("#tableShowDX")[0].checked;
var showFreqsModes = $("#tableShowFreqsModes")[0].checked;
var showComment = $("#tableShowComment")[0].checked;
var showSource = $("#tableShowSource")[0].checked;
var showRef = $("#tableShowRef")[0].checked;
const showStartTime = $("#tableShowStartTime")[0].checked;
const showEndTime = $("#tableShowEndTime")[0].checked;
const showDX = $("#tableShowDX")[0].checked;
const showFreqsModes = $("#tableShowFreqsModes")[0].checked;
const showComment = $("#tableShowComment")[0].checked;
const showSource = $("#tableShowSource")[0].checked;
const showRef = $("#tableShowRef")[0].checked;
// Populate table with headers
let table = $("#table");
@@ -83,10 +83,10 @@ function updateTable() {
// Split alerts into three types, each of which will get its own table header: On now, next 24h, and later. "On now"
// is considered to be events with an end_time where start<now<end, or events with no end time that started in the
// last hour.
onNow = alerts.filter(a => (a["end_time"] != null && a["end_time"] != 0 && moment.unix(a["end_time"]).utc().isSameOrAfter() && moment.unix(a["start_time"]).utc().isBefore())
|| ((a["end_time"] == null || a["end_time"] == 0) && moment.unix(a["start_time"]).utc().add(1, 'hours').isSameOrAfter() && moment.unix(a["start_time"]).utc().isBefore()));
next24h = alerts.filter(a => moment.unix(a["start_time"]).utc().isSameOrAfter() && moment.unix(a["start_time"]).utc().subtract(24, 'hours').isBefore());
later = alerts.filter(a => moment.unix(a["start_time"]).utc().subtract(24, 'hours').isSameOrAfter());
const onNow = alerts.filter(a => (a["end_time"] != null && a["end_time"] !== 0 && moment.unix(a["end_time"]).utc().isSameOrAfter() && moment.unix(a["start_time"]).utc().isBefore())
|| ((a["end_time"] == null || a["end_time"] === 0) && moment.unix(a["start_time"]).utc().add(1, 'hours').isSameOrAfter() && moment.unix(a["start_time"]).utc().isBefore()));
const next24h = alerts.filter(a => moment.unix(a["start_time"]).utc().isSameOrAfter() && moment.unix(a["start_time"]).utc().subtract(24, 'hours').isBefore());
const later = alerts.filter(a => moment.unix(a["start_time"]).utc().subtract(24, 'hours').isSameOrAfter());
if (onNow.length > 0) {
table.find('tbody').append('<tr><td colspan="100" class="bg-primary-subtle" style="text-align:center;">On Now</td></tr>');
@@ -103,14 +103,14 @@ function updateTable() {
addAlertRowsToTable(table.find('tbody'), later);
}
if (onNow.length == 0 && next24h.length == 0 && later.length == 0) {
if (onNow.length === 0 && next24h.length === 0 && later.length === 0) {
table.find('tbody').append('<tr class="bg-danger-subtle"><td colspan="100" style="text-align:center;">No alerts match your filters.</td></tr>');
}
}
// Add a row to tbody for each alert in the provided list
function addAlertRowsToTable(tbody, alerts) {
var count = 0;
let count = 0;
alerts.forEach(a => {
// Create row
let $tr = $('<tr>');
@@ -118,29 +118,29 @@ function addAlertRowsToTable(tbody, alerts) {
// 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) {
if (count % 2 === 1) {
$tr.addClass("table-active");
}
// Use local time instead of UTC?
var useLocalTime = $("#timeZone")[0].value == "local";
const useLocalTime = $("#timeZone")[0].value === "local";
// Table data toggles
var showStartTime = $("#tableShowStartTime")[0].checked;
var showEndTime = $("#tableShowEndTime")[0].checked;
var showDX = $("#tableShowDX")[0].checked;
var showFreqsModes = $("#tableShowFreqsModes")[0].checked;
var showComment = $("#tableShowComment")[0].checked;
var showSource = $("#tableShowSource")[0].checked;
var showRef = $("#tableShowRef")[0].checked;
const showStartTime = $("#tableShowStartTime")[0].checked;
const showEndTime = $("#tableShowEndTime")[0].checked;
const showDX = $("#tableShowDX")[0].checked;
const showFreqsModes = $("#tableShowFreqsModes")[0].checked;
const showComment = $("#tableShowComment")[0].checked;
const showSource = $("#tableShowSource")[0].checked;
const showRef = $("#tableShowRef")[0].checked;
// Get times for the alert, and convert to local time if necessary.
var start_time_utc = moment.unix(a["start_time"]).utc();
var start_time_local = start_time_utc.clone().local();
start_time = useLocalTime ? start_time_local : start_time_utc;
var end_time_utc = moment.unix(a["end_time"]).utc();
var end_time_local = end_time_utc.clone().local();
end_time = useLocalTime ? end_time_local : end_time_utc;
const start_time_utc = moment.unix(a["start_time"]).utc();
const start_time_local = start_time_utc.clone().local();
const start_time = useLocalTime ? start_time_local : start_time_utc;
const end_time_utc = moment.unix(a["end_time"]).utc();
const end_time_local = end_time_utc.clone().local();
const end_time = useLocalTime ? end_time_local : end_time_utc;
// Format the times for display. Start time is displayed as e.g. 7 Oct 12:34 unless the time is in a
// different year to the current year, in which case the year is inserted between month and hour.
@@ -150,22 +150,22 @@ function addAlertRowsToTable(tbody, alerts) {
// Overriding all of that, if the start time is 00:00 and the end time is 23:59 when considered in UTC, the
// hours and minutes are stripped out from the display, as we assume the server is just giving us full days.
// Finally, if there is no end date set, "---" is displayed.
var whole_days = start_time_utc.format("HH:mm") == "00:00" &&
(end_time_utc != null || end_time_utc > 0 || end_time_utc.format("HH:mm") == "23:59");
var hours_minutes_format = whole_days ? "" : " HH:mm";
var start_time_formatted = start_time.format("D MMM" + hours_minutes_format);
if (start_time.format("YYYY") != moment().format("YYYY")) {
const whole_days = start_time_utc.format("HH:mm") === "00:00" &&
(end_time_utc === 0 || end_time_utc.format("HH:mm") === "23:59");
const hours_minutes_format = whole_days ? "" : " HH:mm";
let start_time_formatted = start_time.format("D MMM" + hours_minutes_format);
if (start_time.format("YYYY") !== moment().format("YYYY")) {
start_time_formatted = start_time.format("D MMM YYYY" + hours_minutes_format);
} else if (useLocalTime && start_time.format("D MMM YYYY") == moment().format("D MMM YYYY")) {
} else if (useLocalTime && start_time.format("D MMM YYYY") === moment().format("D MMM YYYY")) {
start_time_formatted = start_time.format("[Today]" + hours_minutes_format);
}
var end_time_formatted = "---";
if (end_time_utc != null && end_time_utc > 0 && end_time != null) {
var end_time_formatted = whole_days ? start_time_formatted : end_time.format("HH:mm");
if (end_time.format("D MMM") != start_time.format("D MMM")) {
if (end_time.format("YYYY") != moment().format("YYYY")) {
let end_time_formatted = "---";
if (end_time_utc > 0 && end_time != null) {
end_time_formatted = whole_days ? start_time_formatted : end_time.format("HH:mm");
if (end_time.format("D MMM") !== start_time.format("D MMM")) {
if (end_time.format("YYYY") !== moment().format("YYYY")) {
end_time_formatted = end_time.format("D MMM YYYY" + hours_minutes_format);
} else if (useLocalTime && end_time.format("D MMM YYYY") == moment().format("D MMM YYYY")) {
} else if (useLocalTime && end_time.format("D MMM YYYY") === moment().format("D MMM YYYY")) {
end_time_formatted = end_time.format("[Today]" + hours_minutes_format);
} else {
end_time_formatted = end_time.format("D MMM" + hours_minutes_format);
@@ -174,52 +174,52 @@ function addAlertRowsToTable(tbody, alerts) {
}
// Format dx country
var dx_country = a["dx_country"]
let dx_country = a["dx_country"];
if (dx_country == null) {
dx_country = "Unknown or not a country"
}
// Format DX flag
var dx_flag = "<i class='fa-solid fa-globe-africa'></i>";
if (a["dx_dxcc_id"] && a["dx_dxcc_id"] != null && a["dx_dxcc_id"] != 0) {
let dx_flag = "<i class='fa-solid fa-globe-africa'></i>";
if (a["dx_dxcc_id"] && a["dx_dxcc_id"] != null && a["dx_dxcc_id"] !== 0) {
dx_flag = `<img src="img/flags/${a['dx_dxcc_id']}.png" class="flag" width="24" alt="${dx_country}" title="${dx_country}"/>`;
}
// Format dx calls
var dx_calls_html = "";
let dx_calls_html = "";
if (a["dx_calls"] != null) {
dx_calls_html = a["dx_calls"].map(call => `<a class='dx-link' href='https://qrz.com/db/${call}' target='_new'>${call}</a>`).join(", ");
}
// Format DXpedition country
var dx_country_html = "";
if (a["is_dxpedition"] == true && a["dx_country"] != null && a["dx_country"] != "") {
let dx_country_html = "";
if (a["is_dxpedition"] === true && a["dx_country"] != null && a["dx_country"] !== "") {
dx_country_html = `<br/>${a["dx_country"]}`;
}
// Format freqs & modes
var freqsModesText = "";
let freqsModesText = "";
if (a["freqs_modes"] != null) {
freqsModesText = escapeHtml(a["freqs_modes"]);
}
// Format comment
var commentText = "";
let commentText = "";
if (a["comment"] != null) {
commentText = escapeHtml(a["comment"]);
}
// Sig or fallback to source
var sigSourceText = a["source"];
let sigSourceText = a["source"];
if (a["sig"]) {
sigSourceText = a["sig"];
}
// Format sig_refs
var sig_refs = "";
let sig_refs = "";
if (a["sig_refs"] != null) {
var items = []
for (var i = 0; i < a["sig_refs"].length; i++) {
const items = [];
for (let i = 0; i < a["sig_refs"].length; i++) {
if (a["sig_refs"][i]["url"] != null) {
items[i] = `<a href='${encodeURI(a["sig_refs"][i]["url"])}' title='${escapeHtml(a["sig_refs"][i]["name"])}' target='_new' class='sig-ref-link'>${escapeHtml(a["sig_refs"][i]["id"])}</a>`
} else {
@@ -254,11 +254,11 @@ function addAlertRowsToTable(tbody, alerts) {
tbody.append($tr);
// Second row for mobile view only, containing source, ref, freqs/modes & comment
$tr2 = $("<tr class='hidenotonmobile'>");
if (count % 2 == 1) {
const $tr2 = $("<tr class='hidenotonmobile'>");
if (count % 2 === 1) {
$tr2.addClass("table-active");
}
$td2 = $("<td colspan='100'>");
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> `);
}
@@ -319,7 +319,7 @@ $(document).ready(function() {
// Reload alerts 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.
addEventListener("visibilitychange", (event) => {
addEventListener("visibilitychange", () => {
if (!document.hidden) {
loadAlerts();
}

View File

@@ -25,7 +25,7 @@ function loadSpots() {
// Build a query string for the API, based on the filters that the user has selected.
function buildQueryString(includeCredentials) {
var str = "?";
let str = "?";
["dx_continent", "de_continent", "mode", "source", "band", "sig"].forEach(fn => {
if (!allFilterOptionsSelected(fn)) {
str = str + getQueryStringFor(fn) + "&";
@@ -43,7 +43,7 @@ function buildQueryString(includeCredentials) {
// Update the bands display
function updateBands() {
// Stop here if nothing to display
var bandsContainer = $("#bands-container");
const bandsContainer = $("#bands-container");
if (spots.length === 0) {
bandsContainer.html("<div class='alert alert-danger' role='alert'>No spots match your filters.</div>");
return;
@@ -52,7 +52,7 @@ function updateBands() {
// Do some harsher de-duping. Because we only display callsign, frequency and mode here, the previous
// de-duplication could have let some through that don't look like dupes on the map, but would do here.
// Typically that's a person activating two programs at the same time, e.g. POTA & WWFF.
spotList = removeDuplicatesForBandPanel(spots);
const spotList = removeDuplicatesForBandPanel(spots);
// Convert to a map of band names to the spots on that band. Bands with no
// spots in view will not be present.
@@ -67,10 +67,10 @@ function updateBands() {
});
// Track if any columns end up taller than expected, so we can resize the container and avoid vertical scroll.
var maxHeightBand = 0;
let maxHeightBand = 0;
// Build up table content for each band
var table = $('<table id="bands-table">').append('<thead><tr></tr></thead><tbody><tr></tr></tbody>');
const table = $('<table id="bands-table">').append('<thead><tr></tr></thead><tbody><tr></tr></tbody>');
bandToSpots.forEach(function (spotList, bandName) {
// Get the colours for the band from the first spot, and prepare the header
table.find('thead tr').append(`<th style='background-color:${bandToColor(spotList[0].band)}; color:${bandToContrastColor(spotList[0].band)}'>${spotList[0].band}</th>`);
@@ -82,7 +82,7 @@ function updateBands() {
// Print the frequency band markers. This is 41 steps to divide the band evenly into 40 markers. One in every
// four will show the actual frequency, the others will just be dashes.
bandMarkersDiv = $('<div class="band-markers">');
const bandMarkersDiv = $('<div class="band-markers">');
const freqStep = (band.end_freq - band.start_freq) / 40.0;
for (let i = 0; i <= 40; i++) {
if (i % 4 === 0) {
@@ -95,8 +95,8 @@ function updateBands() {
}
// Prepare the spots list
var bandSpotsDiv = $("<div class='band-spots'>");
var lastSpotPxDownBand = -999;
const bandSpotsDiv = $("<div class='band-spots'>");
let lastSpotPxDownBand = -999;
// Sort by frequency so have a consistent order in which to plan where they will appear on the band div.
spotList.sort(function(a, b) { return a.freq - b.freq; });
// First calculate how we should be displaying the spots. There are three "modes" to try to place them in a
@@ -118,8 +118,8 @@ function updateBands() {
// Mode 1 or 2. Run through adding things to the list forwards as a test.
spotList.forEach(s => {
// Work out how far down the div to draw it
var percentDownBand = (s.freq - band.start_freq) / (band.end_freq - band.start_freq) * 0.97; // not 100% due to fudge, the first and last dashes are not exactly at the top and bottom of the div as some space is needed for text
var pxDownBand = percentDownBand * BAND_COLUMN_HEIGHT_PX;
const percentDownBand = (s.freq - band.start_freq) / (band.end_freq - band.start_freq) * 0.97; // not 100% due to fudge, the first and last dashes are not exactly at the top and bottom of the div as some space is needed for text
let pxDownBand = percentDownBand * BAND_COLUMN_HEIGHT_PX;
if (pxDownBand < lastSpotPxDownBand + BAND_COLUMN_SPOT_DIV_HEIGHT_PX) {
pxDownBand = lastSpotPxDownBand + BAND_COLUMN_SPOT_DIV_HEIGHT_PX; // Prevent overlap
}
@@ -135,8 +135,8 @@ function updateBands() {
lastSpotPxDownBand = 999999;
spotList.reverse().forEach(s => {
// Work out how far down the div to draw it
var percentDownBand = (s.freq - band.start_freq) / (band.end_freq - band.start_freq) * 0.97; // not 100% due to fudge, the first and last dashes are not exactly at the top and bottom of the div as some space is needed for text
var pxDownBand = percentDownBand * BAND_COLUMN_HEIGHT_PX;
const percentDownBand = (s.freq - band.start_freq) / (band.end_freq - band.start_freq) * 0.97; // not 100% due to fudge, the first and last dashes are not exactly at the top and bottom of the div as some space is needed for text
let pxDownBand = percentDownBand * BAND_COLUMN_HEIGHT_PX;
if (pxDownBand > lastSpotPxDownBand - BAND_COLUMN_SPOT_DIV_HEIGHT_PX) {
pxDownBand = lastSpotPxDownBand - BAND_COLUMN_SPOT_DIV_HEIGHT_PX; // Prevent overlap
}
@@ -155,19 +155,19 @@ function updateBands() {
// Work out how tall the canvas should be. Normally this is matching the normal band column height, but if some
// spots have gone off the end of the band markers and stretched their div, we need to resize the canvas to
// match, otherwise we have nowhere to draw their connecting lines.
var canvasHeight = Math.max(BAND_COLUMN_HEIGHT_PX, lastSpotPxDownBand + BAND_COLUMN_SPOT_DIV_HEIGHT_PX);
const canvasHeight = Math.max(BAND_COLUMN_HEIGHT_PX, lastSpotPxDownBand + BAND_COLUMN_SPOT_DIV_HEIGHT_PX);
maxHeightBand = Math.max(maxHeightBand, canvasHeight);
// Draw horizontal or diagonal lines to join up the "real" frequency with where the spot div ended up
var bandLinesCanvas = $(`<canvas class='band-lines-canvas' width='${BAND_COLUMN_CANVAS_WIDTH_PX}px' height='${canvasHeight}px' style='height:${canvasHeight}px !important;'>`);
const bandLinesCanvas = $(`<canvas class='band-lines-canvas' width='${BAND_COLUMN_CANVAS_WIDTH_PX}px' height='${canvasHeight}px' style='height:${canvasHeight}px !important;'>`);
spotList.forEach(s => {
// Work out how far down the div to draw it
var percentDownBand = (s.freq - band.start_freq) / (band.end_freq - band.start_freq) * 0.97; // not 100% due to fudge, the first and last dashes are not exactly at the top and bottom of the div as some space is needed for text
var pxDownBandFreq = (percentDownBand + 0.015) * BAND_COLUMN_HEIGHT_PX; // same fudge but add half to put the left end of the line in the right place
var pxDownBandLabel = s["pxDownBandLabel"] + (BAND_COLUMN_SPOT_DIV_HEIGHT_PX / 1.75); // line should be to the vertical text-centre spot, not to the top corner
const percentDownBand = (s.freq - band.start_freq) / (band.end_freq - band.start_freq) * 0.97; // not 100% due to fudge, the first and last dashes are not exactly at the top and bottom of the div as some space is needed for text
const pxDownBandFreq = (percentDownBand + 0.015) * BAND_COLUMN_HEIGHT_PX; // same fudge but add half to put the left end of the line in the right place
const pxDownBandLabel = s["pxDownBandLabel"] + (BAND_COLUMN_SPOT_DIV_HEIGHT_PX / 1.75); // line should be to the vertical text-centre spot, not to the top corner
// Draw the line on the canvas
var ctx = bandLinesCanvas[0].getContext('2d');
const ctx = bandLinesCanvas[0].getContext('2d');
ctx.beginPath();
ctx.lineWidth = 2;
ctx.lineCap = "round";
@@ -178,8 +178,8 @@ function updateBands() {
});
// Assemble the table cell
td = $("<td>");
container = $("<div class='band-container'>");
const td = $("<td>");
const container = $("<div class='band-container'>");
container.append(bandLinesCanvas);
container.append(bandMarkersDiv);
container.append(bandSpotsDiv);
@@ -213,7 +213,6 @@ function removeDuplicatesForBandPanel(spotList) {
if (s.dx_call === check.dx_call && s.freq === check.freq && s.mode === check.mode) {
// Find which one to keep and which to delete
const checkSpotNewer = check.time > s.time;
const keepSpot = checkSpotNewer ? check : s;
const deleteSpot = checkSpotNewer ? s : check;
// Aggregate list of spots to remove
spotsToRemove.push(deleteSpot.uid);

View File

@@ -1,7 +1,7 @@
// Storage for the options that the server gives us. This will define our filters.
var options = {};
let options = {};
// Last time we updated the spots/alerts list on display.
var lastUpdateTime;
let lastUpdateTime;
// Normally load user settings from local storage, unless embedded mode is in use
let useLocalStorage = true;
@@ -21,8 +21,8 @@ function saveSettings() {
});
// Password fields are only saved if the corresponding "remember password" checkbox is ticked.
$(".password-field").each(function () {
var pwKey = "#" + $(this)[0].id + ":value";
var rememberCheckboxId = $(this).data("remember-checkbox");
const pwKey = "#" + $(this)[0].id + ":value";
const rememberCheckboxId = $(this).data("remember-checkbox");
if (rememberCheckboxId && $("#" + rememberCheckboxId)[0] && $("#" + rememberCheckboxId)[0].checked) {
localStorage.setItem(pwKey, JSON.stringify($(this)[0].value));
} else {
@@ -39,7 +39,7 @@ function loadSettings() {
Object.keys(localStorage).forEach(function (key) {
if (key.startsWith("#") && key.includes(":")) {
// Split the key back into an element ID and a property
var split = key.split(":");
const split = key.split(":");
$(split[0]).prop(split[1], JSON.parse(localStorage.getItem(key)));
}
});
@@ -76,21 +76,13 @@ function loadURLParams() {
updateFilterFromParam(params, "de_continent", "de_continent");
}
// Update an HTML checkbox element so that its selected matches the given parameter (which must have a true or false value)
function updateCheckboxFromParam(params, paramName, checkboxID) {
let v = params.get(paramName);
if (v != null) {
$("#" + checkboxID).prop("checked", (v === "true") ? true : false);
}
}
// Update an HTML select element so that its value matches the given parameter
function updateSelectFromParam(params, paramName, selectID) {
let v = params.get(paramName);
if (v != null) {
$("#" + selectID).prop("value", v);
// Extra check if this is the "color scheme" select
if (selectID == "color-scheme") {
if (selectID === "color-scheme") {
setColorScheme(v);
}
}
@@ -128,16 +120,16 @@ function getSelectedFilterOptions(parameter) {
// For a parameter, such as dx_continent, return true if all possible options are enabled. (In this case, we don't need
// to bother sending this as one of the query parameters to the API; no parameter provided implies "send everything".)
function allFilterOptionsSelected(parameter) {
var filter = $(".filter-button-" + parameter).filter(function () {
const filter = $(".filter-button-" + parameter).filter(function () {
return !this.checked;
}).get();
return filter.length == 0;
return filter.length === 0;
}
// Generate a filter card with inline checkboxes plus All/None links.
function generateMultiToggleFilterCard(elementID, filterQuery, options) {
var $row = $('<div>');
const $row = $('<div>');
options.forEach(o => {
$row.append(`<div class="form-check form-check-inline"><input type="checkbox" class="form-check-input filter-button-${filterQuery} storeable-checkbox" id="filter-button-${filterQuery}-${o}" value="${o}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" for="filter-button-${filterQuery}-${o}">${o}</label></div>`);
});
@@ -161,12 +153,13 @@ function updateRefreshDisplay() {
let updatingString = "Updating..."
if (secSinceUpdate < REFRESH_INTERVAL_SEC) {
count = REFRESH_INTERVAL_SEC - secSinceUpdate;
let number;
if (count <= 60) {
var number = count.toFixed(0);
updatingString = "<span class='nowrap'>Updating in " + number + " second" + (number != "1" ? "s" : "") + ".</span>";
number = count.toFixed(0);
updatingString = "<span class='nowrap'>Updating in " + number + " second" + (number !== "1" ? "s" : "") + ".</span>";
} else {
var number = Math.round(count / 60.0).toFixed(0);
updatingString = "<span class='nowrap'>Updating in " + number + " minute" + (number != "1" ? "s" : "") + ".</span>";
number = Math.round(count / 60.0).toFixed(0);
updatingString = "<span class='nowrap'>Updating in " + number + " minute" + (number !== "1" ? "s" : "") + ".</span>";
}
}
$("#timing-container").html("Last updated at " + lastUpdateTime.format('HH:mm') + " UTC. " + updatingString);
@@ -188,7 +181,7 @@ function columnsUpdated() {
// Function to set the colour scheme based on the state of the UI select box
function setColorSchemeFromUI() {
let theme = $("#color-scheme option:selected").val();
if (theme != "") {
if (theme !== "") {
setColorScheme(theme);
saveSettings();
}
@@ -196,8 +189,8 @@ function setColorSchemeFromUI() {
// Function to set the color scheme. Supported values: "dark", "light", "auto"
function setColorScheme(mode) {
let effectiveModeDark = mode == "dark";
if (mode == "auto") {
let effectiveModeDark = mode === "dark";
if (mode === "auto") {
effectiveModeDark = window.matchMedia('(prefers-color-scheme: dark)').matches
}
$("html").attr("data-bs-theme", effectiveModeDark ? "dark" : "light");
@@ -283,16 +276,16 @@ function closeDataPanel() {
// Build a query string fragment containing any QRZ.com / HamQTH credentials the user has supplied,
// provided the corresponding "enabled" checkbox is ticked.
function getCredentialQueryString() {
var str = "";
let str = "";
if ($("#qrz-enabled")[0] && $("#qrz-enabled")[0].checked) {
var qrzUsername = $("#qrz-username").val();
var qrzPassword = $("#qrz-password").val();
const qrzUsername = $("#qrz-username").val();
const qrzPassword = $("#qrz-password").val();
if (qrzUsername) str += "&qrz_username=" + encodeURIComponent(qrzUsername);
if (qrzPassword) str += "&qrz_password=" + encodeURIComponent(qrzPassword);
}
if ($("#hamqth-enabled")[0] && $("#hamqth-enabled")[0].checked) {
var hamqthUsername = $("#hamqth-username").val();
var hamqthPassword = $("#hamqth-password").val();
const hamqthUsername = $("#hamqth-username").val();
const hamqthPassword = $("#hamqth-password").val();
if (hamqthUsername) str += "&hamqth_username=" + encodeURIComponent(hamqthUsername);
if (hamqthPassword) str += "&hamqth_password=" + encodeURIComponent(hamqthPassword);
}

View File

@@ -9,10 +9,10 @@ function calcBearing(lat1, lon1, lat2, lon2) {
lon1 *= Math.PI / 180;
lat2 *= Math.PI / 180;
lon2 *= Math.PI / 180;
var lonDelta = lon2 - lon1;
var y = Math.sin(lonDelta) * Math.cos(lat2);
var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lonDelta);
var bearing = Math.atan2(y, x);
const lonDelta = lon2 - lon1;
const y = Math.sin(lonDelta) * Math.cos(lat2);
const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lonDelta);
let bearing = Math.atan2(y, x);
bearing = bearing * (180 / Math.PI);
if ( bearing < 0 ) { bearing += 360; }
return bearing;

View File

@@ -12,19 +12,19 @@ const ITU_ZONES_COLOR_DARK = 'rgba(120, 120, 60, 1.0)';
const WAB_WAI_GRID_COLOR_DARK = 'rgba(60, 60, 120, 1.0)';
// Map layers
var backgroundTileLayer;
var markersLayer;
var geodesicsLayer;
var oms;
var terminator;
var maidenheadGrid;
var cqZones;
var ituZones;
var wabwaiGrid;
let backgroundTileLayer;
let markersLayer;
let geodesicsLayer;
let oms;
let terminator;
let maidenheadGrid;
let cqZones;
let ituZones;
let wabwaiGrid;
// Tracks the currently-loaded basemap provider string to avoid unnecessary tile reloads
var loadedBasemap;
let loadedBasemap;
// Tracks whether this is the first display of markers after page load
var firstLoad = true;
let firstLoad = true;
// Load spots and populate the map.
function loadSpots() {
@@ -41,7 +41,7 @@ function loadSpots() {
// Build a query string for the API, based on the filters that the user has selected.
function buildQueryString(includeCredentials) {
var str = "?";
let str = "?";
["dx_continent", "de_continent", "mode", "source", "band", "sig"].forEach(fn => {
if (!allFilterOptionsSelected(fn)) {
str = str + getQueryStringFor(fn) + "&";
@@ -65,7 +65,7 @@ function updateMap() {
// Make new markers for all spots that match the filter
spots.forEach(function (s) {
var m = L.marker([s["dx_latitude"], s["dx_longitude"]], {icon: getIcon(s)});
const m = L.marker([s["dx_latitude"], s["dx_longitude"]], {icon: getIcon(s)});
m.bindPopup(getTooltipText(s));
markersLayer.addLayer(m);
oms.addMarker(m);
@@ -73,7 +73,7 @@ function updateMap() {
// Create geodesics if required
if ($("#mapShowGeodesics")[0].checked && s["de_latitude"] != null && s["de_longitude"] != null) {
try {
var geodesic = L.geodesic([[s["de_latitude"], s["de_longitude"]], m.getLatLng()], {
const geodesic = L.geodesic([[s["de_latitude"], s["de_longitude"]], m.getLatLng()], {
color: bandToColor(s['band']),
wrap: false,
steps: 5
@@ -88,7 +88,7 @@ function updateMap() {
// On first load, zoom to the extent of the markers
if (firstLoad) {
if (markersLayer.getLayers().length >= 2) {
var group = new L.featureGroup(markersLayer.getLayers());
const group = new L.featureGroup(markersLayer.getLayers());
map.fitBounds(group.getBounds().pad(0.1));
}
firstLoad = false;
@@ -110,48 +110,50 @@ function getIcon(s) {
// Tooltip text for the markers
function getTooltipText(s) {
// Format DX call
var dx_call = s["dx_call"];
let dx_call = s["dx_call"];
if (dx_call == null) {
dx_call = "";
dx_flag = "";
}
if (s["dx_ssid"] != null) {
dx_call = dx_call + "-" + s["dx_ssid"];
}
// Format DX flag
var dx_flag = "<i class='fa-solid fa-globe-africa'></i>";
if (s["dx_flag"] && s["dx_flag"] != null && s["dx_flag"] != "") {
let dx_flag = "<i class='fa-solid fa-globe-africa'></i>";
if (dx_call == null) {
dx_flag = "";
}
if (s["dx_flag"] && s["dx_flag"] != null && s["dx_flag"] !== "") {
dx_flag = s["dx_flag"];
}
// Format the frequency
var freq_string = "Unknown"
let freq_string = "Unknown";
if (s["freq"] != null) {
var mhz = Math.floor(s["freq"] / 1000000.0);
var khz = Math.floor((s["freq"] - (mhz * 1000000.0)) / 1000.0);
var hz = Math.floor(s["freq"] - (mhz * 1000000.0) - (khz * 1000.0));
var hz_string = (hz > 0) ? hz.toFixed(0)[0] : "";
const mhz = Math.floor(s["freq"] / 1000000.0);
const khz = Math.floor((s["freq"] - (mhz * 1000000.0)) / 1000.0);
const hz = Math.floor(s["freq"] - (mhz * 1000000.0) - (khz * 1000.0));
const hz_string = (hz > 0) ? hz.toFixed(0)[0] : "";
freq_string = `<span class='freq-mhz freq-mhz-pad'>${mhz.toFixed(0)}</span><span class='freq-khz'>${khz.toFixed(0).padStart(3, '0')}</span><span class='freq-hz hideonmobile'>${hz_string}</span>`
}
// Format comment
var commentText = "";
let commentText = "";
if (s["comment"] != null) {
commentText = escapeHtml(s["comment"]);
}
// Sig or fallback to source
var sigSourceText = s["source"];
let sigSourceText = s["source"];
if (s["sig"]) {
sigSourceText = s["sig"];
}
// Format sig_refs
var sig_refs = "";
let sig_refs = "";
if (s["sig_refs"] != null) {
var items = []
for (var i = 0; i < s["sig_refs"].length; i++) {
const items = [];
for (let i = 0; i < s["sig_refs"].length; i++) {
if (s["sig_refs"][i]["url"] != null) {
items[i] = `<a href='${s["sig_refs"][i]["url"]}' title='${s["sig_refs"][i]["name"]}' target='_new' class='sig-ref-link'>${s["sig_refs"][i]["id"]}</a>`
} else {
@@ -162,7 +164,7 @@ function getTooltipText(s) {
}
// DX
ttt = `<span class='nowrap'><span class='icon-wrapper'>${dx_flag}</span> <a href='https://www.qrz.com/db/${dx_call}' target='_blank' class="dx-link">${dx_call}</a></span><br/>`;
let ttt = `<span class='nowrap'><span class='icon-wrapper'>${dx_flag}</span> <a href='https://www.qrz.com/db/${dx_call}' target='_blank' class="dx-link">${dx_call}</a></span><br/>`;
// Frequency & band
ttt += `<span class='icon-wrapper'><i class='fa-solid fa-radio markerPopupIcon'></i></span>&nbsp;${freq_string}`;
@@ -273,7 +275,7 @@ function setBasemap(basemapname) {
backgroundTileLayer.addTo(map);
backgroundTileLayer.bringToBack();
if (basemapname === "OpenStreetMap.Mapnik.Dark") {
var container = backgroundTileLayer.getContainer();
const container = backgroundTileLayer.getContainer();
if (container) {
container.style.filter = 'invert(100%) hue-rotate(180deg) brightness(80%)';
}
@@ -409,7 +411,7 @@ function setUpMap() {
backgroundTileLayer.addTo(map);
backgroundTileLayer.bringToBack();
if (loadedBasemap === "OpenStreetMap.Mapnik.Dark") {
var container = backgroundTileLayer.getContainer();
const container = backgroundTileLayer.getContainer();
if (container) {
container.style.filter = 'invert(100%) hue-rotate(180deg) brightness(80%)';
}

View File

@@ -43,7 +43,7 @@ function startSSEConnection() {
evtSource.onmessage = function(event) {
// Get the new spot
newSpot = JSON.parse(event.data);
const newSpot = JSON.parse(event.data);
// Awful fudge to ensure new incoming spots at the top of the list don't have timestamps that make them look
// like they belong further down the list. If the spot is older than the latest one we already have, bump its
// time up to match it. This isn't great but since we poll spot providers every 2 minutes anyway, it shouldn't
@@ -63,7 +63,7 @@ function startSSEConnection() {
}
// If we had zero spots before (i.e. one now), the table will have a "No spots" row that we need to remove now
// that we have one.
if (spots.length == 1) {
if (spots.length === 1) {
$("#table tbody tr").last().remove();
}
@@ -76,7 +76,7 @@ function startSSEConnection() {
}
};
evtSource.onerror = function(err) {
evtSource.onerror = function() {
if (evtSource != null) {
evtSource.close();
}
@@ -87,14 +87,14 @@ function startSSEConnection() {
// Build a query string for the API, based on the filters that the user has selected.
function buildQueryString(includeCredentials) {
var str = "?";
let str = "?";
["dx_continent", "de_continent", "mode", "source", "band", "sig"].forEach(fn => {
if (!allFilterOptionsSelected(fn)) {
str = str + getQueryStringFor(fn) + "&";
}
});
str = str + "limit=" + $("#spots-to-fetch option:selected").val();
if ($("#search").val() != "") {
if ($("#search").val() !== "") {
str = str + "&text_includes=" + encodeURIComponent($("#search").val());
}
if (includeCredentials) {
@@ -106,22 +106,22 @@ function buildQueryString(includeCredentials) {
// Update the spots table
function updateTable() {
// Use local time instead of UTC?
var useLocalTime = $("#timeZone")[0].value == "local";
const useLocalTime = $("#timeZone")[0].value === "local";
// Get user grid if valid, this will be null if it's not.
var userPos = latLonForGridCentre($("#userGrid").val());
const userPos = latLonForGridCentre($("#userGrid").val());
// Table data toggles
var showTime = $("#tableShowTime")[0].checked;
var showDX = $("#tableShowDX")[0].checked;
var showFreq = $("#tableShowFreq")[0].checked;
var showMode = $("#tableShowMode")[0].checked;
var showComment = $("#tableShowComment")[0].checked;
var showBearing = $("#tableShowBearing")[0].checked && userPos != null;
var showType = $("#tableShowType")[0].checked;
var showRef = $("#tableShowRef")[0].checked;
var showDE = $("#tableShowDE")[0].checked;
var showWorkedCheckbox = $("#tableShowWorkedCheckbox")[0].checked;
const showTime = $("#tableShowTime")[0].checked;
const showDX = $("#tableShowDX")[0].checked;
const showFreq = $("#tableShowFreq")[0].checked;
const showMode = $("#tableShowMode")[0].checked;
const showComment = $("#tableShowComment")[0].checked;
const showBearing = $("#tableShowBearing")[0].checked && userPos != null;
const showType = $("#tableShowType")[0].checked;
const showRef = $("#tableShowRef")[0].checked;
const showDE = $("#tableShowDE")[0].checked;
const showWorkedCheckbox = $("#tableShowWorkedCheckbox")[0].checked;
// Populate table with headers
let table = $("#table");
@@ -158,7 +158,7 @@ function updateTable() {
}
table.find('tbody').empty();
if (spots.length == 0) {
if (spots.length === 0) {
table.find('tbody').append('<tr class="bg-danger-subtle"><td colspan="100" style="text-align:center;">No spots match your filters.</td></tr>');
}
@@ -182,22 +182,22 @@ function addSpotToTopOfTable(s, highlightNew) {
// highlightNew = false for an initial load, true for new SSE-loaded spots
function createNewTableRowsForSpot(s, highlightNew) {
// Use local time instead of UTC?
var useLocalTime = $("#timeZone")[0].value == "local";
const useLocalTime = $("#timeZone")[0].value === "local";
// Get user grid if valid, this will be null if it's not.
var userPos = latLonForGridCentre($("#userGrid").val());
const userPos = latLonForGridCentre($("#userGrid").val());
// Table data toggles
var showTime = $("#tableShowTime")[0].checked;
var showDX = $("#tableShowDX")[0].checked;
var showFreq = $("#tableShowFreq")[0].checked;
var showMode = $("#tableShowMode")[0].checked;
var showComment = $("#tableShowComment")[0].checked;
var showBearing = $("#tableShowBearing")[0].checked && userPos != null;
var showType = $("#tableShowType")[0].checked;
var showRef = $("#tableShowRef")[0].checked;
var showDE = $("#tableShowDE")[0].checked;
var showWorkedCheckbox = $("#tableShowWorkedCheckbox")[0].checked;
const showTime = $("#tableShowTime")[0].checked;
const showDX = $("#tableShowDX")[0].checked;
const showFreq = $("#tableShowFreq")[0].checked;
const showMode = $("#tableShowMode")[0].checked;
const showComment = $("#tableShowComment")[0].checked;
const showBearing = $("#tableShowBearing")[0].checked && userPos != null;
const showType = $("#tableShowType")[0].checked;
const showRef = $("#tableShowRef")[0].checked;
const showDE = $("#tableShowDE")[0].checked;
const showWorkedCheckbox = $("#tableShowWorkedCheckbox")[0].checked;
// Create row
let $tr = $('<tr>');
@@ -205,13 +205,13 @@ function createNewTableRowsForSpot(s, highlightNew) {
// 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) {
if (rowCount % 2 === 1) {
$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 (s["qrt"] === true || alreadyWorkedThis) {
$tr.addClass("table-faded");
}
@@ -222,65 +222,67 @@ function createNewTableRowsForSpot(s, highlightNew) {
}
// Format a UTC or local time for display
var time = moment.unix(s["time"]).utc();
const time = moment.unix(s["time"]).utc();
if (useLocalTime) {
time.local();
}
var time_formatted = time.format("HH:mm");
const time_formatted = time.format("HH:mm");
// Format DX call
var dx_call = s["dx_call"];
let dx_call = s["dx_call"];
if (dx_call == null) {
dx_call = "";
dx_flag = "";
}
if (s["dx_ssid"] != null) {
dx_call = dx_call + "-" + s["dx_ssid"];
}
// Format dx country
var dx_country = s["dx_country"];
let dx_country = s["dx_country"];
if (dx_country == null) {
dx_country = "Unknown or not a country";
}
// Format DX flag
var dx_flag = "<i class='fa-solid fa-globe-africa'></i>";
if (s["dx_dxcc_id"] && s["dx_dxcc_id"] != null && s["dx_dxcc_id"] != 0) {
let dx_flag = "<i class='fa-solid fa-globe-africa'></i>";
if (dx_call == null) {
dx_flag = "";
}
if (s["dx_dxcc_id"] && s["dx_dxcc_id"] != null && s["dx_dxcc_id"] !== 0) {
dx_flag = `<img src="img/flags/${s['dx_dxcc_id']}.png" class="flag" width="24" alt="${dx_country}" title="${dx_country}"/>`;
}
// Format the frequency
var freq_string = "Unknown"
let freq_string = "Unknown";
if (s["freq"] != null) {
var mhz = Math.floor(s["freq"] / 1000000.0);
var khz = Math.floor((s["freq"] - (mhz * 1000000.0)) / 1000.0);
var hz = Math.floor(s["freq"] - (mhz * 1000000.0) - (khz * 1000.0));
var hz_string = (hz > 0) ? hz.toFixed(0)[0] : "";
const mhz = Math.floor(s["freq"] / 1000000.0);
const khz = Math.floor((s["freq"] - (mhz * 1000000.0)) / 1000.0);
const hz = Math.floor(s["freq"] - (mhz * 1000000.0) - (khz * 1000.0));
const hz_string = (hz > 0) ? hz.toFixed(0)[0] : "";
freq_string = `<span class='freq-mhz freq-mhz-pad'>${mhz.toFixed(0)}</span><span class='freq-khz'>${khz.toFixed(0).padStart(3, '0')}</span><span class='freq-hz hideonmobile'>${hz_string}</span>`
}
// Format the mode
mode_string = s["mode"];
let mode_string = s["mode"];
if (s["mode"] == null) {
mode_string = "";
} else if (s["mode_source"] == "BANDPLAN") {
} else if (s["mode_source"] === "BANDPLAN") {
mode_string = mode_string + "<span class='mode-q hideonmobile'><i class='fa-solid fa-circle-question' title='The mode was not reported via the spotting service. This is a guess based on the frequency.'></i></span>";
}
// Format comment
var commentText = "";
let commentText = "";
if (s["comment"] != null) {
commentText = escapeHtml(s["comment"]);
}
// Format bearing text
var bearingText = "---<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service, and we could not determine one. A bearing to this DX is not available.'></i></span>";
let bearingText = "---<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service, and we could not determine one. A bearing to this DX is not available.'></i></span>";
if (userPos != null && s["dx_latitude"] != null && s["dx_longitude"] != null) {
var bearing = calcBearing(userPos[0], userPos[1], s["dx_latitude"], s["dx_longitude"]);
const bearing = calcBearing(userPos[0], userPos[1], s["dx_latitude"], s["dx_longitude"]);
bearingText = bearing.toFixed(0).padStart(3, '0') + "°";
if (s["dx_location_good"] == null || s["dx_location_good"] == false) {
if (s["dx_location_source"] == "HOME QTH") {
if (s["dx_location_good"] == null || s["dx_location_good"] === false) {
if (s["dx_location_source"] === "HOME QTH") {
bearingText = bearingText + "<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service. We had to fall back to a QRZ \"home\" location for a portable/mobile/alternative spot, so this bearing may not be accurate if the DX is close to you..'></i></span>";
} else {
bearingText = bearingText + "<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service. We had to fall back to just using the centre of a DXCC entity, so this bearing may not be accurate if the DX is close to you.'></i></span>";
@@ -289,16 +291,16 @@ function createNewTableRowsForSpot(s, highlightNew) {
}
// Format "type" (Sig or fallback to source)
var typeText = s["source"];
let typeText = s["source"];
if (s["sig"]) {
typeText = s["sig"];
}
// Format sig_refs
var sig_refs = "";
let sig_refs = "";
if (s["sig_refs"] != null) {
var items = []
for (var i = 0; i < s["sig_refs"].length; i++) {
const items = [];
for (let i = 0; i < s["sig_refs"].length; i++) {
if (s["sig_refs"][i]["url"] != null) {
items[i] = `<span style="white-space: nowrap;"><a href='${encodeURI(s["sig_refs"][i]["url"])}' title='${escapeHtml(s["sig_refs"][i]["name"])}' target='_new' class='sig-ref-link'>${escapeHtml(s["sig_refs"][i]["id"])}</a></span>`
} else {
@@ -309,19 +311,19 @@ function createNewTableRowsForSpot(s, highlightNew) {
}
// Format de country
var de_country = s["de_country"];
let de_country = s["de_country"];
if (de_country == null) {
de_country = "Unknown or not a country";
}
// Format DE flag
var de_flag = "<i class='fa-solid fa-circle-question'></i>";
if (s["de_dxcc_id"] && s["de_dxcc_id"] != null && s["de_dxcc_id"] != 0) {
let de_flag = "<i class='fa-solid fa-circle-question'></i>";
if (s["de_dxcc_id"] && s["de_dxcc_id"] != null && s["de_dxcc_id"] !== 0) {
de_flag = `<img src="img/flags/${s['de_dxcc_id']}.png" class="flag" width="24" alt="${de_country}" title="${de_country}"/>`;
}
// Format de call
var de_call = s["de_call"];
let de_call = s["de_call"];
if (de_call == null) {
de_call = "";
de_flag = "";
@@ -331,10 +333,10 @@ function createNewTableRowsForSpot(s, highlightNew) {
}
// Format band name
var bandFullName = s['band'] ? s['band'] + " band": "Unknown band";
const 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"});" title="Check this box to record that you have worked this callsign on their current band and mode.">`;
const workedCheckbox = `<input type="checkbox" ${alreadyWorkedThis ? "checked" : ""} onClick="setWorkedState('${s['dx_call']}', '${s['band']}', '${s['mode']}', ${alreadyWorkedThis ? "false" : "true"});" title="Check this box to record that you have worked this callsign on their current band and mode.">`;
// Populate the row
if (showTime) {
@@ -369,21 +371,21 @@ function createNewTableRowsForSpot(s, highlightNew) {
}
// Second row for mobile view only, containing type, ref & comment
$tr2 = $("<tr class='hidenotonmobile'>");
const $tr2 = $("<tr class='hidenotonmobile'>");
// Apply styles as per the first row
if (rowCount % 2 == 1) {
if (rowCount % 2 === 1) {
$tr2.addClass("table-active");
}
if (s["qrt"] == true || alreadyWorkedThis) {
if (s["qrt"] === true || alreadyWorkedThis) {
$tr2.addClass("table-faded");
}
if (highlightNew) {
$tr2.addClass("new");
}
$td2 = $("<td colspan='100'>");
$td2floatleft = $(`<div style="float: left;">`);
const $td2 = $("<td colspan='100'>");
const $td2floatleft = $(`<div style="float: left;">`);
if (showType) {
$td2floatleft.append(`<span class='icon-wrapper'><i class='fa-solid ${sigToIcon(s["sig"], "fa-tower-cell")}'></i></span> ${typeText} `);
}
@@ -391,7 +393,7 @@ function createNewTableRowsForSpot(s, highlightNew) {
$td2floatleft.append(`${sig_refs} `);
}
$td2.append($td2floatleft);
$td2floatright = $(`<div style="float: right;">`);
const $td2floatright = $(`<div style="float: right;">`);
if (showBearing) {
$td2floatright.append(`${bearingText} &nbsp;`);
}
@@ -459,13 +461,13 @@ function loadOptions() {
// Work out if the user's entered grid is a valid Maidenhead grid
function isUserGridValid() {
userGrid = $("#userGrid").val().toUpperCase();
const userGrid = $("#userGrid").val().toUpperCase();
return latLonForGridCentre(userGrid) != null;
}
// Method called when the user's grid input is changed.
function userGridUpdated() {
var userGridValid = isUserGridValid();
const userGridValid = isUserGridValid();
if (userGridValid) {
updateTable();
}

View File

@@ -1,5 +1,5 @@
// Storage for the spot data that the server gives us.
var spots = []
let 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
@@ -9,9 +9,9 @@ let worked = []
// Dynamically add CSS code for the band checkboxes to show in the appropriate colour.
// Some band names contain decimal points which are not allowed in CSS classes, so we text-replace them to "p".
function addBandToggleColourCSS(band_options) {
var $style = $('<style>');
const $style = $('<style>');
band_options.forEach(o => {
var domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
$style.append(`#filter-button-label-band-${domSafeName} { padding-left: 0.3em; border-left: 5px solid ${bandToColor(o['name'])};}`);
});
$('html > head').append($style);
@@ -19,9 +19,9 @@ function addBandToggleColourCSS(band_options) {
// Generate bands filter card. This one is a special case.
function generateBandsMultiToggleFilterCard(band_options) {
var $grid = $('<div class="row row-cols-3 row-cols-md-2 row-cols-lg-3 row-cols-xxl-4 g-1 mb-1">');
const $grid = $('<div class="row row-cols-3 row-cols-md-2 row-cols-lg-3 row-cols-xxl-4 g-1 mb-1">');
band_options.forEach(o => {
var domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
$grid.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-band storeable-checkbox" id="filter-button-band-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked> <label class="form-check-label" id="filter-button-label-band-${domSafeName}" for="filter-button-band-${domSafeName}">${o['name']}</label></div></div>`);
});
$("#band-options").append($grid);
@@ -40,9 +40,9 @@ function setHamHFBandToggles() {
// Generate SIGs filter card. This one is also a special case.
function generateSIGsMultiToggleFilterCard(sig_options) {
var $grid = $('<div class="row row-cols-2 row-cols-xxl-3 g-1 mb-1">');
const $grid = $('<div class="row row-cols-2 row-cols-xxl-3 g-1 mb-1">');
sig_options.forEach(o => {
var domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
$grid.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${sigToIcon(o['name'], 'fa-tower-cell')}"></i> ${o['name']}</label></div></div>`);
});
// Bonus "NO_SIG" / "General DX" option
@@ -53,9 +53,9 @@ function generateSIGsMultiToggleFilterCard(sig_options) {
// Generate modes filter card. This one is also a special case.
function generateModesMultiToggleFilterCard(mode_options) {
var $grid = $('<div class="row row-cols-3 row-cols-md-2 row-cols-lg-3 g-1 mb-1">');
const $grid = $('<div class="row row-cols-3 row-cols-md-2 row-cols-lg-3 g-1 mb-1">');
mode_options.forEach(o => {
var domSafeName = o.replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
const domSafeName = o.replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
$grid.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-mode storeable-checkbox" id="filter-button-mode-${domSafeName}" value="${o}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-mode-${domSafeName}" for="filter-button-mode-${domSafeName}">${o}</label></div></div>`);
});
$("#mode-options").append($grid);
@@ -84,10 +84,10 @@ function setDigiModeToggles() {
// set which ones are enabled by default based on config rather than having them all enabled by default. We also sanitise
// names here for HTML elements.
function generateSourcesMultiToggleFilterCard(source_options, sources_enabled_by_default) {
var $grid = $('<div class="row row-cols-2 row-cols-xxl-3 g-1 mb-1">');
const $grid = $('<div class="row row-cols-2 row-cols-xxl-3 g-1 mb-1">');
source_options.forEach(o => {
var enable = sources_enabled_by_default.includes(o);
var domSafeName = o.replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
const enable = sources_enabled_by_default.includes(o);
const domSafeName = o.replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
$grid.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-source storeable-checkbox" id="filter-button-source-${domSafeName}" value="${o}" autocomplete="off" onClick="filtersUpdated()" ${enable ? "checked" : ""}><label class="form-check-label" for="filter-button-source-${domSafeName}">${o}</label></div></div>`);
});
$("#source-options").append($grid);
@@ -115,7 +115,7 @@ function alreadyWorked(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
// after some time has passed with it in the background.
addEventListener("visibilitychange", (event) => {
addEventListener("visibilitychange", () => {
if (!document.hidden) {
loadSpots();
}
@@ -124,7 +124,7 @@ addEventListener("visibilitychange", (event) => {
// Startup
$(document).ready(function() {
// Load worked list
var tmpWorked = JSON.parse(localStorage.getItem("worked"));
const tmpWorked = JSON.parse(localStorage.getItem("worked"));
if (tmpWorked) {
worked = tmpWorked;
}

View File

@@ -296,16 +296,6 @@ function setBandColorScheme(scheme) {
return ret;
}
// Get the list of known bands
function getKnownBands() {
return Array.from(Object.keys(BAND_COLOR_SCHEMES[bandColorScheme]));
}
// Get the list of available band colour schemes
function getAvailableBandColorSchemes() {
return Array.from(Object.keys(BAND_COLOR_SCHEMES));
}
// Band name to colour (in the current colour scheme). If the band is unknown, black will be returned.
function bandToColor(band) {
let col = (band != null) ? BAND_COLOR_SCHEMES[bandColorScheme][band] : null;
@@ -324,22 +314,6 @@ function bandToContrastColor(band) {
return (lum > 128) ? "#000000" : "#ffffff";
}
const MODE_TYPE_COLOR_SCHEMES = {
"CW": "red",
"PHONE": "green",
"DATA": "blue"
}
// Mode type (CW, PHONE, DATA) to colour. If the mode type is unknown, black will be returned.
function modeTypeToColor(modeType) {
let col = (modeType != null) ? MODE_TYPE_COLOR_SCHEMES[modeType.toUpperCase()] : null;
if (col) {
return col;
} else {
return "#000000";
}
}
const SIG_ICONS = {
"POTA": "fa-tree",
"SOTA": "fa-mountain-sun",
@@ -365,31 +339,6 @@ const SIG_ICONS = {
"TOTA": "fa-toilet"
}
const SIG_NAMES = {
"POTA": "Parks on the Air",
"SOTA": "Summits on the Air",
"WWFF": "Worldwide Flora & Fauna",
"GMA": "Global Mountain Activity",
"WWBOTA": "Bunkers on the Air",
"HEMA": "Humps Excluding Marilyns Award",
"IOTA": "Islands on the Air",
"MOTA": "Mills on the Air",
"ARLHS": "Amateur Radio Lighthouse Society",
"ILLW": "International Lighthouse Lightship Weekend",
"SIOTA": "Silos on the Air",
"WCA": "World Castles Award",
"ZLOTA": "New Zealand on the Air",
"WOTA": "Wainwrights on the Air",
"BOTA": "Beaches on the Air",
"KRMNPA": "Keith Roget Memorial National Parks Award",
"LLOTA": "Lagos y Lagunas on the Air",
"WWTOTA": "Towers on the Air",
"WAB": "Worked All Britain",
"WAI": "Worked All Ireland",
"Tiles": "Tiles on the Air",
"TOTA": "Toilets on the Air"
}
// Get the Font Awesome icon for a given SIG. If the SIG is unknown, the provided default symbol will be returned
function sigToIcon(sig, defaultIcon) {
let col = (sig != null) ? SIG_ICONS[sig] : null;
@@ -403,36 +352,4 @@ function sigToIcon(sig, defaultIcon) {
return defaultIcon;
}
}
}
// Get the full name for a given SIG abbreviation. If the SIG is unknown, an empty string will be returned.
function sigToName(sig) {
let col = (sig != null) ? SIG_NAMES[sig] : null;
if (col) {
return col;
} else {
let col = (sig != null) ? SIG_NAMES[sig.toUpperCase()] : null;
if (col) {
return col;
} else {
return "";
}
}
}
// Get the list of known SIGs
function getKnownSIGs() {
return Array.from(Object.keys(SIG_ICONS));
}
// Format a Maidenhead grid with alternating alphabetic blocks in lower case
function formatGrid(grid) {
grid = grid.toUpperCase();
if (grid.length >= 6) {
grid = grid.substring(0, 4) + grid.substring(4, 6).toLowerCase() + grid.substring(6);
}
if (grid.length >= 12) {
grid = grid.substring(0, 10) + grid.substring(10, 12).toLowerCase() + grid.substring(14);
}
return grid;
}
}