SheetRun now clicks through Java's JConnectionsDialog with a router, a computer and a switch cabled in the GUIContainer: a click marks a card, the same click unmarks it, another card swaps the cables or moves the one; "+" adds 192.168.0.10/24 up to eight cards, "-" removes the last card with its cable down to none, and a router without a card keeps no gateway. The document adds, removes and swaps a router's cards (the same cables, the same devices), Java's click logic is one function for the test and the page, and the sheet names the address at a cable's far end. The page "Verbindungen verwalten" shows Java's RJ45 buttons in Java's colours with each card's far end below. Not adopted: the last card stays. A card tab or a mark whose card is undone away falls back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
// A text field of the device sheet. Java takes a field's text when it loses
|
|
// the focus or Enter is pressed, and colours a text its rule refuses red
|
|
// while it is typed. A refused text stays here, marked, with what the device
|
|
// keeps meanwhile, instead of being replaced without a word as Java does;
|
|
// Escape gives it up. A value the device already holds that the rule
|
|
// refuses (from a file, or written by someone else) is marked, never mended
|
|
// (plan I6).
|
|
|
|
import { useId, useState, type KeyboardEvent } from 'react';
|
|
import { checkField, type CheckedField, type FieldRule } from '@mdg-filius/doc';
|
|
import { useTranslate } from '../i18n/i18n.js';
|
|
|
|
export interface SheetFieldProps {
|
|
readonly label: string;
|
|
/** What the device holds. */
|
|
readonly value: string;
|
|
readonly rule: FieldRule;
|
|
/** Gives the text to the device; says whether it was taken. */
|
|
readonly onCommit: (text: string) => CheckedField;
|
|
readonly disabled?: boolean;
|
|
readonly readOnly?: boolean;
|
|
/** A name for the browser tests and the label's input mode. */
|
|
readonly name: string;
|
|
readonly inputMode?: 'text' | 'decimal' | 'numeric';
|
|
/** A field still to be filled (a new row's cell): empty and untouched, it is not marked. */
|
|
readonly unfilled?: boolean;
|
|
}
|
|
|
|
export function SheetField({ label, value, rule, onCommit, disabled = false, readOnly = false, name, inputMode = 'text', unfilled = false }: SheetFieldProps) {
|
|
const t = useTranslate();
|
|
const id = useId();
|
|
// What is typed and not yet taken; null shows the device's value.
|
|
const [draft, setDraft] = useState<string | null>(null);
|
|
const [refused, setRefused] = useState(false);
|
|
const shown = draft ?? value;
|
|
const marked = !readOnly && !(unfilled && draft === null && shown === '') && 'invalid' in checkField(rule, shown);
|
|
|
|
const commit = () => {
|
|
if (draft === null || readOnly) return;
|
|
if (draft === value) {
|
|
setDraft(null);
|
|
setRefused(false);
|
|
return;
|
|
}
|
|
const result = onCommit(draft);
|
|
if ('value' in result) {
|
|
setDraft(null);
|
|
setRefused(false);
|
|
} else {
|
|
setRefused(true);
|
|
}
|
|
};
|
|
|
|
const key = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
if (event.key === 'Enter') commit();
|
|
if (event.key === 'Escape') {
|
|
setDraft(null);
|
|
setRefused(false);
|
|
}
|
|
};
|
|
|
|
const hint = refused && draft !== null ? t('sheet.notTaken', { value: value === '' ? t('sheet.empty') : value }) : draft === null && marked ? t('sheet.invalidValue') : null;
|
|
return (
|
|
<label className="sheet-field" htmlFor={id}>
|
|
<span>{label}</span>
|
|
<input
|
|
id={id}
|
|
name={name}
|
|
value={shown}
|
|
disabled={disabled}
|
|
readOnly={readOnly}
|
|
inputMode={inputMode}
|
|
autoComplete="off"
|
|
spellCheck={false}
|
|
aria-invalid={marked}
|
|
aria-describedby={hint === null ? undefined : `${id}-hint`}
|
|
onChange={(event) => {
|
|
setDraft(event.target.value);
|
|
setRefused(false);
|
|
}}
|
|
onBlur={commit}
|
|
onKeyDown={key}
|
|
/>
|
|
{hint === null ? null : (
|
|
<span id={`${id}-hint`} className="sheet-hint" role="status">
|
|
{hint}
|
|
</span>
|
|
)}
|
|
</label>
|
|
);
|
|
}
|
|
|
|
export function SheetCheck({
|
|
label,
|
|
checked,
|
|
onChange,
|
|
disabled = false,
|
|
name,
|
|
}: {
|
|
readonly label: string;
|
|
readonly checked: boolean;
|
|
readonly onChange: (checked: boolean) => void;
|
|
readonly disabled?: boolean;
|
|
readonly name: string;
|
|
}) {
|
|
return (
|
|
<label className={disabled ? 'sheet-check disabled' : 'sheet-check'}>
|
|
<input type="checkbox" name={name} checked={checked} disabled={disabled} onChange={(event) => onChange(event.target.checked)} />
|
|
{label}
|
|
</label>
|
|
);
|
|
}
|
|
|
|
/** A text with Java's line breaks as paragraphs. */
|
|
export function SheetNote({ text }: { readonly text: string }) {
|
|
return (
|
|
<>
|
|
{text.split('\n').map((line, index) => (
|
|
<p key={index} className="sheet-note">
|
|
{line}
|
|
</p>
|
|
))}
|
|
</>
|
|
);
|
|
}
|