mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 22:37:44 +00:00
In the JS UI, turn each category of Activity into a collapsible section to save space #147
This commit is contained in:
@@ -917,10 +917,11 @@ components:
|
||||
ActivityType:
|
||||
type: string
|
||||
enum:
|
||||
- WORLDWIDE
|
||||
- TRADITIONAL
|
||||
- ADVENTURE
|
||||
- REGIONAL
|
||||
- EVENT
|
||||
example: WORLDWIDE
|
||||
example: TRADITIONAL
|
||||
|
||||
ActivityNameIncludingNoSig:
|
||||
oneOf:
|
||||
@@ -1597,16 +1598,16 @@ components:
|
||||
sig_type:
|
||||
type: boolean
|
||||
description: >
|
||||
Whether the activity is worldwide, regional, or for a specific event. 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.
|
||||
Whether the activity is traditional (e.g. EME), adventure (e.g. POTA), regional (e.g. WAB), or for a
|
||||
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"
|
||||
comment_names:
|
||||
type: array
|
||||
description: >
|
||||
Names by which this activity may be referred to in cluster spot comments. Most activities have a
|
||||
single entry matching their programme name (e.g. ["POTA"]), but some have none (where
|
||||
the name is ambiguous with other activities, such as Tiles and Toilets on the Air) or multiple
|
||||
the name is ambiguous with other activities, such as "TOTA" for Tiles and Toilets on the Air) or multiple
|
||||
entries (e.g. WWBOTA accepts both "WWBOTA" and "BOTA" since the latter is often used).
|
||||
items:
|
||||
type: string
|
||||
|
||||
@@ -279,6 +279,51 @@ div#map {
|
||||
}
|
||||
|
||||
|
||||
/* COLLAPSIBLE FILTER SECTIONS (e.g. Activities panel) */
|
||||
|
||||
ul.filter-section-list {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
button.btn-toggle {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: .25rem 0;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
color: var(--bs-emphasis-color);
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
button.btn-toggle:hover,
|
||||
button.btn-toggle:focus {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
button.btn-toggle::before {
|
||||
display: inline-block;
|
||||
width: 1.25em;
|
||||
line-height: 0;
|
||||
content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%280,0,0,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e");
|
||||
transition: transform .35s ease;
|
||||
transform-origin: .5em 50%;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] button.btn-toggle::before {
|
||||
content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%28255,255,255,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e");
|
||||
}
|
||||
|
||||
button.btn-toggle[aria-expanded="true"]::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
div.filter-section-body {
|
||||
padding-left: 1.25em;
|
||||
}
|
||||
|
||||
|
||||
/* BANDS PANEL */
|
||||
|
||||
div#bands-container {
|
||||
|
||||
@@ -38,33 +38,61 @@ function setHamHFBandToggles() {
|
||||
filtersUpdated();
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Generate activities filter card. This one is also a special case.
|
||||
function generateActivitiesMultiToggleFilterCard(activity_options) {
|
||||
const $grid1 = $('<div class="row row-cols-2 g-1 mb-1">');
|
||||
$("#sig-options").append('<strong>Worldwide</strong>');
|
||||
activity_options.filter(o => o["sig_type"] === "WORLDWIDE").forEach(o => {
|
||||
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||
$grid1.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 $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>`);
|
||||
});
|
||||
$body.append(`<div class="w-100"><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);
|
||||
});
|
||||
$("#sig-options").append($grid1);
|
||||
const $grid2 = $('<div class="row row-cols-2 g-1 mb-1">');
|
||||
$("#sig-options").append('<strong>Regional</strong>');
|
||||
activity_options.filter(o => o["sig_type"] === "REGIONAL").forEach(o => {
|
||||
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||
$grid2.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>`);
|
||||
|
||||
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);
|
||||
});
|
||||
$("#sig-options").append($grid2);
|
||||
const $grid3 = $('<div class="row row-cols-2 g-1 mb-1">');
|
||||
$("#sig-options").append('<strong>Event</strong>');
|
||||
activity_options.filter(o => o["sig_type"] === "EVENT").forEach(o => {
|
||||
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
|
||||
$grid3.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>`);
|
||||
|
||||
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);
|
||||
});
|
||||
$("#sig-options").append($grid3);
|
||||
$("#sig-options").append('<strong>Other</strong>');
|
||||
// Bonus "NO_SIG" / "General DX" option
|
||||
$("#sig-options").append(`<div class="w-100"><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>`);
|
||||
$("#sig-options").append(`<div class="mt-1"><a href="#" onclick="toggleFilterButtons('sig', true); return false;">All</a> <a href="#" onclick="toggleFilterButtons('sig', false); return false;">None</a></div>`);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
$("#activity-options").append($list);
|
||||
$("#activity-options").append(`<div class="mt-1"><a href="#" onclick="toggleFilterButtons('sig', true); return false;">All</a> <a href="#" onclick="toggleFilterButtons('sig', false); return false;">None</a></div>`);
|
||||
}
|
||||
|
||||
// Generate modes filter card. This one is also a special case.
|
||||
|
||||
Reference in New Issue
Block a user