mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 00:39:26 +00:00
More bands, filters layout, screenshots #7
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
|
||||
(S)pothole is a utility to aggregate "spots" from amateur radio DX clusters and xOTA spotting sites, and provide an open JSON API as well as a website to browse the data.
|
||||
|
||||

|
||||
|
||||
While there are several other web-based interfaces to DX clusters, and sites that aggregate spots from various outfoor activity programmes for amateur radio, (S)pothole differentiates itself by supporting a large number of data sources, and by being "API first" rather than just providing a web front-end. This allows other software to be built on top of it.
|
||||
|
||||
The API is deliberately well-defined with an OpenAPI specification and auto-generated API documentation. The API delivers spots in a consistent format regardless of the data source, freeing developers from needing to know how each individual data source presents its data.
|
||||
@@ -22,7 +24,11 @@ TODO
|
||||
|
||||
The software can take a few seconds to start up, mostly because it is downloading an updated file to match callsigns to countries. This is normal, don't panic!
|
||||
|
||||
### Writing your own Providers
|
||||
### Writing your own client
|
||||
|
||||
TODO
|
||||
|
||||
### Extending the server
|
||||
|
||||
(S)pothole is designed to be easily extensible. If you want to write your own provider, simply add a module to the `providers` package containing your class. (Currently, in order to be loaded correctly, the module (file) name should be the same as the class name, but lower case.)
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ MODE_TYPES = ["CW", "PHONE", "DATA"]
|
||||
|
||||
# Band definitions
|
||||
BANDS = [
|
||||
Band(name="2200m", start_freq=135700, end_freq=137800, color="#ff4500", contrast_color="white"),
|
||||
Band(name="600m", start_freq=472000, end_freq=479000, color="#1e90ff", contrast_color="white"),
|
||||
Band(name="160m", start_freq=1800000, end_freq=2000000, color="#7cfc00", contrast_color="black"),
|
||||
Band(name="80m", start_freq=3500000, end_freq=4000000, color="#e550e5", contrast_color="black"),
|
||||
Band(name="60m", start_freq=5250000, end_freq=5410000, color="#00008b", contrast_color="white"),
|
||||
@@ -25,13 +27,21 @@ BANDS = [
|
||||
Band(name="17m", start_freq=18068000, end_freq=18168000, color="#f2f261", contrast_color="black"),
|
||||
Band(name="15m", start_freq=21000000, end_freq=21450000, color="#cca166", contrast_color="black"),
|
||||
Band(name="12m", start_freq=24890000, end_freq=24990000, color="#b22222", contrast_color="white"),
|
||||
Band(name="11m", start_freq=26965000, end_freq=27405000, color="#00ff00", contrast_color="black"),
|
||||
Band(name="10m", start_freq=28000000, end_freq=29700000, color="#ff69b4", contrast_color="black"),
|
||||
Band(name="6m", start_freq=50000000, end_freq=54000000, color="#FF0000", contrast_color="white"),
|
||||
Band(name="5m", start_freq=56000000, end_freq=60500000, color="#e0e0e0", contrast_color="black"),
|
||||
Band(name="4m", start_freq=70000000, end_freq=70500000, color="#cc0044", contrast_color="white"),
|
||||
Band(name="2m", start_freq=144000000, end_freq=148000000, color="#FF1493", contrast_color="black"),
|
||||
Band(name="1.25m", start_freq=219000000, end_freq=225000000, color="#CCFF00", contrast_color="black"),
|
||||
Band(name="70cm", start_freq=420000000, end_freq=450000000, color="#999900", contrast_color="white"),
|
||||
Band(name="23cm", start_freq=1240000000, end_freq=1325000000, color="#5AB8C7", contrast_color="black"),
|
||||
Band(name="13cm", start_freq=2300000000, end_freq=2450000000, color="#FF7F50", contrast_color="black")]
|
||||
Band(name="2.4GHz", start_freq=2300000000, end_freq=2450000000, color="#FF7F50", contrast_color="black"),
|
||||
Band(name="5.8GHz", start_freq=5725000000, end_freq=5850000000, color="#cc0099", contrast_color="white"),
|
||||
Band(name="10GHz", start_freq=10000000000, end_freq=10500000000, color="#696969", contrast_color="white"),
|
||||
Band(name="24GHz", start_freq=24000000000, end_freq=24050000000, color="#f3edc6", contrast_color="black"),
|
||||
Band(name="47GHz", start_freq=47000000000, end_freq=47200000000, color="#ffe786", contrast_color="black"),
|
||||
Band(name="76GHz", start_freq=75500000000, end_freq=81500000000, color="#baf9d8", contrast_color="black")]
|
||||
UNKNOWN_BAND = Band(name="Unknown", start_freq=0, end_freq=0, color="black", contrast_color="white")
|
||||
|
||||
# Continents
|
||||
|
||||
BIN
images/screenshot-filters.png
Normal file
BIN
images/screenshot-filters.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
BIN
images/screenshot.png
Normal file
BIN
images/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 189 KiB |
@@ -43,7 +43,8 @@
|
||||
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="filters-container" class="row row-cols-1 row-cols-md-4 g-4"></div>
|
||||
<div id="filters-container-1" class="row row-cols-1 g-4 mb-4"></div>
|
||||
<div id="filters-container-2" class="row row-cols-1 row-cols-md-4 g-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@ span.flag-wrapper {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
span.band-bullet {
|
||||
display: inline-block;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
span.icon-wrapper {
|
||||
display: inline-block;
|
||||
width: 1.5em;
|
||||
@@ -25,7 +30,7 @@ span.icon-wrapper {
|
||||
|
||||
span.freq-mhz {
|
||||
display: inline-block;
|
||||
min-width: 1.5em;
|
||||
min-width: 1.7em;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -53,10 +58,6 @@ div.appearing-panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.status-card {
|
||||
max-width: 18rem;
|
||||
}
|
||||
|
||||
p.filter-card-text {
|
||||
line-height: 2.5em !important;
|
||||
}
|
||||
@@ -7,8 +7,6 @@ var spots = []
|
||||
var options = {};
|
||||
// Last time we updated the spots list on display.
|
||||
var lastUpdateTime;
|
||||
// Options-based lookups for band colours
|
||||
band_colors = {}
|
||||
|
||||
// Load spots and populate the table.
|
||||
function loadSpots() {
|
||||
@@ -65,12 +63,6 @@ function updateTable() {
|
||||
mode_string = mode_string + "<span class='mode-q'><i class='fa-solid fa-circle-question' title='The mode was not reported via the spotting service. This is a guess based on the frequency.'></i></span>"
|
||||
}
|
||||
|
||||
// Band-based colour
|
||||
var band_dot_style = ""
|
||||
if (band_colors[s["band"]]) {
|
||||
band_dot_style = `color: ${band_colors[s["band"]]}; `
|
||||
}
|
||||
|
||||
// Format sig_refs
|
||||
var sig_refs = ""
|
||||
if (s["sig_refs"]) {
|
||||
@@ -89,10 +81,15 @@ function updateTable() {
|
||||
de_country = "Unknown or not a country"
|
||||
}
|
||||
|
||||
// CSS doesn't like classes with decimal points in, so we need to replace that in the same way as when we originally
|
||||
// queried the options endpoint and set our CSS.
|
||||
var cssFormattedBandName = s['band'] ? s['band'].replace('.', 'p') : "unknown";
|
||||
var bandFullName = s['band'] ? s['band'] + " band": "Unknown band";
|
||||
|
||||
// Populate the row
|
||||
$tr.append(`<td>${time_formatted}</td>`);
|
||||
$tr.append(`<td><span class='flag-wrapper' title='${dx_country}'>${s["dx_flag"]}</span>${s["dx_call"]}</td>`);
|
||||
$tr.append(`<td><span class='band-dot' style='${band_dot_style}'>■</span>${freq_string}</td>`);
|
||||
$tr.append(`<td><span class='band-bullet band-bullet-${cssFormattedBandName}' title='${bandFullName}'>■</span>${freq_string}</td>`);
|
||||
$tr.append(`<td>${mode_string}</td>`);
|
||||
$tr.append('<td>' + escapeHtml(`${s["comment"]}`) + '</td>');
|
||||
$tr.append(`<td><span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i></span> ${s["source"]}</td>`);
|
||||
@@ -137,7 +134,7 @@ function loadStatus() {
|
||||
// Generate a status card
|
||||
function generateStatusCard(title, textLines) {
|
||||
let $col = $("<div class='col'>")
|
||||
let $card = $("<div class='card status-card'>");
|
||||
let $card = $("<div class='card'>");
|
||||
let $card_body = $("<div class='card-body'>");
|
||||
$card_body.append(`<h5 class='card-title'>${title}</h5>`);
|
||||
$card_body.append(`<p class='card-text'>${textLines.join("<br/>")}</p>`);
|
||||
@@ -153,20 +150,15 @@ function loadOptions() {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
// Separately store colour lookups for bands
|
||||
options["bands"].forEach(b => {
|
||||
band_colors[b["name"]] = b["color"];
|
||||
});
|
||||
|
||||
// Add CSS for band toggle buttons
|
||||
addBandToggleButtonCSS(options["bands"]);
|
||||
// Add CSS for band bullets and band toggle buttons
|
||||
addBandColourCSS(options["bands"]);
|
||||
|
||||
// Populate the filters panel
|
||||
$("#filters-container").append(generateFilterCard("DX Continent", "dx_continent", options["continents"]));
|
||||
$("#filters-container").append(generateFilterCard("DE Continent", "de_continent", options["continents"]));
|
||||
$("#filters-container").append(generateFilterCard("Modes", "mode_type", options["mode_types"]));
|
||||
$("#filters-container").append(generateFilterCard("Sources", "source", options["sources"]));
|
||||
$("#filters-container").append(generateBandsFilterCard("Bands", "band", options["bands"]));
|
||||
$("#filters-container-1").append(generateBandsFilterCard("Bands", "band", options["bands"]));
|
||||
$("#filters-container-2").append(generateFilterCard("DX Continent", "dx_continent", options["continents"]));
|
||||
$("#filters-container-2").append(generateFilterCard("DE Continent", "de_continent", options["continents"]));
|
||||
$("#filters-container-2").append(generateFilterCard("Modes", "mode_type", options["mode_types"]));
|
||||
$("#filters-container-2").append(generateFilterCard("Sources", "source", options["sources"]));
|
||||
|
||||
// Load spots and set up the timer
|
||||
loadSpots();
|
||||
@@ -174,20 +166,24 @@ function loadOptions() {
|
||||
});
|
||||
}
|
||||
|
||||
// Dynamically add CSS code for the band toggle buttons to be in the appropriate colour
|
||||
function addBandToggleButtonCSS(band_options) {
|
||||
// Dynamically add CSS code for the band bullets and 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".
|
||||
function addBandColourCSS(band_options) {
|
||||
var $style = $('<style>');
|
||||
band_options.forEach(o => {
|
||||
$style.append(`#filter-button-label-band-${o['name']} { border-color: ${o['color']}; color: var(--bs-primary);}`);
|
||||
$style.append(`.btn-check:checked + #filter-button-label-band-${o['name']} { background-color: ${o['color']}; color: ${o['contrast_color']};}`);
|
||||
// CSS doesn't like IDs with decimal points in, so we need to replace that
|
||||
var cssFormattedBandName = o['name'] ? o['name'].replace('.', 'p') : "unknown";
|
||||
$style.append(`.band-bullet-${cssFormattedBandName} { color: ${o['color']}; }`);
|
||||
$style.append(`#filter-button-label-band-${cssFormattedBandName} { border-color: ${o['color']}; color: var(--bs-primary);}`);
|
||||
$style.append(`.btn-check:checked + #filter-button-label-band-${cssFormattedBandName} { background-color: ${o['color']}; color: ${o['contrast_color']};}`);
|
||||
});
|
||||
$('html > head').append($style);
|
||||
}
|
||||
|
||||
// Generate filter card
|
||||
function generateFilterCard(displayName, filterQuery, options) {
|
||||
let $col = $("<div class='col-3'>")
|
||||
let $card = $("<div class='card status-card'>");
|
||||
let $col = $("<div class='col'>")
|
||||
let $card = $("<div class='card'>");
|
||||
let $card_body = $("<div class='card-body'>");
|
||||
$card_body.append(`<h5 class='card-title'>${displayName}</h5>`);
|
||||
$p = $("<p class='card-text filter-card-text'>");
|
||||
@@ -202,13 +198,16 @@ function generateFilterCard(displayName, filterQuery, options) {
|
||||
|
||||
// Generate bands filter card. This one is a special case.
|
||||
function generateBandsFilterCard(displayName, filterQuery, band_options) {
|
||||
let $col = $("<div class='col-12'>")
|
||||
let $card = $("<div class='card status-card'>");
|
||||
let $col = $("<div class='col'>")
|
||||
let $card = $("<div class='card'>");
|
||||
let $card_body = $("<div class='card-body'>");
|
||||
$card_body.append(`<h5 class='card-title'>${displayName}</h5>`);
|
||||
$p = $("<p class='card-text filter-card-text'>");
|
||||
band_options.forEach(o => {
|
||||
$p.append(`<input type="checkbox" class="btn-check filter-button-${filterQuery}" name="options" id="filter-button-${filterQuery}-${o['name']}" value="${filterQuery}:${o['name']}" autocomplete="off" checked><label class="btn btn-outline" id="filter-button-label-${filterQuery}-${o['name']}" for="filter-button-${filterQuery}-${o['name']}">${o['name']}</label> `);
|
||||
// CSS doesn't like IDs with decimal points in, so we need to replace that in the same way as when we originally
|
||||
// queried the options endpoint and set our CSS.
|
||||
var cssFormattedBandName = o['name'] ? o['name'].replace('.', 'p') : "unknown";
|
||||
$p.append(`<input type="checkbox" class="btn-check filter-button-${filterQuery}" name="options" id="filter-button-${filterQuery}-${o['name']}" value="${filterQuery}:${o['name']}" autocomplete="off" checked><label class="btn btn-outline" id="filter-button-label-${filterQuery}-${cssFormattedBandName}" for="filter-button-${filterQuery}-${o['name']}">${o['name']}</label> `);
|
||||
});
|
||||
$card_body.append($p);
|
||||
$card.append($card_body);
|
||||
|
||||
Reference in New Issue
Block a user