mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 14:27:42 +00:00
Restrict the activities that we display on the alerts page's filters to just ones that can actually be in alerts #147
This commit is contained in:
@@ -12,6 +12,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
# any spots with "contest" in the comment will get allocated to this activity.
|
||||
comment_names=["CONTEST"],
|
||||
icon="fa-trophy",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.DXPEDITION: Activity(
|
||||
name=ActivityName.DXPEDITION,
|
||||
@@ -23,6 +24,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
# this activity to a spot other ways.
|
||||
comment_names=[],
|
||||
icon="fa-book-atlas",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.SATELLITE: Activity(
|
||||
name=ActivityName.SATELLITE,
|
||||
@@ -32,6 +34,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
refs_globally_unique=False,
|
||||
comment_names=[],
|
||||
icon="fa-satellite",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.EME: Activity(
|
||||
name=ActivityName.EME,
|
||||
@@ -70,6 +73,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
refs_globally_unique=False,
|
||||
comment_names=["QRP"],
|
||||
icon="fa-volume-low",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.POTA: Activity(
|
||||
name=ActivityName.POTA,
|
||||
@@ -81,6 +85,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
ref_regex=r"[A-Z]{2}\-\d{4,5}|K\-TEST",
|
||||
comment_names=["POTA"],
|
||||
icon="fa-tree",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.SOTA: Activity(
|
||||
name=ActivityName.SOTA,
|
||||
@@ -92,6 +97,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
|
||||
comment_names=["SOTA"],
|
||||
icon="fa-mountain-sun",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.WWFF: Activity(
|
||||
name=ActivityName.WWFF,
|
||||
@@ -103,6 +109,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
ref_regex=r"[A-Z0-9]{1,3}FF\-\d{4}",
|
||||
comment_names=["WWFF"],
|
||||
icon="fa-seedling",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.GMA: Activity(
|
||||
name=ActivityName.GMA,
|
||||
@@ -136,6 +143,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{3}\-\d{3}",
|
||||
comment_names=["HEMA"],
|
||||
icon="fa-mound",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.IOTA: Activity(
|
||||
name=ActivityName.IOTA,
|
||||
@@ -202,6 +210,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d",
|
||||
comment_names=["SIOTA"],
|
||||
icon="fa-wheat-awn",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.WCA: Activity(
|
||||
name=ActivityName.WCA,
|
||||
@@ -225,6 +234,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
comment_names=["ZLOTA"],
|
||||
icon="fa-kiwi-bird",
|
||||
region_flag="🇳🇿",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.WOTA: Activity(
|
||||
name=ActivityName.WOTA,
|
||||
@@ -237,6 +247,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
comment_names=["WOTA"],
|
||||
icon="fa-w",
|
||||
region_flag="🇬🇧",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.BOTA: Activity(
|
||||
name=ActivityName.BOTA,
|
||||
@@ -247,6 +258,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
ref_type=ActivityRefType.BEACH,
|
||||
comment_names=[],
|
||||
icon="fa-umbrella-beach",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.KRMNPA: Activity(
|
||||
name=ActivityName.KRMNPA,
|
||||
@@ -259,6 +271,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
comment_names=["KRMNPA"],
|
||||
icon="fa-earth-oceania",
|
||||
region_flag="🇦🇺",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.SANPCPA: Activity(
|
||||
name=ActivityName.SANPCPA,
|
||||
@@ -271,6 +284,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
comment_names=["SANPCPA"],
|
||||
icon="fa-earth-oceania",
|
||||
region_flag="🇦🇺",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.LLOTA: Activity(
|
||||
name=ActivityName.LLOTA,
|
||||
@@ -282,6 +296,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
ref_regex=r"LL[A-Z]{2}\-\d{4}",
|
||||
comment_names=["LLOTA"],
|
||||
icon="fa-water",
|
||||
alerts_possible=True,
|
||||
),
|
||||
ActivityName.TOWERS: Activity(
|
||||
name=ActivityName.TOWERS,
|
||||
|
||||
@@ -43,3 +43,7 @@ class Activity:
|
||||
# Emoji flag for the country or region where this activity is relevant, if any. If None, this implies the
|
||||
# activity is in worldwide usage.
|
||||
region_flag: str | None = None
|
||||
# Can alerts ever have this activity? A lot of activities can be figured out from spot comments but can never
|
||||
# occur in alerts, so to avoid showing all activities on the "upcoming" page, we allow it to filter based on
|
||||
# this.
|
||||
alerts_possible: bool = False
|
||||
|
||||
@@ -20,6 +20,7 @@ info:
|
||||
* Renamed AMSAT SIG to "Satellite" as AMSAT is a specific organisation not just a general term for satellite QSOs
|
||||
* Removed `alert_type` from alert data. Contest, DXpedition and Satellite alerts now give those values in `sig` instead, alongside the existing outdoor activity programmes. Teeeeechnically a breaking change but AlertType is so new I doubt anyone is using it yet, so slipped this one in anyway. Sorry :)
|
||||
* Added QRP, RaDAR Rally, /AM and /MM activities
|
||||
* Added `has_refs` and `alerts_possible` to Activity data
|
||||
|
||||
### 2.1
|
||||
|
||||
@@ -1604,6 +1605,15 @@ components:
|
||||
specific event (e.g. MOTA). Generally for Spothole's own internal use, clients probably won't need this.
|
||||
Used to group them in the web UI. Still named "sig_type" in the API for backwards compatibility.
|
||||
$ref: "#/components/schemas/ActivityType"
|
||||
has_refs:
|
||||
type: boolean
|
||||
description: >
|
||||
Whether this activity has a fixed set of references (e.g. parks, summits) with an ID that gets
|
||||
"activated", as opposed to activities like Contest or DXpedition which don't.
|
||||
ref_type:
|
||||
description: >
|
||||
The type of reference that gets activated for this activity, e.g. Park or Summit.
|
||||
$ref: "#/components/schemas/ActivityRefType"
|
||||
comment_names:
|
||||
type: array
|
||||
description: >
|
||||
@@ -1639,6 +1649,11 @@ components:
|
||||
Flag emoji, if this activity is specific to a country or region. If null, this activity is treated as
|
||||
worldwide.
|
||||
example: "🇺🇳"
|
||||
alerts_possible:
|
||||
type: boolean
|
||||
description: >
|
||||
Whether alerts can ever have this activity. While Spothole can assign any activity from spot comments, there
|
||||
are only a limited set that can ever crop up in alerts, because we don't parse general comments for alerts.
|
||||
|
||||
SolarConditions:
|
||||
type: object
|
||||
|
||||
+2
-1
@@ -332,7 +332,8 @@ function loadOptions() {
|
||||
// Populate the filters panel
|
||||
generateMultiToggleFilterCard("#dx_continent_options", "dx_continent", options["continents"]);
|
||||
generateMultiToggleFilterCard("#source-options", "source", options["alert_providers"]);
|
||||
generateActivitiesMultiToggleFilterCard(options["sigs"]);
|
||||
// Alerts can only ever be tagged with activities that have an alert source, so only offer those as filters here
|
||||
generateActivitiesMultiToggleFilterCard(options["sigs"].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
|
||||
|
||||
+33
-32
@@ -136,8 +136,12 @@ function generateMultiToggleFilterCard(elementID, filterQuery, options) {
|
||||
}
|
||||
|
||||
// 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.
|
||||
function appendActivityFilterSection($list, sectionId, heading, isExapanded, buildBody) {
|
||||
// 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>`);
|
||||
@@ -147,45 +151,42 @@ function appendActivityFilterSection($list, sectionId, heading, isExapanded, bui
|
||||
$list.append($li);
|
||||
}
|
||||
|
||||
// Generate activities filter card. This one is also a special case.
|
||||
function generateActivitiesMultiToggleFilterCard(activity_options) {
|
||||
// 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-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${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_SIG) 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">');
|
||||
|
||||
appendActivityFilterSection($list, 'traditional', 'Traditional', true, $body => {
|
||||
const $grid = $('<div class="row row-cols-2 g-1 mb-1">');
|
||||
activity_options.filter(o => o["sig_type"] === "TRADITIONAL").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-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']} ${(o["region_flag"] != null) ? o['region_flag'] : ''}</label></div></div>`);
|
||||
});
|
||||
const traditional = activity_options.filter(o => o["sig_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-sig storeable-checkbox" id="filter-button-sig-NO_SIG" value="NO_SIG" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-NO_SIG" for="filter-button-sig-NO_SIG"><i class="fa-solid fa-tower-cell"></i> General DX</label></div></div>`);
|
||||
$body.append($grid);
|
||||
}
|
||||
$body.append(buildActivityFilterGrid(traditional));
|
||||
});
|
||||
|
||||
appendActivityFilterSection($list, 'adventure', 'Adventure', true, $body => {
|
||||
const $grid = $('<div class="row row-cols-2 g-1 mb-1">');
|
||||
activity_options.filter(o => o["sig_type"] === "ADVENTURE").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-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']} ${(o["region_flag"] != null) ? o['region_flag'] : ''}</label></div></div>`);
|
||||
});
|
||||
$body.append($grid);
|
||||
const adventure = activity_options.filter(o => o["sig_type"] === "ADVENTURE");
|
||||
appendActivityFilterSection($list, 'adventure', 'Adventure', true, adventure.length > 0, $body => {
|
||||
$body.append(buildActivityFilterGrid(adventure));
|
||||
});
|
||||
|
||||
appendActivityFilterSection($list, 'regional', 'Regional', false, $body => {
|
||||
const $grid = $('<div class="row row-cols-2 g-1 mb-1">');
|
||||
activity_options.filter(o => o["sig_type"] === "REGIONAL").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-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']} ${(o["region_flag"] != null) ? o['region_flag'] : ''}</label></div></div>`);
|
||||
});
|
||||
$body.append($grid);
|
||||
const regional = activity_options.filter(o => o["sig_type"] === "REGIONAL");
|
||||
appendActivityFilterSection($list, 'regional', 'Regional', false, regional.length > 0, $body => {
|
||||
$body.append(buildActivityFilterGrid(regional));
|
||||
});
|
||||
|
||||
appendActivityFilterSection($list, 'event', 'Event', false, $body => {
|
||||
const $grid = $('<div class="row row-cols-2 g-1 mb-1">');
|
||||
activity_options.filter(o => o["sig_type"] === "EVENT").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-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']} ${(o["region_flag"] != null) ? o['region_flag'] : ''}</label></div></div>`);
|
||||
});
|
||||
$body.append($grid);
|
||||
const event = activity_options.filter(o => o["sig_type"] === "EVENT");
|
||||
appendActivityFilterSection($list, 'event', 'Event', false, event.length > 0, $body => {
|
||||
$body.append(buildActivityFilterGrid(event));
|
||||
});
|
||||
|
||||
$("#activity-options").append($list);
|
||||
|
||||
Reference in New Issue
Block a user