feat(cluster): single click fills the call, double click works the spot

Reading the cluster means running down dozens of lines, and a single
click did the whole job — QSY, mode, callsign — so every line looked at
dragged the radio with it. One stray click while reading took the
operator off the station they were working.

Looking and going are two different intentions, so they are two gestures
now. The band map is unchanged: clicking a spot there is already a
deliberate act, not a way of reading a list.
This commit is contained in:
2026-08-24 19:08:33 +02:00
parent 0848c275d2
commit 4e9b1eebe9
3 changed files with 32 additions and 5 deletions
+15 -1
View File
@@ -3159,6 +3159,19 @@ export default function App() {
// Clicking a spot (cluster grid or any band map): tune the rig, set the mode,
// fill the call, pre-fill POTA, (re)start the recording. Shared so every spot
// source behaves identically.
// Single click on a cluster line: fill the callsign, nothing else.
//
// Reading the cluster means running down dozens of lines, and while a single
// click WORKED the spot every pass dragged the radio with it — one stray click
// while looking took the operator off the station they were working. Looking
// and going are two different intentions, so they are now two gestures: click
// to look the call up, double-click to go there.
function handleSpotSelect(s: any) {
if (!s?.dx_call?.trim()) return;
onCallsignInput(s.dx_call, { force: true });
applySpotPOTA((s as any).pota_ref);
}
function handleSpotClick(s: any) {
const m = inferSpotMode(s.comment ?? '', s.freq_hz);
// Reflect the spot's freq/band in the entry strip IMMEDIATELY (optimistic),
@@ -6074,7 +6087,7 @@ export default function App() {
</div>
<div className="flex-1 min-h-0 flex">
<div className="flex-1 min-w-0 flex flex-col min-h-0">
<ClusterGrid key={`clg-${activeProfileId ?? 'x'}`} rows={clusterRenderedRows as any} spotStatus={spotStatus} onSpotClick={handleSpotClick} />
<ClusterGrid key={`clg-${activeProfileId ?? 'x'}`} rows={clusterRenderedRows as any} spotStatus={spotStatus} onSpotClick={handleSpotClick} onSpotSelect={handleSpotSelect} />
</div>
{clusterShowFilters && renderClusterFilters()}
</div>
@@ -7752,6 +7765,7 @@ export default function App() {
rows={rendered as any}
spotStatus={spotStatus}
onSpotClick={handleSpotClick}
onSpotSelect={handleSpotSelect}
/>
);
})()}
+13 -2
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
AllCommunityModule, ModuleRegistry,
type ColDef, type ColumnState, type GridReadyEvent, type RowClickedEvent,
type ColDef, type ColumnState, type GridReadyEvent, type RowClickedEvent, type RowDoubleClickedEvent,
} from 'ag-grid-community';
import { hamlogGridTheme } from '@/lib/gridTheme';
import { AgGridReact } from 'ag-grid-react';
@@ -75,7 +75,13 @@ export type SpotStatusEntry = {
type Props = {
rows: ClusterSpot[];
spotStatus: Record<string, SpotStatusEntry>;
// A DOUBLE click works the spot: QSY, mode, callsign, the lot.
onSpotClick?: (s: ClusterSpot) => void;
// A SINGLE click only fills the callsign. Reading the cluster means passing
// over dozens of lines, and every pass used to drag the radio with it — one
// stray click while looking took the operator off the station they were
// working. Looking and going are now two different gestures.
onSpotSelect?: (s: ClusterSpot) => void;
};
const COL_STATE_KEY = 'hamlog.clusterColState.v1';
@@ -500,7 +506,7 @@ const GROUP_ORDER = ['Spot', 'Geo'];
const CLG_GRP_KEYS: Record<string, string> = { Spot: 'clg2.grpSpot', Geo: 'clg2.grpGeo' };
const groupLabel = (t: TFn, g: string): string => t(CLG_GRP_KEYS[g] ?? g);
export function ClusterGrid({ rows, spotStatus, onSpotClick }: Props) {
export function ClusterGrid({ rows, spotStatus, onSpotClick, onSpotSelect }: Props) {
const { t } = useI18n();
const gridRef = useRef<any>(null);
const [pickerOpen, setPickerOpen] = useState(false);
@@ -583,6 +589,10 @@ export function ClusterGrid({ rows, spotStatus, onSpotClick }: Props) {
}, []);
function handleRowClicked(e: RowClickedEvent<ClusterSpot>) {
if (e.data && onSpotSelect) onSpotSelect(e.data);
}
function handleRowDoubleClicked(e: RowDoubleClickedEvent<ClusterSpot>) {
if (e.data && onSpotClick) onSpotClick(e.data);
}
@@ -647,6 +657,7 @@ export function ClusterGrid({ rows, spotStatus, onSpotClick }: Props) {
onColumnVisible={saveColumnState}
onSortChanged={saveColumnState}
onRowClicked={handleRowClicked}
onRowDoubleClicked={handleRowDoubleClicked}
animateRows={false}
suppressCellFocus
getRowId={(p) => `${(p.data as any).received_at}-${(p.data as any).dx_call}-${(p.data as any).source_id}`}