Initial codebase: Go + Wails amateur radio logbook
Backend (Go 1.25 / Wails v2): - QSO storage on SQLite (modernc) with embedded migrations (0001..0005) - Streaming ADIF import (batch insert) + WorkedBefore per callsign and DXCC - Callsign lookup with QRZ.com + HamQTH providers (primary/failsafe routing) and SQLite-backed TTL cache - DXCC resolver from cty.dat (auto-download, longest-prefix-match) - Multi-profile operator identities (home/portable/SOTA/contest) — every QSO stamps MY_* from the active profile - CAT control via OmniRig COM on a single OS-locked goroutine, with bidirectional sync (freq/mode/band/split/VFOs) and Rig1/Rig2 hot-swap - Settings store (key/value), CAT debug log at %APPDATA%/HamLog/cat.log Frontend (React 18 + TypeScript + Tailwind v4 + shadcn-style): - Single-row entry strip with CAT-aware band/mode/freq, RST, Start/End UTC, per-field locks (band/mode/freq/start/end) for backdated QSOs - Topbar: live freq (MHz.kHz.Hz dotted), live UTC, band/mode/SPLIT badges, CAT pill with rig selector and clickable Azimuth pill (rotor TODO) - Settings tree: Profiles (Log4OM-style manager), Station Information (edits the active profile), unified Callsign Lookup with Test buttons, Bands/Modes lists, CAT - Worked-before matrix (band × mode × class) with new-DXCC highlighting - ADIF import from menu + Maintenance > Refresh cty.dat Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
DropdownMenu, DropdownMenuTrigger, DropdownMenuContent,
|
||||
DropdownMenuItem, DropdownMenuSeparator, DropdownMenuShortcut,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export type MenuItem =
|
||||
| { type: 'item'; label: string; action: string; shortcut?: string; disabled?: boolean }
|
||||
| { type: 'separator' };
|
||||
|
||||
export interface Menu {
|
||||
name: string;
|
||||
label: string;
|
||||
items: MenuItem[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
menus: Menu[];
|
||||
onAction: (action: string) => void;
|
||||
}
|
||||
|
||||
export function Menubar({ menus, onAction }: Props) {
|
||||
// Track which menu is open so hover-to-switch works like a desktop menubar.
|
||||
const [openMenu, setOpenMenu] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<nav className="flex items-stretch h-full">
|
||||
{menus.map((menu) => (
|
||||
<DropdownMenu
|
||||
key={menu.name}
|
||||
open={openMenu === menu.name}
|
||||
onOpenChange={(o) => setOpenMenu(o ? menu.name : null)}
|
||||
>
|
||||
<DropdownMenuTrigger
|
||||
onMouseEnter={() => {
|
||||
// Only switch on hover if a menu is already open.
|
||||
if (openMenu !== null && openMenu !== menu.name) setOpenMenu(menu.name);
|
||||
}}
|
||||
className={cn(
|
||||
'px-3 text-sm rounded-md text-muted-foreground hover:bg-muted hover:text-foreground transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
||||
openMenu === menu.name && 'bg-muted text-primary',
|
||||
)}
|
||||
>
|
||||
{menu.label}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" sideOffset={4} className="min-w-[240px]">
|
||||
{menu.items.map((item, i) =>
|
||||
item.type === 'separator' ? (
|
||||
<DropdownMenuSeparator key={i} />
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
key={i}
|
||||
disabled={item.disabled}
|
||||
onSelect={() => onAction(item.action)}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
{item.shortcut && <DropdownMenuShortcut>{item.shortcut}</DropdownMenuShortcut>}
|
||||
</DropdownMenuItem>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user