From 18680b35e5b2ca0d1fb2868f60e499df2acd51a4 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 5 May 2026 13:56:02 -0500 Subject: [PATCH] fix(contacts/map): align line colors with sidebar legend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The polylines were sourcing from --band-* CSS vars (a separate, theme-aware design palette) while the legend swatches rendered from the Elixir @band_colors map, so the two diverged for every band — e.g. 10 GHz showed lime on the map and emerald in the legend. Emit `data-band-color` on each band checkbox and have the JS hook read from there, making the legend the single source of truth. Drops the now-redundant theme observer / restyleLines plumbing. --- assets/js/contacts_map_hook.ts | 69 +++---------------- .../live/contact_map_live.ex | 2 + 2 files changed, 13 insertions(+), 58 deletions(-) diff --git a/assets/js/contacts_map_hook.ts b/assets/js/contacts_map_hook.ts index df98b527..7ca739a6 100644 --- a/assets/js/contacts_map_hook.ts +++ b/assets/js/contacts_map_hook.ts @@ -40,7 +40,6 @@ interface ContactsMapHook extends ViewHook { map: L.Map delegatedChange: (e: Event) => void delegatedClick: (e: Event) => void - themeObserver: MutationObserver | null initMap(this: ContactsMapHook): void buildLines(this: ContactsMapHook): void syncBandLayers(this: ContactsMapHook): void @@ -49,7 +48,6 @@ interface ContactsMapHook extends ViewHook { updateCount(this: ContactsMapHook, count: number): void wireControls(this: ContactsMapHook): void unwireControls(this: ContactsMapHook): void - restyleLines(this: ContactsMapHook): void } function inDateRange(tsDate: string, startDate: string, endDate: string): boolean { @@ -60,48 +58,26 @@ function inDateRange(tsDate: string, startDate: string, endDate: string): boolea return true } -// Amateur-band palette. Nine of these map to the design system's --band-* -// CSS vars (defined in assets/css/app.css); the higher mm-wave bands above -// 47 GHz fall back to hard-coded hues since the design set only spans up -// to 6mm. CSS vars are resolved once per page via getComputedStyle because -// Leaflet writes the color onto the SVG `stroke` attribute, which does not -// evaluate `var()` references. -const BAND_VAR: Record = { - 1296: "--band-23cm", - 2304: "--band-13cm", - 3400: "--band-9cm", - 5760: "--band-6cm", - 10000: "--band-3cm", - 24000: "--band-1-25cm", - 47000: "--band-6mm", -} - -const BAND_FALLBACK: Record = { - 68000: "#dc2626", - 75000: "#c026d3", - 122000: "#db2777", - 134000: "#e11d48", - 241000: "#b91c1c", -} - +// Band colors are sourced from the legend swatches in the sidebar — each +// band checkbox carries `data-band-color` set server-side from +// `Microwaveprop.ContactMapLive`'s @band_colors map. Reading from the DOM +// keeps the legend the single source of truth: change the Elixir map and +// both the swatch and the polyline update in lockstep. let BAND_COLORS: Record | null = null function resolveBandColors(): Record { if (BAND_COLORS) return BAND_COLORS - const styles = getComputedStyle(document.documentElement) - const resolved: Record = { ...BAND_FALLBACK } - for (const [band, cssVar] of Object.entries(BAND_VAR)) { - const value = styles.getPropertyValue(cssVar).trim() - if (value) resolved[Number(band)] = value + const resolved: Record = {} + const inputs = document.querySelectorAll("input[data-band][data-band-color]") + for (const inp of inputs) { + const band = parseInt(inp.dataset.band || "", 10) + const color = inp.dataset.bandColor + if (Number.isFinite(band) && color) resolved[band] = color } BAND_COLORS = resolved return resolved } -function invalidateBandColors(): void { - BAND_COLORS = null -} - function bandLabel(mhz: number): string { if (mhz >= 1000) { const ghz = mhz / 1000 @@ -125,7 +101,6 @@ export const ContactsMap = { this.bandGroups = new Map() this.lines = [] this.dotLayer = L.layerGroup() - this.themeObserver = null // Callsign filter still roundtrips through LiveView (debounced) — it's // cross-cutting and fires rarely. Band toggles are purely client-side. @@ -144,18 +119,6 @@ export const ContactsMap = { this.wireControls() - // Re-color polylines when the theme toggles. Leaflet writes `stroke` as - // an SVG attribute (which does not resolve CSS vars), so we re-read the - // resolved --band-* values and call setStyle on each line. - this.themeObserver = new MutationObserver(() => { - invalidateBandColors() - this.restyleLines() - }) - this.themeObserver.observe(document.documentElement, { - attributes: true, - attributeFilter: ["data-theme"] - }) - requestAnimationFrame(() => this.initMap()) fetch(fetchUrl) @@ -174,16 +137,6 @@ export const ContactsMap = { destroyed(this: ContactsMapHook) { this.unwireControls() - if (this.themeObserver) { - this.themeObserver.disconnect() - this.themeObserver = null - } - }, - - restyleLines(this: ContactsMapHook) { - for (const entry of this.lines) { - entry.line.setStyle({ color: bandColor(entry.band) }) - } }, wireControls(this: ContactsMapHook) { diff --git a/lib/microwaveprop_web/live/contact_map_live.ex b/lib/microwaveprop_web/live/contact_map_live.ex index 8426741c..93d4d7bf 100644 --- a/lib/microwaveprop_web/live/contact_map_live.ex +++ b/lib/microwaveprop_web/live/contact_map_live.ex @@ -202,6 +202,7 @@ defmodule MicrowavepropWeb.ContactMapLive do type="checkbox" checked data-band={band} + data-band-color={band_color(band)} style={"accent-color:#{band_color(band)}"} />