import { useEffect, useState } from 'react'; import { GetRowColors, SaveRowColors } from '../../wailsjs/go/main/App'; import { Checkbox } from '@/components/ui/checkbox'; import { useI18n } from '@/lib/i18n'; import { cn } from '@/lib/utils'; import type { RowColorSettings } from '@/lib/rowColors'; // A fixed palette plus a free picker. Muted values on purpose: they are // composited at low opacity over a dark grid, where a saturated colour reads as // an error state rather than a status. const PALETTE = [ '#16a34a', '#0ea5e9', '#f59e0b', '#a855f7', '#dc2626', '#14b8a6', '#eab308', '#ec4899', '#64748b', '#84cc16', '#6366f1', '#f97316', ]; // The rule ids the backend orders; the labels live here so a translation never // travels through the settings row. const LABELS: Record = { to_send: 'appr.ruleToSend', confirmed: 'appr.ruleConfirmed', sent: 'appr.ruleSent', worked: 'appr.ruleWorked', }; // The channels a rule can be scoped to. "worked" is the catch-all — it means // nothing on ANY channel — so narrowing it would say nothing. const CHANNELS = ['qsl', 'lotw', 'eqsl', 'qrz'] as const; const CHANNEL_LABELS: Record = { qsl: 'appr.chQsl', lotw: 'LoTW', eqsl: 'eQSL', qrz: 'QRZ.com', }; export function AppearancePanel() { const { t } = useI18n(); const [cfg, setCfg] = useState(null); useEffect(() => { (async () => { try { setCfg((await GetRowColors()) as any); } catch { /* defaults on the backend */ } })(); }, []); const save = (next: RowColorSettings) => { setCfg(next); SaveRowColors(next as any).catch(() => {}); }; const patchRule = (id: string, patch: Partial<{ color: string; enabled: boolean; channels: string[] }>) => { if (!cfg) return; save({ ...cfg, rules: cfg.rules.map((r) => (r.id === id ? { ...r, ...patch } : r)) }); }; // The rule card shows the row exactly as the grid will draw it, so the choice // is made by looking rather than by imagining. const preview = (color: string): Record => { const st = cfg?.style ?? 'bar'; const pct = cfg?.intensity ?? 12; const out: Record = {}; if (st === 'tint' || st === 'both') out.backgroundColor = `color-mix(in srgb, ${color} ${pct}%, transparent)`; if (st === 'bar' || st === 'both') out.boxShadow = `inset 3px 0 0 ${color}`; return out; }; if (!cfg) return
; return (
{cfg.enabled && (
{/* Style first: it decides whether the colours below are a signal or a wallpaper, which matters more than which hue they are. */}
{t('appr.style')}
{(['bar', 'tint', 'both'] as const).map((v) => ( ))}
{(cfg.style ?? 'bar') !== 'bar' && ( )}
{/* Order matters and is shown: a contact is usually several of these at once, and the first match wins. */}

{t('appr.orderHint')}

{cfg.rules.map((r, i) => (
{/* Which channels this category looks at. None ticked = all of them, which is what an unnarrowed rule should mean. */} {r.enabled && r.id !== 'worked' && (
{CHANNELS.map((c) => { const on = !r.channels?.length || r.channels.includes(c); return ( ); })}
)} {r.enabled && (
{PALETTE.map((c) => (
)}
))}
)}
); }