feat(cluster): start from a known node instead of a blank form
Setting up a telnet cluster is the step operators get stuck on. The address and the port are two pieces of information nobody has to hand, and a typo in either looks exactly like a node that is down — there is nothing to read, only a connection that never comes. So the editor offers a list, and choosing one fills the fields. Six to begin with: F4BPO, DXFun, SOTA, POTA, and the two Reverse Beacon feeds — listed separately and named for what they carry, because they are one network on two ports where 7000 is CW and RTTY and 7001 is FT8 and FT4, and no amount of trying will tell you which is which. The preset then gets out of the way. Everything stays editable, the name is only filled when the operator has not chosen one of their own, and a node typed in by hand behaves exactly the same. Reopening a node created from a preset shows it selected, so the list also answers "which one is this".
This commit is contained in:
@@ -94,6 +94,7 @@ import { OperatingPanel } from '@/components/OperatingPanel';
|
||||
import { AppearancePanel } from '@/components/AppearancePanel';
|
||||
import { UDPIntegrationsPanel } from '@/components/UDPIntegrationsPanel';
|
||||
import { loadClusterMacros, saveClusterMacros, type ClusterMacro } from '@/lib/clusterMacros';
|
||||
import { CLUSTER_PRESETS } from '@/lib/clusterPresets';
|
||||
|
||||
type LookupSettings = LookupSettingsForm;
|
||||
type StationSettings = StationSettingsForm;
|
||||
@@ -8946,8 +8947,13 @@ interface ClusterEditorProps {
|
||||
}
|
||||
|
||||
function ClusterServerEditor({ value, onCancel, onSave }: ClusterEditorProps) {
|
||||
const { t } = useI18n();
|
||||
const [s, setS] = useState(value);
|
||||
const update = (patch: Partial<typeof s>) => setS((cur) => ({ ...cur, ...patch }));
|
||||
// Which preset the fields currently match, so reopening a node created from
|
||||
// one shows it selected rather than blank.
|
||||
const presetIdx = CLUSTER_PRESETS.findIndex(
|
||||
(p) => p.host.toLowerCase() === (s.host ?? '').trim().toLowerCase() && p.port === s.port);
|
||||
return (
|
||||
<Dialog open onOpenChange={(o) => { if (!o) onCancel(); }}>
|
||||
<DialogContent className="max-w-[640px] px-6">
|
||||
@@ -8958,6 +8964,39 @@ function ClusterServerEditor({ value, onCancel, onSave }: ClusterEditorProps) {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-3 py-2 px-2">
|
||||
{/* Start from a node that is known to work. The host and the port are
|
||||
two pieces of information nobody has to hand, and a typo in either
|
||||
looks exactly like a node that is down. Everything stays editable
|
||||
afterwards. */}
|
||||
<div className="space-y-1 col-span-2">
|
||||
<Label>{t('clu.preset')}</Label>
|
||||
<Select
|
||||
value={presetIdx >= 0 ? String(presetIdx) : '_'}
|
||||
onValueChange={(v) => {
|
||||
const p = CLUSTER_PRESETS[Number(v)];
|
||||
if (!p) return;
|
||||
update({
|
||||
host: p.host, port: p.port,
|
||||
// The name is only filled when the operator has not chosen one
|
||||
// of their own: renaming a node is the first thing anybody with
|
||||
// two of them does.
|
||||
name: s.name.trim() ? s.name : p.name,
|
||||
init_commands: s.init_commands?.trim() ? s.init_commands : (p.init ?? ''),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9"><SelectValue placeholder={t('clu.presetPick')} /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="_" disabled>{t('clu.presetPick')}</SelectItem>
|
||||
{CLUSTER_PRESETS.map((p, i) => (
|
||||
<SelectItem key={`${p.host}:${p.port}`} value={String(i)}>
|
||||
{p.name} — {p.about}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-[11px] text-muted-foreground">{t('clu.presetHint')}</p>
|
||||
</div>
|
||||
<div className="space-y-1 col-span-2">
|
||||
<Label>Display name</Label>
|
||||
<Input autoFocus value={s.name} onChange={(e) => update({ name: e.target.value })} placeholder="VE7CC, F4BPO home…" />
|
||||
|
||||
Reference in New Issue
Block a user