feat(bandmap): mark LoTW users, with the badge the cluster already uses
Logger32 draws a green square for a LoTW user and an X for a worked one. The square is the part worth copying; the way it is drawn is not. Colour on this map is already spoken for — it carries the entity status, and the left edge of each pill stacks the new-park / new-county markers. Adding a green fill for LoTW would put two unrelated meanings on one channel, and an operator would have to work out which green meant what. So it is the same "L" badge the cluster list draws, in the same muted blue, for the reason written there: whether a station uploads to LoTW says nothing about whether the spot is worth chasing. Worked stations already read as worked here — the pill goes grey and the status markers speak — so no X is needed. Switchable in Settings → Appearance, and on by default: a `configured` flag distinguishes "saved with this off" from "saved before the option existed", so turning it off sticks instead of being undone by the next default.
This commit is contained in:
+15
-1
@@ -34,6 +34,14 @@ type RowColorSettings struct {
|
||||
Style string `json:"style"`
|
||||
// Intensity is the tint strength in percent. Only used by "tint"/"both".
|
||||
Intensity int `json:"intensity"`
|
||||
// BandMapLotw marks stations that upload to LoTW on the band map, with the
|
||||
// same "L" badge the cluster list uses — one visual vocabulary across the two
|
||||
// views rather than a second invention.
|
||||
BandMapLotw bool `json:"bandmap_lotw"`
|
||||
// Configured distinguishes "saved with this off" from "saved before the
|
||||
// option existed", so a new marker can default ON without silently turning
|
||||
// itself back on for an operator who switched it off.
|
||||
Configured bool `json:"configured"`
|
||||
Rules []RowColorRule `json:"rules"`
|
||||
}
|
||||
|
||||
@@ -66,7 +74,13 @@ func normRowColors(s RowColorSettings) RowColorSettings {
|
||||
for _, r := range s.Rules {
|
||||
byID[r.ID] = r
|
||||
}
|
||||
out := RowColorSettings{Enabled: s.Enabled, Style: s.Style, Intensity: s.Intensity}
|
||||
out := RowColorSettings{
|
||||
Enabled: s.Enabled, Style: s.Style, Intensity: s.Intensity,
|
||||
BandMapLotw: s.BandMapLotw, Configured: true,
|
||||
}
|
||||
if !s.Configured {
|
||||
out.BandMapLotw = true // new option, on unless the operator says otherwise
|
||||
}
|
||||
switch out.Style {
|
||||
case "bar", "tint", "both":
|
||||
default:
|
||||
|
||||
+4
-2
@@ -4,11 +4,13 @@
|
||||
"date": "",
|
||||
"en": [
|
||||
"Appearance: row colouring now defaults to a left stripe, with a filled row and its strength offered as choices.",
|
||||
"The band map now follows the cluster filters — LoTW only, spotter continent, hide worked, status and mode chips."
|
||||
"The band map now follows the cluster filters — LoTW only, spotter continent, hide worked, status and mode chips.",
|
||||
"Band map: stations that upload to LoTW now carry the same L badge as the cluster list, switchable in Appearance."
|
||||
],
|
||||
"fr": [
|
||||
"Apparence : la coloration des lignes se fait par défaut sur une barre à gauche, la ligne remplie et son intensité restant proposées.",
|
||||
"La band map suit désormais les filtres du cluster — LoTW seulement, continent du spotter, masquer les contactés, statuts et modes."
|
||||
"La band map suit désormais les filtres du cluster — LoTW seulement, continent du spotter, masquer les contactés, statuts et modes.",
|
||||
"Band map : les stations qui utilisent LoTW portent le même badge L que la liste du cluster, activable dans Apparence."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6800,6 +6800,7 @@ export default function App() {
|
||||
onClose={() => toggleBandMapBand(b)}
|
||||
hideDigital={bandMapHideFt}
|
||||
fitToBand={bandMapFit}
|
||||
showLotw={!!rowColors?.bandmap_lotw}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -57,6 +57,12 @@ export function AppearancePanel() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<label className="flex items-start gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={!!cfg.bandmap_lotw} className="mt-0.5"
|
||||
onCheckedChange={(c) => save({ ...cfg, bandmap_lotw: !!c } as any)} />
|
||||
<span>{t('appr.bandmapLotw')} <span className="text-xs text-muted-foreground">{t('appr.bandmapLotwHint')}</span></span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-start gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={cfg.enabled} className="mt-0.5"
|
||||
onCheckedChange={(c) => save({ ...cfg, enabled: !!c })} />
|
||||
|
||||
@@ -38,6 +38,9 @@ type SpotStatusEntry = {
|
||||
new_county?: boolean;
|
||||
new_pota?: boolean;
|
||||
new_pfx?: boolean;
|
||||
// Whether the station uploads to LoTW. Not a status — it says nothing about
|
||||
// whether the spot is worth chasing — so it is drawn as a badge, never a colour.
|
||||
lotw?: boolean;
|
||||
};
|
||||
|
||||
// The extra markers are ORTHOGONAL to the entity status: a spot can be a worked
|
||||
@@ -79,6 +82,8 @@ interface Props {
|
||||
// globally from the band-map tab toolbar.
|
||||
hideDigital?: boolean;
|
||||
fitToBand?: boolean;
|
||||
// Mark stations that upload to LoTW (Settings → Appearance).
|
||||
showLotw?: boolean;
|
||||
// keyNav enables Ctrl+↑ / Ctrl+↓ to hop to the next spot above / below the rig
|
||||
// frequency (and tune to it). Only the docked Main-view band map sets this, so
|
||||
// the multi-band Band Map tab (several maps) doesn't fight over the shortcut.
|
||||
@@ -227,7 +232,7 @@ const BOT_PAD = 14; // the top-most freq label isn't clipped at y=0
|
||||
// last; ties broken by closeness to the rig freq).
|
||||
const MAX_VISIBLE_SPOTS = 30;
|
||||
|
||||
export function BandMap({ band, spots, spotStatus: spotStatusRaw, currentFreqHz, onSpotClick, onClose, side = 'right', onToggleSide, hideDigital = false, fitToBand = false, keyNav = false }: Props) {
|
||||
export function BandMap({ band, spots, spotStatus: spotStatusRaw, currentFreqHz, onSpotClick, onClose, side = 'right', onToggleSide, hideDigital = false, fitToBand = false, keyNav = false, showLotw = false }: Props) {
|
||||
const { t } = useI18n();
|
||||
|
||||
// The two display options are applied ONCE here, on the whole map, so the
|
||||
@@ -649,6 +654,15 @@ export function BandMap({ band, spots, spotStatus: spotStatusRaw, currentFreqHz,
|
||||
})()}
|
||||
<span className="flex items-center gap-1.5 px-2 font-mono text-[11px] font-bold leading-none">
|
||||
<span>{p.spot.dx_call}</span>
|
||||
{/* Same badge the cluster list draws, for the same reason: the
|
||||
muted-blue of a confirmation, never a status colour —
|
||||
whether a station uploads to LoTW says nothing about
|
||||
whether the spot is worth chasing. A glyph rather than a
|
||||
colour also keeps it off the channel the statuses use. */}
|
||||
{showLotw && entry?.lotw && (
|
||||
<span title={t('clu.lotwBadge')} className="text-[9px] font-normal rounded px-1 py-px"
|
||||
style={{ background: 'color-mix(in srgb, var(--info) 22%, transparent)', color: 'var(--info)' }}>L</span>
|
||||
)}
|
||||
{mode && (
|
||||
<span className="text-[9px] font-normal text-current/70 bg-current/10 rounded px-1 py-px">
|
||||
{mode}
|
||||
|
||||
@@ -114,7 +114,7 @@ const en: Dict = {
|
||||
'nav.user': 'User Configuration', 'nav.software': 'Software Configuration', 'nav.hardware': 'Hardware Configuration', 'nav.lists': 'Lists',
|
||||
'sec.station': 'Station Information', 'sec.profiles': 'Profiles', 'sec.operating': 'Operating conditions',
|
||||
'sec.confirmations': 'Confirmations', 'sec.external': 'External services',
|
||||
'sec.general': 'General', 'sec.appearance': 'Appearance', 'appr.enable': 'Colour whole rows by QSL status', 'appr.enableHint': '(in the log grid, like Logger32)', 'appr.orderHint': 'A contact is often several of these at once — the first rule that matches decides the colour.', 'appr.confirmedLotw': 'Confirmed on LoTW', 'appr.confirmedPaper': 'Confirmed by card or eQSL', 'appr.sentWaiting': 'Sent, no answer yet', 'appr.toSend': 'Queued to send (card, LoTW or eQSL)', 'appr.custom': 'Pick any colour', 'appr.style': 'Style', 'appr.styleBar': 'Left stripe', 'appr.styleTint': 'Filled row', 'appr.styleBoth': 'Both', 'appr.intensity': 'Strength', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': 'Callsign Lookup',
|
||||
'sec.general': 'General', 'sec.appearance': 'Appearance', 'appr.enable': 'Colour whole rows by QSL status', 'appr.enableHint': '(in the log grid, like Logger32)', 'appr.orderHint': 'A contact is often several of these at once — the first rule that matches decides the colour.', 'appr.confirmedLotw': 'Confirmed on LoTW', 'appr.confirmedPaper': 'Confirmed by card or eQSL', 'appr.sentWaiting': 'Sent, no answer yet', 'appr.toSend': 'Queued to send (card, LoTW or eQSL)', 'appr.custom': 'Pick any colour', 'appr.style': 'Style', 'appr.styleBar': 'Left stripe', 'appr.styleTint': 'Filled row', 'appr.styleBoth': 'Both', 'appr.intensity': 'Strength', 'appr.bandmapLotw': 'Mark LoTW users on the band map', 'appr.bandmapLotwHint': '(the same L badge the cluster list uses)', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': 'Callsign Lookup',
|
||||
'sec.bands': 'Bands', 'sec.modes': 'Modes & default RST', 'sec.cluster': 'DX Cluster',
|
||||
'sec.udp': 'UDP integrations', 'sec.database': 'Database', 'sec.autostart': 'Autostart', 'sec.backup': 'Database backup', 'sec.uscounties': 'US Counties',
|
||||
'sec.webpublish': 'Web publishing', 'wpub.hint': 'Publishes your log as a file for a website: a standalone HTML page or a CSV, written locally and optionally uploaded by FTP. It is refreshed when you log a QSO and, if you set an interval, on a timer.', 'wpub.enable': 'Publish the log to a file', 'wpub.fileSection': 'The file', 'wpub.format': 'Format', 'wpub.formatHtml': 'HTML page', 'wpub.formatCsv': 'CSV', 'wpub.folder': 'Output folder', 'wpub.browse': 'Browse…', 'wpub.fileName': 'File name', 'wpub.title': 'Page title', 'wpub.titlePh': 'blank = your callsign', 'wpub.count': 'Last N QSOs', 'wpub.every': 'Refresh every', 'wpub.everyHint': 'minutes — 0 = only when a QSO is logged', 'wpub.columns': 'Columns', 'wpub.columnsCount': '{n} of {total} chosen', 'wpub.columnsPick': 'Choose columns…', 'wpub.columnsSearch': 'Search a field…', 'wpub.removeColumn': 'Click to remove', 'wpub.columnsHint': 'Click to add or remove. The order shown here is the order in the file.', 'wpub.ftpEnable': 'Upload by FTP', 'wpub.ftpHost': 'Server / port', 'wpub.ftpUser': 'User', 'wpub.ftpPassword': 'Password', 'wpub.ftpFolder': 'Remote folder', 'wpub.ftpFileName': 'Remote file name', 'wpub.ftpTls': 'Use TLS (FTPS)', 'wpub.publishNow': 'Publish now', 'wpub.testFtp': 'Test connection', 'wpub.lastRun': 'Last run:', 'sec.adifmon': 'ADIF monitor',
|
||||
@@ -541,7 +541,7 @@ const fr: Dict = {
|
||||
'nav.user': 'Configuration utilisateur', 'nav.software': 'Configuration logicielle', 'nav.hardware': 'Configuration matérielle', 'nav.lists': 'Listes',
|
||||
'sec.station': 'Informations station', 'sec.profiles': 'Profils', 'sec.operating': "Conditions d'opération",
|
||||
'sec.confirmations': 'Confirmations', 'sec.external': 'Services externes',
|
||||
'sec.general': 'Général', 'sec.appearance': 'Apparence', 'appr.enable': 'Colorer les lignes entières selon le statut QSL', 'appr.enableHint': '(dans le tableau du log, comme Logger32)', 'appr.orderHint': "Un contact est souvent plusieurs de ces états à la fois — la première règle qui correspond décide de la couleur.", 'appr.confirmedLotw': 'Confirmé sur LoTW', 'appr.confirmedPaper': 'Confirmé par carte ou eQSL', 'appr.sentWaiting': 'Envoyé, sans réponse', 'appr.toSend': 'En attente d envoi (carte, LoTW ou eQSL)', 'appr.custom': 'Choisir une couleur', 'appr.style': 'Style', 'appr.styleBar': 'Barre à gauche', 'appr.styleTint': 'Ligne remplie', 'appr.styleBoth': 'Les deux', 'appr.intensity': 'Intensité', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': "Recherche d'indicatif",
|
||||
'sec.general': 'Général', 'sec.appearance': 'Apparence', 'appr.enable': 'Colorer les lignes entières selon le statut QSL', 'appr.enableHint': '(dans le tableau du log, comme Logger32)', 'appr.orderHint': "Un contact est souvent plusieurs de ces états à la fois — la première règle qui correspond décide de la couleur.", 'appr.confirmedLotw': 'Confirmé sur LoTW', 'appr.confirmedPaper': 'Confirmé par carte ou eQSL', 'appr.sentWaiting': 'Envoyé, sans réponse', 'appr.toSend': 'En attente d envoi (carte, LoTW ou eQSL)', 'appr.custom': 'Choisir une couleur', 'appr.style': 'Style', 'appr.styleBar': 'Barre à gauche', 'appr.styleTint': 'Ligne remplie', 'appr.styleBoth': 'Les deux', 'appr.intensity': 'Intensité', 'appr.bandmapLotw': 'Marquer les utilisateurs LoTW sur la band map', 'appr.bandmapLotwHint': '(le même badge L que la liste du cluster)', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': "Recherche d'indicatif",
|
||||
'sec.bands': 'Bandes', 'sec.modes': 'Modes & RST par défaut', 'sec.cluster': 'DX Cluster',
|
||||
'sec.udp': 'Intégrations UDP', 'sec.database': 'Base de données', 'sec.autostart': 'Démarrage auto', 'sec.backup': 'Sauvegarde base', 'sec.uscounties': 'Comtés US',
|
||||
'sec.webpublish': 'Publication web', 'wpub.hint': "Publie ton journal dans un fichier destiné à un site web : une page HTML autonome ou un CSV, écrit en local et envoyé par FTP si tu le souhaites. Il est rafraîchi à chaque QSO enregistré et, si tu règles un intervalle, périodiquement.", 'wpub.enable': 'Publier le journal dans un fichier', 'wpub.fileSection': 'Le fichier', 'wpub.format': 'Format', 'wpub.formatHtml': 'Page HTML', 'wpub.formatCsv': 'CSV', 'wpub.folder': 'Dossier de sortie', 'wpub.browse': 'Parcourir…', 'wpub.fileName': 'Nom du fichier', 'wpub.title': 'Titre de la page', 'wpub.titlePh': 'vide = ton indicatif', 'wpub.count': 'N derniers QSO', 'wpub.every': 'Rafraîchir toutes les', 'wpub.everyHint': 'minutes — 0 = seulement à chaque QSO', 'wpub.columns': 'Colonnes', 'wpub.columnsCount': '{n} sur {total} choisis', 'wpub.columnsPick': 'Choisir les colonnes…', 'wpub.columnsSearch': 'Chercher un champ…', 'wpub.removeColumn': 'Cliquer pour retirer', 'wpub.columnsHint': 'Clique pour ajouter ou retirer. L ordre affiché ici est celui du fichier.', 'wpub.ftpEnable': 'Envoyer par FTP', 'wpub.ftpHost': 'Serveur / port', 'wpub.ftpUser': 'Utilisateur', 'wpub.ftpPassword': 'Mot de passe', 'wpub.ftpFolder': 'Dossier distant', 'wpub.ftpFileName': 'Nom du fichier distant', 'wpub.ftpTls': 'Utiliser TLS (FTPS)', 'wpub.publishNow': 'Publier maintenant', 'wpub.testFtp': 'Tester la connexion', 'wpub.lastRun': 'Dernière exécution :', 'sec.adifmon': 'Moniteur ADIF',
|
||||
|
||||
@@ -9,6 +9,7 @@ export type RowColorSettings = {
|
||||
enabled: boolean;
|
||||
style?: 'bar' | 'tint' | 'both';
|
||||
intensity?: number;
|
||||
bandmap_lotw?: boolean;
|
||||
rules: RowColorRule[];
|
||||
};
|
||||
|
||||
|
||||
@@ -3012,6 +3012,8 @@ export namespace main {
|
||||
enabled: boolean;
|
||||
style: string;
|
||||
intensity: number;
|
||||
bandmap_lotw: boolean;
|
||||
configured: boolean;
|
||||
rules: RowColorRule[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
@@ -3023,6 +3025,8 @@ export namespace main {
|
||||
this.enabled = source["enabled"];
|
||||
this.style = source["style"];
|
||||
this.intensity = source["intensity"];
|
||||
this.bandmap_lotw = source["bandmap_lotw"];
|
||||
this.configured = source["configured"];
|
||||
this.rules = this.convertValues(source["rules"], RowColorRule);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user