chore: release v0.23.2
This commit is contained in:
+26
-6
@@ -120,6 +120,7 @@ import {
|
||||
import { cn } from '@/lib/utils';
|
||||
import { pathBetween, pathBetweenLatLon, gridToLatLon, latLonToGrid } from '@/lib/maidenhead';
|
||||
import { flagURL } from '@/lib/flags';
|
||||
import { LogViewer } from '@/components/LogViewer';
|
||||
|
||||
type QSO = QSOForm;
|
||||
type ImportResult = adifModels.ImportResult;
|
||||
@@ -547,7 +548,7 @@ export default function App() {
|
||||
}, []);
|
||||
// LoTW-user lookup for the entered callsign (from the cached ARRL activity list),
|
||||
// debounced. Drives a colour-coded LoTW badge by last-upload recency.
|
||||
const [lotwInfo, setLotwInfo] = useState<{ is_user: boolean; last_upload?: string; days_ago: number } | null>(null);
|
||||
const [lotwInfo, setLotwInfo] = useState<{ is_user: boolean; last_upload?: string; days_ago: number; feed_date?: string; feed_stale?: boolean } | null>(null);
|
||||
useEffect(() => {
|
||||
const c = callsign.trim();
|
||||
if (!c) { setLotwInfo(null); return; }
|
||||
@@ -1547,6 +1548,7 @@ export default function App() {
|
||||
const [showAbout, setShowAbout] = useState(false);
|
||||
// "What's new": the changelog for the version(s) since the operator last ran,
|
||||
// shown once on the first launch after an update (EN/FR per the UI language).
|
||||
const [showLogViewer, setShowLogViewer] = useState(false);
|
||||
const [whatsNew, setWhatsNew] = useState<Array<{ version: string; date: string; en: string[]; fr: string[] }> | null>(null);
|
||||
// Which language the What's-new modal shows — defaults to the UI language but
|
||||
// toggleable, so a French operator running the English UI can still read it.
|
||||
@@ -3293,10 +3295,16 @@ export default function App() {
|
||||
if (!ue.has('name') && empty(r?.name) && last.name) setName(last.name);
|
||||
if (!ue.has('qth') && empty(r?.qth) && last.qth) setQth(last.qth);
|
||||
if (!ue.has('country') && empty(r?.country) && last.country) setCountry(last.country);
|
||||
// Grid: a REAL provider grid always wins. Otherwise the last QSO's precise
|
||||
// locator beats the coarse cty.dat entity centroid the provider block set — so
|
||||
// a French call resolves to its real JNxx, not the country's JN16 centroid.
|
||||
const adoptLastGrid = !ue.has('grid') && empty(r?.grid) && !!last.grid;
|
||||
// Grid: the provider (QRZ / HamQTH) leads, but only while it is at least as
|
||||
// PRECISE as what we already worked. It used to lead whenever it answered at
|
||||
// all, so a 4-character square — which is what a provider returns when the
|
||||
// station only published a rough locator, and what the cty.dat entity
|
||||
// centroid gives — overwrote the full 6-character locator confirmed on a
|
||||
// previous contact. Comparing lengths keeps the finer of the two, whichever
|
||||
// side it came from.
|
||||
const provGrid = String(r?.grid ?? '').trim();
|
||||
const lastGrid = String(last.grid ?? '').trim();
|
||||
const adoptLastGrid = !ue.has('grid') && !!lastGrid && lastGrid.length > provGrid.length;
|
||||
if (adoptLastGrid) setGrid(last.grid);
|
||||
setDetails((d) => {
|
||||
// When we adopt the last QSO's locator, take its coordinates too (derive them
|
||||
@@ -3618,6 +3626,8 @@ export default function App() {
|
||||
{ type: 'item' as const, label: sendingLog ? t('help.sendLogBusy') : t('help.sendLog'), action: 'help.sendlog', disabled: sendingLog },
|
||||
] : []),
|
||||
{ type: 'separator' },
|
||||
{ type: 'item', label: t('logview.title'), action: 'help.log' },
|
||||
{ type: 'separator' },
|
||||
{ type: 'item', label: t('help.donate'), action: 'help.donate', accent: true },
|
||||
{ type: 'separator' },
|
||||
{ type: 'item', label: t('help.about'), action: 'help.about' },
|
||||
@@ -3651,6 +3661,7 @@ export default function App() {
|
||||
case 'tools.refreshCty': refreshCtyDat(); break;
|
||||
case 'tools.downloadRefs': downloadRefs(); break;
|
||||
case 'help.about': setShowAbout(true); checkUpdateNow(); break;
|
||||
case 'help.log': setShowLogViewer(true); break;
|
||||
case 'help.whatsnew': GetChangelog().then((e: any) => { if (Array.isArray(e) && e.length) setWhatsNew(e); else showToast(t('whatsnew.none')); }).catch(() => {}); break;
|
||||
case 'help.sendlog': sendLogToDeveloper(); break;
|
||||
// Opens in the system browser, NOT the app WebView: a payment page must show
|
||||
@@ -4059,8 +4070,14 @@ export default function App() {
|
||||
const cls = d >= 0 && d < 7 ? 'border-success text-success bg-success/15'
|
||||
: d < 30 ? 'border-warning text-warning bg-warning/15'
|
||||
: 'border-danger text-danger bg-danger/15';
|
||||
// ARRL regenerates lotw-user-activity.csv on its own schedule and has been
|
||||
// seen frozen for a week (2026-08-02: newest line 2026-07-27). "Uploaded 7
|
||||
// days ago" then says nothing about the station, so the tooltip names the
|
||||
// day the feed itself was built whenever that day is not recent.
|
||||
const tip = t('lotw.userTip', { date: lotwInfo.last_upload || '?', days: d })
|
||||
+ (lotwInfo.feed_stale && lotwInfo.feed_date ? ' — ' + t('lotw.feedStale', { date: lotwInfo.feed_date }) : '');
|
||||
return (
|
||||
<div className="shrink-0" title={t('lotw.userTip', { date: lotwInfo.last_upload || '?', days: d })}>
|
||||
<div className="shrink-0" title={tip}>
|
||||
<span className={cn('inline-flex items-center rounded-md border px-1.5 py-1 text-[9px] font-extrabold tracking-wide leading-none', cls)}>
|
||||
LoTW
|
||||
</span>
|
||||
@@ -5248,6 +5265,9 @@ export default function App() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Live diagnostic log (Help → …). Polls only while open. */}
|
||||
<LogViewer open={showLogViewer} onOpenChange={setShowLogViewer} />
|
||||
|
||||
{/* What's new — shown once on the first launch after an update. */}
|
||||
{whatsNew && (
|
||||
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/40 backdrop-blur-sm p-4" onClick={() => setWhatsNew(null)}>
|
||||
|
||||
Reference in New Issue
Block a user