// A computer's Gnutella program, after Java's // GUIApplicationPeerToPeerAnwendungWindow (measured with tools/java-oracle's // GnutellaWindowRun): four tabs with Java's symbols. "Netzwerk": a peer's // address (it starts as Java's, 192.168.0.1; red while it is no address) // with "Netz beitreten", the table of known peers, "Netz verlassen". // "Suche": the words with "Suchen" and "Stopp!", the table of results (a row // chosen), "Herunterladen" and "Liste leeren". "Dateien": the files of // /peer2peer. "Einstellungen": how many peers it keeps at most, with // "Speichern". The program is the room's: the panel shows its view and hands // over what is done. // // Better than Java: // - the tables show what the program keeps at once: Java's results still // show the last search's after a new one until a hit comes, and its files // only what was there when the program last told the window of a change. // - any whole number of peers is taken (Java's check takes one digit only). // - what is not taken is said under its field (Java opens a dialog), and // "Herunterladen" can be pressed only with a result chosen. import { useEffect, useState } from 'react'; import { gnutellaPeerCountOfText, IP_ADDRESS_PATTERN } from '@mdg-filius/core'; import type { GnutellaView } from '@mdg-filius/protocol'; import networkIcon from '../assets/desktop/peertopeer_netzwerk_klein.png'; import searchIcon from '../assets/desktop/peertopeer_suchen_klein.png'; import filesIcon from '../assets/desktop/peertopeer_dateien_klein.png'; import settingsIcon from '../assets/desktop/peertopeer_einstellungen.png'; import { useTranslate } from '../i18n/i18n.js'; import type { Simulation } from '../simulation/useSimulation.js'; type Tab = 'network' | 'search' | 'files' | 'settings'; const TABS: readonly (readonly [Tab, string, string])[] = [ ['network', 'gnutella.networkTab', networkIcon], ['search', 'gnutella.searchTab', searchIcon], ['files', 'gnutella.filesTab', filesIcon], ['settings', 'gnutella.settingsTab', settingsIcon], ]; export function GnutellaPanel({ node, simulation }: { readonly node: string; readonly simulation: Simulation }) { const t = useTranslate(); const topic = `app:${node}/gnutella` as const; const { watch, act } = simulation; useEffect(() => watch([topic]), [watch, topic]); const view = simulation.views.get(topic) as GnutellaView | null | undefined; const [tab, setTab] = useState('network'); const [address, setAddress] = useState('192.168.0.1'); const [criteria, setCriteria] = useState(''); const [chosen, setChosen] = useState(null); const [maximum, setMaximum] = useState(null); const [notice, setNotice] = useState(null); const results = JSON.stringify(view?.results ?? []); // Java's table is filled anew with each change and loses its choice. useEffect(() => setChosen(null), [results]); if (view === null || view === undefined) return
; const addressValid = IP_ADDRESS_PATTERN.test(address); const maximumText = maximum ?? String(view.maximumKnownPeers); const count = gnutellaPeerCountOfText(maximumText); const join = () => { if (!addressValid) { setNotice(t('gnutella.invalidAddress')); return; } act(node, 'gnutella', 'join', { address }); setNotice(null); }; const search = () => act(node, 'gnutella', 'search', { criteria }); const save = () => { if (count === undefined) { setNotice(t('gnutella.onlyNumbers')); return; } act(node, 'gnutella', 'setMaximumPeers', { count }); setMaximum(null); setNotice(null); }; const noticeLine = notice === null ? null : (

{notice}

); return (
{TABS.map(([each, label, icon]) => ( ))}
{tab === 'network' ? (
{noticeLine}

{t('gnutella.neighbours')}

{view.peers.map((peer, index) => ( ))}
{t('gnutella.number')} {t('gnutella.addressColumn')}
{index + 1} {peer}
) : tab === 'search' ? (
{view.results.map((cells, row) => ( setChosen(row)} onKeyDown={(event) => { if (event.key !== 'Enter' && event.key !== ' ') return; event.preventDefault(); setChosen(row); }} > {cells.map((cell, column) => ( ))} ))}
{t('gnutella.resultAddress')} {t('gnutella.resultName')} {t('gnutella.resultSize')}
{cell}
) : tab === 'files' ? (

{t('gnutella.files')}

{view.files.map(([name, type]) => ( ))}
{t('gnutella.fileName')} {t('gnutella.fileType')}
{name} {type}
) : (
{noticeLine}
)}
); }