feat: Added Net control

This commit is contained in:
2026-06-22 23:40:25 +02:00
parent 81c60628c6
commit 8b831145ad
8 changed files with 1198 additions and 66 deletions
+30 -3
View File
@@ -66,6 +66,7 @@ import { ShutdownProgress } from '@/components/ShutdownProgress';
import { ClusterGrid } from '@/components/ClusterGrid';
import { cleanSpotter, inferSpotMode, spotModeCategory, spotStatusKey } from '@/lib/spot';
import { WorkedBeforeGrid } from '@/components/WorkedBeforeGrid';
import { NetControlPanel } from '@/components/NetControlPanel';
import { BulkEditModal } from '@/components/BulkEditModal';
import { ChatPanel, type ChatMsg, type ChatPresence } from '@/components/ChatPopover';
import { DetailsPanel, type DetailsState } from '@/components/DetailsPanel';
@@ -695,6 +696,10 @@ export default function App() {
}
const chatShown = chatOpen && chatAvailable;
// NET Control tab — enabled from Tools (persisted; once on it's a tab like Cluster).
const [netEnabled, setNetEnabled] = useState(() => localStorage.getItem('opslog.netEnabled') === '1');
useEffect(() => { localStorage.setItem('opslog.netEnabled', netEnabled ? '1' : '0'); }, [netEnabled]);
const [dvkEnabled, setDvkEnabled] = useState(false);
const [dvkMsgs, setDvkMsgs] = useState<DVKMsg[]>([]);
const [dvkStat, setDvkStat] = useState<DVKStat>({ recording: false, playing: false, rec_slot: 0 });
@@ -1709,8 +1714,6 @@ export default function App() {
};
applyAwardRefs(payload, details.award_refs ?? '', awardFieldRef.current);
await AddQSO(payload);
// Same green toast as a QSL upload, so the op gets visual confirmation.
showToast(`QSO logged — ${payload.callsign}${band ? ` · ${band}` : ''}${mode ? ` ${mode}` : ''}`);
resetEntry(); // clears the call AND the Worked-before matrix
callsignRef.current?.focus(); // return focus to the call field, wherever it was (e.g. Name)
await refresh();
@@ -2104,6 +2107,8 @@ export default function App() {
{ type: 'item', label: dvkEnabled ? '✓ Digital Voice Keyer' : 'Digital Voice Keyer', action: 'tools.dvk' },
{ type: 'item', label: cwEnabled ? '✓ CW decoder (RX audio)' : 'CW decoder (RX audio)', action: 'tools.cwdecoder' },
{ type: 'separator' },
{ type: 'item', label: netEnabled ? '✓ NET Control' : 'NET Control', action: 'tools.net' },
{ type: 'separator' },
// Maintenance — bumped here while we only have one entry. Will move
// to a Tools → Maintenance submenu once Clublog + LoTW refresh land.
{ type: 'item', label: ctyRefreshing ? 'Refreshing cty.dat…' : 'Refresh cty.dat', action: 'tools.refreshCty', disabled: ctyRefreshing },
@@ -2112,7 +2117,7 @@ export default function App() {
{ name: 'help', label: 'Help', items: [
{ type: 'item', label: 'About OpsLog', action: 'help.about' },
]},
], [total, selectedId, selectedIds, ctyRefreshing, refsDownloading, exporting, wkEnabled, dvkEnabled, cwEnabled]);
], [total, selectedId, selectedIds, ctyRefreshing, refsDownloading, exporting, wkEnabled, dvkEnabled, cwEnabled, netEnabled]);
function handleMenu(action: string) {
switch (action) {
@@ -2130,6 +2135,7 @@ export default function App() {
case 'tools.winkeyer': wkSetEnabled(!wkEnabled); break;
case 'tools.dvk': setDvkEnabled((v) => !v); break;
case 'tools.cwdecoder': toggleCwDecoder(); break;
case 'tools.net': setNetEnabled((v) => { const nv = !v; if (nv) setActiveTab('net'); return nv; }); break;
case 'tools.refreshCty': refreshCtyDat(); break;
case 'tools.downloadRefs': downloadRefs(); break;
case 'help.about': setShowAbout(true); break;
@@ -3461,6 +3467,21 @@ export default function App() {
</TabsTrigger>
<TabsTrigger value="awards">Awards</TabsTrigger>
<TabsTrigger value="bandmap">Band Map</TabsTrigger>
{netEnabled && (
<TabsTrigger value="net" className="gap-1.5">
Net
<span
role="button"
aria-label="Close Net"
title="Close"
className="inline-flex items-center justify-center size-4 rounded hover:bg-foreground/10 text-muted-foreground hover:text-foreground"
onPointerDown={(e) => { e.stopPropagation(); }}
onClick={(e) => { e.stopPropagation(); setNetEnabled(false); setActiveTab((t) => (t === 'net' ? 'recent' : t)); }}
>
<X className="size-3" />
</span>
</TabsTrigger>
)}
{catState.backend === 'flex' && <TabsTrigger value="flex">FlexRadio</TabsTrigger>}
{catState.backend === 'icom' && <TabsTrigger value="icom">Icom</TabsTrigger>}
{/* Not a tab — QRZ blocks embedding, so this opens the call's
@@ -3788,6 +3809,12 @@ export default function App() {
{/* Band Map: several bands shown side-by-side (panadapter-style
strips). Pick bands with the chips; each strip is clickable to
tune the rig. */}
{netEnabled && (
<TabsContent value="net" className="mt-0 flex flex-col min-h-0 flex-1">
<NetControlPanel onLogged={refresh} rstChoices={rstOptions(mode, rstLists)} />
</TabsContent>
)}
<TabsContent value="bandmap" className="mt-0 flex flex-col min-h-0 flex-1">
<div className="flex items-center gap-1 px-3 py-1.5 border-b border-border/60 shrink-0 flex-wrap">
<span className="text-xs text-muted-foreground mr-1">Bands:</span>
+386
View File
@@ -0,0 +1,386 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
AllCommunityModule, ModuleRegistry, themeQuartz,
type ColDef, type RowDoubleClickedEvent, type CellValueChangedEvent,
} from 'ag-grid-community';
import { AgGridReact } from 'ag-grid-react';
import { Plus, Trash2, Radio, PlusCircle, MinusCircle, Search, UserPlus } from 'lucide-react';
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import {
NetList, NetCreate, NetRename, NetDelete, NetOpen, NetClose, NetOpenID,
NetRoster, NetRosterUpsert, NetRosterRemove, NetLookup,
NetActiveList, NetActivate, NetDeactivate, NetUpdateActive,
} from '@/../wailsjs/go/main/App';
import { netctl } from '@/../wailsjs/go/models';
ModuleRegistry.registerModules([AllCommunityModule]);
const hamlogTheme = themeQuartz.withParams({
fontFamily: 'inherit',
fontSize: 12.5,
backgroundColor: '#faf6ea',
foregroundColor: '#2a2419',
headerBackgroundColor: '#e8dfc9',
headerTextColor: '#5a4f3a',
headerFontWeight: 600,
oddRowBackgroundColor: '#f5efe0',
rowHoverColor: '#ecdcb4',
selectedRowBackgroundColor: '#f0d9a8',
borderColor: '#c8b994',
rowBorder: { color: '#d8c9a8', width: 1 },
columnBorder: { color: '#d8c9a8', width: 1 },
cellHorizontalPadding: 10,
rowHeight: 30,
headerHeight: 32,
spacing: 4,
accentColor: '#b8410c',
iconSize: 12,
});
type Net = netctl.Net;
type Station = netctl.Station;
type Active = {
callsign: string; name: string; qth: string; country: string;
rst_sent: string; rst_rcvd: string; comment: string; time_on: any;
};
function fmtTimeOn(s: any): string {
if (!s) return '';
const d = new Date(s);
if (isNaN(d.getTime())) return '';
const p = (n: number) => String(n).padStart(2, '0');
return `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}:${p(d.getUTCSeconds())}Z`;
}
const emptyStation = (): Station => netctl.Station.createFrom({ callsign: '' });
export function NetControlPanel({ onLogged, rstChoices }: { onLogged?: () => void; rstChoices?: string[] }) {
const [nets, setNets] = useState<Net[]>([]);
const [selId, setSelId] = useState<string>('');
const [openId, setOpenId] = useState<string>('');
const [roster, setRoster] = useState<Station[]>([]);
const [active, setActive] = useState<Active[]>([]);
const [error, setError] = useState('');
// Add/edit-contact dialog.
const [contactOpen, setContactOpen] = useState(false);
const [contact, setContact] = useState<Station>(emptyStation());
const [looking, setLooking] = useState(false);
const activeGrid = useRef<any>(null);
const rosterGrid = useRef<any>(null);
const isOpen = openId !== '' && openId === selId;
const refreshNets = useCallback(async () => {
try {
const [list, oid] = await Promise.all([NetList(), NetOpenID()]);
const arr = (list ?? []) as Net[];
setNets(arr);
setOpenId(oid ?? '');
setSelId((cur) => cur || (oid ?? '') || (arr[0]?.id ?? ''));
} catch (e: any) { setError(String(e?.message ?? e)); }
}, []);
const refreshRoster = useCallback(async (id: string) => {
if (!id) { setRoster([]); return; }
try { setRoster(((await NetRoster(id)) ?? []) as Station[]); }
catch (e: any) { setError(String(e?.message ?? e)); }
}, []);
const refreshActive = useCallback(async () => {
try { setActive(((await NetActiveList()) ?? []) as Active[]); }
catch { /* ignore */ }
}, []);
useEffect(() => { refreshNets(); }, [refreshNets]);
useEffect(() => { refreshRoster(selId); }, [selId, refreshRoster]);
useEffect(() => { if (isOpen) refreshActive(); else setActive([]); }, [isOpen, refreshActive]);
// The roster side hides callsigns that are currently on the air (they live in
// the left grid until logged), mirroring Log4OM's two-list behaviour.
const activeCalls = useMemo(() => new Set(active.map((a) => a.callsign.toUpperCase())), [active]);
const rosterShown = useMemo(
() => roster.filter((s) => !activeCalls.has((s.callsign ?? '').toUpperCase())),
[roster, activeCalls],
);
async function newNet() {
const name = window.prompt('New NET name:');
if (!name?.trim()) return;
try { const n = await NetCreate(name.trim()); await refreshNets(); setSelId(n.id); }
catch (e: any) { setError(String(e?.message ?? e)); }
}
async function renameNet() {
if (!selId) return;
const cur = nets.find((n) => n.id === selId);
const name = window.prompt('Rename NET:', cur?.name ?? '');
if (!name?.trim()) return;
try { await NetRename(selId, name.trim()); await refreshNets(); }
catch (e: any) { setError(String(e?.message ?? e)); }
}
async function deleteNet() {
if (!selId) return;
const cur = nets.find((n) => n.id === selId);
if (!window.confirm(`Delete NET "${cur?.name}" and its roster? This cannot be undone.`)) return;
try { await NetDelete(selId); setSelId(''); await refreshNets(); }
catch (e: any) { setError(String(e?.message ?? e)); }
}
async function toggleOpen() {
try {
if (isOpen) {
if (active.length > 0 &&
!window.confirm(`${active.length} station(s) still on the air will be dropped WITHOUT logging. Close anyway?`)) return;
await NetClose(); setOpenId('');
} else {
await NetOpen(selId); setOpenId(selId); await refreshActive();
}
} catch (e: any) { setError(String(e?.message ?? e)); }
}
// Roster → active (start QSO).
async function activate(call: string) {
if (!isOpen || !call) return;
try { await NetActivate(call); await refreshActive(); }
catch (e: any) { setError(String(e?.message ?? e)); }
}
// Active → logged (end QSO, removed from session, written to the logbook).
async function deactivate(call: string) {
if (!call) return;
try { await NetDeactivate(call); await refreshActive(); onLogged?.(); }
catch (e: any) { setError(String(e?.message ?? e)); }
}
function onActiveDblClick(e: RowDoubleClickedEvent<Active>) {
// Double-clicking a non-editable area logs the QSO; editable cells open the
// editor instead (ag-grid handles that before this fires only for blanks),
// so we gate on the column being the callsign.
if (e.data && (e as any).column?.getColId?.() === 'callsign') deactivate(e.data.callsign);
}
async function onActiveCellChanged(e: CellValueChangedEvent<Active>) {
const d = e.data;
if (!d) return;
try {
await NetUpdateActive({
callsign: d.callsign, name: d.name ?? '', qth: d.qth ?? '', country: d.country ?? '',
rst_sent: d.rst_sent ?? '', rst_rcvd: d.rst_rcvd ?? '', comment: d.comment ?? '', time_on: d.time_on,
} as any);
} catch (err: any) { setError(String(err?.message ?? err)); }
}
// Add-contact dialog.
function openAddContact() { setContact(emptyStation()); setContactOpen(true); }
async function lookupContact() {
const call = (contact.callsign ?? '').trim();
if (!call) return;
setLooking(true);
try {
const r = await NetLookup(call);
setContact((c) => netctl.Station.createFrom({
...c, callsign: (r.callsign || call).toUpperCase(),
name: r.name || c.name, qth: r.qth || c.qth, country: r.country || c.country,
dxcc: r.dxcc || c.dxcc, itu: r.itu || c.itu, cq: r.cq || c.cq,
}));
} catch (e: any) { setError(String(e?.message ?? e)); }
finally { setLooking(false); }
}
async function saveContact() {
const call = (contact.callsign ?? '').trim().toUpperCase();
if (!call || !selId) return;
try {
await NetRosterUpsert(selId, netctl.Station.createFrom({ ...contact, callsign: call }));
setContactOpen(false);
await refreshRoster(selId);
} catch (e: any) { setError(String(e?.message ?? e)); }
}
async function removeSelectedRoster() {
const sel = (rosterGrid.current?.api?.getSelectedRows() ?? []) as Station[];
if (sel.length === 0) return;
if (!window.confirm(`Remove ${sel.length} station(s) from this NET's roster?`)) return;
try {
for (const s of sel) await NetRosterRemove(selId, s.callsign);
await refreshRoster(selId);
} catch (e: any) { setError(String(e?.message ?? e)); }
}
const activeCols = useMemo<ColDef<Active>[]>(() => [
{ colId: 'callsign', headerName: 'Callsign', field: 'callsign', width: 110, cellClass: 'font-mono font-semibold' },
{ headerName: 'Name', field: 'name', flex: 1, editable: true },
{ headerName: 'QTH', field: 'qth', flex: 1, editable: true },
{ headerName: 'Time on', valueGetter: (p) => fmtTimeOn(p.data?.time_on), width: 90, cellClass: 'font-mono text-[11px]' },
{ headerName: 'Country', field: 'country', width: 120 },
{
headerName: 'RST S', field: 'rst_sent', width: 80, editable: true, cellClass: 'font-mono',
cellEditor: 'agSelectCellEditor', cellEditorParams: { values: rstChoices ?? [] },
},
{
headerName: 'RST R', field: 'rst_rcvd', width: 80, editable: true, cellClass: 'font-mono',
cellEditor: 'agSelectCellEditor', cellEditorParams: { values: rstChoices ?? [] },
},
{ headerName: 'Comment', field: 'comment', flex: 1.5, editable: true },
], [rstChoices]);
const rosterCols = useMemo<ColDef<Station>[]>(() => [
{ headerName: 'Callsign', field: 'callsign', width: 110, cellClass: 'font-mono font-semibold' },
{ headerName: 'Name', field: 'name', flex: 1 },
{ headerName: 'QTH', field: 'qth', flex: 1 },
{ headerName: 'Country', field: 'country', width: 130 },
], []);
const defaultColDef = useMemo<ColDef>(() => ({ sortable: true, resizable: true, suppressMovable: false }), []);
return (
<div className="flex flex-col min-h-0 flex-1">
{/* Toolbar */}
<div className="flex items-center gap-2 px-3 py-2 border-b border-border/60 bg-muted/30">
<Button variant="outline" size="sm" className="h-8" onClick={newNet}>
<Plus className="size-3.5" /> New NET
</Button>
<select
className="h-8 rounded-md border border-input bg-background px-2 text-sm min-w-[180px]"
value={selId}
disabled={isOpen}
onChange={(e) => setSelId(e.target.value)}
title={isOpen ? 'Close the NET to switch' : 'Select a NET'}
>
<option value=""> select a NET </option>
{nets.map((n) => <option key={n.id} value={n.id}>{n.name}</option>)}
</select>
<Button variant={isOpen ? 'destructive' : 'default'} size="sm" className="h-8" disabled={!selId} onClick={toggleOpen}>
<Radio className="size-3.5" /> {isOpen ? 'Close NET' : 'Open NET'}
</Button>
<Button variant="ghost" size="sm" className="h-8" disabled={!selId || isOpen} onClick={renameNet}>Rename</Button>
<Button variant="ghost" size="sm" className="h-8 text-rose-700" disabled={!selId || isOpen} onClick={deleteNet}>
<Trash2 className="size-3.5" /> Delete
</Button>
<div className="flex-1" />
{isOpen && <Badge className="bg-emerald-600 text-white tracking-wider">NET OPEN</Badge>}
<span className="text-[11px] text-muted-foreground">
On air: <strong className="text-foreground">{active.length}</strong> ·
Roster: <strong className="text-foreground">{roster.length}</strong>
</span>
{error && <Badge variant="destructive" className="max-w-[280px] truncate" title={error}>{error}</Badge>}
</div>
<div className="flex min-h-0 flex-1">
{/* ACTIVE USERS (left) */}
<div className="flex flex-col min-h-0 flex-1 border-r border-border/60">
<div className="flex items-center gap-2 px-3 py-1.5 bg-red-600 text-white text-[11px] font-semibold uppercase tracking-wider">
On air active QSOs
<span className="ml-auto font-normal normal-case opacity-90">double-click callsign log &amp; end QSO</span>
</div>
<div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
<div style={{ position: 'absolute', inset: 0 }}>
<AgGridReact<Active>
ref={activeGrid}
theme={hamlogTheme}
rowData={active}
columnDefs={activeCols}
defaultColDef={defaultColDef}
onRowDoubleClicked={onActiveDblClick}
onCellValueChanged={onActiveCellChanged}
animateRows={false}
getRowId={(p) => String((p.data as any).callsign)}
stopEditingWhenCellsLoseFocus
/>
</div>
</div>
{isOpen && active.length > 0 && (
<div className="px-3 py-1.5 border-t border-border/60 bg-muted/30 flex items-center gap-2">
<Button variant="ghost" size="sm" className="h-7 text-[11px]"
onClick={() => { const r = activeGrid.current?.api?.getSelectedRows?.()?.[0] as Active; if (r) deactivate(r.callsign); }}>
<MinusCircle className="size-3.5" /> Log &amp; end selected
</Button>
</div>
)}
</div>
{/* NET USERS / roster (right) */}
<div className="flex flex-col min-h-0 w-[40%] max-w-[560px]">
<div className="flex items-center gap-2 px-3 py-1.5 bg-emerald-700 text-white text-[11px] font-semibold uppercase tracking-wider">
NET users roster
<span className="ml-auto font-normal normal-case opacity-90">double-click put on air</span>
</div>
<div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
<div style={{ position: 'absolute', inset: 0 }}>
<AgGridReact<Station>
ref={rosterGrid}
theme={hamlogTheme}
rowData={rosterShown}
columnDefs={rosterCols}
defaultColDef={defaultColDef}
rowSelection={{ mode: 'multiRow', checkboxes: false, headerCheckbox: false, enableClickSelection: true }}
onRowDoubleClicked={(e) => e.data && activate(e.data.callsign)}
animateRows={false}
getRowId={(p) => String((p.data as any).callsign)}
/>
</div>
</div>
<div className="px-3 py-1.5 border-t border-border/60 bg-muted/30 flex items-center gap-2">
<Button variant="ghost" size="sm" className="h-7 text-[11px]" disabled={!selId} onClick={openAddContact}>
<UserPlus className="size-3.5" /> Add contact
</Button>
<Button variant="ghost" size="sm" className="h-7 text-[11px] text-rose-700" disabled={!selId} onClick={removeSelectedRoster}>
<MinusCircle className="size-3.5" /> Remove
</Button>
{isOpen && (
<Button variant="ghost" size="sm" className="h-7 text-[11px] ml-auto"
onClick={() => { const r = rosterGrid.current?.api?.getSelectedRows?.()?.[0] as Station; if (r) activate(r.callsign); }}>
<PlusCircle className="size-3.5" /> Put selected on air
</Button>
)}
</div>
</div>
</div>
{/* Add / edit contact dialog */}
<Dialog open={contactOpen} onOpenChange={setContactOpen}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Add contact to NET</DialogTitle>
<DialogDescription>Saved in this NET's roster (reused next time you open it).</DialogDescription>
</DialogHeader>
<div className="grid gap-3 px-5 py-2">
<div className="flex items-end gap-2">
<div className="flex-1">
<Label className="text-[11px]">Callsign</Label>
<Input className="font-mono uppercase" value={contact.callsign ?? ''}
onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, callsign: e.target.value.toUpperCase() }))}
onKeyDown={(e) => { if (e.key === 'Enter') lookupContact(); }} />
</div>
<Button variant="outline" size="sm" className="h-9" onClick={lookupContact} disabled={looking || !(contact.callsign ?? '').trim()}>
<Search className="size-3.5" /> {looking ? '' : 'Search'}
</Button>
</div>
<div>
<Label className="text-[11px]">Name</Label>
<Input value={contact.name ?? ''} onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, name: e.target.value }))} />
</div>
<div>
<Label className="text-[11px]">QTH</Label>
<Input value={contact.qth ?? ''} onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, qth: e.target.value }))} />
</div>
<div>
<Label className="text-[11px]">Country</Label>
<Input value={contact.country ?? ''} onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, country: e.target.value }))} />
</div>
</div>
<DialogFooter>
<Button variant="ghost" size="sm" onClick={() => setContactOpen(false)}>Cancel</Button>
<Button size="sm" onClick={saveContact} disabled={!(contact.callsign ?? '').trim()}>
<PlusCircle className="size-3.5" /> Save in NET
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}
+1 -1
View File
@@ -283,7 +283,7 @@ export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => voi
<label className="text-[10px] uppercase tracking-wider text-muted-foreground">Service</label>
<Select value={service} onValueChange={setService}>
<SelectTrigger className="h-8 w-36"><SelectValue /></SelectTrigger>
<SelectContent>{SERVICES.map((s) => <SelectItem key={s.v} value={s.v}>{s.label}</SelectItem>)}</SelectContent>
<SelectContent>{[...SERVICES].sort((a, b) => a.label.localeCompare(b.label)).map((s) => <SelectItem key={s.v} value={s.v}>{s.label}</SelectItem>)}</SelectContent>
</Select>
</div>
{uploadCall && (
+33
View File
@@ -16,6 +16,7 @@ import {audio} from '../models';
import {operating} from '../models';
import {udp} from '../models';
import {lookup} from '../models';
import {netctl} from '../models';
export function ADIFFields():Promise<Array<adif.FieldDef>>;
@@ -371,6 +372,38 @@ export function LookupCallsign(arg1:string):Promise<lookup.Result>;
export function MoveDatabase(arg1:string):Promise<void>;
export function NetActivate(arg1:string):Promise<main.netActiveEntry>;
export function NetActiveList():Promise<Array<main.netActiveEntry>>;
export function NetClose():Promise<void>;
export function NetCreate(arg1:string):Promise<netctl.Net>;
export function NetDeactivate(arg1:string):Promise<number>;
export function NetDelete(arg1:string):Promise<void>;
export function NetList():Promise<Array<netctl.Net>>;
export function NetLookup(arg1:string):Promise<netctl.Station>;
export function NetOpen(arg1:string):Promise<void>;
export function NetOpenID():Promise<string>;
export function NetRename(arg1:string,arg2:string):Promise<void>;
export function NetRoster(arg1:string):Promise<Array<netctl.Station>>;
export function NetRosterRemove(arg1:string,arg2:string):Promise<void>;
export function NetRosterUpsert(arg1:string,arg2:netctl.Station):Promise<void>;
export function NetSetDefaults(arg1:string,arg2:string,arg3:string,arg4:string):Promise<void>;
export function NetUpdateActive(arg1:main.netActiveEntry):Promise<void>;
export function OpenADIFFile():Promise<string>;
export function OpenDatabase(arg1:string):Promise<void>;
+64
View File
@@ -710,6 +710,70 @@ export function MoveDatabase(arg1) {
return window['go']['main']['App']['MoveDatabase'](arg1);
}
export function NetActivate(arg1) {
return window['go']['main']['App']['NetActivate'](arg1);
}
export function NetActiveList() {
return window['go']['main']['App']['NetActiveList']();
}
export function NetClose() {
return window['go']['main']['App']['NetClose']();
}
export function NetCreate(arg1) {
return window['go']['main']['App']['NetCreate'](arg1);
}
export function NetDeactivate(arg1) {
return window['go']['main']['App']['NetDeactivate'](arg1);
}
export function NetDelete(arg1) {
return window['go']['main']['App']['NetDelete'](arg1);
}
export function NetList() {
return window['go']['main']['App']['NetList']();
}
export function NetLookup(arg1) {
return window['go']['main']['App']['NetLookup'](arg1);
}
export function NetOpen(arg1) {
return window['go']['main']['App']['NetOpen'](arg1);
}
export function NetOpenID() {
return window['go']['main']['App']['NetOpenID']();
}
export function NetRename(arg1, arg2) {
return window['go']['main']['App']['NetRename'](arg1, arg2);
}
export function NetRoster(arg1) {
return window['go']['main']['App']['NetRoster'](arg1);
}
export function NetRosterRemove(arg1, arg2) {
return window['go']['main']['App']['NetRosterRemove'](arg1, arg2);
}
export function NetRosterUpsert(arg1, arg2) {
return window['go']['main']['App']['NetRosterUpsert'](arg1, arg2);
}
export function NetSetDefaults(arg1, arg2, arg3, arg4) {
return window['go']['main']['App']['NetSetDefaults'](arg1, arg2, arg3, arg4);
}
export function NetUpdateActive(arg1) {
return window['go']['main']['App']['NetUpdateActive'](arg1);
}
export function OpenADIFFile() {
return window['go']['main']['App']['OpenADIFFile']();
}
+120
View File
@@ -2004,6 +2004,126 @@ export namespace main {
return a;
}
}
export class netActiveEntry {
callsign: string;
name: string;
qth: string;
country: string;
rst_sent: string;
rst_rcvd: string;
comment: string;
// Go type: time
time_on: any;
static createFrom(source: any = {}) {
return new netActiveEntry(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.callsign = source["callsign"];
this.name = source["name"];
this.qth = source["qth"];
this.country = source["country"];
this.rst_sent = source["rst_sent"];
this.rst_rcvd = source["rst_rcvd"];
this.comment = source["comment"];
this.time_on = this.convertValues(source["time_on"], null);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
export namespace netctl {
export class Station {
callsign: string;
name?: string;
qth?: string;
country?: string;
dxcc?: number;
itu?: number;
cq?: number;
groups?: string;
sig?: string;
sig_info?: string;
static createFrom(source: any = {}) {
return new Station(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.callsign = source["callsign"];
this.name = source["name"];
this.qth = source["qth"];
this.country = source["country"];
this.dxcc = source["dxcc"];
this.itu = source["itu"];
this.cq = source["cq"];
this.groups = source["groups"];
this.sig = source["sig"];
this.sig_info = source["sig_info"];
}
}
export class Net {
id: string;
name: string;
default_rst_sent?: string;
default_rst_rcvd?: string;
default_comment?: string;
stations?: Station[];
static createFrom(source: any = {}) {
return new Net(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.name = source["name"];
this.default_rst_sent = source["default_rst_sent"];
this.default_rst_rcvd = source["default_rst_rcvd"];
this.default_comment = source["default_comment"];
this.stations = this.convertValues(source["stations"], Station);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}