feat(grids): the square map filters by mode, band and satellite
The FTx button is gone. It was the wrong grain in both directions: the Digital class already sat a contest RTTY square next to an FT8 one, and FTx then sat FT8 next to FT4, when what this map answers is where ONE mode has been heard. The four classes stay as buttons and a dropdown beside them offers a single mode. Its contents come from the log, not from a list in the code. That is what settles FT2: it is not a registered ADIF mode yet, so a hardcoded list meant either leaving out the operators already using it or shipping a mode that does not officially exist. A query does neither, and needs no change here the day it is registered. The mode offered is the SUBMODE where there is one, because ADIF files PSK63 under PSK and "PSK" is not the name anybody is looking for. The band list is the station's own, unioned with anything worked outside it so nothing in the log is unreachable, ordered by frequency through the band plan's index — sorting the names puts 10m between 1.25m and 12m. The satellite list is drawn from SAT_NAME on the squares themselves and the control is absent altogether on a terrestrial log: a VHF square worked through AO-91 and one worked line-of-sight are not the same achievement, and until now nothing separated them. The mode dropdown shares the scope with the class buttons rather than narrowing on top of them — mode is one question, and two controls both answering it is how a map ends up showing PHONE ∩ FT8 and nothing else. A stored FTX preference is read as Digital, so it cannot leave the map filtered by something no control shows as selected.
This commit is contained in:
+60
-5
@@ -1860,9 +1860,19 @@ type GridSquare struct {
|
||||
// return false to drop a QSO. Aggregation to 4 characters happens HERE rather
|
||||
// than in SQL — the column holds 4, 6 and 8-character grids, and lower(substr)
|
||||
// in SQL would differ between SQLite and MySQL for no gain.
|
||||
func (r *Repo) GridSquares(ctx context.Context, keep func(mode string) bool) ([]GridSquare, error) {
|
||||
// GridSquareRow is one contact as the grid map decides whether to keep it.
|
||||
//
|
||||
// Submode is here beside Mode because the mode an operator filters by is not
|
||||
// always the one in MODE: ADIF puts PSK63 in SUBMODE with PSK above it, so a
|
||||
// filter that read MODE alone offered "PSK" for a log full of PSK63.
|
||||
type GridSquareRow struct {
|
||||
Mode, Submode, Band, SatName string
|
||||
}
|
||||
|
||||
func (r *Repo) GridSquares(ctx context.Context, keep func(GridSquareRow) bool) ([]GridSquare, error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT COALESCE(grid,''), UPPER(COALESCE(mode,'')), LOWER(COALESCE(band,'')),
|
||||
SELECT COALESCE(grid,''), UPPER(COALESCE(mode,'')), UPPER(COALESCE(submode,'')),
|
||||
LOWER(COALESCE(band,'')), UPPER(COALESCE(sat_name,'')),
|
||||
COALESCE(lotw_rcvd,''), COALESCE(qsl_rcvd,''), COALESCE(eqsl_rcvd,'')
|
||||
FROM qso
|
||||
WHERE grid IS NOT NULL AND grid != ''
|
||||
@@ -1873,11 +1883,11 @@ func (r *Repo) GridSquares(ctx context.Context, keep func(mode string) bool) ([]
|
||||
defer rows.Close()
|
||||
out := map[string]*GridSquare{}
|
||||
for rows.Next() {
|
||||
var grid, mode, band, lotw, card, eqsl string
|
||||
if err := rows.Scan(&grid, &mode, &band, &lotw, &card, &eqsl); err != nil {
|
||||
var grid, mode, submode, band, satName, lotw, card, eqsl string
|
||||
if err := rows.Scan(&grid, &mode, &submode, &band, &satName, &lotw, &card, &eqsl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if keep != nil && !keep(mode) {
|
||||
if keep != nil && !keep(GridSquareRow{Mode: mode, Submode: submode, Band: band, SatName: satName}) {
|
||||
continue
|
||||
}
|
||||
g := strings.ToUpper(strings.TrimSpace(grid))
|
||||
@@ -1914,6 +1924,51 @@ func (r *Repo) GridSquares(ctx context.Context, keep func(mode string) bool) ([]
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// GridSquareChoices is every mode, band and satellite that the squares on the
|
||||
// map were actually worked on.
|
||||
//
|
||||
// Taken from the log rather than from a list in the code, so a filter can only
|
||||
// ever offer something there is something to see behind — and so a mode that
|
||||
// does not exist yet needs no change here the day an operator starts using it.
|
||||
// The mode is the SUBMODE when there is one: PSK63 is the answer, not PSK.
|
||||
func (r *Repo) GridSquareChoices(ctx context.Context) (modes, bands, sats []string, err error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT DISTINCT UPPER(COALESCE(mode,'')), UPPER(COALESCE(submode,'')),
|
||||
LOWER(COALESCE(band,'')), UPPER(COALESCE(sat_name,''))
|
||||
FROM qso
|
||||
WHERE grid IS NOT NULL AND grid != ''`)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("query grid choices: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
seenM, seenB, seenS := map[string]bool{}, map[string]bool{}, map[string]bool{}
|
||||
for rows.Next() {
|
||||
var mode, submode, band, sat string
|
||||
if err := rows.Scan(&mode, &submode, &band, &sat); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if m := strings.TrimSpace(submode); m != "" {
|
||||
mode = m
|
||||
}
|
||||
if mode = strings.TrimSpace(mode); mode != "" && !seenM[mode] {
|
||||
seenM[mode] = true
|
||||
modes = append(modes, mode)
|
||||
}
|
||||
if band = strings.TrimSpace(band); band != "" && !seenB[band] {
|
||||
seenB[band] = true
|
||||
bands = append(bands, band)
|
||||
}
|
||||
if sat = strings.TrimSpace(sat); sat != "" && !seenS[sat] {
|
||||
seenS[sat] = true
|
||||
sats = append(sats, sat)
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return modes, bands, sats, nil
|
||||
}
|
||||
|
||||
// BandSlotQSOs returns every contact on one band that belongs to a slot of the
|
||||
// entry matrix: the exact callsign, or any callsign in the same DXCC entity.
|
||||
// Mode is NOT filtered here — the class (phone / CW / digital) is a derived
|
||||
|
||||
Reference in New Issue
Block a user