// The device sheet (plan §7.1): the settings of the selected device, the // fields of FILIUS' settings panel of its kind (JHostKonfiguration, // JSwitchKonfiguration, JModemKonfiguration; a router's and a home router's // in RouterSheets.tsx) in the panels' order. Java shows the panel when a // device is chosen; so does the sheet, beside the surface or below it on a // phone. Each change is one step of the person's undo. import { useEffect, useState } from 'react'; import type * as Y from 'yjs'; import { deviceSheet, setDhcpClient, setHostAddress, setHostLabels, setHostWireless, setIpForwarding, setModemPort, setSwitchCloud, setSwitchRetention, setSwitchSsid, type CheckedField, type DeviceSheet as Sheet, type HostSheet, type ModemSheet, type SwitchSheet, type WirelessError, } from '@mdg-filius/doc'; import { iconOf } from '../canvas/layout.js'; import { ICON_URLS } from '../canvas/icons.js'; import { useTranslate } from '../i18n/i18n.js'; import { NameField, type Change } from './NameField.js'; import { HomeRouterFields, RouterFields } from './RouterSheets.js'; import { DhcpServerPanel, PortForwardingPanel } from './ServicePanels.js'; import { SheetCheck, SheetField } from './SheetField.js'; import './sheet.css'; /** The device's sheet, read anew after every change of the document. */ function useDeviceSheet(doc: Y.Doc, id: string): Sheet | undefined { const [sheet, setSheet] = useState(() => deviceSheet(doc, id)); useEffect(() => { setSheet(deviceSheet(doc, id)); const update = () => setSheet(deviceSheet(doc, id)); doc.on('update', update); return () => doc.off('update', update); }, [doc, id]); return sheet; } export interface DeviceSheetProps { readonly doc: Y.Doc; readonly id: string; /** What the surface writes under the icon (a host's address or MAC address, when it shows them). */ readonly label: string; readonly origin: object; /** After each change: ends the undo step. */ readonly onChanged: () => void; readonly onMessage: (message: string) => void; readonly onClose: () => void; /** The cable to draw active (a router's card whose tab is open), or none. */ readonly onHighlight: (link: string | null) => void; } /** A page of the sheet that Java opens as a dialog. */ export type SheetPage = 'dhcp' | 'forwarding'; export function DeviceSheet({ doc, id, label, origin, onChanged, onMessage, onClose, onHighlight }: DeviceSheetProps) { const t = useTranslate(); const sheet = useDeviceSheet(doc, id); // The page open, if any; the editor makes a sheet per device, so another device starts at its fields. const [open, setOpen] = useState(null); if (sheet === undefined) return null; const onOpen = (each: SheetPage) => setOpen(each); const change: Change = { doc, id, origin, run: (each) => { const result = each(); onChanged(); return result; }, }; const title = t(`device.${sheet.kind}`); return ( ); } function HostFields({ sheet, label, change, onMessage, onOpen, }: { readonly sheet: HostSheet; readonly label: string; readonly change: Change; readonly onMessage: (message: string) => void; readonly onOpen: (page: SheetPage) => void; }) { const t = useTranslate(); const address = (field: 'ipAddress' | 'subnetMask' | 'defaultGateway' | 'dnsServer') => (text: string): CheckedField => change.run(() => setHostAddress(change.doc, change.id, field, text, change.origin)); // Java's list offers the switches' networks; an SSID none of them has shows as "please choose" (JHostKonfiguration.updateSsid). const chosen = sheet.networks.includes(sheet.ssid) ? sheet.ssid : ''; const wireless = (on: boolean, ssid: string) => { const result = change.run(() => setHostWireless(change.doc, change.id, on, ssid, crypto.randomUUID(), change.origin)); if ('error' in result) onMessage(wirelessMessage(t, result.error)); }; const labelled = sheet.labels.ipAddress || sheet.labels.macAddress; return ( <> {/* Java's name field shows what the surface writes, and is closed while that is the address. */} ({ invalid: true })} />
change.run(() => setHostLabels(change.doc, change.id, { ...sheet.labels, ipAddress: on }, change.origin))} /> change.run(() => setHostLabels(change.doc, change.id, { ...sheet.labels, macAddress: on }, change.origin))} /> change.run(() => setIpForwarding(change.doc, change.id, on, change.origin))} /> change.run(() => setDhcpClient(change.doc, change.id, on, change.origin))} /> {/* Java closes the server's dialog while DHCP configures the card. */}
); } function wirelessMessage(t: ReturnType, error: WirelessError): string { return error === 'cablePlugged' ? t('sheet.cablePlugged') : t(`cable.${error}`); } function SwitchFields({ sheet, change }: { readonly sheet: SwitchSheet; readonly change: Change }) { const t = useTranslate(); return ( <> change.run(() => setSwitchSsid(change.doc, change.id, text, change.origin))} /> change.run(() => setSwitchCloud(change.doc, change.id, on, change.origin))} /> change.run(() => setSwitchRetention(change.doc, change.id, text, change.origin))} /> ); } function ModemFields({ sheet, change }: { readonly sheet: ModemSheet; readonly change: Change }) { const t = useTranslate(); return ( <> change.run(() => setModemPort(change.doc, change.id, text, change.origin))} /> ); }