mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-02-04 17:24:30 +00:00
Compare commits
1 Commits
main
...
web_ui_opt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ed0eed10b |
@@ -344,8 +344,6 @@ The same approach as above is also used for alert providers.
|
||||
|
||||
As well as being my work, I have also gratefully received feature patches from Steven, M1SDH.
|
||||
|
||||
The project contains GeoJSON files for CQ and ITU zones, in the `/datafiles/` directory. These are MIT-licenced and, to my knowledge, created by HA8TKS for his CQ and ITU zone layers for Leaflet.
|
||||
|
||||
The project contains a self-hosted copy of Font Awesome's free library, in the `/webassets/fa/` directory. This is subject to Font Awesome's licence and is not covered by the overall licence declared in the `LICENSE` file. This approach was taken in preference to using their hosted kits due to the popularity of this project exceeding the page view limit for their free hosted offering.
|
||||
|
||||
The project contains a set of flag icons generated using the "Noto Color Emoji" font on a Debian system, in the `/webassets/img/flags/` directory.
|
||||
|
||||
@@ -189,14 +189,5 @@ web-ui-options:
|
||||
max-spot-age-default: 30
|
||||
alert-count: [25, 50, 100, 200, 500]
|
||||
alert-count-default: 100
|
||||
# Default UI colour scheme. Supported values are "light", "dark" and "auto" (i.e. use the browser/OS colour scheme).
|
||||
# Users can still override this in the UI to their own preference.
|
||||
color-scheme-default: "auto"
|
||||
# Default band colour scheme. Supported values are the full names of any band colour scheme shown in the UI.
|
||||
# Users can still override this in the UI to their own preference.
|
||||
band-color-scheme-default: "PSK Reporter (Adjusted)"
|
||||
# Custom HTML insert. This can be any arbitrary HTML. It will be inserted next to the start/stop buttons on the spots
|
||||
# (home) page, although being arbitrary HTML you can also use a div with absolute, relative, float placement etc. This
|
||||
# is designed for a "donate/support the server" type button, though you are free to do whatever you want with it.
|
||||
# As the server owner you are responsible for the safe usage of this option!
|
||||
support-button-html: ""
|
||||
default-color-scheme: "auto"
|
||||
default-band-color-scheme: "PSK Reporter (Adjusted)"
|
||||
@@ -2,136 +2,12 @@ import logging
|
||||
import re
|
||||
from math import floor
|
||||
|
||||
import geopandas
|
||||
from pyproj import Transformer
|
||||
from shapely.geometry import Point, Polygon
|
||||
|
||||
TRANSFORMER_OS_GRID_TO_WGS84 = Transformer.from_crs("EPSG:27700", "EPSG:4326")
|
||||
TRANSFORMER_IRISH_GRID_TO_WGS84 = Transformer.from_crs("EPSG:29903", "EPSG:4326")
|
||||
TRANSFORMER_CI_UTM_GRID_TO_WGS84 = Transformer.from_crs("+proj=utm +zone=30 +ellps=WGS84", "EPSG:4326")
|
||||
|
||||
cq_zone_data = geopandas.GeoDataFrame.from_features(geopandas.read_file("datafiles/cqzones.geojson"))
|
||||
itu_zone_data = geopandas.GeoDataFrame.from_features(geopandas.read_file("datafiles/ituzones.geojson"))
|
||||
|
||||
|
||||
# Finds out which CQ zone a lat/lon point is in.
|
||||
def lat_lon_to_cq_zone(lat, lon):
|
||||
for index, row in cq_zone_data.iterrows():
|
||||
polygon = Polygon(row["geometry"])
|
||||
test_point = Point(lon, lat)
|
||||
if polygon.contains(test_point):
|
||||
return int(row["name"])
|
||||
return None
|
||||
|
||||
|
||||
# Finds out which ITU zone a lat/lon point is in.
|
||||
def lat_lon_to_itu_zone(lat, lon):
|
||||
for index, row in itu_zone_data.iterrows():
|
||||
polygon = Polygon(row["geometry"])
|
||||
test_point = Point(lon, lat)
|
||||
if polygon.contains(test_point):
|
||||
return int(row["name"])
|
||||
return None
|
||||
|
||||
|
||||
# Convert a Maidenhead grid reference of arbitrary precision to the lat/long of the centre point of the square.
|
||||
# Returns None if the grid format is invalid.
|
||||
def lat_lon_for_grid_centre(grid):
|
||||
lat, lon, lat_cell_size, lon_cell_size = lat_lon_for_grid_sw_corner_plus_size(grid)
|
||||
if lat is not None and lon is not None and lat_cell_size is not None and lon_cell_size is not None:
|
||||
return [lat + lat_cell_size / 2.0, lon + lon_cell_size / 2.0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
# Convert a Maidenhead grid reference of arbitrary precision to the lat/long of the southwest corner of the square.
|
||||
# Returns None if the grid format is invalid.
|
||||
def lat_lon_for_grid_sw_corner(grid):
|
||||
lat, lon, lat_cell_size, lon_cell_size = lat_lon_for_grid_sw_corner_plus_size(grid)
|
||||
if lat is not None and lon is not None:
|
||||
return [lat, lon]
|
||||
else:
|
||||
return None
|
||||
|
||||
# Convert a Maidenhead grid reference of arbitrary precision to the lat/long of the northeast corner of the square.
|
||||
# Returns None if the grid format is invalid.
|
||||
def lat_lon_for_grid_ne_corner(grid):
|
||||
lat, lon, lat_cell_size, lon_cell_size = lat_lon_for_grid_sw_corner_plus_size(grid)
|
||||
if lat is not None and lon is not None and lat_cell_size is not None and lon_cell_size is not None:
|
||||
return [lat + lat_cell_size, lon + lon_cell_size]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
# Convert a Maidenhead grid reference of arbitrary precision to lat/long, including in the result the size of the
|
||||
# lowest grid square. This is a utility method used by the main methods that return the centre, southwest, and
|
||||
# northeast coordinates of a grid square.
|
||||
# The return type is always a tuple of size 4. The elements in it are None if the grid format is invalid.
|
||||
def lat_lon_for_grid_sw_corner_plus_size(grid):
|
||||
# Make sure we are in upper case so our maths works. Case is arbitrary for Maidenhead references
|
||||
grid = grid.upper()
|
||||
|
||||
# Return None if our Maidenhead string is invalid or too short
|
||||
length = len(grid)
|
||||
if length <= 0 or (length % 2) != 0:
|
||||
return (None, None, None, None)
|
||||
|
||||
lat = 0.0 # aggregated latitude
|
||||
lon = 0.0 # aggregated longitude
|
||||
lat_cell_size = 10.0 # Size in degrees latitude of the current cell. Starts at 10 and gets smaller as the calculation progresses
|
||||
lon_cell_size = 20.0 # Size in degrees longitude of the current cell. Starts at 20 and gets smaller as the calculation progresses
|
||||
|
||||
# Iterate through blocks (two-character sections)
|
||||
block = 0
|
||||
while block * 2 < length:
|
||||
if block % 2 == 0:
|
||||
# Letters in this block
|
||||
lon_cell_no = ord(grid[block * 2]) - ord('A')
|
||||
lat_cell_no = ord(grid[block * 2 + 1]) - ord('A')
|
||||
# Bail if the values aren't in range. Allowed values are A-R (0-17) for the first letter block, or
|
||||
# A-X (0-23) thereafter.
|
||||
max_cell_no = 17 if block == 0 else 23
|
||||
if lat_cell_no < 0 or lat_cell_no > max_cell_no or lon_cell_no < 0 or lon_cell_no > max_cell_no:
|
||||
return (None, None, None, None)
|
||||
else:
|
||||
# Numbers in this block
|
||||
try:
|
||||
lon_cell_no = int(grid[block * 2])
|
||||
lat_cell_no = int(grid[block * 2 + 1])
|
||||
except ValueError:
|
||||
return (None, None, None, None)
|
||||
# Bail if the values aren't in range 0-9
|
||||
if lat_cell_no < 0 or lat_cell_no > 9 or lon_cell_no < 0 or lon_cell_no > 9:
|
||||
return (None, None, None, None)
|
||||
|
||||
# Aggregate the angles
|
||||
lat += lat_cell_no * lat_cell_size
|
||||
lon += lon_cell_no * lon_cell_size
|
||||
|
||||
# Reduce the cell size for the next block, unless we are on the last cell.
|
||||
if block * 2 < length - 2:
|
||||
# Still have more work to do, so reduce the cell size
|
||||
if block % 2 == 0:
|
||||
# Just dealt with letters, next block will be numbers so cells will be 1/10 the current size
|
||||
lat_cell_size = lat_cell_size / 10.0
|
||||
lon_cell_size = lon_cell_size / 10.0
|
||||
else:
|
||||
# Just dealt with numbers, next block will be letters so cells will be 1/24 the current size
|
||||
lat_cell_size = lat_cell_size / 24.0
|
||||
lon_cell_size = lon_cell_size / 24.0
|
||||
|
||||
block += 1
|
||||
|
||||
# Offset back to (-180, -90) where the grid starts
|
||||
lon -= 180.0
|
||||
lat -= 90.0
|
||||
|
||||
# Return None values on maths errors
|
||||
if any(x != x for x in [lat, lon, lat_cell_size, lon_cell_size]): # NaN check
|
||||
return None, None, None, None
|
||||
|
||||
return lat, lon, lat_cell_size, lon_cell_size
|
||||
|
||||
|
||||
# Convert a Worked All Britain or Worked All Ireland reference to a lat/lon point.
|
||||
def wab_wai_square_to_lat_lon(ref):
|
||||
@@ -144,7 +20,7 @@ def wab_wai_square_to_lat_lon(ref):
|
||||
elif re.match(r"^W[AV][0-9]{2}$", ref):
|
||||
return utm_grid_square_to_lat_lon(ref)
|
||||
else:
|
||||
logging.warning("Invalid WAB/WAI square: " + ref)
|
||||
logging.warn("Invalid WAB/WAI square: " + ref)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -79,8 +79,7 @@ class Alert:
|
||||
if self.received_time and not self.received_time_iso:
|
||||
self.received_time_iso = datetime.fromtimestamp(self.received_time, pytz.UTC).isoformat()
|
||||
|
||||
# DX country, continent, zones etc. from callsign. CQ/ITU zone are better looked up with a location but we don't
|
||||
# have a real location for alerts.
|
||||
# DX country, continent, zones etc. from callsign
|
||||
if self.dx_calls and self.dx_calls[0] and not self.dx_country:
|
||||
self.dx_country = lookup_helper.infer_country_from_callsign(self.dx_calls[0])
|
||||
if self.dx_calls and self.dx_calls[0] and not self.dx_continent:
|
||||
|
||||
19
data/spot.py
19
data/spot.py
@@ -11,7 +11,6 @@ from pyhamtools.locator import locator_to_latlong, latlong_to_locator
|
||||
|
||||
from core.config import MAX_SPOT_AGE
|
||||
from core.constants import MODE_ALIASES
|
||||
from core.geo_utils import lat_lon_to_cq_zone, lat_lon_to_itu_zone
|
||||
from core.lookup_helper import lookup_helper
|
||||
from core.sig_utils import populate_sig_ref_info, ANY_SIG_REGEX, get_ref_regex_for_sig
|
||||
from data.sig_ref import SIGRef
|
||||
@@ -153,11 +152,15 @@ class Spot:
|
||||
if len(split) > 1 and split[1] != "#":
|
||||
self.dx_ssid = split[1]
|
||||
|
||||
# DX country, continent etc. from callsign
|
||||
# DX country, continent, zones etc. from callsign
|
||||
if self.dx_call and not self.dx_country:
|
||||
self.dx_country = lookup_helper.infer_country_from_callsign(self.dx_call)
|
||||
if self.dx_call and not self.dx_continent:
|
||||
self.dx_continent = lookup_helper.infer_continent_from_callsign(self.dx_call)
|
||||
if self.dx_call and not self.dx_cq_zone:
|
||||
self.dx_cq_zone = lookup_helper.infer_cq_zone_from_callsign(self.dx_call)
|
||||
if self.dx_call and not self.dx_itu_zone:
|
||||
self.dx_itu_zone = lookup_helper.infer_itu_zone_from_callsign(self.dx_call)
|
||||
if self.dx_call and not self.dx_dxcc_id:
|
||||
self.dx_dxcc_id = lookup_helper.infer_dxcc_id_from_callsign(self.dx_call)
|
||||
if self.dx_dxcc_id and not self.dx_flag:
|
||||
@@ -329,18 +332,6 @@ class Spot:
|
||||
self.dx_grid = lookup_helper.infer_grid_from_callsign_dxcc(self.dx_call)
|
||||
self.dx_location_source = "DXCC"
|
||||
|
||||
# CQ and ITU zone lookup, preferably from location but failing that, from callsign
|
||||
if not self.dx_cq_zone:
|
||||
if self.dx_latitude:
|
||||
self.dx_cq_zone = lat_lon_to_cq_zone(self.dx_latitude, self.dx_longitude)
|
||||
elif self.dx_call:
|
||||
self.dx_cq_zone = lookup_helper.infer_cq_zone_from_callsign(self.dx_call)
|
||||
if not self.dx_itu_zone:
|
||||
if self.dx_latitude:
|
||||
self.dx_itu_zone = lat_lon_to_itu_zone(self.dx_latitude, self.dx_longitude)
|
||||
elif self.dx_call:
|
||||
self.dx_itu_zone = lookup_helper.infer_itu_zone_from_callsign(self.dx_call)
|
||||
|
||||
# DX Location is "good" if it is from a spot, or from QRZ if the callsign doesn't contain a slash, so the operator
|
||||
# is likely at home.
|
||||
self.dx_location_good = self.dx_latitude and self.dx_longitude and (
|
||||
|
||||
134817
datafiles/cqzones.geojson
134817
datafiles/cqzones.geojson
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,4 +15,3 @@ beautifulsoup4~=4.14.2
|
||||
websocket-client~=1.9.0
|
||||
tornado~=6.5.4
|
||||
tornado_eventsource~=3.0.0
|
||||
geopandas~=1.1.2
|
||||
@@ -5,10 +5,8 @@ from datetime import datetime
|
||||
|
||||
import pytz
|
||||
import tornado
|
||||
from pyhamtools.locator import locator_to_latlong
|
||||
|
||||
from core.constants import SIGS
|
||||
from core.geo_utils import lat_lon_for_grid_sw_corner_plus_size, lat_lon_to_cq_zone, lat_lon_to_itu_zone
|
||||
from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.sig_utils import get_ref_regex_for_sig, populate_sig_ref_info
|
||||
from core.utils import serialize_everything
|
||||
@@ -121,61 +119,3 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
|
||||
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
|
||||
|
||||
|
||||
# API request handler for /api/v1/lookup/grid
|
||||
class APILookupGridHandler(tornado.web.RequestHandler):
|
||||
def initialize(self, web_server_metrics):
|
||||
self.web_server_metrics = web_server_metrics
|
||||
|
||||
def get(self):
|
||||
try:
|
||||
# Metrics
|
||||
self.web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
|
||||
self.web_server_metrics["api_access_counter"] += 1
|
||||
self.web_server_metrics["status"] = "OK"
|
||||
api_requests_counter.inc()
|
||||
|
||||
# request.arguments contains lists for each param key because technically the client can supply multiple,
|
||||
# reduce that to just the first entry, and convert bytes to string
|
||||
query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
|
||||
|
||||
# "grid" query param must exist.
|
||||
if "grid" in query_params.keys():
|
||||
grid = query_params.get("grid").upper()
|
||||
lat, lon, lat_cell_size, lon_cell_size = lat_lon_for_grid_sw_corner_plus_size(grid)
|
||||
if lat is not None and lon is not None and lat_cell_size is not None and lon_cell_size is not None:
|
||||
center_lat = lat + lat_cell_size / 2.0
|
||||
center_lon = lon + lon_cell_size / 2.0
|
||||
center_cq_zone = lat_lon_to_cq_zone(center_lat, center_lon)
|
||||
center_itu_zone = lat_lon_to_itu_zone(center_lat, center_lon)
|
||||
|
||||
response = {
|
||||
"center" : {
|
||||
"latitude": center_lat,
|
||||
"longitude": center_lon,
|
||||
"cq_zone": center_cq_zone,
|
||||
"itu_zone": center_itu_zone
|
||||
},
|
||||
"southwest" : {
|
||||
"latitude": lat,
|
||||
"longitude": lon,
|
||||
},
|
||||
"northeast" : {
|
||||
"latitude": lat + lat_cell_size,
|
||||
"longitude": lon + lon_cell_size,
|
||||
}}
|
||||
self.write(json.dumps(response, default=serialize_everything))
|
||||
|
||||
else:
|
||||
self.write(json.dumps("Error - grid must be provided", default=serialize_everything))
|
||||
self.set_status(422)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
self.write(json.dumps("Error - " + str(e), default=serialize_everything))
|
||||
self.set_status(500)
|
||||
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
|
||||
@@ -2,6 +2,7 @@ from datetime import datetime
|
||||
|
||||
import pytz
|
||||
import tornado
|
||||
import json
|
||||
|
||||
from core.config import ALLOW_SPOTTING, WEB_UI_OPTIONS
|
||||
from core.constants import SOFTWARE_VERSION
|
||||
@@ -23,5 +24,5 @@ class PageTemplateHandler(tornado.web.RequestHandler):
|
||||
|
||||
# Load named template, and provide variables used in templates
|
||||
self.render(self.template_name + ".html", software_version=SOFTWARE_VERSION, allow_spotting=ALLOW_SPOTTING,
|
||||
web_ui_options=WEB_UI_OPTIONS)
|
||||
web_ui_options=json.dumps(WEB_UI_OPTIONS))
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from tornado.web import StaticFileHandler
|
||||
|
||||
from server.handlers.api.addspot import APISpotHandler
|
||||
from server.handlers.api.alerts import APIAlertsHandler, APIAlertsStreamHandler
|
||||
from server.handlers.api.lookups import APILookupCallHandler, APILookupSIGRefHandler, APILookupGridHandler
|
||||
from server.handlers.api.lookups import APILookupCallHandler, APILookupSIGRefHandler
|
||||
from server.handlers.api.options import APIOptionsHandler
|
||||
from server.handlers.api.spots import APISpotsHandler, APISpotsStreamHandler
|
||||
from server.handlers.api.status import APIStatusHandler
|
||||
@@ -54,7 +54,6 @@ class WebServer:
|
||||
(r"/api/v1/status", APIStatusHandler, {"status_data": self.status_data, "web_server_metrics": self.web_server_metrics}),
|
||||
(r"/api/v1/lookup/call", APILookupCallHandler, {"web_server_metrics": self.web_server_metrics}),
|
||||
(r"/api/v1/lookup/sigref", APILookupSIGRefHandler, {"web_server_metrics": self.web_server_metrics}),
|
||||
(r"/api/v1/lookup/grid", APILookupGridHandler, {"web_server_metrics": self.web_server_metrics}),
|
||||
(r"/api/v1/spot", APISpotHandler, {"spots": self.spots, "web_server_metrics": self.web_server_metrics}),
|
||||
# Routes for templated pages
|
||||
(r"/", PageTemplateHandler, {"template_name": "spots", "web_server_metrics": self.web_server_metrics}),
|
||||
|
||||
@@ -56,17 +56,14 @@
|
||||
<p>Spothole collects no data about you, and there is no way to enter personally identifying information into the site apart from by spotting and alerting through Spothole or the various services it connects to. All spots and alerts are "timed out" and deleted from the system after a set interval, which by default is one hour for spots and one week for alerts.</p>
|
||||
<p>Settings you select from Spothole's menus are sent to the server, in order to provide the data with the requested filters. They are also stored in your browser's local storage, so that your preferences are remembered between sessions.</p>
|
||||
<p>There are no trackers, no ads, and no cookies.</p>
|
||||
{% if len(web_ui_options["support-button-html"]) > 0 %}
|
||||
<p><strong>Caveat: </strong> The owner of this server has chosen to inject their own content into the "spots" page. This is designed for a "donate" or "support this server" button. The functionality of this injected content is the responsibility of the server owner, rather than the Spothole software.</p>
|
||||
{% end %}
|
||||
<p>Spothole is open source, so you can audit <a href="https://git.ianrenton.com/ian/spothole">the code</a> if you like.</p>
|
||||
<h2 class="mt-4">Thanks</h2>
|
||||
<p>This project would not have been possible without those volunteers who have taken it upon themselves to run DX clusters, xOTA programmes, DXpedition lists, callsign lookup databases, and other online tools on which Spothole's data is based.</p>
|
||||
<p>Spothole is also dependent on a number of Python libraries, in particular pyhamtools, and many JavaScript libraries, as well as the Font Awesome icon set and flag icons from the Noto Color Emoji set, and MIT-licenced GeoJSON files for CQ and ITU zones from HA8TKS.</p>
|
||||
<p>Spothole is also dependent on a number of Python libraries, in particular pyhamtools, and many JavaScript libraries, as well as the Font Awesome icon set and flag icons from the Noto Color Emoji set.</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>
|
||||
|
||||
<script src="/js/common.js?v=8"></script>
|
||||
<script src="/js/common.js?v=7"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -69,8 +69,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js?v=8"></script>
|
||||
<script src="/js/add-spot.js?v=8"></script>
|
||||
<script src="/js/common.js?v=7"></script>
|
||||
<script src="/js/add-spot.js?v=7"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -2,49 +2,163 @@
|
||||
{% block content %}
|
||||
|
||||
<div class="mt-3">
|
||||
<div id="settingsButtonRow" class="row mb-3">
|
||||
<div id="settingsButtonRow" class="row">
|
||||
<div class="col-auto me-auto pt-3">
|
||||
{% module Template("widgets/refresh-timer.html", web_ui_options=web_ui_options) %}
|
||||
<p id="timing-container">Loading...</p>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="d-inline-flex gap-1">
|
||||
{% module Template("widgets/filters-display-buttons.html", web_ui_options=web_ui_options) %}
|
||||
</div>
|
||||
<p class="d-inline-flex gap-1">
|
||||
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i> Filters</button>
|
||||
<button id="display-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i> Display</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="filters-area" class="appearing-panel card mb-3">
|
||||
{% module Template("widgets/filters-area-header.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Filters
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-filters-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeFiltersPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row row-cols-1 row-cols-md-3 g-4">
|
||||
<div class="col">
|
||||
{% module Template("cards/dx-continent.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">DX Continent</h5>
|
||||
<p id="dx-continent-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/sources.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Sources</h5>
|
||||
<p id="source-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/duration-limit-alerts.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Duration Limit <i class='fa-solid fa-circle-question' title='Some users create long-duration alerts for the period they will be generally in and around xOTA references, when they are not indending to be on the air most of the time. Use this control to restrict the maximum duration of spots that the software will display, and exclude any with a long duration, to avoid these filling up the list. By default, we allow DXpeditions to be displayed even if they are longer than this limit, because on a DXpedition the operators typically ARE on the air most of the time.'></i></h5>
|
||||
<p class="card-text spothole-card-text">
|
||||
Hide any alerts lasting more than:<br/>
|
||||
<select id="max-duration" class="storeable-select form-select" onclick="filtersUpdated();" style="width: 8em; display: inline-block;">
|
||||
<option value="10800">3 hours</option>
|
||||
<option value="43200">12 hours</option>
|
||||
<option value="86400" selected>24 hours</option>
|
||||
<option value="604800">1 week</option>
|
||||
<option value="2419200">4 weeks</option>
|
||||
<option value="9999999999">No limit</option>
|
||||
</select>
|
||||
</p>
|
||||
<p class='card-text spothole-card-text' style='line-height: 1.5em !important;'>
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" value="" onclick="filtersUpdated();" id="dxpeditions_skip_max_duration_check" checked><label class="form-check-label ms-2" for="dxpeditions_skip_max_duration_check">Allow DXpeditions that are longer</label>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="display-area" class="appearing-panel card mb-3">
|
||||
{% module Template("widgets/display-area-header.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Display
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-display-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeDisplayPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="display-container" class="row row-cols-1 row-cols-md-3 g-4">
|
||||
<div class="col">
|
||||
{% module Template("cards/time-zone.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Time Zone</h5>
|
||||
<p class="card-text spothole-card-text"> Use
|
||||
<select id="timeZone" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="timeZoneUpdated();" style="width: 8em; display: inline-block;">
|
||||
<option value="UTC" selected>UTC</option>
|
||||
<option value="local">Local time</option>
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/number-of-alerts.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Number of Alerts</h5>
|
||||
<p class="card-text spothole-card-text">Show up to
|
||||
<select id="alerts-to-fetch" class="storeable-select form-select ms-2" oninput="filtersUpdated();" style="width: 5em;display: inline-block;">
|
||||
</select>
|
||||
alerts
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/color-scheme.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Theme</h5>
|
||||
<p class="card-text spothole-card-text">
|
||||
<label class="form-check-label" for="color-scheme">UI color scheme</label>
|
||||
<select id="color-scheme" class="storeable-select form-select d-inline-block" oninput="setColorSchemeFromUI();" style="display: inline-block;">
|
||||
<option value="auto">Automatic</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/table-columns-alerts.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Table Data</h5>
|
||||
<div class="form-group">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowStartTime" value="tableShowStartTime" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowStartTime">Start Time</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowEndTime" value="tableShowEndTime" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowEndTime">End Time</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowDX" value="tableShowDX" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowDX">DX</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowFreqsModes" value="tableShowFreqsModes" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowFreqsModes">Frequencies & Modes</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowComment" value="tableShowComment" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowComment">Comment</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowSource" value="tableShowSource" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowSource">Source</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowRef" value="tableShowRef" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowRef">Ref.</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,8 +170,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js?v=8"></script>
|
||||
<script src="/js/alerts.js?v=8"></script>
|
||||
<script src="/js/common.js?v=7"></script>
|
||||
<script src="/js/alerts.js?v=7"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -2,54 +2,131 @@
|
||||
{% block content %}
|
||||
|
||||
<div class="mt-3">
|
||||
<div id="settingsButtonRow" class="row mb-3">
|
||||
<div id="settingsButtonRow" class="row">
|
||||
<div class="col-auto me-auto pt-3">
|
||||
{% module Template("widgets/refresh-timer.html", web_ui_options=web_ui_options) %}
|
||||
<p id="timing-container">Loading...</p>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="d-inline-flex gap-1">
|
||||
{% module Template("widgets/filters-display-buttons.html", web_ui_options=web_ui_options) %}
|
||||
</div>
|
||||
<p class="d-inline-flex gap-1">
|
||||
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i> Filters</button>
|
||||
<button id="display-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i> Display</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="filters-area" class="appearing-panel card mb-3">
|
||||
{% module Template("widgets/filters-area-header.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Filters
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-filters-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeFiltersPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row row-cols-1 g-4 mb-4 row-cols-md-3">
|
||||
<div class="col">
|
||||
{% module Template("cards/bands.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Bands</h5>
|
||||
<p id="band-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/sigs.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">SIGs</h5>
|
||||
<p id="sig-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/sources.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Sources</h5>
|
||||
<p id="source-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-cols-1 row-cols-md-3 g-4">
|
||||
<div class="col">
|
||||
{% module Template("cards/dx-continent.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">DX Continent</h5>
|
||||
<p id="dx-continent-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/de-continent.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">DE Continent</h5>
|
||||
<p id="de-continent-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/modes.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Modes</h5>
|
||||
<p id="mode-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="display-area" class="appearing-panel card mb-3">
|
||||
{% module Template("widgets/display-area-header.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Display
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-display-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeDisplayPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="display-container" class="row row-cols-1 row-cols-md-4 g-4">
|
||||
<div class="col">
|
||||
{% module Template("cards/spot-age.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Spot Age</h5>
|
||||
<p class="card-text spothole-card-text">Last
|
||||
<select id="max-spot-age" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
||||
</select>
|
||||
minutes
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/color-scheme-and-band-color-scheme.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Theme</h5>
|
||||
<p class="card-text spothole-card-text">
|
||||
<label class="form-check-label" for="color-scheme">UI color scheme</label>
|
||||
<select id="color-scheme" class="storeable-select form-select d-inline-block" oninput="setColorSchemeFromUI();" style="display: inline-block;">
|
||||
<option value="auto">Automatic</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</p>
|
||||
<p class="card-text spothole-card-text">
|
||||
<label class="form-check-label" for="band-color-scheme">Band color scheme</label><br/>
|
||||
<select id="band-color-scheme" class="storeable-select form-select d-inline-block" oninput="setBandColorSchemeFromUI();" style="display: inline-block;">
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,12 +136,9 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=8"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=8"></script>
|
||||
<script src="/js/bands.js?v=8"></script>
|
||||
<script src="/js/common.js?v=7"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=7"></script>
|
||||
<script src="/js/bands.js?v=7"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -46,10 +46,14 @@
|
||||
crossorigin="anonymous"></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=8"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/storage.js?v=8"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=8"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=8"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=7"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/storage.js?v=7"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=7"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=7"></script>
|
||||
<script>
|
||||
// Get Web UI Options from the backend to the frontend as a JS object.
|
||||
let web_ui_options = JSON.parse('{{ web_ui_options }}'.replace(/"/g, '\"'));
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Bands</h5>
|
||||
<p id="band-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Theme</h5>
|
||||
<p class="card-text spothole-card-text">
|
||||
{% module Template("widgets/color-scheme.html", web_ui_options=web_ui_options) %}
|
||||
</p>
|
||||
<p class="card-text spothole-card-text">
|
||||
{% module Template("widgets/band-color-scheme.html", web_ui_options=web_ui_options) %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,8 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Theme</h5>
|
||||
<p class="card-text spothole-card-text">
|
||||
{% module Template("widgets/color-scheme.html", web_ui_options=web_ui_options) %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">DE Continent</h5>
|
||||
<p id="de-continent-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,19 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Duration Limit <i class='fa-solid fa-circle-question' title='Some users create long-duration alerts for the period they will be generally in and around xOTA references, when they are not indending to be on the air most of the time. Use this control to restrict the maximum duration of spots that the software will display, and exclude any with a long duration, to avoid these filling up the list. By default, we allow DXpeditions to be displayed even if they are longer than this limit, because on a DXpedition the operators typically ARE on the air most of the time.'></i></h5>
|
||||
<p class="card-text spothole-card-text">
|
||||
Hide any alerts lasting more than:<br/>
|
||||
<select id="max-duration" class="storeable-select form-select" onclick="filtersUpdated();" style="width: 8em; display: inline-block;">
|
||||
<option value="10800">3 hours</option>
|
||||
<option value="43200">12 hours</option>
|
||||
<option value="86400" selected>24 hours</option>
|
||||
<option value="604800">1 week</option>
|
||||
<option value="2419200">4 weeks</option>
|
||||
<option value="9999999999">No limit</option>
|
||||
</select>
|
||||
</p>
|
||||
<p class='card-text spothole-card-text' style='line-height: 1.5em !important;'>
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" value="" onclick="filtersUpdated();" id="dxpeditions_skip_max_duration_check" checked><label class="form-check-label ms-2" for="dxpeditions_skip_max_duration_check">Allow DXpeditions that are longer</label>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">DX Continent</h5>
|
||||
<p id="dx-continent-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,9 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Location</h5>
|
||||
<div class="form-group spothole-card-text">
|
||||
<label for="userGrid">Your grid:</label>
|
||||
<input type="text" class="storeable-text form-control" id="userGrid" placeholder="AA00aa" oninput="userGridUpdated();" style="width: 10em; display: inline-block;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Map Features</h5>
|
||||
<div class="form-group">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="mapShowGeodesics" value="mapShowGeodesics" oninput="displayUpdated();">
|
||||
<label class="form-check-label" for="mapShowGeodesics">Geodesic Lines</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Modes</h5>
|
||||
<p id="mode-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,13 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Number of Alerts</h5>
|
||||
<p class="card-text spothole-card-text">Show up to
|
||||
<select id="alerts-to-fetch" class="storeable-select form-select ms-2" oninput="filtersUpdated();" style="width: 5em;display: inline-block;">
|
||||
{% for c in web_ui_options["alert-count"] %}
|
||||
<option value="{{c}}" {% if web_ui_options["alert-count-default"] == c %}selected{% end %}>{{c}}</option>
|
||||
{% end %}
|
||||
</select>
|
||||
alerts
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,13 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Number of Spots</h5>
|
||||
<p class="card-text spothole-card-text">Show up to
|
||||
<select id="spots-to-fetch" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
||||
{% for c in web_ui_options["spot-count"] %}
|
||||
<option value="{{c}}" {% if web_ui_options["spot-count-default"] == c %}selected{% end %}>{{c}}</option>
|
||||
{% end %}
|
||||
</select>
|
||||
spots
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">SIGs</h5>
|
||||
<p id="sig-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Sources</h5>
|
||||
<p id="source-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,13 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Spot Age</h5>
|
||||
<p class="card-text spothole-card-text">Last
|
||||
<select id="max-spot-age" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
||||
{% for a in web_ui_options["max-spot-age"] %}
|
||||
<option value="{{a*60}}" {% if web_ui_options["max-spot-age-default"] == a*60 %}selected{% end %}>{{a}}</option>
|
||||
{% end %}
|
||||
</select>
|
||||
minutes
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,35 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Table Columns</h5>
|
||||
<div class="form-group">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowStartTime" value="tableShowStartTime" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowStartTime">Start Time</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowEndTime" value="tableShowEndTime" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowEndTime">End Time</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowDX" value="tableShowDX" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowDX">DX</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowFreqsModes" value="tableShowFreqsModes" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowFreqsModes">Frequencies & Modes</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowComment" value="tableShowComment" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowComment">Comment</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowSource" value="tableShowSource" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowSource">Source</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowRef" value="tableShowRef" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowRef">Ref.</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,47 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Table Columns</h5>
|
||||
<div class="form-group">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowTime" value="tableShowTime" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowTime">Time</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowDX" value="tableShowDX" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowDX">DX</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowFreq" value="tableShowFreq" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowFreq">Frequency</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowMode" value="tableShowMode" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowMode">Mode</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowComment" value="tableShowComment" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowComment">Comment</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowBearing" value="tableShowBearing" oninput="columnsUpdated();">
|
||||
<label class="form-check-label" for="tableShowBearing">Bearing</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowType" value="tableShowType" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowType">Type</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowRef" value="tableShowRef" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowRef">Ref.</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<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>
|
||||
</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>
|
||||
@@ -1,11 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Time Zone</h5>
|
||||
<p class="card-text spothole-card-text"> Use
|
||||
<select id="timeZone" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="timeZoneUpdated();" style="width: 8em; display: inline-block;">
|
||||
<option value="UTC" selected>UTC</option>
|
||||
<option value="local">Local time</option>
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
<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>
|
||||
@@ -3,55 +3,142 @@
|
||||
|
||||
<div id="map">
|
||||
<div id="settingsButtonRowMap" class="mt-3 px-3" style="z-index: 1002; position: relative;">
|
||||
<div class="row mb-3">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto pt-3"></div>
|
||||
<div class="col-auto">
|
||||
<div class="d-inline-flex gap-1">
|
||||
{% module Template("widgets/filters-display-buttons.html", web_ui_options=web_ui_options) %}
|
||||
</div>
|
||||
<p class="d-inline-flex gap-1">
|
||||
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i> Filters</button>
|
||||
<button id="display-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i> Display</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="filters-area" class="appearing-panel card mb-3">
|
||||
{% module Template("widgets/filters-area-header.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Filters
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-filters-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeFiltersPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row row-cols-1 g-4 mb-4 row-cols-md-3">
|
||||
<div class="col">
|
||||
{% module Template("cards/bands.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Bands</h5>
|
||||
<p id="band-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/sigs.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">SIGs</h5>
|
||||
<p id="sig-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/sources.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Sources</h5>
|
||||
<p id="source-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-cols-1 row-cols-md-3 g-4">
|
||||
<div class="col">
|
||||
{% module Template("cards/dx-continent.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">DX Continent</h5>
|
||||
<p id="dx-continent-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/de-continent.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">DE Continent</h5>
|
||||
<p id="de-continent-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/modes.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Modes</h5>
|
||||
<p id="mode-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="display-area" class="appearing-panel card mb-3">
|
||||
{% module Template("widgets/display-area-header.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Display
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-display-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeDisplayPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="display-container" class="row row-cols-1 row-cols-md-4 g-4">
|
||||
<div class="col">
|
||||
{% module Template("cards/spot-age.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Spot Age</h5>
|
||||
<p class="card-text spothole-card-text">Last
|
||||
<select id="max-spot-age" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
||||
</select>
|
||||
minutes
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/map-features.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Map Features</h5>
|
||||
<div class="form-group">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="mapShowGeodesics" value="mapShowGeodesics" oninput="displayUpdated();">
|
||||
<label class="form-check-label" for="mapShowGeodesics">Geodesic Lines</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/color-scheme-and-band-color-scheme.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Theme</h5>
|
||||
<p class="card-text spothole-card-text">
|
||||
<label class="form-check-label" for="color-scheme">UI color scheme</label>
|
||||
<select id="color-scheme" class="storeable-select form-select d-inline-block" oninput="setColorSchemeFromUI();" style="display: inline-block;">
|
||||
<option value="auto">Automatic</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</p>
|
||||
<p class="card-text spothole-card-text">
|
||||
<label class="form-check-label" for="band-color-scheme">Band color scheme</label><br/>
|
||||
<select id="band-color-scheme" class="storeable-select form-select d-inline-block" oninput="setBandColorSchemeFromUI();" style="display: inline-block;">
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,12 +154,9 @@
|
||||
<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>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=8"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=8"></script>
|
||||
<script src="/js/map.js?v=8"></script>
|
||||
<script src="/js/common.js?v=7"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=7"></script>
|
||||
<script src="/js/map.js?v=7"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -9,70 +9,212 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div id="settingsButtonRow" class="row mb-3">
|
||||
<div class="col-md-4 mb-3 mb-md-0">
|
||||
<div class="d-inline-flex gap-3">
|
||||
{% module Template("widgets/run-pause.html", web_ui_options=web_ui_options) %}
|
||||
<div class="d-inline-flex">{% raw web_ui_options["support-button-html"] %}</div>
|
||||
</div>
|
||||
<div id="settingsButtonRow" class="row">
|
||||
<div class="col-4">
|
||||
<p class="d-inline-flex gap-1">
|
||||
<span class="btn-group" role="group">
|
||||
<input type="radio" class="btn-check" name="runPause" id="runButton" autocomplete="off" checked>
|
||||
<label class="btn btn-outline-primary" for="runButton"><i class="fa-solid fa-play"></i><span class="hideonmobile"> Run</span></label>
|
||||
|
||||
<input type="radio" class="btn-check" name="runPause" id="pauseButton" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="pauseButton"><i class="fa-solid fa-pause"></i><span class="hideonmobile"> Pause</span></label>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-8 text-end">
|
||||
<div class="d-inline-flex gap-3">
|
||||
{% module Template("widgets/search.html", web_ui_options=web_ui_options) %}
|
||||
{% module Template("widgets/filters-display-buttons.html", web_ui_options=web_ui_options) %}
|
||||
</div>
|
||||
<div class="col-8 text-end">
|
||||
<p class="d-inline-flex gap-1">
|
||||
<span style="position: relative;">
|
||||
<i id="searchicon" class="fa-solid fa-magnifying-glass"></i>
|
||||
<input id="search" type="search" class="form-control" oninput="filtersUpdated();" placeholder="Search">
|
||||
</span>
|
||||
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i><span class="hideonmobile"> Filters</span></button>
|
||||
<button id="display-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i><span class="hideonmobile"> Display</span></button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="filters-area" class="appearing-panel card mb-3">
|
||||
{% module Template("widgets/filters-area-header.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Filters
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-filters-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeFiltersPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row row-cols-1 g-4 mb-4 row-cols-md-3">
|
||||
<div class="col">
|
||||
{% module Template("cards/bands.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Bands</h5>
|
||||
<p id="band-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/sigs.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">SIGs</h5>
|
||||
<p id="sig-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/sources.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Sources</h5>
|
||||
<p id="source-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-cols-1 row-cols-md-3 g-4">
|
||||
<div class="col">
|
||||
{% module Template("cards/dx-continent.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">DX Continent</h5>
|
||||
<p id="dx-continent-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/de-continent.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">DE Continent</h5>
|
||||
<p id="de-continent-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/modes.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Modes</h5>
|
||||
<p id="mode-options" class="card-text spothole-card-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="display-area" class="appearing-panel card mb-3">
|
||||
{% module Template("widgets/display-area-header.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Display
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-display-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeDisplayPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="display-container" class="row row-cols-1 row-cols-md-4 g-4">
|
||||
<div class="col">
|
||||
{% module Template("cards/time-zone.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Time Zone</h5>
|
||||
<p class="card-text spothole-card-text"> Use
|
||||
<select id="timeZone" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="timeZoneUpdated();" style="width: 8em; display: inline-block;">
|
||||
<option value="UTC" selected>UTC</option>
|
||||
<option value="local">Local time</option>
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/number-of-spots.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Number of Spots</h5>
|
||||
<p class="card-text spothole-card-text">Show up to
|
||||
<select id="spots-to-fetch" class="storeable-select form-select ms-2 me-2 d-inline-block" oninput="filtersUpdated();" style="width: 5em; display: inline-block;">
|
||||
</select>
|
||||
spots
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/location.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Location</h5>
|
||||
<div class="form-group spothole-card-text">
|
||||
<label for="userGrid">Your grid:</label>
|
||||
<input type="text" class="storeable-text form-control" id="userGrid" placeholder="AA00aa" oninput="userGridUpdated();" style="width: 10em; display: inline-block;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/worked-calls.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Theme</h5>
|
||||
<p class="card-text spothole-card-text">
|
||||
<label class="form-check-label" for="color-scheme">UI color scheme</label>
|
||||
<select id="color-scheme" class="storeable-select form-select d-inline-block" oninput="setColorSchemeFromUI();" style="display: inline-block;">
|
||||
<option value="auto">Automatic</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</p>
|
||||
<p class="card-text spothole-card-text">
|
||||
<label class="form-check-label" for="band-color-scheme">Band color scheme</label><br/>
|
||||
<select id="band-color-scheme" class="storeable-select form-select d-inline-block" oninput="setBandColorSchemeFromUI();" style="display: inline-block;">
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/color-scheme-and-band-color-scheme.html", web_ui_options=web_ui_options) %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% module Template("cards/table-columns-spots.html", web_ui_options=web_ui_options) %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Table Columns</h5>
|
||||
<div class="form-group">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowTime" value="tableShowTime" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowTime">Time</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowDX" value="tableShowDX" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowDX">DX</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowFreq" value="tableShowFreq" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowFreq">Frequency</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowMode" value="tableShowMode" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowMode">Mode</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowComment" value="tableShowComment" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowComment">Comment</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowBearing" value="tableShowBearing" oninput="columnsUpdated();">
|
||||
<label class="form-check-label" for="tableShowBearing">Bearing</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowType" value="tableShowType" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowType">Type</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowRef" value="tableShowRef" oninput="columnsUpdated();" checked>
|
||||
<label class="form-check-label" for="tableShowRef">Ref.</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,12 +226,9 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=8"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=8"></script>
|
||||
<script src="/js/spots.js?v=8"></script>
|
||||
<script src="/js/common.js?v=7"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=7"></script>
|
||||
<script src="/js/spots.js?v=7"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
<div id="status-container" class="row row-cols-1 row-cols-md-4 g-4 mt-4"></div>
|
||||
|
||||
<script src="/js/common.js?v=8"></script>
|
||||
<script src="/js/status.js?v=8"></script>
|
||||
<script src="/js/common.js?v=7"></script>
|
||||
<script src="/js/status.js?v=7"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -1,13 +0,0 @@
|
||||
<label class="form-check-label" for="band-color-scheme">Band color scheme</label><br/>
|
||||
<select id="band-color-scheme" class="storeable-select form-select d-inline-block" oninput="setBandColorSchemeFromUI();" style="display: inline-block;">
|
||||
<option value="PSK Reporter" {% if web_ui_options["band-color-scheme-default"] == "PSK Reporter" %}selected{% end %}>PSK Reporter</option>
|
||||
<option value="PSK Reporter (Adjusted)" {% if web_ui_options["band-color-scheme-default"] == "PSK Reporter (Adjusted)" %}selected{% end %}>PSK Reporter (Adjusted)</option>
|
||||
<option value="RBN" {% if web_ui_options["band-color-scheme-default"] == "RBN" %}selected{% end %}>RBN</option>
|
||||
<option value="Ham Rainbow" {% if web_ui_options["band-color-scheme-default"] == "Ham Rainbow" %}selected{% end %}>Ham Rainbow</option>
|
||||
<option value="Ham Rainbow (Reverse)" {% if web_ui_options["band-color-scheme-default"] == "Ham Rainbow (Reverse)" %}selected{% end %}>Ham Rainbow (Reverse)</option>
|
||||
<option value="Kate Morley" {% if web_ui_options["band-color-scheme-default"] == "Kate Morley" %}selected{% end %}>Kate Morley</option>
|
||||
<option value="ColorBrewer" {% if web_ui_options["band-color-scheme-default"] == "ColorBrewer" %}selected{% end %}>ColorBrewer</option>
|
||||
<option value="IWantHue" {% if web_ui_options["band-color-scheme-default"] == "IWantHue" %}selected{% end %}>IWantHue</option>
|
||||
<option value="IWantHue (Color Blind)" {% if web_ui_options["band-color-scheme-default"] == "IWantHue (Color Blind)" %}selected{% end %}>IWantHue (Color Blind)</option>
|
||||
<option value="Mokole" {% if web_ui_options["band-color-scheme-default"] == "Mokole" %}selected{% end %}>Mokole</option>
|
||||
</select>
|
||||
@@ -1,6 +0,0 @@
|
||||
<label class="form-check-label" for="color-scheme">UI color scheme</label>
|
||||
<select id="color-scheme" class="storeable-select form-select d-inline-block" oninput="setColorSchemeFromUI();" style="display: inline-block;">
|
||||
<option value="auto" {% if web_ui_options["color-scheme-default"] == "auto" %}selected{% end %}>Automatic</option>
|
||||
<option value="light" {% if web_ui_options["color-scheme-default"] == "light" %}selected{% end %}>Light</option>
|
||||
<option value="dark" {% if web_ui_options["color-scheme-default"] == "dark" %}selected{% end %}>Dark</option>
|
||||
</select>
|
||||
@@ -1,10 +0,0 @@
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Display
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-display-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeDisplayPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +0,0 @@
|
||||
<div class="card-header text-white bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-auto me-auto">
|
||||
Filters
|
||||
</div>
|
||||
<div class="col-auto d-inline-flex">
|
||||
<button id="close-filters-button" type="button" class="btn-close btn-close-white" aria-label="Close" onclick="closeFiltersPanel();"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,4 +0,0 @@
|
||||
<div class="d-inline-flex gap-1">
|
||||
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i> Filters</button>
|
||||
<button id="display-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i> Display</button>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<div id="timing-container">Loading...</div>
|
||||
@@ -1,7 +0,0 @@
|
||||
<span class="btn-group" role="group">
|
||||
<input type="radio" class="btn-check" name="runPause" id="runButton" autocomplete="off" checked>
|
||||
<label class="btn btn-outline-primary" for="runButton"><i class="fa-solid fa-play"></i> Run</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="runPause" id="pauseButton" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="pauseButton"><i class="fa-solid fa-pause"></i> Pause</label>
|
||||
</span>
|
||||
@@ -1,4 +0,0 @@
|
||||
<span style="position: relative;">
|
||||
<i id="searchicon" class="fa-solid fa-magnifying-glass"></i>
|
||||
<input id="search" type="search" class="form-control" oninput="filtersUpdated();" placeholder="Search">
|
||||
</span>
|
||||
@@ -483,7 +483,7 @@ paths:
|
||||
tags:
|
||||
- General
|
||||
summary: Get enumeration options
|
||||
description: Retrieves the list of options for various enumerated types, which can be found in the spots and also provided back to the API as query parameters. While these enumerated options are defined in this spec anyway, providing them in an API call allows us to define extra parameters, like the colours associated with bands, and also allows clients to set up their filters and features without having to have internal knowledge about, for example, what bands the server knows about. The call also returns a variety of other parameters that may be of use to a web UI or other client.
|
||||
description: Retrieves the list of options for various enumerated types, which can be found in the spots and also provided back to the API as query parameters. While these enumerated options are defined in this spec anyway, providing them in an API call allows us to define extra parameters, like the colours associated with bands, and also allows clients to set up their filters and features without having to have internal knowledge about, for example, what bands the server knows about. The call also returns a variety of other parameters that may be of use to a web UI, including the contents of the "web-ui-options" config section, which provides guidance for web UI implementations such as the built-in one on sensible configuration options such as the number of spots/alerts to retrieve, or the maximum age of spots to retrieve.
|
||||
operationId: options
|
||||
responses:
|
||||
'200':
|
||||
@@ -535,6 +535,46 @@ paths:
|
||||
type: boolean
|
||||
description: Whether the POST /spot call, to add spots to the server directly via its API, is permitted on this server.
|
||||
example: true
|
||||
web-ui-options:
|
||||
type: object
|
||||
properties:
|
||||
spot-count:
|
||||
type: array
|
||||
description: An array of suggested "spot counts" that the web UI can retrieve from the API
|
||||
items:
|
||||
type: integer
|
||||
example: 50
|
||||
spot-count-default:
|
||||
type: integer
|
||||
example: 50
|
||||
description: The suggested default "spot count" that the web UI should retrieve from the API
|
||||
max-spot-age:
|
||||
type: array
|
||||
description: An array of suggested "maximum spot ages" that the web UI can retrieve from the API
|
||||
items:
|
||||
type: integer
|
||||
example: 30
|
||||
max-spot-age-default:
|
||||
type: integer
|
||||
example: 30
|
||||
description: The suggested default "maximum spot age" that the web UI should retrieve from the API
|
||||
spot-providers-enabled-by-default:
|
||||
type: array
|
||||
description: A list of the spot providers that should be enabled in the web UI on first load, if the user hasn't already got a localStorage setting that sets their preference. This is to allow some high-volume providers like RBN to be enabled in Spothole's back-end and displayable in the web UI if the user wants, but by default the experience will not include them.
|
||||
items:
|
||||
type: string
|
||||
example: "POTA"
|
||||
alert-count:
|
||||
type: array
|
||||
description: An array of suggested "alert counts" that the web UI can retrieve from the API
|
||||
items:
|
||||
type: integer
|
||||
example: 100
|
||||
alert-count-default:
|
||||
type: integer
|
||||
example: 100
|
||||
description: The suggested default "alert count" that the web UI should retrieve from the API
|
||||
|
||||
|
||||
/lookup/call:
|
||||
get:
|
||||
@@ -653,80 +693,6 @@ paths:
|
||||
example: "Failed"
|
||||
|
||||
|
||||
|
||||
|
||||
/lookup/grid:
|
||||
get:
|
||||
tags:
|
||||
- Utilities
|
||||
summary: Look up grid details
|
||||
description: Perform a lookup of data about a Maidenhead grid square.
|
||||
operationId: grid
|
||||
parameters:
|
||||
- name: grid
|
||||
in: query
|
||||
description: Maidenhead grid, to any accuracy
|
||||
required: true
|
||||
type: string
|
||||
example: "AA00aa"
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
center:
|
||||
type: object
|
||||
properties:
|
||||
latitude:
|
||||
type: number
|
||||
description: Latitude of the centre of the grid reference.
|
||||
example: 0.0
|
||||
longitude:
|
||||
type: number
|
||||
description: Latitude of the centre of the grid reference.
|
||||
example: 0.0
|
||||
cq_zone:
|
||||
type: number
|
||||
description: CQ zone of the centre of the grid reference.
|
||||
example: 1
|
||||
itu_zone:
|
||||
type: number
|
||||
description: ITU zone of the centre of the grid reference.
|
||||
example: 1
|
||||
southwest:
|
||||
type: object
|
||||
properties:
|
||||
latitude:
|
||||
type: number
|
||||
description: Latitude of the south-west corner of the grid square.
|
||||
example: 0.0
|
||||
longitude:
|
||||
type: number
|
||||
description: Latitude of the south-west corner of the grid square.
|
||||
example: 0.0
|
||||
northeast:
|
||||
type: object
|
||||
properties:
|
||||
latitude:
|
||||
type: number
|
||||
description: Latitude of the north-east corner of the grid square.
|
||||
example: 0.0
|
||||
longitude:
|
||||
type: number
|
||||
description: Latitude of the north-east corner of the grid square.
|
||||
example: 0.0
|
||||
'422':
|
||||
description: Validation error e.g. reference format incorrect
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
example: "Failed"
|
||||
|
||||
|
||||
/spot:
|
||||
post:
|
||||
tags:
|
||||
|
||||
@@ -82,12 +82,14 @@ div.container {
|
||||
|
||||
input#search {
|
||||
max-width: 12em;
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
i#searchicon {
|
||||
position: absolute;
|
||||
left: 0rem;
|
||||
left: 1rem;
|
||||
top: 2px;
|
||||
padding: 10px;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -285,6 +285,13 @@ function loadOptions() {
|
||||
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#source-options", "source", options["alert_sources"]);
|
||||
|
||||
// Populate the Display panel
|
||||
web_ui_options["alert-count"].forEach(sc => $("#alerts-to-fetch").append($('<option>', {
|
||||
value: sc,
|
||||
text: sc
|
||||
})));
|
||||
$("#alerts-to-fetch").val(web_ui_options["alert-count-default"]);
|
||||
|
||||
// Load URL params. These may select things from the various filter & display options, so the function needs
|
||||
// to be called after these are set up, but if the URL params ask for "embedded mode", this will suppress
|
||||
// loading settings, so this needs to be called before that.
|
||||
@@ -292,7 +299,6 @@ function loadOptions() {
|
||||
|
||||
// Load filters from settings storage
|
||||
loadSettings();
|
||||
setColorScheme($("#color-scheme option:selected").val());
|
||||
|
||||
// Load alerts and set up the timer
|
||||
loadAlerts();
|
||||
|
||||
@@ -145,8 +145,7 @@ function updateBands() {
|
||||
|
||||
// Now each spot is tagged with how far down the div it should go, add them to the DOM.
|
||||
spotList.forEach(s => {
|
||||
let worked = alreadyWorked(s["dx_call"], s["band"], s["mode"]);
|
||||
bandSpotsDiv.append(`<div class="band-spot" style="top: ${s['pxDownBandLabel']}px; border-top: 1px solid ${bandToColor(s['band'])}; border-left: 5px solid ${bandToColor(s['band'])}; border-bottom: 1px solid ${bandToColor(s['band'])}; border-right: 1px solid ${bandToColor(s['band'])}; text-decoration: ${worked ? 'line-through' : 'none'};"><span class="band-spot-call">${s.dx_call}${s.dx_ssid != null ? "-" + s.dx_ssid : ""}</span><span class="band-spot-info">${s.dx_call}${s.dx_ssid != null ? "-" + s.dx_ssid : ""} ${(s.freq/1000000).toFixed(3)} ${s.mode}</span></div>`);
|
||||
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>`);
|
||||
});
|
||||
|
||||
// Work out how tall the canvas should be. Normally this is matching the normal band column height, but if some
|
||||
@@ -229,9 +228,20 @@ function loadOptions() {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
// Populate the Display panel
|
||||
web_ui_options["max-spot-age"].forEach(sc => $("#max-spot-age").append($('<option>', {
|
||||
value: sc * 60,
|
||||
text: sc
|
||||
})));
|
||||
$("#max-spot-age").val(web_ui_options["max-spot-age-default"] * 60);
|
||||
getAvailableBandColorSchemes().forEach(sc => $("#band-color-scheme").append($('<option>', {
|
||||
value: sc,
|
||||
text: sc
|
||||
})));
|
||||
$("#band-color-scheme").val(web_ui_options["default-band-color-scheme"]);
|
||||
|
||||
// First pass loading settings, so we can load the band colour scheme before the filters that need to use it
|
||||
loadSettings();
|
||||
setColorScheme($("#color-scheme option:selected").val());
|
||||
setBandColorScheme($("#band-color-scheme option:selected").val());
|
||||
|
||||
// Add CSS for band toggle buttons
|
||||
@@ -243,7 +253,7 @@ function loadOptions() {
|
||||
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]);
|
||||
generateModesMultiToggleFilterCard(options["modes"]);
|
||||
generateSourcesMultiToggleFilterCard(options["spot_sources"], spotProvidersEnabledByDefault);
|
||||
generateSourcesMultiToggleFilterCard(options["spot_sources"], web_ui_options["spot-providers-enabled-by-default"]);
|
||||
|
||||
// Load URL params. These may select things from the various filter & display options, so the function needs
|
||||
// to be called after these are set up, but if the URL params ask for "embedded mode", this will suppress
|
||||
|
||||
@@ -144,11 +144,8 @@ 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 != "") {
|
||||
setColorScheme(theme);
|
||||
saveSettings();
|
||||
}
|
||||
setColorScheme($("#color-scheme option:selected").val());
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
// Function to set the color scheme. Supported values: "dark", "light", "auto"
|
||||
@@ -166,12 +163,16 @@ function setColorScheme(mode) {
|
||||
|
||||
// Startup function to determine whether to use light or dark mode, or leave as auto
|
||||
function usePreferredTheme() {
|
||||
// Set the value of the select box to the server's default
|
||||
$("#color-scheme").val(web_ui_options["default-color-scheme"]);
|
||||
|
||||
// Work out if we have ever explicitly saved the value of our select box. If so, we set our colour scheme now based
|
||||
// on that. If not, we let the select stay with nothing selected, so that the server sets it to whatever the
|
||||
// server's default is when the options call is retrieved.
|
||||
// on that. If not, we let the select retain its default value from Spothole config and apply that.
|
||||
let val = localStorage.getItem("#color-scheme:value");
|
||||
if (val != null) {
|
||||
setColorScheme(JSON.parse(val));
|
||||
} else {
|
||||
setColorSchemeFromUI();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,9 +160,20 @@ function loadOptions() {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
// Populate the Display panel
|
||||
web_ui_options["max-spot-age"].forEach(sc => $("#max-spot-age").append($('<option>', {
|
||||
value: sc * 60,
|
||||
text: sc
|
||||
})));
|
||||
$("#max-spot-age").val(web_ui_options["max-spot-age-default"] * 60);
|
||||
getAvailableBandColorSchemes().forEach(sc => $("#band-color-scheme").append($('<option>', {
|
||||
value: sc,
|
||||
text: sc
|
||||
})));
|
||||
$("#band-color-scheme").val(web_ui_options["default-band-color-scheme"]);
|
||||
|
||||
// First pass loading settings, so we can load the band colour scheme before the filters that need to use it
|
||||
loadSettings();
|
||||
setColorScheme($("#color-scheme option:selected").val());
|
||||
setBandColorScheme($("#band-color-scheme option:selected").val());
|
||||
|
||||
// Add CSS for band toggle buttons
|
||||
@@ -174,7 +185,7 @@ function loadOptions() {
|
||||
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]);
|
||||
generateModesMultiToggleFilterCard(options["modes"]);
|
||||
generateSourcesMultiToggleFilterCard(options["spot_sources"], spotProvidersEnabledByDefault);
|
||||
generateSourcesMultiToggleFilterCard(options["spot_sources"], web_ui_options["spot-providers-enabled-by-default"]);
|
||||
|
||||
// Load URL params. These may select things from the various filter & display options, so the function needs
|
||||
// to be called after these are set up, but if the URL params ask for "embedded mode", this will suppress
|
||||
|
||||
@@ -105,7 +105,6 @@ function updateTable() {
|
||||
var showType = $("#tableShowType")[0].checked;
|
||||
var showRef = $("#tableShowRef")[0].checked;
|
||||
var showDE = $("#tableShowDE")[0].checked;
|
||||
var showWorkedCheckbox = $("#tableShowWorkedCheckbox")[0].checked;
|
||||
|
||||
// Populate table with headers
|
||||
let table = $("#table");
|
||||
@@ -137,18 +136,12 @@ function updateTable() {
|
||||
if (showDE) {
|
||||
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();
|
||||
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>');
|
||||
}
|
||||
|
||||
// We are regenerating the entire table not just adding a new row, so reset the row counter
|
||||
rowCount = 0;
|
||||
|
||||
let spotsNewestFirst = spots.toReversed();
|
||||
spotsNewestFirst.forEach(s => addSpotToTopOfTable(s, false));
|
||||
}
|
||||
@@ -181,7 +174,6 @@ function createNewTableRowsForSpot(s, highlightNew) {
|
||||
var showType = $("#tableShowType")[0].checked;
|
||||
var showRef = $("#tableShowRef")[0].checked;
|
||||
var showDE = $("#tableShowDE")[0].checked;
|
||||
var showWorkedCheckbox = $("#tableShowWorkedCheckbox")[0].checked;
|
||||
|
||||
// Create row
|
||||
let $tr = $('<tr>');
|
||||
@@ -193,9 +185,8 @@ function createNewTableRowsForSpot(s, highlightNew) {
|
||||
$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) {
|
||||
// Show faded out if QRT
|
||||
if (s["qrt"] == true) {
|
||||
$tr.addClass("table-faded");
|
||||
}
|
||||
|
||||
@@ -317,9 +308,6 @@ function createNewTableRowsForSpot(s, highlightNew) {
|
||||
// Format band name
|
||||
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"});" title="Check this box to record that you have worked this callsign on their current band and mode.">`;
|
||||
|
||||
// Populate the row
|
||||
if (showTime) {
|
||||
$tr.append(`<td class='nowrap'>${time_formatted}</td>`);
|
||||
@@ -348,9 +336,6 @@ function createNewTableRowsForSpot(s, highlightNew) {
|
||||
if (showDE) {
|
||||
$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
|
||||
$tr2 = $("<tr class='hidenotonmobile'>");
|
||||
@@ -359,7 +344,7 @@ function createNewTableRowsForSpot(s, highlightNew) {
|
||||
if (rowCount % 2 == 1) {
|
||||
$tr2.addClass("table-active");
|
||||
}
|
||||
if (s["qrt"] == true || alreadyWorkedThis) {
|
||||
if (s["qrt"] == true) {
|
||||
$tr2.addClass("table-faded");
|
||||
}
|
||||
if (highlightNew) {
|
||||
@@ -382,9 +367,6 @@ function createNewTableRowsForSpot(s, highlightNew) {
|
||||
if (showDE) {
|
||||
$td2floatright.append(` de ${de_call} `);
|
||||
}
|
||||
if (showWorkedCheckbox) {
|
||||
$td2floatright.append(` ${workedCheckbox} `);
|
||||
}
|
||||
$td2.append($td2floatright);
|
||||
$td2.append(`</div><div style="clear: both;"></div>`);
|
||||
if (showComment) {
|
||||
@@ -404,9 +386,21 @@ function loadOptions() {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
// Populate the Display panel
|
||||
web_ui_options["spot-count"].forEach(sc => $("#spots-to-fetch").append($('<option>', {
|
||||
value: sc,
|
||||
text: sc,
|
||||
selected: sc == web_ui_options["spot-count-default"] // todo remove this?
|
||||
})));
|
||||
$("#spots-to-fetch").val(web_ui_options["spot-count-default"]); // todo setting val doesn't update UI?
|
||||
getAvailableBandColorSchemes().forEach(sc => $("#band-color-scheme").append($('<option>', {
|
||||
value: sc,
|
||||
text: sc
|
||||
})));
|
||||
$("#band-color-scheme").val(web_ui_options["default-band-color-scheme"]);
|
||||
|
||||
// First pass loading settings, so we can load the band colour scheme before the filters that need to use it
|
||||
loadSettings();
|
||||
setColorScheme($("#color-scheme option:selected").val());
|
||||
setBandColorScheme($("#band-color-scheme option:selected").val());
|
||||
|
||||
// Add CSS for band toggle buttons
|
||||
@@ -418,7 +412,7 @@ function loadOptions() {
|
||||
generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]);
|
||||
generateModesMultiToggleFilterCard(options["modes"]);
|
||||
generateSourcesMultiToggleFilterCard(options["spot_sources"], spotProvidersEnabledByDefault);
|
||||
generateSourcesMultiToggleFilterCard(options["spot_sources"], web_ui_options["spot-providers-enabled-by-default"]);
|
||||
|
||||
// Load URL params. These may select things from the various filter & display options, so the function needs
|
||||
// to be called after these are set up, but if the URL params ask for "embedded mode", this will suppress
|
||||
@@ -499,27 +493,6 @@ 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
|
||||
$(document).ready(function() {
|
||||
// Call loadOptions(), this will then trigger loading spots and setting up timers.
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
// Storage for the spot data that the server gives us.
|
||||
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.
|
||||
// Some band names contain decimal points which are not allowed in CSS classes, so we text-replace them to "p".
|
||||
@@ -123,11 +118,6 @@ function setBandColorSchemeFromUI() {
|
||||
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
|
||||
// after some time has passed with it in the background.
|
||||
addEventListener("visibilitychange", (event) => {
|
||||
@@ -135,12 +125,3 @@ addEventListener("visibilitychange", (event) => {
|
||||
loadSpots();
|
||||
}
|
||||
});
|
||||
|
||||
// Startup
|
||||
$(document).ready(function() {
|
||||
// Load worked list
|
||||
var tmpWorked = JSON.parse(localStorage.getItem("worked"));
|
||||
if (tmpWorked) {
|
||||
worked = tmpWorked;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user