Files
mdg-filius/packages/doc/src/fields.ts
T
julian.kiedaischandClaude Opus 5.5 9b7ec2fc72 feat(editor): F4.5a the device sheet for PCs, notebooks, switches and modems
Measured first: the new oracle SheetRun types 77 texts into FILIUS' own
settings panels and records what was marked red, what the device took and
what the field showed afterwards. Java's two judgements disagree (red text
taken, black text refused, any int as a modem port, a refused text dropped
without a word); here one rule decides both (packages/doc fields.ts), a
refused text stays marked in the field, and only what Java's model accepts
reaches the document.

- doc: settings.ts (name, label policy, addresses, IP forwarding, DHCP
  stopping the own DHCP server, LAN/WLAN with the wireless connection,
  switch SSID dropping its wireless hosts, retention time, cloud, modem
  port as its pair), sheet.ts (what the sheet shows)
- fls: modemPair/modemPort in one place
- editor: DeviceSheet beside the surface or below it on a phone, opened
  with the selection; every change one undo step
- not adopted from Java: the name overwritten by the MAC address, the
  access point search depending on unrelated devices

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:22:28 +00:00

70 lines
3.5 KiB
TypeScript

// The device sheet's rules for what may be typed into a field: Java's
// EingabenUeberpruefung as FILIUS' settings panels use it
// (JHostKonfiguration, JSwitchKonfiguration, JModemKonfiguration), measured
// with tools/java-oracle's SheetRun (test/sheet.java.json).
//
// Java judges a text twice: while it is typed, the panel colours it red when
// the pattern does not match; when the field loses the focus, the model's
// setter takes it or keeps the old value — and the two disagree. Java takes
// red text ("10..0.0.1", "+10.0.0.1", "-0.0.0.1", Arabic-Indic digits, a
// retention time of "-5"), refuses black text (".0.0.1", a retention time
// past Long's range), and a modem takes any int as its port (0, 65536, -1),
// unchecked. Here one rule decides both: a text is taken exactly when it is
// not marked. What is not taken is not thrown away either (Java puts the old
// value back into the field without a word): the sheet keeps it, marked,
// until it is mended (plan I6).
//
// Spaces around an address or a number are left out (Java refuses
// " 10.0.0.1"; a phone's keyboard puts a space after a word). A name and an
// SSID are taken as typed, spaces included, as Java takes them.
import { IP_ADDRESS_PATTERN, javaIpCheck } from '@mdg-filius/core';
export type FieldRule = 'name' | 'ipAddress' | 'ipAddressOrEmpty' | 'subnetMask' | 'ssid' | 'seconds' | 'port';
/** What a field's text comes to: the value the device takes, or that it takes none. */
export type CheckedField = { readonly value: string } | { readonly invalid: true };
/** EingabenUeberpruefung.musterSubNetz. */
const SUBNET_MASK_PATTERN = /^(0*([1-9]|1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))\.((0*([0-9]|1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))\.){2}(0*([0-9]|1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))$/;
/** EingabenUeberpruefung.musterServiceSetIdentifier: printable ASCII, at least one character. */
const SSID_PATTERN = /^[ -~]+$/;
/** EingabenUeberpruefung.musterPort: 1 to 65535, no leading zero. */
const PORT_PATTERN = /^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{1,4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/;
/** EingabenUeberpruefung.musterNurZahlen, at least one digit (Java's Long.parseLong refuses none). */
const DIGITS_PATTERN = /^[0-9]+$/;
const INVALID: CheckedField = { invalid: true };
/**
* An address as the panel's pattern and the model's IP.ipCheck both accept
* it: neither alone (the pattern lets ".0.0.1" through, ipCheck "10..0.0.1"
* and "+10.0.0.1"); the device keeps ipCheck's form ("010.0.0.1" is 10.0.0.1).
*/
function address(text: string, pattern: RegExp): CheckedField {
const canonical = javaIpCheck(text);
return canonical !== null && pattern.test(text) ? { value: canonical } : INVALID;
}
export function checkField(rule: FieldRule, text: string): CheckedField {
switch (rule) {
case 'name':
return { value: text };
case 'ssid':
return SSID_PATTERN.test(text) ? { value: text } : INVALID;
case 'ipAddress':
return address(text.trim(), IP_ADDRESS_PATTERN);
case 'ipAddressOrEmpty':
return text.trim() === '' ? { value: '' } : address(text.trim(), IP_ADDRESS_PATTERN);
case 'subnetMask':
return address(text.trim(), SUBNET_MASK_PATTERN);
case 'seconds': {
const digits = text.trim();
// In milliseconds the device's number must stay exact.
return DIGITS_PATTERN.test(digits) && Number(digits) * 1000 <= Number.MAX_SAFE_INTEGER ? { value: String(Number(digits)) } : INVALID;
}
case 'port':
return PORT_PATTERN.test(text.trim()) ? { value: text.trim() } : INVALID;
}
}