feat(editor): a modem offers a code or joins one, and says under it how it stands (K4, K5)
The modem's sheet chooses how it finds its partner: the other modem on its port in this project, as FILIUS' file pairs them; another project's, by offering this modem's code (shown large, with a copy button) or by joining a code typed in as one reads it off the board. In the action mode each modem says under it how it stands — connected, waiting, taken, no second modem on the port, cable missing, on the server only. Accepted in three browsers on a real F, each in a copy of FILIUS' modem example: Ben's notebook pings the computer behind Mia's access point; Rita joins the same code, reads that it is taken, and gets it once Ben stops. FILIUS' modem example runs in the page's own room as well. The .fls export writes an offering modem as Java's server and a joining one as its client; the code has no place there and is named as lost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -107,9 +107,22 @@ export interface CanvasProps {
|
||||
readonly renderDocText?: (item: DocumentationView, frame: ScreenFrame) => React.ReactNode;
|
||||
/** The cables that carry a frame now, drawn as Java draws them then (JCablePanel.farbeBlinken). */
|
||||
readonly activeLinks?: ReadonlySet<string>;
|
||||
/** A line under a device's label, by node (how a modem stands while the simulation runs). */
|
||||
readonly notes?: ReadonlyMap<string, DeviceNote>;
|
||||
}
|
||||
|
||||
/** A line under a device's label: its text, and whether it is good news, a wait or trouble. */
|
||||
export interface DeviceNote {
|
||||
readonly text: string;
|
||||
readonly tone: 'good' | 'waiting' | 'trouble';
|
||||
}
|
||||
|
||||
const NOTE_COLORS: Readonly<Record<DeviceNote['tone'], string>> = { good: 'var(--accent)', waiting: 'var(--text-secondary)', trouble: 'var(--danger)' };
|
||||
/** How far the note sits below the label's baseline. */
|
||||
const NOTE_LINE = 14;
|
||||
|
||||
const NO_LINKS: ReadonlySet<string> = new Set();
|
||||
const NO_NOTES: ReadonlyMap<string, DeviceNote> = new Map();
|
||||
const NO_CHOICES: ReadonlyMap<string, readonly string[]> = new Map();
|
||||
const NO_COLORS: readonly string[] = [];
|
||||
|
||||
@@ -195,6 +208,7 @@ function Device({
|
||||
selected,
|
||||
pending,
|
||||
chosenBy,
|
||||
note,
|
||||
}: {
|
||||
readonly node: NodeView;
|
||||
readonly box: DeviceBox;
|
||||
@@ -203,6 +217,7 @@ function Device({
|
||||
readonly pending: boolean;
|
||||
/** The colours of the others who have chosen it, each a frame further out. */
|
||||
readonly chosenBy: readonly string[];
|
||||
readonly note: DeviceNote | undefined;
|
||||
}) {
|
||||
return (
|
||||
<g
|
||||
@@ -211,7 +226,7 @@ function Device({
|
||||
data-box={`${box.x} ${box.y} ${box.width} ${box.height}`}
|
||||
data-selected={selected ? 'true' : undefined}
|
||||
role="img"
|
||||
aria-label={`${kindName}: ${node.label}`}
|
||||
aria-label={note === undefined ? `${kindName}: ${node.label}` : `${kindName}: ${node.label} – ${note.text}`}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{/* The whole button takes the pointer, as Java's label does. */}
|
||||
@@ -249,6 +264,11 @@ function Device({
|
||||
<text x={box.label.x} y={box.label.y} textAnchor="middle" style={{ font: JAVA_LABEL_FONT, fill: 'var(--canvas-label)' }}>
|
||||
{node.label}
|
||||
</text>
|
||||
{note === undefined ? null : (
|
||||
<text data-note={note.tone} x={box.label.x} y={box.label.y + NOTE_LINE} textAnchor="middle" style={{ font: JAVA_LABEL_FONT, fill: NOTE_COLORS[note.tone] }}>
|
||||
{note.text}
|
||||
</text>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
@@ -288,6 +308,7 @@ export const Canvas = forwardRef<CanvasHandle, CanvasProps>(function Canvas(
|
||||
locked = false,
|
||||
chosenBy = NO_CHOICES,
|
||||
activeLinks = NO_LINKS,
|
||||
notes = NO_NOTES,
|
||||
onDocFrame,
|
||||
renderDocText,
|
||||
},
|
||||
@@ -741,6 +762,7 @@ export const Canvas = forwardRef<CanvasHandle, CanvasProps>(function Canvas(
|
||||
selected={selection?.kind === 'device' && selection.id === node.id}
|
||||
chosenBy={chosenBy.get(node.id) ?? NO_COLORS}
|
||||
pending={cableFrom?.id === node.id}
|
||||
note={notes.get(node.id)}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
|
||||
Reference in New Issue
Block a user