feat(editor): F4.5b the device sheet for routers and home routers

Measured first: SheetRun now types into FILIUS' router and home router
panels, records five forwarding tables (holeTabelle) and steps in the
panel's table. Java turns a card address that passes the pattern but not
IP.ipCheck (".0.0.1") into null and the router panel throws; an empty
gateway is marked red yet taken; a refused table cell is stored as null;
a home router's DHCP client leaves its DHCP server running. None of the
nulls is adopted. The core's routeTableRows matches all five tables.

- doc: card addresses (the first card mirrored in the device's settings),
  router gateway, RIP, manual routes (a new row once all four cells pass,
  a cell, removal); the sheet reads cards with what their cable joins and
  the whole table
- editor: Java's tabs (general, each card titled by its address, the
  forwarding table greyed out under RIP; general, LAN, WAN on a home
  router); the open card's cable drawn active in Java's green

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 11:51:03 +00:00
co-authored by Claude Opus 5.5
parent 9b7ec2fc72
commit eff9433673
22 changed files with 2201 additions and 119 deletions
+19 -19
View File
@@ -55,8 +55,10 @@ export interface TopologyView {
const settingsOf = (node: Y.Map<unknown>) => node.get('settings') as Y.Map<Y.Map<unknown>> | undefined;
/** Host.holeAnzeigeName: a PC's or notebook's address and MAC address, as its label policy asks. */
function labelOf(node: Y.Map<unknown>, kind: NodeKind, name: string): string {
/** Host.holeAnzeigeName: a PC's or notebook's address and MAC address, as its label policy asks; any other device's name. */
export function nodeLabel(node: Y.Map<unknown>): string {
const kind = node.get('kind') as NodeKind;
const name = node.get('name') as string;
const policy = node.get('labelPolicy') as HostLabelPolicy;
if ((kind !== 'pc' && kind !== 'notebook') || policy === 'manual') return name;
const ip = (settingsOf(node)?.get('device')?.get('ipAddress') as string | undefined) ?? '';
@@ -80,7 +82,7 @@ export function topologyView(doc: Y.Doc): TopologyView {
id,
kind,
name,
label: labelOf(node, kind, name),
label: nodeLabel(node),
x: node.get('x') as number,
y: node.get('y') as number,
cloud: kind === 'networkSwitch' && settingsOf(node)?.get('switch')?.get('showsAsCloud') === true,
@@ -94,22 +96,20 @@ export function topologyView(doc: Y.Doc): TopologyView {
const targetNode = link.get('targetNode') as string;
return { id, sourceNode, sourcePort: link.get('sourcePort') as string, targetNode, targetPort: link.get('targetPort') as string, wireless: wireless(sourceNode, targetNode) };
});
const docs = [...doc.getMap<Y.Map<unknown>>(ROOTS.docs).entries()].map(
([id, item]): DocumentationView => ({
id,
kind: item.get('kind') as DocumentationItemKind,
x: item.get('x') as number,
y: item.get('y') as number,
width: item.get('width') as number,
height: item.get('height') as number,
text: (item.get('text') as Y.Text).toString(),
color: { red: item.get('red') as number, green: item.get('green') as number, blue: item.get('blue') as number, alpha: item.get('alpha') as number },
font: item.get('font') as string,
size: item.get('size') as number,
bold: item.get('bold') as boolean,
order: item.get('order') as number,
}),
);
const docs = [...doc.getMap<Y.Map<unknown>>(ROOTS.docs).entries()].map(([id, item]): DocumentationView => ({
id,
kind: item.get('kind') as DocumentationItemKind,
x: item.get('x') as number,
y: item.get('y') as number,
width: item.get('width') as number,
height: item.get('height') as number,
text: (item.get('text') as Y.Text).toString(),
color: { red: item.get('red') as number, green: item.get('green') as number, blue: item.get('blue') as number, alpha: item.get('alpha') as number },
font: item.get('font') as string,
size: item.get('size') as number,
bold: item.get('bold') as boolean,
order: item.get('order') as number,
}));
// The model's render order (inDeterministicRenderOrder): rectangles below texts, then by order, then by id.
const layer = (item: DocumentationView) => (item.kind === 'rectangle' ? 0 : 1);
docs.sort((left, right) => layer(left) - layer(right) || left.order - right.order || compareIds(left.id, right.id));