package main // Row colouring for the log grid, by QSL / LoTW status — the thing Logger32 does // and the reason an operator can tell at a glance what still needs sending. // // Rules are ORDERED and the first match wins, because a contact is usually // several things at once: one confirmed on LoTW and by card is confirmed, not // "sent, awaiting reply". Putting the order in the data rather than in a chain // of ifs is what lets the settings panel show it in the same order it applies. import ( "encoding/json" "regexp" "strings" ) // RowColorRule is one status and the colour it paints. type RowColorRule struct { ID string `json:"id"` Color string `json:"color"` Enabled bool `json:"enabled"` } // RowColorSettings is the whole appearance block. type RowColorSettings struct { Enabled bool `json:"enabled"` Rules []RowColorRule `json:"rules"` } // The rule ids, in priority order. The frontend matches on these and holds the // labels, so a translated name never has to travel through the settings. var rowColorOrder = []string{ "confirmed_lotw", // LoTW confirmation received "confirmed_paper", // card or eQSL received "sent_waiting", // sent by some route, nothing back yet "to_send", // a card is requested / queued and has not gone out } // Defaults: green for done, amber for waiting, blue for owed. Deliberately // muted — they are composited at low opacity over a dark grid, and a saturated // value there reads as an error state rather than a status. var rowColorDefaults = map[string]string{ "confirmed_lotw": "#16a34a", "confirmed_paper": "#0ea5e9", "sent_waiting": "#f59e0b", "to_send": "#a855f7", } // hexColor guards what reaches the stylesheet. The value is interpolated into a // CSS color-mix() by the grid, so anything that is not plainly a hex colour is // refused rather than passed through. var hexColor = regexp.MustCompile(`^#[0-9a-fA-F]{6}$`) func normRowColors(s RowColorSettings) RowColorSettings { byID := map[string]RowColorRule{} for _, r := range s.Rules { byID[r.ID] = r } out := RowColorSettings{Enabled: s.Enabled} for _, id := range rowColorOrder { r := byID[id] r.ID = id if !hexColor.MatchString(strings.TrimSpace(r.Color)) { r.Color = rowColorDefaults[id] } out.Rules = append(out.Rules, r) } return out } // GetRowColors returns the row-colouring configuration, defaults included so the // panel never has to invent one. func (a *App) GetRowColors() RowColorSettings { var s RowColorSettings if raw := a.settingOr(keyRowColors, ""); raw != "" { _ = json.Unmarshal([]byte(raw), &s) } return normRowColors(s) } // SaveRowColors persists it. func (a *App) SaveRowColors(s RowColorSettings) error { b, err := json.Marshal(normRowColors(s)) if err != nil { return err } a.setSetting(keyRowColors, string(b)) return nil }