mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-28 18:22:05 +00:00
Replace pages' use of the options call with directly building the HTML components using templates, as the syntax is much nicer. Use a common set of data provided to all modules via pagetemplate.py instead of passing things like web_ui_options around. Closes #153
This commit is contained in:
+14
-47
@@ -26,45 +26,8 @@ const PROVIDER_CREDENTIAL_SCHEMAS = {
|
||||
]
|
||||
};
|
||||
|
||||
// Load server options. Once a successful callback is made from this, we can populate the choice boxes in the form and load
|
||||
// any saved values from local storage.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v3/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
// Populate modes drop-down
|
||||
$.each(options["modes"], function (i, m) {
|
||||
$('#mode').append($('<option>', {
|
||||
value: m,
|
||||
text: m
|
||||
}));
|
||||
});
|
||||
|
||||
// Populate activity drop-down
|
||||
$.each(options["activities"], function (i, activity) {
|
||||
$('#activity').append($('<option>', {
|
||||
value: activity.name,
|
||||
text: activity.name
|
||||
}));
|
||||
});
|
||||
|
||||
// Load reCAPTCHA if a site key is configured (key is inlined into page by server)
|
||||
if (window._recaptchaSiteKey) {
|
||||
loadRecaptcha(window._recaptchaSiteKey);
|
||||
}
|
||||
|
||||
// Load settings from settings storage now all the controls are available
|
||||
loadSettings();
|
||||
|
||||
// Update the upstream area for any pre-selected activity
|
||||
updateUpstreamArea();
|
||||
});
|
||||
}
|
||||
|
||||
// Load and inject the reCAPTCHA script
|
||||
function loadRecaptcha(siteKey) {
|
||||
window._recaptchaSiteKey = siteKey;
|
||||
function loadRecaptcha() {
|
||||
if (!document.getElementById('recaptcha-script')) {
|
||||
const script = document.createElement('script');
|
||||
script.id = 'recaptcha-script';
|
||||
@@ -79,20 +42,19 @@ function loadRecaptcha(siteKey) {
|
||||
// Called by reCAPTCHA after its script loads
|
||||
function renderRecaptcha() {
|
||||
window._recaptchaWidgetId = grecaptcha.render('recaptcha-widget', {
|
||||
sitekey: window._recaptchaSiteKey,
|
||||
sitekey: RECAPTCHA_SITE_KEY,
|
||||
size: 'normal'
|
||||
});
|
||||
}
|
||||
|
||||
// Update the "Send spot to..." area based on the currently selected activity
|
||||
function updateUpstreamArea() {
|
||||
if (!window._allowUpstreamSpotting || !options || !options["spot_submit_providers"]) {
|
||||
if (!ALLOW_UPSTREAM_SPOTTING) {
|
||||
$("#upstream-area").hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const activity = $("#activity").val();
|
||||
const providers = (activity && options["spot_submit_providers"][activity]) ? options["spot_submit_providers"][activity] : [];
|
||||
const providers = SPOT_SUBMIT_PROVIDERS[$("#activity").val()] || [];
|
||||
|
||||
if (providers.length === 0) {
|
||||
$("#upstream-area").hide();
|
||||
@@ -131,9 +93,7 @@ function updateCredentialsButton() {
|
||||
|
||||
// Get the currently selected upstream provider name
|
||||
function getSelectedUpstreamProvider() {
|
||||
const providers = (options && options["spot_submit_providers"] && $("#activity").val())
|
||||
? (options["spot_submit_providers"][$("#activity").val()] || [])
|
||||
: [];
|
||||
const providers = SPOT_SUBMIT_PROVIDERS[$("#activity").val()] || [];
|
||||
if (providers.length === 0) return null;
|
||||
if (providers.length === 1) return providers[0];
|
||||
return $("#upstream-provider-select").val();
|
||||
@@ -302,6 +262,13 @@ $("#upstream-provider-select").change(function () {
|
||||
|
||||
// Startup
|
||||
$(document).ready(function () {
|
||||
// Load options
|
||||
loadOptions();
|
||||
// Load reCAPTCHA if a site key is configured
|
||||
if (RECAPTCHA_SITE_KEY) {
|
||||
loadRecaptcha();
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
|
||||
// Update the upstream area for any pre-selected activity
|
||||
updateUpstreamArea();
|
||||
});
|
||||
|
||||
+9
-29
@@ -328,33 +328,6 @@ function addAlertRowsToTable(tbody, alerts) {
|
||||
});
|
||||
}
|
||||
|
||||
// Load server options. Once a successful callback is made from this, we then query alerts.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v3/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
// Populate the filters panel
|
||||
generateMultiToggleFilterCard("#dx_continent_options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#source-options", "source", options["alert_providers"]);
|
||||
// Alerts can only ever be tagged with activities that have an alert source, so only offer those as filters here
|
||||
generateActivitiesMultiToggleFilterCard(options["activities"].filter(o => o["alerts_possible"]), false);
|
||||
|
||||
// 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.
|
||||
loadURLParams();
|
||||
|
||||
// Load filters from settings storage
|
||||
loadSettings();
|
||||
setColorScheme($("#color_scheme option:selected").val());
|
||||
|
||||
// Load alerts and set up the timer
|
||||
loadAlerts();
|
||||
setInterval(loadAlerts, REFRESH_INTERVAL_SEC * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
// Method called when any filter is changed to reload the alerts and persist the filter settings.
|
||||
function filtersUpdated() {
|
||||
loadAlerts();
|
||||
@@ -363,8 +336,15 @@ function filtersUpdated() {
|
||||
|
||||
// Startup
|
||||
$(document).ready(function () {
|
||||
// Call loadOptions(), this will then trigger loading alerts and setting up timers.
|
||||
loadOptions();
|
||||
// Load URL params. If these ask for "embedded mode", this will suppress loading settings, so this needs to be
|
||||
// called first.
|
||||
loadURLParams();
|
||||
loadSettings();
|
||||
setColorScheme($("#color_scheme option:selected").val());
|
||||
|
||||
// Load alerts and set up the timer
|
||||
loadAlerts();
|
||||
setInterval(loadAlerts, REFRESH_INTERVAL_SEC * 1000);
|
||||
});
|
||||
|
||||
// Reload alerts on becoming visible. This forces a refresh when used as a PWA and the user switches back to the PWA
|
||||
|
||||
+14
-42
@@ -108,7 +108,7 @@ function updateBands() {
|
||||
// Convert to a map of band names to the spots on that band. Bands with no
|
||||
// spots in view will not be present.
|
||||
const bandToSpots = new Map();
|
||||
options["bands"].forEach(function (band) {
|
||||
BANDS.forEach(function (band) {
|
||||
const matchingSpots = spotList.filter(function (s) {
|
||||
return s.band === band.name;
|
||||
});
|
||||
@@ -127,7 +127,7 @@ function updateBands() {
|
||||
table.find('thead tr').append(`<th style='background-color:${bandToColor(spotList[0].band)}; color:${bandToContrastColor(spotList[0].band)}'>${spotList[0].band}</th>`);
|
||||
|
||||
// Get the band data to fetch start and end frequencies
|
||||
let band = options["bands"].filter(function (b) {
|
||||
let band = BANDS.filter(function (b) {
|
||||
return b.name === bandName;
|
||||
})[0];
|
||||
|
||||
@@ -278,44 +278,6 @@ function removeDuplicatesForBandPanel(spotList) {
|
||||
return spotList.filter(s => !spotsToRemove.includes(s.uid));
|
||||
}
|
||||
|
||||
// Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query
|
||||
// spots repeatedly.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v3/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
// 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
|
||||
addBandToggleColourCSS(options["bands"]);
|
||||
|
||||
// Populate the filters panel
|
||||
generateBandsMultiToggleFilterCard(options["bands"]);
|
||||
generateActivitiesMultiToggleFilterCard(options["activities"]);
|
||||
generateMultiToggleFilterCard("#dx_continent_options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#de_continent_options", "de_continent", options["continents"]);
|
||||
generateModesMultiToggleFilterCard(options["modes"]);
|
||||
generateSourcesMultiToggleFilterCard(options["spot_providers"], 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
|
||||
// loading settings, so this needs to be called before that.
|
||||
loadURLParams();
|
||||
|
||||
// Load settings from settings storage now all the controls are available
|
||||
loadSettings();
|
||||
|
||||
// Load spots and start SSE for live updates
|
||||
loadSpots();
|
||||
// Set up spot expiry checker
|
||||
setInterval(expireOldSpots, 60 * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
// Method called when any display property is changed to reload the bands display and persist settings.
|
||||
function displayUpdated() {
|
||||
updateBands();
|
||||
@@ -331,6 +293,16 @@ $(document).ready(function () {
|
||||
}
|
||||
});
|
||||
|
||||
// Call loadOptions(), this will then trigger loading spots and setting up timers.
|
||||
loadOptions();
|
||||
// Load URL params. If these ask for "embedded mode", this will suppress loading settings, so this needs to be
|
||||
// called first.
|
||||
loadURLParams();
|
||||
loadSettings();
|
||||
setColorScheme($("#color_scheme option:selected").val());
|
||||
setBandColorScheme($("#band_color_scheme option:selected").val());
|
||||
addBandToggleColourCSS();
|
||||
|
||||
// Load spots and start SSE for live updates
|
||||
loadSpots();
|
||||
// Set up spot expiry checker
|
||||
setInterval(expireOldSpots, 60 * 1000);
|
||||
});
|
||||
+1
-73
@@ -1,5 +1,3 @@
|
||||
// Storage for the options that the server gives us. This will define our filters.
|
||||
let options = {};
|
||||
// Normally load user settings from local storage, unless embedded mode is in use
|
||||
let useLocalStorage = true;
|
||||
|
||||
@@ -124,75 +122,6 @@ function allFilterOptionsSelected(parameter) {
|
||||
return filter.length === 0;
|
||||
}
|
||||
|
||||
|
||||
// Generate a filter card with inline checkboxes plus All/None links.
|
||||
function generateMultiToggleFilterCard(elementID, filterQuery, options) {
|
||||
const $row = $('<div>');
|
||||
options.forEach(o => {
|
||||
$row.append(`<div class="form-check form-check-inline"><input type="checkbox" class="form-check-input filter-button-${filterQuery} storeable-checkbox" id="filter-button-${filterQuery}-${o}" value="${o}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" for="filter-button-${filterQuery}-${o}">${o}</label></div>`);
|
||||
});
|
||||
$(elementID).append($row);
|
||||
$(elementID).append(`<div class="mt-1"><a href="#" onclick="toggleFilterButtons('${filterQuery}', true); return false;">All</a> <a href="#" onclick="toggleFilterButtons('${filterQuery}', false); return false;">None</a></div>`);
|
||||
}
|
||||
|
||||
// Generate a collapsable section for the activities filter card, and append it to the given list. The section is
|
||||
// expanded by default if isExpanded is true. If hasContent is false (i.e. there are no activities to show in this
|
||||
// section), the section is omitted entirely rather than showing an empty collapsible heading.
|
||||
function appendActivityFilterSection($list, sectionId, heading, isExapanded, hasContent, buildBody) {
|
||||
if (!hasContent) {
|
||||
return;
|
||||
}
|
||||
const collapseId = `activity-options-${sectionId}-collapse`;
|
||||
const $li = $('<li class="mb-1">');
|
||||
$li.append(`<button class="btn-toggle${isExapanded ? '' : ' collapsed'}" data-bs-toggle="collapse" data-bs-target="#${collapseId}" aria-expanded="${isExapanded ? 'true' : 'false'}" aria-controls="${collapseId}">${heading}</button>`);
|
||||
const $body = $(`<div class="collapse filter-section-body${isExapanded ? ' show' : ''}" id="${collapseId}">`);
|
||||
buildBody($body);
|
||||
$li.append($body);
|
||||
$list.append($li);
|
||||
}
|
||||
|
||||
// Build the grid of activity checkboxes for a given (already filtered) list of activities.
|
||||
function buildActivityFilterGrid(activity_options) {
|
||||
const $grid = $('<div class="row row-cols-2 g-1 mb-1">');
|
||||
activity_options.forEach(o => {
|
||||
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||
$grid.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-activity storeable-checkbox" id="filter-button-activity-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-activity-${domSafeName}" for="filter-button-activity-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']} ${(o["region_flag"] != null) ? o['region_flag'] : ''}</label></div></div>`);
|
||||
});
|
||||
return $grid;
|
||||
}
|
||||
|
||||
// Generate activities filter card. This one is also a special case. includeGeneralDX controls whether the "General
|
||||
// DX" (NO_ACTIVITY) option is offered - this doesn't apply on the alerts page, where every alert has an activity.
|
||||
function generateActivitiesMultiToggleFilterCard(activity_options, includeGeneralDX = true) {
|
||||
const $list = $('<ul class="list-unstyled filter-section-list ps-0">');
|
||||
|
||||
const traditional = activity_options.filter(o => o["activity_type"] === "TRADITIONAL");
|
||||
appendActivityFilterSection($list, 'traditional', 'Traditional', true, includeGeneralDX || traditional.length > 0, $body => {
|
||||
if (includeGeneralDX) {
|
||||
$body.append(`<div class="w-100 mb-1"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-activity storeable-checkbox" id="filter-button-activity-NO_ACTIVITY" value="NO_ACTIVITY" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-activity-NO_ACTIVITY" for="filter-button-activity-NO_ACTIVITY"><i class="fa-solid fa-tower-cell"></i> General DX</label></div></div>`);
|
||||
}
|
||||
$body.append(buildActivityFilterGrid(traditional));
|
||||
});
|
||||
|
||||
const adventure = activity_options.filter(o => o["activity_type"] === "ADVENTURE");
|
||||
appendActivityFilterSection($list, 'adventure', 'Adventure', true, adventure.length > 0, $body => {
|
||||
$body.append(buildActivityFilterGrid(adventure));
|
||||
});
|
||||
|
||||
const regional = activity_options.filter(o => o["activity_type"] === "REGIONAL");
|
||||
appendActivityFilterSection($list, 'regional', 'Regional', false, regional.length > 0, $body => {
|
||||
$body.append(buildActivityFilterGrid(regional));
|
||||
});
|
||||
|
||||
const event = activity_options.filter(o => o["activity_type"] === "EVENT");
|
||||
appendActivityFilterSection($list, 'event', 'Event', false, event.length > 0, $body => {
|
||||
$body.append(buildActivityFilterGrid(event));
|
||||
});
|
||||
|
||||
$("#activity-options").append($list);
|
||||
$("#activity-options").append(`<div class="mt-1"><a href="#" onclick="toggleFilterButtons('activity', true); return false;">All</a> <a href="#" onclick="toggleFilterButtons('activity', false); return false;">None</a></div>`);
|
||||
}
|
||||
|
||||
// Method called when "All" or "None" is clicked
|
||||
function toggleFilterButtons(filterQuery, state) {
|
||||
$(".filter-button-" + filterQuery).each(function () {
|
||||
@@ -238,8 +167,7 @@ function setColorScheme(mode) {
|
||||
// Startup function to determine whether to use light or dark mode, or leave as auto
|
||||
function usePreferredTheme() {
|
||||
// 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 leave the colour scheme at the server's default, which is set in the page template.
|
||||
let val = localStorage.getItem("#color_scheme:value");
|
||||
if (val != null) {
|
||||
setColorScheme(JSON.parse(val));
|
||||
|
||||
+30
-57
@@ -322,61 +322,6 @@ function getTooltipText(s) {
|
||||
return ttt;
|
||||
}
|
||||
|
||||
// Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query
|
||||
// spots repeatedly.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v3/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
// 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
|
||||
addBandToggleColourCSS(options["bands"]);
|
||||
|
||||
// Populate the filters panel
|
||||
generateBandsMultiToggleFilterCard(options["bands"]);
|
||||
generateActivitiesMultiToggleFilterCard(options["activities"]);
|
||||
generateMultiToggleFilterCard("#dx_continent_options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#de_continent_options", "de_continent", options["continents"]);
|
||||
generateModesMultiToggleFilterCard(options["modes"]);
|
||||
generateSourcesMultiToggleFilterCard(options["spot_providers"], 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
|
||||
// loading settings, so this needs to be called before that.
|
||||
loadURLParams();
|
||||
loadMapURLParams();
|
||||
|
||||
// Load settings from settings storage now all the controls are available
|
||||
loadSettings();
|
||||
|
||||
// If no basemap has been explicitly saved and the UI is in dark mode, default to dark Mapnik
|
||||
if (localStorage.getItem("#basemap:value") === null) {
|
||||
if (document.documentElement.getAttribute("data-bs-theme") === "dark") {
|
||||
$("#basemap").val("OpenStreetMap.Mapnik.Dark");
|
||||
}
|
||||
}
|
||||
|
||||
// Apply basemap and overlay settings now that controls have their saved values
|
||||
setBasemap($("#basemap").val());
|
||||
setBasemapOpacity(parseFloat($("#basemapOpacity").val()));
|
||||
enableTerminator($("#showTerminator")[0].checked);
|
||||
enableMaidenheadGrid($("#showMaidenheadGrid")[0].checked);
|
||||
enableCQZones($("#showCQZones")[0].checked);
|
||||
enableITUZones($("#showITUZones")[0].checked);
|
||||
enableWABWAIGrid($("#showWABWAIGrid")[0].checked);
|
||||
|
||||
// Load spots and start SSE for live updates
|
||||
loadSpots();
|
||||
// Set up spot expiry checker
|
||||
setInterval(expireOldSpots, 60 * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
// Method called when any display property is changed to reload the map and persist the display settings.
|
||||
function displayUpdated() {
|
||||
updateMap();
|
||||
@@ -639,8 +584,36 @@ $(document).ready(function () {
|
||||
$(".hideonmap").hide();
|
||||
// Set up map
|
||||
setUpMap();
|
||||
// Call loadOptions(), this will then trigger loading spots and setting up timers.
|
||||
loadOptions();
|
||||
// Load URL params. If these ask for "embedded mode", this will suppress loading settings, so this needs to be
|
||||
// called first.
|
||||
loadURLParams();
|
||||
loadMapURLParams();
|
||||
loadSettings();
|
||||
setColorScheme($("#color_scheme option:selected").val());
|
||||
setBandColorScheme($("#band_color_scheme option:selected").val());
|
||||
addBandToggleColourCSS();
|
||||
|
||||
// If no basemap has been explicitly saved and the UI is in dark mode, default to dark Mapnik
|
||||
if (localStorage.getItem("#basemap:value") === null) {
|
||||
if (document.documentElement.getAttribute("data-bs-theme") === "dark") {
|
||||
$("#basemap").val("OpenStreetMap.Mapnik.Dark");
|
||||
}
|
||||
}
|
||||
|
||||
// Apply basemap and overlay settings now that controls have their saved values
|
||||
setBasemap($("#basemap").val());
|
||||
setBasemapOpacity(parseFloat($("#basemapOpacity").val()));
|
||||
enableTerminator($("#showTerminator")[0].checked);
|
||||
enableMaidenheadGrid($("#showMaidenheadGrid")[0].checked);
|
||||
enableCQZones($("#showCQZones")[0].checked);
|
||||
enableITUZones($("#showITUZones")[0].checked);
|
||||
enableWABWAIGrid($("#showWABWAIGrid")[0].checked);
|
||||
|
||||
// Load spots and start SSE for live updates
|
||||
loadSpots();
|
||||
// Set up spot expiry checker
|
||||
setInterval(expireOldSpots, 60 * 1000);
|
||||
|
||||
// Display intro box
|
||||
displayIntroBox();
|
||||
// Prevent mouse scroll and touch actions in the popup menus being passed through to the map
|
||||
|
||||
+21
-48
@@ -461,52 +461,6 @@ function createNewTableRowsForSpot(s, highlightNew) {
|
||||
return [$tr, $tr2];
|
||||
}
|
||||
|
||||
// Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query
|
||||
// spots repeatedly.
|
||||
function loadOptions() {
|
||||
$.getJSON('/api/v3/options', function (jsonData) {
|
||||
// Store options
|
||||
options = jsonData;
|
||||
|
||||
// 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
|
||||
addBandToggleColourCSS(options["bands"]);
|
||||
|
||||
// Populate the filters panel
|
||||
generateBandsMultiToggleFilterCard(options["bands"]);
|
||||
generateActivitiesMultiToggleFilterCard(options["activities"]);
|
||||
generateMultiToggleFilterCard("#dx_continent_options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#de_continent_options", "de_continent", options["continents"]);
|
||||
generateModesMultiToggleFilterCard(options["modes"]);
|
||||
generateSourcesMultiToggleFilterCard(options["spot_providers"], 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
|
||||
// loading settings, so this needs to be called before that.
|
||||
loadURLParams();
|
||||
|
||||
// Load settings from settings storage now all the controls are available
|
||||
loadSettings();
|
||||
|
||||
// Extra setting - the toggle for the "bearing" & "distance" columns are disabled if the user has not entered a
|
||||
// valid grid, and normally this logic is handled on user input to the grid field, but we might have just loaded
|
||||
// a value direct into the field, so apply the same logic here.
|
||||
$("#tableShowBearing").prop('disabled', !isUserGridValid());
|
||||
$("#tableShowDistance").prop('disabled', !isUserGridValid());
|
||||
if (!isUserGridValid()) {
|
||||
$("#tableShowBearing").prop('checked', false);
|
||||
$("#tableShowDistance").prop('checked', false);
|
||||
}
|
||||
|
||||
// Load spots (this will also set up the SSE connection to update them too)
|
||||
loadSpots();
|
||||
});
|
||||
}
|
||||
|
||||
// Work out if the user's entered grid is a valid Maidenhead grid
|
||||
function isUserGridValid() {
|
||||
const userGrid = $("#userGrid").val().toUpperCase();
|
||||
@@ -572,8 +526,27 @@ function clearWorked() {
|
||||
|
||||
// Startup
|
||||
$(document).ready(function () {
|
||||
// Call loadOptions(), this will then trigger loading spots and setting up timers.
|
||||
loadOptions();
|
||||
// Load URL params. If these ask for "embedded mode", this will suppress loading settings, so this needs to be
|
||||
// called first.
|
||||
loadURLParams();
|
||||
loadSettings();
|
||||
setColorScheme($("#color_scheme option:selected").val());
|
||||
setBandColorScheme($("#band_color_scheme option:selected").val());
|
||||
addBandToggleColourCSS();
|
||||
|
||||
// Extra setting - the toggle for the "bearing" & "distance" columns are disabled if the user has not entered a
|
||||
// valid grid, and normally this logic is handled on user input to the grid field, but we might have just loaded
|
||||
// a value direct into the field, so apply the same logic here.
|
||||
$("#tableShowBearing").prop('disabled', !isUserGridValid());
|
||||
$("#tableShowDistance").prop('disabled', !isUserGridValid());
|
||||
if (!isUserGridValid()) {
|
||||
$("#tableShowBearing").prop('checked', false);
|
||||
$("#tableShowDistance").prop('checked', false);
|
||||
}
|
||||
|
||||
// Load spots (this will also set up the SSE connection to update them too)
|
||||
loadSpots();
|
||||
|
||||
// Display intro box
|
||||
displayIntroBox();
|
||||
|
||||
|
||||
@@ -7,80 +7,38 @@ let spots = [];
|
||||
let worked = []
|
||||
|
||||
// Dynamically add CSS code for the band checkboxes to show in the appropriate colour.
|
||||
// Some band names contain decimal points which are not allowed in CSS classes, so we text-replace them to "p".
|
||||
function addBandToggleColourCSS(band_options) {
|
||||
function addBandToggleColourCSS() {
|
||||
const $style = $('<style>');
|
||||
band_options.forEach(o => {
|
||||
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||
$style.append(`#filter-button-label-band-${domSafeName} { padding-left: 0.3em; border-left: 5px solid ${bandToColor(o['name'])};}`);
|
||||
$(".filter-button-band").each(function () {
|
||||
$style.append(`label[for="${this.id}"] { padding-left: 0.3em; border-left: 5px solid ${bandToColor(this.value)};}`);
|
||||
});
|
||||
$('html > head').append($style);
|
||||
}
|
||||
|
||||
// Generate bands filter card. This one is a special case.
|
||||
function generateBandsMultiToggleFilterCard(band_options) {
|
||||
const $grid = $('<div class="row row-cols-3 row-cols-md-2 row-cols-lg-3 row-cols-xxl-4 g-1 mb-1">');
|
||||
band_options.forEach(o => {
|
||||
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||
$grid.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-band storeable-checkbox" id="filter-button-band-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked> <label class="form-check-label" id="filter-button-label-band-${domSafeName}" for="filter-button-band-${domSafeName}">${o['name']}</label></div></div>`);
|
||||
});
|
||||
$("#band-options").append($grid);
|
||||
$("#band-options").append(`<div class="mt-1"><a href="#" onclick="toggleFilterButtons('band', true); return false;">All</a> <a href="#" onclick="toggleFilterButtons('band', false); return false;">None</a> <a href="#" onclick="setHamHFBandToggles(); return false;">Ham HF only</a></div>`);
|
||||
}
|
||||
|
||||
// Set the band toggles so that only the amateur radio HF bands are selected. This includes 160m and 6m because that's
|
||||
// widely expected by hams to be included. Special case of toggleFilterButtons().
|
||||
// Set the band toggles so that only the amateur radio HF bands are selected. Special case of toggleFilterButtons().
|
||||
function setHamHFBandToggles() {
|
||||
const hamHFBands = ["160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m", "6m"];
|
||||
$(".filter-button-band").each(function () {
|
||||
$(this).prop('checked', hamHFBands.includes($(this).val().replace("filter-button-band-", "")));
|
||||
$(this).prop('checked', $(this).data("ham-hf"));
|
||||
});
|
||||
filtersUpdated();
|
||||
}
|
||||
|
||||
// Generate modes filter card. This one is also a special case.
|
||||
function generateModesMultiToggleFilterCard(mode_options) {
|
||||
const $grid = $('<div class="row row-cols-3 row-cols-md-2 row-cols-lg-3 g-1 mb-1">');
|
||||
mode_options.forEach(o => {
|
||||
const domSafeName = o.replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||
$grid.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-mode storeable-checkbox" id="filter-button-mode-${domSafeName}" value="${o}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-mode-${domSafeName}" for="filter-button-mode-${domSafeName}">${o}</label></div></div>`);
|
||||
});
|
||||
$("#mode-options").append($grid);
|
||||
$("#mode-options").append(`<div class="mt-1"><a href="#" onclick="toggleFilterButtons('mode', true); return false;">All</a> <a href="#" onclick="toggleFilterButtons('mode', false); return false;">None</a> <a href="#" onclick="setVoiceModeToggles(); return false;">Voice only</a> <a href="#" onclick="setDigiModeToggles(); return false;">Digimodes only</a></div>`);
|
||||
}
|
||||
|
||||
// Set the mode toggles that relate to Analog Voice.
|
||||
function setVoiceModeToggles() {
|
||||
const modes = ["PHONE", "SSB", "AM", "FM", "DV"];
|
||||
$(".filter-button-mode").each(function () {
|
||||
$(this).prop('checked', modes.includes($(this).val().replace("filter-button-mode-", "")));
|
||||
$(this).prop('checked', $(this).data("mode-type") === "PHONE");
|
||||
});
|
||||
filtersUpdated();
|
||||
}
|
||||
|
||||
// Set the mode toggles that relate to Digimodes.
|
||||
function setDigiModeToggles() {
|
||||
const modes = ["DATA", "FT8", "FT4", "RTTY", "SSTV", "JS8", "HELL", "PSK", "OLIVIA", "PKT", "MSK144"];
|
||||
$(".filter-button-mode").each(function () {
|
||||
$(this).prop('checked', modes.includes($(this).val().replace("filter-button-mode-", "")));
|
||||
$(this).prop('checked', $(this).data("mode-type") === "DATA");
|
||||
});
|
||||
filtersUpdated();
|
||||
}
|
||||
|
||||
// Generate Sources filter card. This one is a minor special case as we create the checkboxes in the normal way, but
|
||||
// set which ones are enabled by default based on config rather than having them all enabled by default. We also sanitise
|
||||
// names here for HTML elements.
|
||||
function generateSourcesMultiToggleFilterCard(source_options, sources_enabled_by_default) {
|
||||
const $grid = $('<div class="row row-cols-2 row-cols-xxl-3 g-1 mb-1">');
|
||||
source_options.forEach(o => {
|
||||
const enable = sources_enabled_by_default.includes(o);
|
||||
const domSafeName = o.replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||
$grid.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-source storeable-checkbox" id="filter-button-source-${domSafeName}" value="${o}" autocomplete="off" onClick="filtersUpdated()" ${enable ? "checked" : ""}><label class="form-check-label" for="filter-button-source-${domSafeName}">${o}</label></div></div>`);
|
||||
});
|
||||
$("#source-options").append($grid);
|
||||
$("#source-options").append(`<div class="mt-1"><a href="#" onclick="toggleFilterButtons('source', true); return false;">All</a> <a href="#" onclick="toggleFilterButtons('source', false); return false;">None</a></div>`);
|
||||
}
|
||||
|
||||
// Method called when any filter is changed to reload the spots and persist the filter settings.
|
||||
function filtersUpdated() {
|
||||
loadSpots();
|
||||
|
||||
Reference in New Issue
Block a user