fix: added functionality to net control

This commit is contained in:
2026-06-24 19:09:34 +02:00
parent 8b831145ad
commit d6626d96d0
6 changed files with 205 additions and 207 deletions
+129 -103
View File
@@ -406,7 +406,8 @@ type App struct {
netStore *netctl.Store netStore *netctl.Store
netMu sync.Mutex netMu sync.Mutex
netOpenID string // id of the currently open net ("" = none) netOpenID string // id of the currently open net ("" = none)
netActive []*netActiveEntry // stations on the air right now, in check-in order netActive []*qso.QSO // on-air QSO drafts (transient negative ids), check-in order
netSeq int64 // transient-id counter for on-air drafts (decrements: -1, -2, …)
cwMu sync.Mutex // guards the CW decoder lifecycle cwMu sync.Mutex // guards the CW decoder lifecycle
cwStop chan struct{} // stops the CW decoder capture loop; nil when off cwStop chan struct{} // stops the CW decoder capture loop; nil when off
@@ -4284,18 +4285,6 @@ func (a *App) RestartQSORecorder() { a.startQSORecorderIfEnabled() }
// the session. The session is RAM-only — closing the app mid-net drops any // the session. The session is RAM-only — closing the app mid-net drops any
// active stations that were never logged. // active stations that were never logged.
// netActiveEntry is one station currently on the air in the open net.
type netActiveEntry struct {
Callsign string `json:"callsign"`
Name string `json:"name"`
QTH string `json:"qth"`
Country string `json:"country"`
RSTSent string `json:"rst_sent"`
RSTRcvd string `json:"rst_rcvd"`
Comment string `json:"comment"`
TimeOn time.Time `json:"time_on"`
}
// NetList returns all nets (with rosters), ordered by name. // NetList returns all nets (with rosters), ordered by name.
func (a *App) NetList() []netctl.Net { func (a *App) NetList() []netctl.Net {
if a.netStore == nil { if a.netStore == nil {
@@ -4410,97 +4399,26 @@ func (a *App) NetOpenID() string {
} }
// NetActiveList returns the stations currently on the air, in check-in order. // NetActiveList returns the stations currently on the air, in check-in order.
func (a *App) NetActiveList() []netActiveEntry { // Each is a full QSO *draft* (not yet in the DB) carrying a negative transient
// id so the same QSOEditModal as Recent QSOs can edit every field.
func (a *App) NetActiveList() []qso.QSO {
a.netMu.Lock() a.netMu.Lock()
defer a.netMu.Unlock() defer a.netMu.Unlock()
out := make([]netActiveEntry, len(a.netActive)) out := make([]qso.QSO, len(a.netActive))
for i, e := range a.netActive { for i, e := range a.netActive {
out[i] = *e out[i] = *e
} }
return out return out
} }
// NetActivate puts a station on the air (records time_on, seeds defaults from // netLiveFreq returns the rig's live freq/band/mode, falling back to the last
// the net + roster). No-op if already active. The net must be open. // UI-reported values when CAT is off.
func (a *App) NetActivate(callsign string) (netActiveEntry, error) { func (a *App) netLiveFreq() (freq int64, band, mode string) {
call := strings.ToUpper(strings.TrimSpace(callsign))
if call == "" {
return netActiveEntry{}, fmt.Errorf("callsign required")
}
a.netMu.Lock()
defer a.netMu.Unlock()
if a.netOpenID == "" {
return netActiveEntry{}, fmt.Errorf("no net open")
}
for _, e := range a.netActive {
if e.Callsign == call {
return *e, nil // already on the air
}
}
e := &netActiveEntry{Callsign: call, TimeOn: time.Now().UTC()}
if net, ok := a.netStore.Get(a.netOpenID); ok {
e.RSTSent, e.RSTRcvd, e.Comment = net.DefaultRSTSent, net.DefaultRSTRcvd, net.DefaultComment
for _, st := range net.Stations {
if strings.EqualFold(st.Callsign, call) {
e.Name, e.QTH, e.Country = st.Name, st.QTH, st.Country
break
}
}
}
if e.RSTSent == "" {
e.RSTSent = "59"
}
if e.RSTRcvd == "" {
e.RSTRcvd = "59"
}
a.netActive = append(a.netActive, e)
return *e, nil
}
// NetUpdateActive edits the live fields (report/QTH/name/comment) of a station
// already on the air. TimeOn is preserved.
func (a *App) NetUpdateActive(e netActiveEntry) error {
call := strings.ToUpper(strings.TrimSpace(e.Callsign))
a.netMu.Lock()
defer a.netMu.Unlock()
for _, cur := range a.netActive {
if cur.Callsign == call {
cur.Name, cur.QTH, cur.Country = e.Name, e.QTH, e.Country
cur.RSTSent, cur.RSTRcvd, cur.Comment = e.RSTSent, e.RSTRcvd, e.Comment
return nil
}
}
return fmt.Errorf("station not active")
}
// NetDeactivate ends a station's QSO: it logs the contact to the active logbook
// (live CAT freq/mode, time_on→now) and removes it from the session. Returns
// the new QSO id.
func (a *App) NetDeactivate(callsign string) (int64, error) {
call := strings.ToUpper(strings.TrimSpace(callsign))
a.netMu.Lock()
var entry *netActiveEntry
idx := -1
for i, e := range a.netActive {
if e.Callsign == call {
entry, idx = e, i
break
}
}
if entry == nil {
a.netMu.Unlock()
return 0, fmt.Errorf("station not active")
}
a.netActive = append(a.netActive[:idx], a.netActive[idx+1:]...)
a.netMu.Unlock()
// Frequency/mode come live from the rig; fall back to the last UI-reported
// values when CAT is off.
var st cat.RigState var st cat.RigState
if a.cat != nil { if a.cat != nil {
st = a.cat.State() st = a.cat.State()
} }
freq, band, mode := st.FreqHz, st.Band, st.Mode freq, band, mode = st.FreqHz, st.Band, st.Mode
if freq == 0 { if freq == 0 {
a.liveActMu.Lock() a.liveActMu.Lock()
freq, band, mode = a.liveFreqHz, a.liveBand, a.liveMode freq, band, mode = a.liveFreqHz, a.liveBand, a.liveMode
@@ -4509,22 +4427,130 @@ func (a *App) NetDeactivate(callsign string) (int64, error) {
if band == "" && freq > 0 { if band == "" && freq > 0 {
band = bandForHz(freq) band = bandForHz(freq)
} }
q := qso.QSO{ return
Callsign: call, }
QSODate: entry.TimeOn,
QSODateOff: time.Now().UTC(), // NetActivate puts a station on the air: it builds a QSO draft (time_on now,
Band: band, // live freq/mode, defaults + roster info) with a transient negative id and
Mode: mode, // returns it. No-op (returns the existing draft) if already active.
RSTSent: entry.RSTSent, func (a *App) NetActivate(callsign string) (qso.QSO, error) {
RSTRcvd: entry.RSTRcvd, call := strings.ToUpper(strings.TrimSpace(callsign))
Name: entry.Name, if call == "" {
QTH: entry.QTH, return qso.QSO{}, fmt.Errorf("callsign required")
Comment: entry.Comment,
} }
a.netMu.Lock()
defer a.netMu.Unlock()
if a.netOpenID == "" {
return qso.QSO{}, fmt.Errorf("no net open")
}
for _, e := range a.netActive {
if strings.EqualFold(e.Callsign, call) {
return *e, nil // already on the air
}
}
a.netSeq--
q := &qso.QSO{ID: a.netSeq, Callsign: call, QSODate: time.Now().UTC()}
if net, ok := a.netStore.Get(a.netOpenID); ok {
q.RSTSent, q.RSTRcvd, q.Comment = net.DefaultRSTSent, net.DefaultRSTRcvd, net.DefaultComment
for _, st := range net.Stations {
if strings.EqualFold(st.Callsign, call) {
q.Name, q.QTH, q.Country = st.Name, st.QTH, st.Country
if st.DXCC != 0 {
d := st.DXCC
q.DXCC = &d
}
if st.CQ != 0 {
c := st.CQ
q.CQZ = &c
}
if st.ITU != 0 {
i := st.ITU
q.ITUZ = &i
}
break
}
}
}
if q.RSTSent == "" {
q.RSTSent = "59"
}
if q.RSTRcvd == "" {
q.RSTRcvd = "59"
}
freq, band, mode := a.netLiveFreq()
q.Band, q.Mode = band, mode
if freq > 0 { if freq > 0 {
f := freq f := freq
q.FreqHz = &f q.FreqHz = &f
} }
a.applyDXCCNumber(q) // fill country/dxcc/zones for display
a.refineDistrictZones(q)
a.netActive = append(a.netActive, q)
return *q, nil
}
// NetUpdateActive replaces an on-air QSO draft (matched by its transient id)
// with the edited version from the QSOEditModal. Lets the operator change every
// field of a station before it's logged.
func (a *App) NetUpdateActive(q qso.QSO) error {
a.netMu.Lock()
defer a.netMu.Unlock()
for i, cur := range a.netActive {
if cur.ID == q.ID {
qq := q
a.netActive[i] = &qq
return nil
}
}
return fmt.Errorf("station not active")
}
// NetDiscardActive removes an on-air draft (by transient id) WITHOUT logging it
// — i.e. cancel a station added by mistake (the modal's Delete button).
func (a *App) NetDiscardActive(id int64) error {
a.netMu.Lock()
defer a.netMu.Unlock()
for i, e := range a.netActive {
if e.ID == id {
a.netActive = append(a.netActive[:i], a.netActive[i+1:]...)
return nil
}
}
return nil
}
// NetDeactivate ends a station's QSO (by transient id): it logs the draft to the
// active logbook (time_off = now; freq/mode refreshed from the rig only if the
// draft still has none, so manual edits are respected) and removes it from the
// session. Returns the new QSO id.
func (a *App) NetDeactivate(id int64) (int64, error) {
a.netMu.Lock()
var draft *qso.QSO
idx := -1
for i, e := range a.netActive {
if e.ID == id {
draft, idx = e, i
break
}
}
if draft == nil {
a.netMu.Unlock()
return 0, fmt.Errorf("station not active")
}
a.netActive = append(a.netActive[:idx], a.netActive[idx+1:]...)
a.netMu.Unlock()
q := *draft
q.ID = 0 // transient id must not reach the DB (AddQSO inserts a fresh row)
q.QSODateOff = time.Now().UTC()
if q.FreqHz == nil && q.Band == "" {
freq, band, mode := a.netLiveFreq()
q.Band, q.Mode = band, mode
if freq > 0 {
f := freq
q.FreqHz = &f
}
}
return a.AddQSO(q) return a.AddQSO(q)
} }
+1 -1
View File
@@ -3811,7 +3811,7 @@ export default function App() {
tune the rig. */} tune the rig. */}
{netEnabled && ( {netEnabled && (
<TabsContent value="net" className="mt-0 flex flex-col min-h-0 flex-1"> <TabsContent value="net" className="mt-0 flex flex-col min-h-0 flex-1">
<NetControlPanel onLogged={refresh} rstChoices={rstOptions(mode, rstLists)} /> <NetControlPanel onLogged={refresh} countries={countries} bands={bands} modes={modes} />
</TabsContent> </TabsContent>
)} )}
+64 -53
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { import {
AllCommunityModule, ModuleRegistry, themeQuartz, AllCommunityModule, ModuleRegistry, themeQuartz,
type ColDef, type RowDoubleClickedEvent, type CellValueChangedEvent, type ColDef,
} from 'ag-grid-community'; } from 'ag-grid-community';
import { AgGridReact } from 'ag-grid-react'; import { AgGridReact } from 'ag-grid-react';
import { Plus, Trash2, Radio, PlusCircle, MinusCircle, Search, UserPlus } from 'lucide-react'; import { Plus, Trash2, Radio, PlusCircle, MinusCircle, Search, UserPlus } from 'lucide-react';
@@ -12,10 +12,12 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { QSOEditModal } from '@/components/QSOEditModal';
import type { QSOForm } from '@/types';
import { import {
NetList, NetCreate, NetRename, NetDelete, NetOpen, NetClose, NetOpenID, NetList, NetCreate, NetRename, NetDelete, NetOpen, NetClose, NetOpenID,
NetRoster, NetRosterUpsert, NetRosterRemove, NetLookup, NetRoster, NetRosterUpsert, NetRosterRemove, NetLookup,
NetActiveList, NetActivate, NetDeactivate, NetUpdateActive, NetActiveList, NetActivate, NetDeactivate, NetUpdateActive, NetDiscardActive,
} from '@/../wailsjs/go/main/App'; } from '@/../wailsjs/go/main/App';
import { netctl } from '@/../wailsjs/go/models'; import { netctl } from '@/../wailsjs/go/models';
@@ -45,10 +47,6 @@ const hamlogTheme = themeQuartz.withParams({
type Net = netctl.Net; type Net = netctl.Net;
type Station = netctl.Station; 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 { function fmtTimeOn(s: any): string {
if (!s) return ''; if (!s) return '';
@@ -60,14 +58,24 @@ function fmtTimeOn(s: any): string {
const emptyStation = (): Station => netctl.Station.createFrom({ callsign: '' }); const emptyStation = (): Station => netctl.Station.createFrom({ callsign: '' });
export function NetControlPanel({ onLogged, rstChoices }: { onLogged?: () => void; rstChoices?: string[] }) { type Props = {
onLogged?: () => void;
countries?: string[];
bands?: string[];
modes?: string[];
};
export function NetControlPanel({ onLogged, countries, bands, modes }: Props) {
const [nets, setNets] = useState<Net[]>([]); const [nets, setNets] = useState<Net[]>([]);
const [selId, setSelId] = useState<string>(''); const [selId, setSelId] = useState<string>('');
const [openId, setOpenId] = useState<string>(''); const [openId, setOpenId] = useState<string>('');
const [roster, setRoster] = useState<Station[]>([]); const [roster, setRoster] = useState<Station[]>([]);
const [active, setActive] = useState<Active[]>([]); const [active, setActive] = useState<QSOForm[]>([]);
const [error, setError] = useState(''); const [error, setError] = useState('');
// Full-QSO edit modal for an on-air draft (same one Recent QSOs uses).
const [editingDraft, setEditingDraft] = useState<QSOForm | null>(null);
// Add/edit-contact dialog. // Add/edit-contact dialog.
const [contactOpen, setContactOpen] = useState(false); const [contactOpen, setContactOpen] = useState(false);
const [contact, setContact] = useState<Station>(emptyStation()); const [contact, setContact] = useState<Station>(emptyStation());
@@ -95,7 +103,7 @@ export function NetControlPanel({ onLogged, rstChoices }: { onLogged?: () => voi
}, []); }, []);
const refreshActive = useCallback(async () => { const refreshActive = useCallback(async () => {
try { setActive(((await NetActiveList()) ?? []) as Active[]); } try { setActive(((await NetActiveList()) ?? []) as unknown as QSOForm[]); }
catch { /* ignore */ } catch { /* ignore */ }
}, []); }, []);
@@ -103,9 +111,12 @@ export function NetControlPanel({ onLogged, rstChoices }: { onLogged?: () => voi
useEffect(() => { refreshRoster(selId); }, [selId, refreshRoster]); useEffect(() => { refreshRoster(selId); }, [selId, refreshRoster]);
useEffect(() => { if (isOpen) refreshActive(); else setActive([]); }, [isOpen, refreshActive]); useEffect(() => { if (isOpen) refreshActive(); else setActive([]); }, [isOpen, refreshActive]);
// The roster side hides callsigns that are currently on the air (they live in // The roster side hides callsigns currently on the air (they live in the left
// the left grid until logged), mirroring Log4OM's two-list behaviour. // grid until logged), mirroring Log4OM's two-list behaviour.
const activeCalls = useMemo(() => new Set(active.map((a) => a.callsign.toUpperCase())), [active]); const activeCalls = useMemo(
() => new Set(active.map((a) => (a.callsign ?? '').toUpperCase())),
[active],
);
const rosterShown = useMemo( const rosterShown = useMemo(
() => roster.filter((s) => !activeCalls.has((s.callsign ?? '').toUpperCase())), () => roster.filter((s) => !activeCalls.has((s.callsign ?? '').toUpperCase())),
[roster, activeCalls], [roster, activeCalls],
@@ -151,28 +162,20 @@ export function NetControlPanel({ onLogged, rstChoices }: { onLogged?: () => voi
catch (e: any) { setError(String(e?.message ?? e)); } catch (e: any) { setError(String(e?.message ?? e)); }
} }
// Active → logged (end QSO, removed from session, written to the logbook). // Active → logged (end QSO, removed from session, written to the logbook).
async function deactivate(call: string) { async function deactivate(id?: number) {
if (!call) return; if (id == null) return;
try { await NetDeactivate(call); await refreshActive(); onLogged?.(); } try { await NetDeactivate(id); await refreshActive(); onLogged?.(); }
catch (e: any) { setError(String(e?.message ?? e)); } catch (e: any) { setError(String(e?.message ?? e)); }
} }
function onActiveDblClick(e: RowDoubleClickedEvent<Active>) { // Edit-modal handlers (operate on the in-memory draft, not the DB).
// Double-clicking a non-editable area logs the QSO; editable cells open the async function saveDraft(q: QSOForm) {
// editor instead (ag-grid handles that before this fires only for blanks), try { await NetUpdateActive(q as any); setEditingDraft(null); await refreshActive(); }
// so we gate on the column being the callsign. catch (e: any) { setError(String(e?.message ?? e)); }
if (e.data && (e as any).column?.getColId?.() === 'callsign') deactivate(e.data.callsign);
} }
async function discardDraft(id: number) {
async function onActiveCellChanged(e: CellValueChangedEvent<Active>) { try { await NetDiscardActive(id); setEditingDraft(null); await refreshActive(); }
const d = e.data; catch (e: any) { setError(String(e?.message ?? e)); }
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. // Add-contact dialog.
@@ -210,22 +213,17 @@ export function NetControlPanel({ onLogged, rstChoices }: { onLogged?: () => voi
} catch (e: any) { setError(String(e?.message ?? e)); } } catch (e: any) { setError(String(e?.message ?? e)); }
} }
const activeCols = useMemo<ColDef<Active>[]>(() => [ const activeCols = useMemo<ColDef<QSOForm>[]>(() => [
{ colId: 'callsign', headerName: 'Callsign', field: 'callsign', width: 110, cellClass: 'font-mono font-semibold' }, { headerName: 'Callsign', field: 'callsign', width: 110, cellClass: 'font-mono font-semibold' },
{ headerName: 'Name', field: 'name', flex: 1, editable: true }, { headerName: 'Name', field: 'name', flex: 1 },
{ headerName: 'QTH', field: 'qth', flex: 1, editable: true }, { headerName: 'QTH', field: 'qth', flex: 1 },
{ headerName: 'Time on', valueGetter: (p) => fmtTimeOn(p.data?.time_on), width: 90, cellClass: 'font-mono text-[11px]' }, { headerName: 'Time on', valueGetter: (p) => fmtTimeOn((p.data as any)?.qso_date), width: 90, cellClass: 'font-mono text-[11px]' },
{ headerName: 'Country', field: 'country', width: 120 }, { headerName: 'Band', field: 'band', width: 70, cellClass: 'font-mono' },
{ { headerName: 'Mode', field: 'mode', width: 70, cellClass: 'font-mono' },
headerName: 'RST S', field: 'rst_sent', width: 80, editable: true, cellClass: 'font-mono', { headerName: 'RST S', field: 'rst_sent', width: 70, cellClass: 'font-mono' },
cellEditor: 'agSelectCellEditor', cellEditorParams: { values: rstChoices ?? [] }, { headerName: 'RST R', field: 'rst_rcvd', width: 70, cellClass: 'font-mono' },
}, { headerName: 'Comment', field: 'comment', flex: 1.5 },
{ ], []);
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>[]>(() => [ const rosterCols = useMemo<ColDef<Station>[]>(() => [
{ headerName: 'Callsign', field: 'callsign', width: 110, cellClass: 'font-mono font-semibold' }, { headerName: 'Callsign', field: 'callsign', width: 110, cellClass: 'font-mono font-semibold' },
@@ -275,28 +273,27 @@ export function NetControlPanel({ onLogged, rstChoices }: { onLogged?: () => voi
<div className="flex flex-col min-h-0 flex-1 border-r border-border/60"> <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"> <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 On air active QSOs
<span className="ml-auto font-normal normal-case opacity-90">double-click callsign log &amp; end QSO</span> <span className="ml-auto font-normal normal-case opacity-90">double-click edit all fields · "Log &amp; end" to save</span>
</div> </div>
<div style={{ flex: 1, minHeight: 0, position: 'relative' }}> <div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
<div style={{ position: 'absolute', inset: 0 }}> <div style={{ position: 'absolute', inset: 0 }}>
<AgGridReact<Active> <AgGridReact<QSOForm>
ref={activeGrid} ref={activeGrid}
theme={hamlogTheme} theme={hamlogTheme}
rowData={active} rowData={active}
columnDefs={activeCols} columnDefs={activeCols}
defaultColDef={defaultColDef} defaultColDef={defaultColDef}
onRowDoubleClicked={onActiveDblClick} onRowDoubleClicked={(e) => e.data && setEditingDraft(e.data)}
onCellValueChanged={onActiveCellChanged} rowSelection={{ mode: 'singleRow', checkboxes: false, enableClickSelection: true }}
animateRows={false} animateRows={false}
getRowId={(p) => String((p.data as any).callsign)} getRowId={(p) => String((p.data as any).id)}
stopEditingWhenCellsLoseFocus
/> />
</div> </div>
</div> </div>
{isOpen && active.length > 0 && ( {isOpen && active.length > 0 && (
<div className="px-3 py-1.5 border-t border-border/60 bg-muted/30 flex items-center gap-2"> <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]" <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); }}> onClick={() => { const r = activeGrid.current?.api?.getSelectedRows?.()?.[0] as QSOForm; if (r) deactivate(r.id as number); }}>
<MinusCircle className="size-3.5" /> Log &amp; end selected <MinusCircle className="size-3.5" /> Log &amp; end selected
</Button> </Button>
</div> </div>
@@ -341,6 +338,20 @@ export function NetControlPanel({ onLogged, rstChoices }: { onLogged?: () => voi
</div> </div>
</div> </div>
{/* Full-QSO edit modal for the selected on-air draft. Save writes back to
the in-memory draft (NetUpdateActive); Delete cancels it (no log). */}
{editingDraft && (
<QSOEditModal
qso={editingDraft}
onSave={saveDraft}
onDelete={discardDraft}
onClose={() => setEditingDraft(null)}
countries={countries}
bands={bands}
modes={modes}
/>
)}
{/* Add / edit contact dialog */} {/* Add / edit contact dialog */}
<Dialog open={contactOpen} onOpenChange={setContactOpen}> <Dialog open={contactOpen} onOpenChange={setContactOpen}>
<DialogContent className="max-w-md"> <DialogContent className="max-w-md">
+6 -4
View File
@@ -372,18 +372,20 @@ export function LookupCallsign(arg1:string):Promise<lookup.Result>;
export function MoveDatabase(arg1:string):Promise<void>; export function MoveDatabase(arg1:string):Promise<void>;
export function NetActivate(arg1:string):Promise<main.netActiveEntry>; export function NetActivate(arg1:string):Promise<qso.QSO>;
export function NetActiveList():Promise<Array<main.netActiveEntry>>; export function NetActiveList():Promise<Array<qso.QSO>>;
export function NetClose():Promise<void>; export function NetClose():Promise<void>;
export function NetCreate(arg1:string):Promise<netctl.Net>; export function NetCreate(arg1:string):Promise<netctl.Net>;
export function NetDeactivate(arg1:string):Promise<number>; export function NetDeactivate(arg1:number):Promise<number>;
export function NetDelete(arg1:string):Promise<void>; export function NetDelete(arg1:string):Promise<void>;
export function NetDiscardActive(arg1:number):Promise<void>;
export function NetList():Promise<Array<netctl.Net>>; export function NetList():Promise<Array<netctl.Net>>;
export function NetLookup(arg1:string):Promise<netctl.Station>; export function NetLookup(arg1:string):Promise<netctl.Station>;
@@ -402,7 +404,7 @@ 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 NetSetDefaults(arg1:string,arg2:string,arg3:string,arg4:string):Promise<void>;
export function NetUpdateActive(arg1:main.netActiveEntry):Promise<void>; export function NetUpdateActive(arg1:qso.QSO):Promise<void>;
export function OpenADIFFile():Promise<string>; export function OpenADIFFile():Promise<string>;
+4
View File
@@ -734,6 +734,10 @@ export function NetDelete(arg1) {
return window['go']['main']['App']['NetDelete'](arg1); return window['go']['main']['App']['NetDelete'](arg1);
} }
export function NetDiscardActive(arg1) {
return window['go']['main']['App']['NetDiscardActive'](arg1);
}
export function NetList() { export function NetList() {
return window['go']['main']['App']['NetList'](); return window['go']['main']['App']['NetList']();
} }
-45
View File
@@ -2004,51 +2004,6 @@ export namespace main {
return a; 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;
}
}
} }