feat: persistent Ctrl+wheel zoom, saved award-column widths, F9 + hide-empty CW macros
- Zoom: our own Ctrl+wheel zoom (CSS zoom on the root, 50–250%), persisted in localStorage and restored at startup; Ctrl+0 resets. Replaces the non-persistent native WebView2 zoom. The freq-digit wheel now ignores Ctrl so it passes through to zoom. - Award column widths: they were stripped from AG Grid's saved column state (that strip fixes a visibility desync) which also dropped their width. Now persisted separately per award code (localStorage + portable DB copy) and re-applied on rebuild/reopen. - CW keyer widget: macros padded to 9 (F1–F9 slot) and empty macros hidden like the voice keyer, with the F-number kept tied to the real macro index so the F-key shortcuts still line up. New QRZ? default for F9.
This commit is contained in:
@@ -328,6 +328,39 @@ export function RecentQSOsGrid({ rows, selectAllSignal, selectRowSignal, rowDrag
|
||||
saveState(AWARD_SHOWN_KEY, [...next]);
|
||||
}, []);
|
||||
|
||||
// Award-column WIDTHS are persisted separately too, for the same reason as
|
||||
// visibility: award columns are stripped from AG Grid's saved column-state
|
||||
// round-trip, so their width would otherwise reset to the default on reopen.
|
||||
// Stored as an array of { code, width } (code upper-cased); mirrored to the DB.
|
||||
const AWARD_WIDTH_KEY = storageKey ? `hamlog.awardColWidths.${storageKey}` : 'hamlog.awardColWidths';
|
||||
const awardWidthsRef = useRef<Record<string, number>>({});
|
||||
const awardWidthsInit = useRef(false);
|
||||
if (!awardWidthsInit.current) {
|
||||
awardWidthsInit.current = true;
|
||||
for (const it of (loadLocal(AWARD_WIDTH_KEY) ?? []) as any[]) {
|
||||
if (it?.code && it?.width) awardWidthsRef.current[String(it.code).toUpperCase()] = Number(it.width);
|
||||
}
|
||||
}
|
||||
// Fresh machine: hydrate widths from the portable DB copy, seed the cache, and
|
||||
// apply them to any award columns already on screen.
|
||||
useEffect(() => {
|
||||
if (loadLocal(AWARD_WIDTH_KEY)) return;
|
||||
loadRemote(AWARD_WIDTH_KEY).then((remote) => {
|
||||
if (!remote || !remote.length) return;
|
||||
for (const it of remote as any[]) {
|
||||
if (it?.code && it?.width) awardWidthsRef.current[String(it.code).toUpperCase()] = Number(it.width);
|
||||
}
|
||||
seedLocal(AWARD_WIDTH_KEY, remote);
|
||||
const api = gridRef.current?.api;
|
||||
if (api && awardCols?.length) {
|
||||
const ups = awardCols
|
||||
.map((a) => ({ key: `award_${a.code}`, newWidth: awardWidthsRef.current[a.code.toUpperCase()] }))
|
||||
.filter((u) => !!u.newWidth);
|
||||
if (ups.length) api.setColumnWidths(ups);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const columnDefs = useMemo<ColDef<QSOForm>[]>(() => {
|
||||
restoringRef.current = true;
|
||||
const base = COL_CATALOG.map((c) => {
|
||||
@@ -354,7 +387,7 @@ export function RecentQSOsGrid({ rows, selectAllSignal, selectRowSignal, rowDrag
|
||||
colId: `award_${a.code}`,
|
||||
headerName: a.code,
|
||||
headerTooltip: t('rqg.awardTip', { name: a.name }),
|
||||
width: 110,
|
||||
width: awardWidthsRef.current[a.code.toUpperCase()] ?? 110,
|
||||
cellClass: 'text-[11px]',
|
||||
// Visibility comes from the persisted award-code set, so a column the user
|
||||
// showed reappears on reopen and one they didn't stays hidden.
|
||||
@@ -370,11 +403,17 @@ export function RecentQSOsGrid({ rows, selectAllSignal, selectRowSignal, rowDrag
|
||||
useEffect(() => {
|
||||
const api = gridRef.current?.api;
|
||||
if (!api || !awardCols?.length) return;
|
||||
const widthUps: { key: string; newWidth: number }[] = [];
|
||||
for (const a of awardCols) {
|
||||
const want = awardShown.has(a.code.toUpperCase());
|
||||
const col = api.getColumn(`award_${a.code}`);
|
||||
if (col && col.isVisible() !== want) api.setColumnsVisible([`award_${a.code}`], want);
|
||||
// Re-apply the saved width — AG Grid keeps an existing column's width across
|
||||
// a columnDefs rebuild instead of re-reading colDef.width (same quirk as hide).
|
||||
const w = awardWidthsRef.current[a.code.toUpperCase()];
|
||||
if (col && w && Math.round(col.getActualWidth()) !== w) widthUps.push({ key: `award_${a.code}`, newWidth: w });
|
||||
}
|
||||
if (widthUps.length) api.setColumnWidths(widthUps);
|
||||
}, [awardCols, awardShown]);
|
||||
|
||||
const defaultColDef = useMemo<ColDef>(() => ({
|
||||
@@ -421,8 +460,24 @@ export function RecentQSOsGrid({ rows, selectAllSignal, selectRowSignal, rowDrag
|
||||
const saveColumnState = useCallback(() => {
|
||||
if (restoringRef.current) return; // ignore the events fired by a column rebuild
|
||||
const state = gridRef.current?.api?.getColumnState();
|
||||
if (state) saveState(colStateKey, stripAwardCols(state));
|
||||
}, []);
|
||||
if (!state) return;
|
||||
saveState(colStateKey, stripAwardCols(state));
|
||||
// Award columns are stripped above, so persist their widths on the side.
|
||||
let changed = false;
|
||||
for (const s of state) {
|
||||
const id = String((s as any)?.colId ?? '');
|
||||
if (id.startsWith('award_') && (s as any).width) {
|
||||
const code = id.slice('award_'.length).toUpperCase();
|
||||
if (awardWidthsRef.current[code] !== (s as any).width) {
|
||||
awardWidthsRef.current[code] = (s as any).width;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
saveState(AWARD_WIDTH_KEY, Object.entries(awardWidthsRef.current).map(([code, width]) => ({ code, width })));
|
||||
}
|
||||
}, [colStateKey, AWARD_WIDTH_KEY]);
|
||||
|
||||
// columnDefs is rebuilt whenever the award columns load OR the user toggles an
|
||||
// award column (both change the memo → restoringRef flips true at line 316). Each
|
||||
|
||||
Reference in New Issue
Block a user