feat: added Cabrillo export
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
import {
|
||||
AddQSO, ListQSO, CountQSO, ListQSOFiltered, CountQSOFiltered,
|
||||
OpenADIFFile, ImportADIF, SaveADIFFile, ExportADIF, ExportADIFFiltered, ExportADIFSelected,
|
||||
SaveCabrilloFile, ExportCabrillo, ExportCabrilloFiltered, ExportCabrilloSelected,
|
||||
GetQSO, UpdateQSO, DeleteQSO, DeleteQSOs, DeleteAllQSO,
|
||||
UpdateQSOsFromCty, UpdateQSOsFromQRZ, UpdateQSOsFromClublog, UploadQSOsManual, SendQSORecordingEmail,
|
||||
LookupCallsign, GetStationSettings, GetListsSettings,
|
||||
@@ -1980,6 +1981,40 @@ export default function App() {
|
||||
showToast(`Exported ${r.count} selected QSO${r.count > 1 ? 's' : ''} → ${r.path}`);
|
||||
} catch (e: any) { setError(String(e?.message ?? e)); }
|
||||
}
|
||||
// Cabrillo (contest log) export — prompts for the contest name, then writes a
|
||||
// .cbr file. All / filtered / selected variants mirror the ADIF ones.
|
||||
async function exportCabrillo() {
|
||||
const contest = window.prompt('Contest name for Cabrillo (e.g. CQ-WW-SSB):', '');
|
||||
if (contest === null) return;
|
||||
try {
|
||||
const path = await SaveCabrilloFile();
|
||||
if (!path) return;
|
||||
const r = await ExportCabrillo(path, contest);
|
||||
showToast(`Cabrillo: ${r.count} QSO${r.count > 1 ? 's' : ''} → ${r.path}`);
|
||||
} catch (e: any) { setError(String(e?.message ?? e)); }
|
||||
}
|
||||
async function exportFilteredCabrillo() {
|
||||
const contest = window.prompt('Contest name for Cabrillo (e.g. CQ-WW-SSB):', '');
|
||||
if (contest === null) return;
|
||||
try {
|
||||
const path = await SaveCabrilloFile();
|
||||
if (!path) return;
|
||||
const f = buildActiveFilter();
|
||||
const r = await ExportCabrilloFiltered(path, contest, { ...f, limit: 0, offset: 0 } as any);
|
||||
showToast(`Cabrillo: ${r.count} QSO${r.count > 1 ? 's' : ''} → ${r.path}`);
|
||||
} catch (e: any) { setError(String(e?.message ?? e)); }
|
||||
}
|
||||
async function exportSelectedCabrillo(ids: number[]) {
|
||||
if (ids.length === 0) return;
|
||||
const contest = window.prompt('Contest name for Cabrillo (e.g. CQ-WW-SSB):', '');
|
||||
if (contest === null) return;
|
||||
try {
|
||||
const path = await SaveCabrilloFile();
|
||||
if (!path) return;
|
||||
const r = await ExportCabrilloSelected(path, contest, ids as any);
|
||||
showToast(`Cabrillo: ${r.count} selected QSO${r.count > 1 ? 's' : ''} → ${r.path}`);
|
||||
} catch (e: any) { setError(String(e?.message ?? e)); }
|
||||
}
|
||||
function askDelete(id: number) { setDeletingIds([id]); }
|
||||
// Delete the whole multi-row selection (Edit menu / Delete key).
|
||||
function askDeleteSelected() {
|
||||
@@ -2216,6 +2251,7 @@ export default function App() {
|
||||
{ name: 'file', label: 'File', items: [
|
||||
{ type: 'item', label: 'Import ADIF…', action: 'file.import', shortcut: 'Ctrl+O' },
|
||||
{ type: 'item', label: exporting ? 'Exporting…' : 'Export ADIF…', action: 'file.export', shortcut: 'Ctrl+E', disabled: exporting || total === 0 },
|
||||
{ type: 'item', label: 'Export Cabrillo…', action: 'file.exportCabrillo', disabled: total === 0 },
|
||||
{ type: 'separator' },
|
||||
{ type: 'item', label: 'Delete all QSOs…', action: 'file.deleteall', disabled: total === 0 },
|
||||
{ type: 'separator' },
|
||||
@@ -2257,6 +2293,7 @@ export default function App() {
|
||||
switch (action) {
|
||||
case 'file.import': importAdif(); break;
|
||||
case 'file.export': exportAdif(); break;
|
||||
case 'file.exportCabrillo': exportCabrillo(); break;
|
||||
case 'file.deleteall': setShowDeleteAll(true); break;
|
||||
case 'view.refresh': refresh(); break;
|
||||
case 'view.clearfilters': setFilterCallsign(''); setActiveFilter({ conditions: [], match: 'AND' }); break;
|
||||
@@ -3746,6 +3783,8 @@ export default function App() {
|
||||
onBulkEdit={openBulkEdit}
|
||||
onExportSelected={exportSelectedADIF}
|
||||
onExportFiltered={exportFilteredADIF}
|
||||
onExportCabrilloSelected={exportSelectedCabrillo}
|
||||
onExportCabrilloFiltered={exportFilteredCabrillo}
|
||||
onDelete={(ids) => setDeletingIds(ids)}
|
||||
onRowSelected={(ids) => { setSelectedIds(ids); setSelectedId(ids[0] ?? null); }}
|
||||
/>
|
||||
|
||||
@@ -52,6 +52,18 @@ export function AwardRefSelector({ dxcc, value, onChange, fieldValues, heightCla
|
||||
|
||||
const entries = value ? value.split(';').filter(Boolean) : [];
|
||||
|
||||
// When the reference set is cleared (a QSO was logged or the call was reset),
|
||||
// also blank the picker's own state — the search box and the highlighted /
|
||||
// selected reference — so the Awards tab starts fresh for the next contact.
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
setSelectedEntry(null);
|
||||
setSelectedRef(null);
|
||||
setQ('');
|
||||
setSearchResults([]);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([GetAwardDefs(), GetAwardReferenceMeta()])
|
||||
.then(([d, m]) => {
|
||||
|
||||
@@ -15,6 +15,8 @@ type Props = {
|
||||
onBulkEdit?: (ids: number[]) => void;
|
||||
onExportSelected?: (ids: number[]) => void;
|
||||
onExportFiltered?: () => void;
|
||||
onExportCabrilloSelected?: (ids: number[]) => void;
|
||||
onExportCabrilloFiltered?: () => void;
|
||||
onDelete?: (ids: number[]) => void;
|
||||
};
|
||||
|
||||
@@ -32,7 +34,7 @@ const UPLOAD_TARGETS: { service: string; label: string }[] = [
|
||||
// or picks a command. (We deliberately do NOT close on scroll/resize: the QSO
|
||||
// list auto-refreshes and AG Grid fires internal scroll events on refresh,
|
||||
// which used to dismiss the menu the instant it appeared.)
|
||||
export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportFiltered, onDelete }: Props) {
|
||||
export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportFiltered, onExportCabrilloSelected, onExportCabrilloFiltered, onDelete }: Props) {
|
||||
useEffect(() => {
|
||||
if (!menu) return;
|
||||
const close = () => onClose();
|
||||
@@ -142,6 +144,24 @@ export function QSOContextMenu({ menu, onClose, onUpdateFromCty, onUpdateFromQRZ
|
||||
<span>Export filtered view to ADIF (no limit)</span>
|
||||
</button>
|
||||
)}
|
||||
{onExportCabrilloSelected && (
|
||||
<button
|
||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-left hover:bg-accent/50"
|
||||
onClick={() => { onExportCabrilloSelected(menu.ids); onClose(); }}
|
||||
>
|
||||
<FileDown className="size-4 text-amber-600" />
|
||||
<span>Export selected to Cabrillo ({n})</span>
|
||||
</button>
|
||||
)}
|
||||
{onExportCabrilloFiltered && (
|
||||
<button
|
||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-left hover:bg-accent/50"
|
||||
onClick={() => { onExportCabrilloFiltered(); onClose(); }}
|
||||
>
|
||||
<FileDown className="size-4 text-amber-700" />
|
||||
<span>Export filtered view to Cabrillo (no limit)</span>
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ type Props = {
|
||||
onBulkEdit?: (ids: number[]) => void;
|
||||
onExportSelected?: (ids: number[]) => void;
|
||||
onExportFiltered?: () => void;
|
||||
onExportCabrilloSelected?: (ids: number[]) => void;
|
||||
onExportCabrilloFiltered?: () => void;
|
||||
onDelete?: (ids: number[]) => void;
|
||||
// One column per defined award; the cell shows the reference this QSO counts
|
||||
// for (from row.award_refs[CODE], attached by the parent). Hidden by default.
|
||||
@@ -228,7 +230,7 @@ export const GROUP_ORDER = [
|
||||
'Contest', 'Propagation', 'My station', 'Misc',
|
||||
];
|
||||
|
||||
export function RecentQSOsGrid({ rows, selectAllSignal, onRowDoubleClicked, onRowSelected, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportFiltered, onDelete, awardCols }: Props) {
|
||||
export function RecentQSOsGrid({ rows, selectAllSignal, onRowDoubleClicked, onRowSelected, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportFiltered, onExportCabrilloSelected, onExportCabrilloFiltered, onDelete, awardCols }: Props) {
|
||||
const gridRef = useRef<any>(null);
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
const [menu, setMenu] = useState<QSOMenuState>(null);
|
||||
@@ -416,6 +418,8 @@ export function RecentQSOsGrid({ rows, selectAllSignal, onRowDoubleClicked, onRo
|
||||
onBulkEdit={onBulkEdit}
|
||||
onExportSelected={onExportSelected}
|
||||
onExportFiltered={onExportFiltered}
|
||||
onExportCabrilloSelected={onExportCabrilloSelected}
|
||||
onExportCabrilloFiltered={onExportCabrilloFiltered}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
|
||||
|
||||
Vendored
+8
@@ -131,6 +131,12 @@ export function ExportADIFSelected(arg1:string,arg2:boolean,arg3:Array<number>):
|
||||
|
||||
export function ExportAwards():Promise<string>;
|
||||
|
||||
export function ExportCabrillo(arg1:string,arg2:string):Promise<main.CabrilloResult>;
|
||||
|
||||
export function ExportCabrilloFiltered(arg1:string,arg2:string,arg3:qso.QueryFilter):Promise<main.CabrilloResult>;
|
||||
|
||||
export function ExportCabrilloSelected(arg1:string,arg2:string,arg3:Array<number>):Promise<main.CabrilloResult>;
|
||||
|
||||
export function FilterFields():Promise<Array<string>>;
|
||||
|
||||
export function FindQSOsForUpload(arg1:string,arg2:string):Promise<Array<qso.QSO>>;
|
||||
@@ -535,6 +541,8 @@ export function SaveBackupSettings(arg1:main.BackupSettings):Promise<void>;
|
||||
|
||||
export function SaveCATSettings(arg1:main.CATSettings):Promise<void>;
|
||||
|
||||
export function SaveCabrilloFile():Promise<string>;
|
||||
|
||||
export function SaveClusterServer(arg1:cluster.ServerConfig):Promise<cluster.ServerConfig>;
|
||||
|
||||
export function SaveEmailSettings(arg1:main.EmailSettings):Promise<void>;
|
||||
|
||||
@@ -226,6 +226,18 @@ export function ExportAwards() {
|
||||
return window['go']['main']['App']['ExportAwards']();
|
||||
}
|
||||
|
||||
export function ExportCabrillo(arg1, arg2) {
|
||||
return window['go']['main']['App']['ExportCabrillo'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function ExportCabrilloFiltered(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['ExportCabrilloFiltered'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function ExportCabrilloSelected(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['ExportCabrilloSelected'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function FilterFields() {
|
||||
return window['go']['main']['App']['FilterFields']();
|
||||
}
|
||||
@@ -1034,6 +1046,10 @@ export function SaveCATSettings(arg1) {
|
||||
return window['go']['main']['App']['SaveCATSettings'](arg1);
|
||||
}
|
||||
|
||||
export function SaveCabrilloFile() {
|
||||
return window['go']['main']['App']['SaveCabrilloFile']();
|
||||
}
|
||||
|
||||
export function SaveClusterServer(arg1) {
|
||||
return window['go']['main']['App']['SaveClusterServer'](arg1);
|
||||
}
|
||||
|
||||
@@ -1253,6 +1253,20 @@ export namespace main {
|
||||
this.digital_default = source["digital_default"];
|
||||
}
|
||||
}
|
||||
export class CabrilloResult {
|
||||
count: number;
|
||||
path: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new CabrilloResult(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.count = source["count"];
|
||||
this.path = source["path"];
|
||||
}
|
||||
}
|
||||
export class ChatMessage {
|
||||
id: number;
|
||||
operator: string;
|
||||
|
||||
Reference in New Issue
Block a user