6 Commits

Author SHA1 Message Date
Ian Renton
ac9e2ff054 Defensive coding 2025-12-15 12:26:41 +00:00
Ian Renton
6eaaca3a6f Up poll interval because TOTA activations are quick 2025-12-15 12:22:39 +00:00
Ian Renton
097c75eadd Improve SIG Ref lookup 2025-12-15 12:13:41 +00:00
Ian Renton
27db248398 39C3 TOTA URL 2025-12-15 12:13:32 +00:00
Ian Renton
b00b4130c5 PWA style tweaks 2025-11-30 18:33:49 +00:00
Ian Renton
b3be6b5ca4 Hacky attempt to force browsers to invalidate caches of JS files 2025-11-30 17:55:35 +00:00
18 changed files with 83 additions and 68 deletions

View File

@@ -85,7 +85,7 @@ spot-providers:
class: "XOTA" class: "XOTA"
name: "39C3 TOTA" name: "39C3 TOTA"
enabled: false enabled: false
url: "https://39c3.c3nav.de/" url: "https://dev.39c3.totawatch.de/"
# Fixed SIG/latitude/longitude for all spots from a provider is currently only a feature for the "XOTA" provider, # Fixed SIG/latitude/longitude for all spots from a provider is currently only a feature for the "XOTA" provider,
# the software found at https://github.com/nischu/xOTA/. This is because this is a generic backend for xOTA # the software found at https://github.com/nischu/xOTA/. This is because this is a generic backend for xOTA
# programmes and so different URLs provide different programmes. # programmes and so different URLs provide different programmes.

View File

@@ -6,7 +6,6 @@ from pyhamtools.locator import latlong_to_locator
from core.cache_utils import SEMI_STATIC_URL_DATA_CACHE from core.cache_utils import SEMI_STATIC_URL_DATA_CACHE
from core.constants import SIGS, HTTP_HEADERS from core.constants import SIGS, HTTP_HEADERS
from core.geo_utils import wab_wai_square_to_lat_lon from core.geo_utils import wab_wai_square_to_lat_lon
from data.sig_ref import SIGRef
# Utility function to get the icon for a named SIG. If no match is found, the "circle-question" icon will be returned. # Utility function to get the icon for a named SIG. If no match is found, the "circle-question" icon will be returned.
@@ -25,46 +24,51 @@ def get_ref_regex_for_sig(sig):
return None return None
# Look up details of a SIG reference (e.g. POTA park) such as name, lat/lon, and grid. # Look up details of a SIG reference (e.g. POTA park) such as name, lat/lon, and grid. Takes in a sig_ref object which
# must at minimum have a "sig" and an "id". The rest of the object will be populated and returned.
# Note there is currently no support for KRMNPA location lookup, see issue #61. # Note there is currently no support for KRMNPA location lookup, see issue #61.
def get_sig_ref_info(sig, sig_ref_id): def populate_sig_ref_info(sig_ref):
sig_ref = SIGRef(id=sig_ref_id, sig=sig) if sig_ref.sig is None or sig_ref.id is None:
logging.warn("Failed to look up sig_ref info, sig or id were not set.")
sig = sig_ref.sig
ref_id = sig_ref.id
try: try:
if sig.upper() == "POTA": if sig.upper() == "POTA":
data = SEMI_STATIC_URL_DATA_CACHE.get("https://api.pota.app/park/" + sig_ref_id, headers=HTTP_HEADERS).json() data = SEMI_STATIC_URL_DATA_CACHE.get("https://api.pota.app/park/" + ref_id, headers=HTTP_HEADERS).json()
if data: if data:
fullname = data["name"] if "name" in data else None fullname = data["name"] if "name" in data else None
if fullname and "parktypeDesc" in data and data["parktypeDesc"] != "": if fullname and "parktypeDesc" in data and data["parktypeDesc"] != "":
fullname = fullname + " " + data["parktypeDesc"] fullname = fullname + " " + data["parktypeDesc"]
sig_ref.name = fullname sig_ref.name = fullname
sig_ref.url = "https://pota.app/#/park/" + sig_ref_id sig_ref.url = "https://pota.app/#/park/" + ref_id
sig_ref.grid = data["grid6"] if "grid6" in data else None sig_ref.grid = data["grid6"] if "grid6" in data else None
sig_ref.latitude = data["latitude"] if "latitude" in data else None sig_ref.latitude = data["latitude"] if "latitude" in data else None
sig_ref.longitude = data["longitude"] if "longitude" in data else None sig_ref.longitude = data["longitude"] if "longitude" in data else None
elif sig.upper() == "SOTA": elif sig.upper() == "SOTA":
data = SEMI_STATIC_URL_DATA_CACHE.get("https://api-db2.sota.org.uk/api/summits/" + sig_ref_id, data = SEMI_STATIC_URL_DATA_CACHE.get("https://api-db2.sota.org.uk/api/summits/" + ref_id,
headers=HTTP_HEADERS).json() headers=HTTP_HEADERS).json()
if data: if data:
sig_ref.name = data["name"] if "name" in data else None sig_ref.name = data["name"] if "name" in data else None
sig_ref.url = "https://www.sotadata.org.uk/en/summit/" + sig_ref_id sig_ref.url = "https://www.sotadata.org.uk/en/summit/" + ref_id
sig_ref.grid = data["locator"] if "locator" in data else None sig_ref.grid = data["locator"] if "locator" in data else None
sig_ref.latitude = data["latitude"] if "latitude" in data else None sig_ref.latitude = data["latitude"] if "latitude" in data else None
sig_ref.longitude = data["longitude"] if "longitude" in data else None sig_ref.longitude = data["longitude"] if "longitude" in data else None
elif sig.upper() == "WWBOTA": elif sig.upper() == "WWBOTA":
data = SEMI_STATIC_URL_DATA_CACHE.get("https://api.wwbota.org/bunkers/" + sig_ref_id, data = SEMI_STATIC_URL_DATA_CACHE.get("https://api.wwbota.org/bunkers/" + ref_id,
headers=HTTP_HEADERS).json() headers=HTTP_HEADERS).json()
if data: if data:
sig_ref.name = data["name"] if "name" in data else None sig_ref.name = data["name"] if "name" in data else None
sig_ref.url = "https://bunkerwiki.org/?s=" + sig_ref_id if sig_ref_id.startswith("B/G") else None sig_ref.url = "https://bunkerwiki.org/?s=" + ref_id if ref_id.startswith("B/G") else None
sig_ref.grid = data["locator"] if "locator" in data else None sig_ref.grid = data["locator"] if "locator" in data else None
sig_ref.latitude = data["lat"] if "lat" in data else None sig_ref.latitude = data["lat"] if "lat" in data else None
sig_ref.longitude = data["long"] if "long" in data else None sig_ref.longitude = data["long"] if "long" in data else None
elif sig.upper() == "GMA" or sig.upper() == "ARLHS" or sig.upper() == "ILLW" or sig.upper() == "WCA" or sig.upper() == "MOTA" or sig.upper() == "IOTA": elif sig.upper() == "GMA" or sig.upper() == "ARLHS" or sig.upper() == "ILLW" or sig.upper() == "WCA" or sig.upper() == "MOTA" or sig.upper() == "IOTA":
data = SEMI_STATIC_URL_DATA_CACHE.get("https://www.cqgma.org/api/ref/?" + sig_ref_id, data = SEMI_STATIC_URL_DATA_CACHE.get("https://www.cqgma.org/api/ref/?" + ref_id,
headers=HTTP_HEADERS).json() headers=HTTP_HEADERS).json()
if data: if data:
sig_ref.name = data["name"] if "name" in data else None sig_ref.name = data["name"] if "name" in data else None
sig_ref.url = "https://www.cqgma.org/zinfo.php?ref=" + sig_ref_id sig_ref.url = "https://www.cqgma.org/zinfo.php?ref=" + ref_id
sig_ref.grid = data["locator"] if "locator" in data else None sig_ref.grid = data["locator"] if "locator" in data else None
sig_ref.latitude = data["latitude"] if "latitude" in data else None sig_ref.latitude = data["latitude"] if "latitude" in data else None
sig_ref.longitude = data["longitude"] if "longitude" in data else None sig_ref.longitude = data["longitude"] if "longitude" in data else None
@@ -73,9 +77,9 @@ def get_sig_ref_info(sig, sig_ref_id):
headers=HTTP_HEADERS) headers=HTTP_HEADERS)
wwff_dr = csv.DictReader(wwff_csv_data.content.decode().splitlines()) wwff_dr = csv.DictReader(wwff_csv_data.content.decode().splitlines())
for row in wwff_dr: for row in wwff_dr:
if row["reference"] == sig_ref_id: if row["reference"] == ref_id:
sig_ref.name = row["name"] if "name" in row else None sig_ref.name = row["name"] if "name" in row else None
sig_ref.url = "https://wwff.co/directory/?showRef=" + sig_ref_id sig_ref.url = "https://wwff.co/directory/?showRef=" + ref_id
sig_ref.grid = row["iaruLocator"] if "iaruLocator" in row else None sig_ref.grid = row["iaruLocator"] if "iaruLocator" in row else None
sig_ref.latitude = float(row["latitude"]) if "latitude" in row else None sig_ref.latitude = float(row["latitude"]) if "latitude" in row else None
sig_ref.longitude = float(row["longitude"]) if "longitude" in row else None sig_ref.longitude = float(row["longitude"]) if "longitude" in row else None
@@ -85,7 +89,7 @@ def get_sig_ref_info(sig, sig_ref_id):
headers=HTTP_HEADERS) headers=HTTP_HEADERS)
siota_dr = csv.DictReader(siota_csv_data.content.decode().splitlines()) siota_dr = csv.DictReader(siota_csv_data.content.decode().splitlines())
for row in siota_dr: for row in siota_dr:
if row["SILO_CODE"] == sig_ref_id: if row["SILO_CODE"] == ref_id:
sig_ref.name = row["NAME"] if "NAME" in row else None sig_ref.name = row["NAME"] if "NAME" in row else None
sig_ref.grid = row["LOCATOR"] if "LOCATOR" in row else None sig_ref.grid = row["LOCATOR"] if "LOCATOR" in row else None
sig_ref.latitude = float(row["LAT"]) if "LAT" in row else None sig_ref.latitude = float(row["LAT"]) if "LAT" in row else None
@@ -96,13 +100,13 @@ def get_sig_ref_info(sig, sig_ref_id):
headers=HTTP_HEADERS).json() headers=HTTP_HEADERS).json()
if data: if data:
for feature in data["features"]: for feature in data["features"]:
if feature["properties"]["wotaId"] == sig_ref_id: if feature["properties"]["wotaId"] == ref_id:
sig_ref.name = feature["properties"]["title"] sig_ref.name = feature["properties"]["title"]
# Fudge WOTA URLs. Outlying fell (LDO) URLs don't match their ID numbers but require 214 to be # Fudge WOTA URLs. Outlying fell (LDO) URLs don't match their ID numbers but require 214 to be
# added to them # added to them
sig_ref.url = "https://www.wota.org.uk/MM_" + sig_ref_id sig_ref.url = "https://www.wota.org.uk/MM_" + ref_id
if sig_ref_id.upper().startswith("LDO-"): if ref_id.upper().startswith("LDO-"):
number = int(sig_ref_id.upper().replace("LDO-", "")) number = int(ref_id.upper().replace("LDO-", ""))
sig_ref.url = "https://www.wota.org.uk/MM_LDO-" + str(number + 214) sig_ref.url = "https://www.wota.org.uk/MM_LDO-" + str(number + 214)
sig_ref.grid = feature["properties"]["qthLocator"] sig_ref.grid = feature["properties"]["qthLocator"]
sig_ref.latitude = feature["geometry"]["coordinates"][1] sig_ref.latitude = feature["geometry"]["coordinates"][1]
@@ -112,9 +116,9 @@ def get_sig_ref_info(sig, sig_ref_id):
data = SEMI_STATIC_URL_DATA_CACHE.get("https://ontheair.nz/assets/assets.json", headers=HTTP_HEADERS).json() data = SEMI_STATIC_URL_DATA_CACHE.get("https://ontheair.nz/assets/assets.json", headers=HTTP_HEADERS).json()
if data: if data:
for asset in data: for asset in data:
if asset["code"] == sig_ref_id: if asset["code"] == ref_id:
sig_ref.name = asset["name"] sig_ref.name = asset["name"]
sig_ref.url = "https://ontheair.nz/assets/ZLI_OT-030" + sig_ref_id.replace("/", "_") sig_ref.url = "https://ontheair.nz/assets/ZLI_OT-030" + ref_id.replace("/", "_")
sig_ref.grid = latlong_to_locator(asset["y"], asset["x"], 6) sig_ref.grid = latlong_to_locator(asset["y"], asset["x"], 6)
sig_ref.latitude = asset["y"] sig_ref.latitude = asset["y"]
sig_ref.longitude = asset["x"] sig_ref.longitude = asset["x"]
@@ -124,14 +128,14 @@ def get_sig_ref_info(sig, sig_ref_id):
sig_ref.name = sig_ref.id sig_ref.name = sig_ref.id
sig_ref.url = "https://www.beachesontheair.com/beaches/" + sig_ref.name.lower().replace(" ", "-") sig_ref.url = "https://www.beachesontheair.com/beaches/" + sig_ref.name.lower().replace(" ", "-")
elif sig.upper() == "WAB" or sig.upper() == "WAI": elif sig.upper() == "WAB" or sig.upper() == "WAI":
ll = wab_wai_square_to_lat_lon(sig_ref_id) ll = wab_wai_square_to_lat_lon(ref_id)
if ll: if ll:
sig_ref.name = sig_ref_id sig_ref.name = ref_id
sig_ref.grid = latlong_to_locator(ll[0], ll[1], 6) sig_ref.grid = latlong_to_locator(ll[0], ll[1], 6)
sig_ref.latitude = ll[0] sig_ref.latitude = ll[0]
sig_ref.longitude = ll[1] sig_ref.longitude = ll[1]
except: except:
logging.warn("Failed to look up sig_ref info for " + sig + " ref " + sig_ref_id + ".") logging.warn("Failed to look up sig_ref info for " + sig + " ref " + ref_id + ".")
return sig_ref return sig_ref

View File

@@ -7,7 +7,7 @@ from datetime import datetime, timedelta
import pytz import pytz
from core.lookup_helper import lookup_helper from core.lookup_helper import lookup_helper
from core.sig_utils import get_icon_for_sig, get_sig_ref_info from core.sig_utils import get_icon_for_sig, populate_sig_ref_info
# Data class that defines an alert. # Data class that defines an alert.
@@ -102,10 +102,7 @@ class Alert:
# from WAB and WAI, which count as a SIG even though there's no real lookup, just maths # from WAB and WAI, which count as a SIG even though there's no real lookup, just maths
if self.sig_refs and len(self.sig_refs) > 0: if self.sig_refs and len(self.sig_refs) > 0:
for sig_ref in self.sig_refs: for sig_ref in self.sig_refs:
lookup_data = get_sig_ref_info(sig_ref.sig, sig_ref.id) populate_sig_ref_info(sig_ref)
if lookup_data:
# Update the sig_ref data from the lookup
sig_ref.__dict__.update(lookup_data.__dict__)
# If the spot itself doesn't have a SIG yet, but we have at least one SIG reference, take that reference's SIG # If the spot itself doesn't have a SIG yet, but we have at least one SIG reference, take that reference's SIG
# and apply it to the whole spot. # and apply it to the whole spot.

View File

@@ -11,7 +11,7 @@ from pyhamtools.locator import locator_to_latlong, latlong_to_locator
from core.config import MAX_SPOT_AGE from core.config import MAX_SPOT_AGE
from core.lookup_helper import lookup_helper from core.lookup_helper import lookup_helper
from core.sig_utils import get_icon_for_sig, get_sig_ref_info, ANY_SIG_REGEX, get_ref_regex_for_sig from core.sig_utils import get_icon_for_sig, populate_sig_ref_info, ANY_SIG_REGEX, get_ref_regex_for_sig
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
@@ -279,16 +279,13 @@ class Spot:
# from WAB and WAI, which count as a SIG even though there's no real lookup, just maths # from WAB and WAI, which count as a SIG even though there's no real lookup, just maths
if self.sig_refs and len(self.sig_refs) > 0: if self.sig_refs and len(self.sig_refs) > 0:
for sig_ref in self.sig_refs: for sig_ref in self.sig_refs:
lookup_data = get_sig_ref_info(sig_ref.sig, sig_ref.id) sig_ref = populate_sig_ref_info(sig_ref)
if lookup_data:
# Update the sig_ref data from the lookup
sig_ref.__dict__.update(lookup_data.__dict__)
# If the spot itself doesn't have location yet, but the SIG ref does, extract it # If the spot itself doesn't have location yet, but the SIG ref does, extract it
if lookup_data.grid and not self.dx_grid: if sig_ref.grid and not self.dx_grid:
self.dx_grid = lookup_data.grid self.dx_grid = sig_ref.grid
if lookup_data.latitude and not self.dx_latitude: if sig_ref.latitude and not self.dx_latitude:
self.dx_latitude = lookup_data.latitude self.dx_latitude = sig_ref.latitude
self.dx_longitude = lookup_data.longitude self.dx_longitude = sig_ref.longitude
if self.sig == "WAB" or self.sig == "WAI": if self.sig == "WAB" or self.sig == "WAI":
self.dx_location_source = "WAB/WAI GRID" self.dx_location_source = "WAB/WAI GRID"
else: else:

View File

@@ -12,7 +12,7 @@ from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING, WEB_UI_OPTIONS
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, SOFTWARE_VERSION, UNKNOWN_BAND from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, SOFTWARE_VERSION, UNKNOWN_BAND
from core.lookup_helper import lookup_helper from core.lookup_helper import lookup_helper
from core.prometheus_metrics_handler import page_requests_counter, get_metrics, api_requests_counter from core.prometheus_metrics_handler import page_requests_counter, get_metrics, api_requests_counter
from core.sig_utils import get_ref_regex_for_sig, get_sig_ref_info from core.sig_utils import get_ref_regex_for_sig, populate_sig_ref_info
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
@@ -170,7 +170,7 @@ class WebServer:
response.status = 422 response.status = 422
return json.dumps("Error - '" + id + "' does not look like a valid reference ID for " + sig + ".", default=serialize_everything) return json.dumps("Error - '" + id + "' does not look like a valid reference ID for " + sig + ".", default=serialize_everything)
data = get_sig_ref_info(sig, id) data = populate_sig_ref_info(SIGRef(id=id, sig=sig))
return self.serve_api(data) return self.serve_api(data)
except Exception as e: except Exception as e:

View File

@@ -53,7 +53,7 @@ class GMA(HTTPSpotProvider):
# and GMA summits come through with reftype=Summit, so we must check for the presence of a "sota" entry # and GMA summits come through with reftype=Summit, so we must check for the presence of a "sota" entry
# to determine if it's a SOTA summit. # to determine if it's a SOTA summit.
if "reftype" in ref_info and ref_info["reftype"] not in ["POTA", "WWFF"] and ( if "reftype" in ref_info and ref_info["reftype"] not in ["POTA", "WWFF"] and (
ref_info["reftype"] != "Summit" or ref_info["sota"] == ""): ref_info["reftype"] != "Summit" or "sota" not in ref_info or ref_info["sota"] == ""):
match ref_info["reftype"]: match ref_info["reftype"]:
case "Summit": case "Summit":
spot.sig_refs[0].sig = "GMA" spot.sig_refs[0].sig = "GMA"

View File

@@ -9,7 +9,7 @@ from spotproviders.http_spot_provider import HTTPSpotProvider
# The provider typically doesn't give us a lat/lon or SIG explicitly, so our own config provides this information. This # The provider typically doesn't give us a lat/lon or SIG explicitly, so our own config provides this information. This
# functionality is implemented for TOTA events. # functionality is implemented for TOTA events.
class XOTA(HTTPSpotProvider): class XOTA(HTTPSpotProvider):
POLL_INTERVAL_SEC = 300 POLL_INTERVAL_SEC = 120
FIXED_LATITUDE = None FIXED_LATITUDE = None
FIXED_LONGITUDE = None FIXED_LONGITUDE = None
SIG = None SIG = None

View File

@@ -62,5 +62,5 @@
<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"></script> <script src="/js/common.js?v=1"></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>

View File

@@ -68,6 +68,6 @@
</div> </div>
<script src="/js/common.js"></script> <script src="/js/common.js?v=1"></script>
<script src="/js/add-spot.js"></script> <script src="/js/add-spot.js?v=1"></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>

View File

@@ -165,6 +165,6 @@
</div> </div>
<script src="/js/common.js"></script> <script src="/js/common.js?v=1"></script>
<script src="/js/alerts.js"></script> <script src="/js/alerts.js?v=1"></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>

View File

@@ -128,7 +128,7 @@
</div> </div>
<script src="/js/common.js"></script> <script src="/js/common.js?v=1"></script>
<script src="/js/spotsbandsandmap.js"></script> <script src="/js/spotsbandsandmap.js?v=1"></script>
<script src="/js/bands.js"></script> <script src="/js/bands.js?v=1"></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>

View File

@@ -4,8 +4,10 @@
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="color-scheme" content="light dark"> <meta name="color-scheme" content="light dark">
<meta name="theme-color" content="white"/>
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="white-translucent">
<meta property="og:title" content="Spothole"/> <meta property="og:title" content="Spothole"/>
<meta property="twitter:title" content="Spothole"/> <meta property="twitter:title" content="Spothole"/>

View File

@@ -146,7 +146,7 @@
<script src="https://cdn.jsdelivr.net/npm/leaflet.geodesic"></script> <script src="https://cdn.jsdelivr.net/npm/leaflet.geodesic"></script>
<script src="https://cdn.jsdelivr.net/npm/@joergdietrich/leaflet.terminator@1.1.0/L.Terminator.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@joergdietrich/leaflet.terminator@1.1.0/L.Terminator.min.js"></script>
<script src="/js/common.js"></script> <script src="/js/common.js?v=1"></script>
<script src="/js/spotsbandsandmap.js"></script> <script src="/js/spotsbandsandmap.js?v=1"></script>
<script src="/js/map.js"></script> <script src="/js/map.js?v=1"></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>

View File

@@ -208,7 +208,7 @@
</div> </div>
<script src="/js/common.js"></script> <script src="/js/common.js?v=1"></script>
<script src="/js/spotsbandsandmap.js"></script> <script src="/js/spotsbandsandmap.js?v=1"></script>
<script src="/js/spots.js"></script> <script src="/js/spots.js?v=1"></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>

View File

@@ -2,6 +2,6 @@
<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"></script> <script src="/js/common.js?v=1"></script>
<script src="/js/status.js"></script> <script src="/js/status.js?v=1"></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>

View File

@@ -479,6 +479,7 @@ paths:
in: query in: query
description: Special Interest Group (SIG), e.g. outdoor activity programme such as POTA description: Special Interest Group (SIG), e.g. outdoor activity programme such as POTA
required: true required: true
schema:
$ref: "#/components/schemas/SIGName" $ref: "#/components/schemas/SIGName"
- name: id - name: id
in: query in: query

View File

@@ -24,7 +24,6 @@ function loadURLParams() {
// Handle other params // Handle other params
updateCheckboxFromParam(params, "dark-mode", "darkMode"); updateCheckboxFromParam(params, "dark-mode", "darkMode");
enableDarkMode($("#darkMode")[0].checked);
updateSelectFromParam(params, "time-zone", "timeZone"); // Only on Spots and Alerts pages updateSelectFromParam(params, "time-zone", "timeZone"); // Only on Spots and Alerts pages
updateSelectFromParam(params, "limit", "spots-to-fetch"); // Only on Spots page updateSelectFromParam(params, "limit", "spots-to-fetch"); // Only on Spots page
updateSelectFromParam(params, "limit", "alerts-to-fetch"); // Only on Alerts page updateSelectFromParam(params, "limit", "alerts-to-fetch"); // Only on Alerts page
@@ -42,6 +41,10 @@ function updateCheckboxFromParam(params, paramName, checkboxID) {
let v = params.get(paramName); let v = params.get(paramName);
if (v != null) { if (v != null) {
$("#" + checkboxID).prop("checked", (v === "true") ? true : false); $("#" + checkboxID).prop("checked", (v === "true") ? true : false);
// Extra check if this is the "dark mode" toggle
if (checkboxID == "darkMode") {
enableDarkMode((v === "true") ? true : false);
}
} }
} }
@@ -266,6 +269,10 @@ function latLonForGridSWCornerPlusSize(grid) {
// Function to set dark mode on or off // Function to set dark mode on or off
function enableDarkMode(dark) { function enableDarkMode(dark) {
$("html").attr("data-bs-theme", dark ? "dark" : "light"); $("html").attr("data-bs-theme", dark ? "dark" : "light");
const metaThemeColor = document.querySelector("meta[name=theme-color]");
metaThemeColor.setAttribute("content", dark ? "black" : "white");
const metaAppleStatusBarStyle = document.querySelector("meta[name=apple-mobile-web-app-status-bar-style]");
metaAppleStatusBarStyle.setAttribute("content", dark ? "black-translucent" : "white-translucent");
} }
// Startup function to determine whether to use light or dark mode // Startup function to determine whether to use light or dark mode

View File

@@ -6,7 +6,14 @@ const CACHE_URLS = [
'apidocs/openapi.yml', 'apidocs/openapi.yml',
'about', 'about',
'css/style.css', 'css/style.css',
'js/code.js', 'js/add-spot.js',
'js/alerts.js',
'js/bands.js',
'js/common.js',
'js/map.js',
'js/spots.js',
'js/spotsbandsandmap.js',
'js/status.js',
'img/logo.png', 'img/logo.png',
'img/favicon.ico', 'img/favicon.ico',
'img/icon-32.png', 'img/icon-32.png',