feat(cluster): quiet the worked spots, light up the empty slots
Two options that change what the eye is pulled towards, both applying to the cluster list AND the band map. Mute worked before: a spot that brings nothing new loses its colour and its badges. It stays in the list - the operator asked for less noise, not less information. "Brings nothing new" reuses the dimming rule the cluster list already had rather than inventing a second notion of done, so it keeps obeying the same-slot option and the digital-mode grouping for free. A new-band or new-slot status is NOT muted: having worked that callsign once on another band says nothing about the band in front of you. Highlight unworked in this slot: colours any callsign not yet worked on this band and this mode, whatever the entity says. For an operator filling slots a common entity on a fresh band+mode is the whole point, and the entity-level status flatly calls it worked. It reuses the existing new-slot status, so no new colour, no new legend, no new badge - both panels already knew how to draw it. WorkedSlot is computed independently of the same-slot preference: it is what this option reads, and it must not change meaning because a different option was toggled. The slot index is now built when either option needs it, and the status cache is keyed on both so a toggle invalidates it. The rules live in one module used by both panels. Marker colours already taught us what happens when the two derive the same thing separately.
This commit is contained in:
@@ -687,17 +687,17 @@ type App struct {
|
||||
liveFreqHz int64 // last freq/band/mode the UI reported (fallback when CAT is off)
|
||||
liveBand string
|
||||
liveMode string
|
||||
livePublishTimer *time.Timer // debounced live-status publish on activity change
|
||||
liveLastQSOAt time.Time // when this operator last logged a NEW contact — drives online/offline
|
||||
liveTableMu sync.Mutex // guards liveTableFor
|
||||
liveTableFor *sql.DB // logbook whose live_status DDL has been ensured (once per connection, not per call)
|
||||
awardSnapMu sync.Mutex // guards the award QSO snapshot
|
||||
awardSnap []qso.QSO // light-scanned + enriched logbook snapshot reused across award computations
|
||||
awardSnapRev string // logbook revision the snapshot was built at ("" = none)
|
||||
awardSnapUsed time.Time // last read — the snapshot is dropped once it goes cold (see awardSnapshotJanitor)
|
||||
livePublishTimer *time.Timer // debounced live-status publish on activity change
|
||||
liveLastQSOAt time.Time // when this operator last logged a NEW contact — drives online/offline
|
||||
liveTableMu sync.Mutex // guards liveTableFor
|
||||
liveTableFor *sql.DB // logbook whose live_status DDL has been ensured (once per connection, not per call)
|
||||
awardSnapMu sync.Mutex // guards the award QSO snapshot
|
||||
awardSnap []qso.QSO // light-scanned + enriched logbook snapshot reused across award computations
|
||||
awardSnapRev string // logbook revision the snapshot was built at ("" = none)
|
||||
awardSnapUsed time.Time // last read — the snapshot is dropped once it goes cold (see awardSnapshotJanitor)
|
||||
webpub webPublisher // log-to-website publishing: debounce timer, periodic ticker, last result
|
||||
bandOpen bandOpenState // sporadic-E / band-opening detector over the spot stream
|
||||
dataDir string // <exeDir>/data — holds config.json, logs, cty.dat
|
||||
dataDir string // <exeDir>/data — holds config.json, logs, cty.dat
|
||||
|
||||
// shuttingDown gates beforeClose re-entry: the first user attempt to
|
||||
// close fires shutdown tasks (backup, future LoTW upload, ...) while
|
||||
@@ -2472,6 +2472,19 @@ func (a *App) groupDigitalSlots() bool {
|
||||
// Off (default) → a call worked on any band/mode reads as already worked. On →
|
||||
// the WORKED-call flag needs the same band and mode (digital-grouped when that
|
||||
// option is also on).
|
||||
// clusterSlotHighlight reports the "colour the stations I have NOT worked on
|
||||
// this band and mode" preference (Settings -> DX Cluster). It needs the same
|
||||
// per-slot index as clusterWorkedSameSlot, which is why the index is built when
|
||||
// EITHER is on: that map is one entry per worked call+band+mode, so on a large
|
||||
// log it is not something to hold for an operator using neither.
|
||||
func (a *App) clusterSlotHighlight() bool {
|
||||
if a.settings == nil {
|
||||
return false
|
||||
}
|
||||
v, _ := a.settings.Get(a.ctx, "ui.opslog.clusterSlotHighlight")
|
||||
return v == "1"
|
||||
}
|
||||
|
||||
func (a *App) clusterWorkedSameSlot() bool {
|
||||
if a.settings == nil {
|
||||
return false
|
||||
@@ -16388,6 +16401,12 @@ type SpotStatus struct {
|
||||
// scanning the cluster for.
|
||||
NewPfx bool `json:"new_pfx"`
|
||||
Pfx string `json:"pfx,omitempty"`
|
||||
// WorkedSlot: this exact callsign already worked on THIS band and mode.
|
||||
// Distinct from WorkedCall, which follows the "same slot" preference and so
|
||||
// means different things depending on it. This one is always slot-scoped, so
|
||||
// the UI can highlight what is still to be worked here without the two
|
||||
// options having to agree. Only filled when the slot index is built.
|
||||
WorkedSlot bool `json:"worked_slot"`
|
||||
}
|
||||
|
||||
// clusterStatusCache holds the whole-logbook maps ClusterSpotStatuses colours
|
||||
@@ -16406,6 +16425,7 @@ type clusterStatusCache struct {
|
||||
normMode func(string) string // nil unless digital-mode grouping is on
|
||||
groupDigital bool // settings the maps were built under —
|
||||
sameSlot bool // a change rebuilds the snapshot
|
||||
slotHighlight bool // (same: the slot index is built for either)
|
||||
}
|
||||
|
||||
// clusterStatusMaps returns the cached worked-index snapshot, building it once
|
||||
@@ -16415,12 +16435,13 @@ type clusterStatusCache struct {
|
||||
func (a *App) clusterStatusMaps() *clusterStatusCache {
|
||||
groupDigital := a.groupDigitalSlots()
|
||||
sameSlot := a.clusterWorkedSameSlot()
|
||||
slotHighlight := a.clusterSlotHighlight()
|
||||
a.clusterStatusMu.Lock()
|
||||
defer a.clusterStatusMu.Unlock()
|
||||
if c := a.clusterStatusIdx; c != nil && c.groupDigital == groupDigital && c.sameSlot == sameSlot {
|
||||
if c := a.clusterStatusIdx; c != nil && c.groupDigital == groupDigital && c.sameSlot == sameSlot && c.slotHighlight == slotHighlight {
|
||||
return c
|
||||
}
|
||||
c := &clusterStatusCache{groupDigital: groupDigital, sameSlot: sameSlot}
|
||||
c := &clusterStatusCache{groupDigital: groupDigital, sameSlot: sameSlot, slotHighlight: slotHighlight}
|
||||
if a.qso == nil {
|
||||
a.clusterStatusIdx = c
|
||||
return c
|
||||
@@ -16460,7 +16481,7 @@ func (a *App) clusterStatusMaps() *clusterStatusCache {
|
||||
// "Already worked only on the same slot" option (Settings → DX Cluster): the
|
||||
// WORKED-call flag then needs the SAME band and mode (digital-grouped through
|
||||
// the same normMode when that option is on) rather than the call anywhere.
|
||||
if sameSlot {
|
||||
if sameSlot || slotHighlight {
|
||||
c.workedCallSlots, _ = a.qso.WorkedCallSlotKeys(a.ctx, c.normMode)
|
||||
}
|
||||
// Orthogonal dimensions: worked US counties (for the ULS callsign→county
|
||||
@@ -16510,6 +16531,21 @@ func (a *App) ClusterSpotStatuses(spots []SpotQuery) []SpotStatus {
|
||||
Band: strings.ToLower(q.Band),
|
||||
Mode: strings.ToUpper(q.Mode),
|
||||
}
|
||||
// Slot-scoped worked flag, independent of the sameSlot preference: it is
|
||||
// what "highlight what I have NOT worked here" reads, and that must not
|
||||
// change meaning because a different option was toggled.
|
||||
if workedCallSlots != nil {
|
||||
upCall := strings.ToUpper(q.Call)
|
||||
cm := out[i].Mode
|
||||
if normMode != nil && cm != "" {
|
||||
cm = normMode(cm)
|
||||
}
|
||||
if cm == "" {
|
||||
_, out[i].WorkedSlot = workedCallSlots[upCall+"|"+out[i].Band]
|
||||
} else {
|
||||
_, out[i].WorkedSlot = workedCallSlots[upCall+"|"+out[i].Band+"|"+cm]
|
||||
}
|
||||
}
|
||||
if sameSlot {
|
||||
// Already worked ONLY when this exact band+mode slot was worked. With no
|
||||
// inferable mode, fall back to same-band (better than claiming the whole
|
||||
|
||||
Reference in New Issue
Block a user