mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-02-04 01:04:33 +00:00
Style improvements and fixes #3
This commit is contained in:
@@ -10,7 +10,7 @@ from data.sig_ref import SIGRef
|
|||||||
|
|
||||||
# Alert provider for Beaches on the Air
|
# Alert provider for Beaches on the Air
|
||||||
class BOTA(HTTPAlertProvider):
|
class BOTA(HTTPAlertProvider):
|
||||||
POLL_INTERVAL_SEC = 3600
|
POLL_INTERVAL_SEC = 1800
|
||||||
ALERTS_URL = "https://www.beachesontheair.com/"
|
ALERTS_URL = "https://www.beachesontheair.com/"
|
||||||
|
|
||||||
def __init__(self, provider_config):
|
def __init__(self, provider_config):
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from data.alert import Alert
|
|||||||
|
|
||||||
# Alert provider NG3K DXpedition list
|
# Alert provider NG3K DXpedition list
|
||||||
class NG3K(HTTPAlertProvider):
|
class NG3K(HTTPAlertProvider):
|
||||||
POLL_INTERVAL_SEC = 3600
|
POLL_INTERVAL_SEC = 1800
|
||||||
ALERTS_URL = "https://www.ng3k.com/adxo.xml"
|
ALERTS_URL = "https://www.ng3k.com/adxo.xml"
|
||||||
AS_CALL_PATTERN = re.compile("as ([a-z0-9/]+)", re.IGNORECASE)
|
AS_CALL_PATTERN = re.compile("as ([a-z0-9/]+)", re.IGNORECASE)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from data.sig_ref import SIGRef
|
|||||||
|
|
||||||
# Alert provider for Parks n Peaks
|
# Alert provider for Parks n Peaks
|
||||||
class ParksNPeaks(HTTPAlertProvider):
|
class ParksNPeaks(HTTPAlertProvider):
|
||||||
POLL_INTERVAL_SEC = 3600
|
POLL_INTERVAL_SEC = 1800
|
||||||
ALERTS_URL = "http://parksnpeaks.org/api/ALERTS/"
|
ALERTS_URL = "http://parksnpeaks.org/api/ALERTS/"
|
||||||
|
|
||||||
def __init__(self, provider_config):
|
def __init__(self, provider_config):
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from data.sig_ref import SIGRef
|
|||||||
|
|
||||||
# Alert provider for Parks on the Air
|
# Alert provider for Parks on the Air
|
||||||
class POTA(HTTPAlertProvider):
|
class POTA(HTTPAlertProvider):
|
||||||
POLL_INTERVAL_SEC = 3600
|
POLL_INTERVAL_SEC = 1800
|
||||||
ALERTS_URL = "https://api.pota.app/activation"
|
ALERTS_URL = "https://api.pota.app/activation"
|
||||||
|
|
||||||
def __init__(self, provider_config):
|
def __init__(self, provider_config):
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from data.sig_ref import SIGRef
|
|||||||
|
|
||||||
# Alert provider for Summits on the Air
|
# Alert provider for Summits on the Air
|
||||||
class SOTA(HTTPAlertProvider):
|
class SOTA(HTTPAlertProvider):
|
||||||
POLL_INTERVAL_SEC = 3600
|
POLL_INTERVAL_SEC = 1800
|
||||||
ALERTS_URL = "https://api-db2.sota.org.uk/api/alerts/365/all/all"
|
ALERTS_URL = "https://api-db2.sota.org.uk/api/alerts/365/all/all"
|
||||||
|
|
||||||
def __init__(self, provider_config):
|
def __init__(self, provider_config):
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from data.sig_ref import SIGRef
|
|||||||
|
|
||||||
# Alert provider for Wainwrights on the Air
|
# Alert provider for Wainwrights on the Air
|
||||||
class WOTA(HTTPAlertProvider):
|
class WOTA(HTTPAlertProvider):
|
||||||
POLL_INTERVAL_SEC = 3600
|
POLL_INTERVAL_SEC = 1800
|
||||||
ALERTS_URL = "https://www.wota.org.uk/alerts_rss.php"
|
ALERTS_URL = "https://www.wota.org.uk/alerts_rss.php"
|
||||||
RSS_DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S %z"
|
RSS_DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S %z"
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from data.sig_ref import SIGRef
|
|||||||
|
|
||||||
# Alert provider for Worldwide Flora and Fauna
|
# Alert provider for Worldwide Flora and Fauna
|
||||||
class WWFF(HTTPAlertProvider):
|
class WWFF(HTTPAlertProvider):
|
||||||
POLL_INTERVAL_SEC = 3600
|
POLL_INTERVAL_SEC = 1800
|
||||||
ALERTS_URL = "https://spots.wwff.co/static/agendas.json"
|
ALERTS_URL = "https://spots.wwff.co/static/agendas.json"
|
||||||
|
|
||||||
def __init__(self, provider_config):
|
def __init__(self, provider_config):
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<p>Spothole is a Progressive Web App, which means you can install it on an Android or iOS device by opening the site in Chrome or Safari respectively, and clicking "Install" on the pop-up panel. It'll only prompt you once, so if you dismiss the prompt and change your mind, you'll find an Install / Add to Home Screen option on your browser's menu.</p>
|
<p>Spothole is a Progressive Web App, which means you can install it on an Android or iOS device by opening the site in Chrome or Safari respectively, and clicking "Install" on the pop-up panel. It'll only prompt you once, so if you dismiss the prompt and change your mind, you'll find an Install / Add to Home Screen option on your browser's menu.</p>
|
||||||
<p>Installing Spothole on your phone is completely optional, the website works exactly the same way as the "app" does.</p>
|
<p>Installing Spothole on your phone is completely optional, the website works exactly the same way as the "app" does.</p>
|
||||||
<h4 class="mt-4">Why hasn't my spot/alert shown up yet?</h4>
|
<h4 class="mt-4">Why hasn't my spot/alert shown up yet?</h4>
|
||||||
<p>To avoid putting too much load on the various servers that Spothole connects to, the Spothole server only polls them once every two minutes for spots, and once every hour for alerts. (Some sources, such as DX clusters, RBN, APRS-IS and WWBOTA use a non-polling mechanism, and their updates will therefore arrive more quickly.) Then if you are using the web interface, that has its own rate at which it reloads the data from Spothole, which is once a minute for spots or 30 minutes for alerts. So you could be waiting around three minutes to see a newly added spot, or 90 minutes to see a newly added alert.</p>
|
<p>To avoid putting too much load on the various servers that Spothole connects to, the Spothole server only polls them once every two minutes for spots, and once every 30 minutes for alerts. (Some sources, such as DX clusters, RBN, APRS-IS and WWBOTA use a non-polling mechanism, and their updates will therefore arrive more quickly.) Then if you are using the web interface, that has its own rate at which it fetches the data from Spothole. This is instant for the main spots list, with new spots appearing immediately at the top of the list, while the map and bands displays update once a minute, and the alerts display updates once every 5 minutes. So you could be waiting around three minutes to see a newly added spot, or 40 minutes to see a newly added alert.</p>
|
||||||
<h4 class="mt-4">What licence does Spothole use?</h4>
|
<h4 class="mt-4">What licence does Spothole use?</h4>
|
||||||
<p>Spothole's source code is licenced under the Public Domain. You can write a Spothole client, run your own server, modify it however you like, you can claim you wrote it and charge people £1000 for a copy, I don't really mind. (Please don't do the last one. But if you're using my code for something cool, it would be nice to hear from you!)</p>
|
<p>Spothole's source code is licenced under the Public Domain. You can write a Spothole client, run your own server, modify it however you like, you can claim you wrote it and charge people £1000 for a copy, I don't really mind. (Please don't do the last one. But if you're using my code for something cool, it would be nice to hear from you!)</p>
|
||||||
<h2 class="mt-4">Data Accuracy</h2>
|
<h2 class="mt-4">Data Accuracy</h2>
|
||||||
|
|||||||
@@ -161,7 +161,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="table-container"></div>
|
<div id="table-container">
|
||||||
|
<table id="table" class="table"><thead><tr class="table-primary"></tr></thead><tbody></tbody></table>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -204,7 +204,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="table-container"></div>
|
<div id="table-container">
|
||||||
|
<table id="table" class="table"><thead><tr class="table-primary"></tr></thead><tbody></tbody></table>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -174,6 +174,19 @@ tr.table-faded td span {
|
|||||||
text-decoration: line-through !important;
|
text-decoration: line-through !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* New spot styles */
|
||||||
|
tr.new td {
|
||||||
|
animation: 2s linear newspotanim;
|
||||||
|
}
|
||||||
|
@keyframes newspotanim {
|
||||||
|
0% {
|
||||||
|
background-color: var(--bs-success-border-subtle);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-color: intial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Fudge apply our own "dark primary" and "dark danger" backgrounds as Bootstrap doesn't do this itself */
|
/* Fudge apply our own "dark primary" and "dark danger" backgrounds as Bootstrap doesn't do this itself */
|
||||||
[data-bs-theme=dark] tr.table-primary {
|
[data-bs-theme=dark] tr.table-primary {
|
||||||
--bs-table-bg: #053680;
|
--bs-table-bg: #053680;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// How often to query the server?
|
// How often to query the server?
|
||||||
const REFRESH_INTERVAL_SEC = 60 * 30;
|
const REFRESH_INTERVAL_SEC = 60 * 10;
|
||||||
|
|
||||||
// Storage for the alert data that the server gives us.
|
// Storage for the alert data that the server gives us.
|
||||||
var alerts = []
|
var alerts = []
|
||||||
@@ -51,7 +51,8 @@ function updateTable() {
|
|||||||
var showRef = $("#tableShowRef")[0].checked;
|
var showRef = $("#tableShowRef")[0].checked;
|
||||||
|
|
||||||
// Populate table with headers
|
// Populate table with headers
|
||||||
let table = $('<table class="table">').append('<thead><tr class="table-primary"></tr></thead><tbody></tbody>');
|
let table = $("#table");
|
||||||
|
table.find('thead tr').empty();
|
||||||
if (showStartTime) {
|
if (showStartTime) {
|
||||||
table.find('thead tr').append(`<th>${useLocalTime ? "Start (Local)" : "Start UTC"}</th>`);
|
table.find('thead tr').append(`<th>${useLocalTime ? "Start (Local)" : "Start UTC"}</th>`);
|
||||||
}
|
}
|
||||||
@@ -74,6 +75,8 @@ function updateTable() {
|
|||||||
table.find('thead tr').append(`<th class='hideonmobile'>Ref.</th>`);
|
table.find('thead tr').append(`<th class='hideonmobile'>Ref.</th>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.find('tbody').empty();
|
||||||
|
|
||||||
// Split alerts into three types, each of which will get its own table header: On now, next 24h, and later. "On now"
|
// Split alerts into three types, each of which will get its own table header: On now, next 24h, and later. "On now"
|
||||||
// is considered to be events with an end_time where start<now<end, or events with no end time that started in the
|
// is considered to be events with an end_time where start<now<end, or events with no end time that started in the
|
||||||
// last hour.
|
// last hour.
|
||||||
@@ -100,9 +103,6 @@ function updateTable() {
|
|||||||
if (onNow.length == 0 && next24h.length == 0 && later.length == 0) {
|
if (onNow.length == 0 && next24h.length == 0 && later.length == 0) {
|
||||||
table.find('tbody').append('<tr class="table-danger"><td colspan="100" style="text-align:center;">No alerts match your filters.</td></tr>');
|
table.find('tbody').append('<tr class="table-danger"><td colspan="100" style="text-align:center;">No alerts match your filters.</td></tr>');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update DOM
|
|
||||||
$('#table-container').html(table);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a row to tbody for each alert in the provided list
|
// Add a row to tbody for each alert in the provided list
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// SSE event source
|
// SSE event source
|
||||||
let evtSource;
|
let evtSource;
|
||||||
|
// Table row count, to alternate shading
|
||||||
|
let rowCount = 0;
|
||||||
|
|
||||||
// Load spots and populate the table.
|
// Load spots and populate the table.
|
||||||
function loadSpots() {
|
function loadSpots() {
|
||||||
@@ -28,12 +30,12 @@ function restartSSEConnection() {
|
|||||||
// Store last updated time
|
// Store last updated time
|
||||||
lastUpdateTime = moment.utc();
|
lastUpdateTime = moment.utc();
|
||||||
updateRefreshDisplay();
|
updateRefreshDisplay();
|
||||||
// Add spot to table
|
// Add spot to internal data store
|
||||||
newSpot = JSON.parse(event.data);
|
newSpot = JSON.parse(event.data);
|
||||||
console.log(newSpot);
|
|
||||||
spots.unshift(newSpot);
|
spots.unshift(newSpot);
|
||||||
spots = spots.slice(0, -1);
|
spots = spots.slice(0, -1);
|
||||||
updateTable();
|
// Add spot to table
|
||||||
|
addSpotToTopOfTable(newSpot, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
evtSource.onerror = function(err) {
|
evtSource.onerror = function(err) {
|
||||||
@@ -76,7 +78,8 @@ function updateTable() {
|
|||||||
var showDE = $("#tableShowDE")[0].checked;
|
var showDE = $("#tableShowDE")[0].checked;
|
||||||
|
|
||||||
// Populate table with headers
|
// Populate table with headers
|
||||||
let table = $('<table class="table">').append('<thead><tr class="table-primary"></tr></thead><tbody></tbody>');
|
let table = $("#table");
|
||||||
|
table.find('thead tr').empty();
|
||||||
if (showTime) {
|
if (showTime) {
|
||||||
table.find('thead tr').append(`<th>${useLocalTime ? "Local" : "UTC"}</th>`);
|
table.find('thead tr').append(`<th>${useLocalTime ? "Local" : "UTC"}</th>`);
|
||||||
}
|
}
|
||||||
@@ -105,19 +108,54 @@ function updateTable() {
|
|||||||
table.find('thead tr').append(`<th class='hideonmobile'>DE</th>`);
|
table.find('thead tr').append(`<th class='hideonmobile'>DE</th>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.find('tbody').empty();
|
||||||
if (spots.length == 0) {
|
if (spots.length == 0) {
|
||||||
table.find('tbody').append('<tr class="table-danger"><td colspan="100" style="text-align:center;">No spots match your filters.</td></tr>');
|
table.find('tbody').append('<tr class="table-danger"><td colspan="100" style="text-align:center;">No spots match your filters.</td></tr>');
|
||||||
}
|
}
|
||||||
|
|
||||||
var count = 0;
|
spots.reverse();
|
||||||
spots.forEach(s => {
|
spots.forEach(s => addSpotToTopOfTable(s, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add rows corresponding to a new spot to the top of the table
|
||||||
|
// highlightNew = false for an initial load, true for new SSE-loaded spots
|
||||||
|
function addSpotToTopOfTable(s, highlightNew) {
|
||||||
|
let rows = createNewTableRowsForSpot(s, highlightNew);
|
||||||
|
$("#table").find('tbody').prepend(rows[1]);
|
||||||
|
$("#table").find('tbody').prepend(rows[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn a spot into a set of table rows to represent it. This is actually two table rows because we need a second
|
||||||
|
// separate row for the mobile view.
|
||||||
|
// highlightNew = false for an initial load, true for new SSE-loaded spots
|
||||||
|
function createNewTableRowsForSpot(s, highlightNew) {
|
||||||
|
// Use local time instead of UTC?
|
||||||
|
var useLocalTime = $("#timeZone")[0].value == "local";
|
||||||
|
|
||||||
|
// Get user grid if valid, this will be null if it's not.
|
||||||
|
var userPos = latLonForGridCentre($("#userGrid").val());
|
||||||
|
|
||||||
|
// Table data toggles
|
||||||
|
var showTime = $("#tableShowTime")[0].checked;
|
||||||
|
var showDX = $("#tableShowDX")[0].checked;
|
||||||
|
var showFreq = $("#tableShowFreq")[0].checked;
|
||||||
|
var showMode = $("#tableShowMode")[0].checked;
|
||||||
|
var showComment = $("#tableShowComment")[0].checked;
|
||||||
|
var showBearing = $("#tableShowBearing")[0].checked && userPos != null;
|
||||||
|
var showType = $("#tableShowType")[0].checked;
|
||||||
|
var showRef = $("#tableShowRef")[0].checked;
|
||||||
|
var showDE = $("#tableShowDE")[0].checked;
|
||||||
|
|
||||||
// Create row
|
// Create row
|
||||||
let $tr = $('<tr>');
|
let $tr = $('<tr>');
|
||||||
|
if (highlightNew) {
|
||||||
|
$tr.addClass("new");
|
||||||
|
}
|
||||||
|
|
||||||
// Apply striping to the table. We can't just use Bootstrap's table-striped class because we have all sorts of
|
// Apply striping to the table. We can't just use Bootstrap's table-striped class because we have all sorts of
|
||||||
// extra faff to deal with, like the mobile view having extra rows, and the On Now / Next 24h / Later banners
|
// extra faff to deal with, like the mobile view having extra rows, and the On Now / Next 24h / Later banners
|
||||||
// which cause the table-striped colouring to go awry.
|
// which cause the table-striped colouring to go awry.
|
||||||
if (count % 2 == 1) {
|
if (rowCount % 2 == 1) {
|
||||||
$tr.addClass("table-active");
|
$tr.addClass("table-active");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,11 +305,10 @@ function updateTable() {
|
|||||||
if (showDE) {
|
if (showDE) {
|
||||||
$tr.append(`<td class='nowrap hideonmobile'><span class='flag-wrapper' title='${de_country}'>${de_flag}</span>${de_call}</td>`);
|
$tr.append(`<td class='nowrap hideonmobile'><span class='flag-wrapper' title='${de_country}'>${de_flag}</span>${de_call}</td>`);
|
||||||
}
|
}
|
||||||
table.find('tbody').append($tr);
|
|
||||||
|
|
||||||
// Second row for mobile view only, containing type, ref & comment
|
// Second row for mobile view only, containing type, ref & comment
|
||||||
$tr2 = $("<tr class='hidenotonmobile'>");
|
$tr2 = $("<tr class='hidenotonmobile'>");
|
||||||
if (count % 2 == 1) {
|
if (rowCount % 2 == 1) {
|
||||||
$tr2.addClass("table-active");
|
$tr2.addClass("table-active");
|
||||||
}
|
}
|
||||||
if (s["qrt"] == true) {
|
if (s["qrt"] == true) {
|
||||||
@@ -291,13 +328,10 @@ function updateTable() {
|
|||||||
$td2.append(`<br/>${commentText}`);
|
$td2.append(`<br/>${commentText}`);
|
||||||
}
|
}
|
||||||
$tr2.append($td2);
|
$tr2.append($td2);
|
||||||
table.find('tbody').append($tr2);
|
|
||||||
|
|
||||||
count++;
|
rowCount++;
|
||||||
});
|
|
||||||
|
|
||||||
// Update DOM
|
return [$tr, $tr2];
|
||||||
$('#table-container').html(table);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query
|
// Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query
|
||||||
|
|||||||
Reference in New Issue
Block a user