// A computer's e-mail server, after Java's GUIApplicationEmailServerWindow: // the start button and the mail domain above (the domain only while the // server is off, red while Java's pattern does not take it), then three // pages — a new account, the list of accounts with the number of mails each // holds and the button that removes the one chosen (after Java's question), // and the server's lines. The server is the room's; the panel shows its view // and lines and checks a new account as Java's window does before it hands // it over. // // Better than Java: the new account's name has a field (Java's window has // its label and no field: every account is "Vorname Nachname" — still so // without a name); a user name the server has already is refused (Java's // window says it made it). import { useEffect, useLayoutEffect, useRef, useState, type FormEvent } from 'react'; import { isDomainName, splitName, type EmailServerLogEntry, type LogLine } from '@mdg-filius/core'; import type { EmailServerView } from '@mdg-filius/protocol'; import { useTranslate, type Translate } from '../i18n/i18n.js'; import type { Simulation } from '../simulation/useSimulation.js'; import { emailServerLine } from './mailLines.js'; const NO_LINES: readonly LogLine[] = []; type Page = 'newAccount' | 'accounts' | 'log'; /** What Java's window says to a new account before it hands it over (emailserver_msg25, msg24), and that the server has it already. */ export function newAccountProblem(username: string, password: string, accounts: readonly { readonly username: string }[]): 'missing' | 'whiteSpace' | 'exists' | undefined { if (username === '' || password === '') return 'missing'; if (/[ \t\n\v\f\r]/.test(username)) return 'whiteSpace'; return accounts.some((account) => account.username.toLowerCase() === username.toLowerCase()) ? 'exists' : undefined; } /** * A new account's names from the name typed (the e-mail program's split); * without one, Java's placeholders "Vorname Nachname" (emailserver_msg21, * msg20): its server takes no new account while one has an empty name. */ export function accountNames(name: string, t: Translate): { readonly firstName: string; readonly lastName: string } { const names = splitName(name); return names.firstName === '' && names.lastName === '' ? { firstName: t('emailServer.firstNamePlaceholder'), lastName: t('emailServer.lastNamePlaceholder') } : names; } export function EmailServerPanel({ node, simulation }: { readonly node: string; readonly simulation: Simulation }) { const t = useTranslate(); const topic = `app:${node}/emailServer` as const; const { watch, act } = simulation; useEffect(() => watch([topic]), [watch, topic]); const view = simulation.views.get(topic) as EmailServerView | null | undefined; const lines = (simulation.apps.get(topic)?.lines as readonly LogLine[] | undefined) ?? NO_LINES; const [page, setPage] = useState('newAccount'); const [domain, setDomain] = useState(view?.domain ?? ''); useEffect(() => setDomain(view?.domain ?? ''), [view?.domain]); const [username, setUsername] = useState(''); const [name, setName] = useState(''); const [password, setPassword] = useState(''); const [notice, setNotice] = useState(null); const [chosen, setChosen] = useState(null); const [asking, setAsking] = useState(false); const log = useRef(null); const text = lines.flatMap((line) => { const shown = emailServerLine(line, t); return shown === undefined ? [] : [`${shown}\n`]; }).join(''); useLayoutEffect(() => { const element = log.current; if (element !== null) element.scrollTop = element.scrollHeight; }, [text, page]); if (view === null || view === undefined) return
; const accounts = view.accounts; const chosenAccount = accounts.find((account) => account.username === chosen); const commitDomain = () => { if (domain !== view.domain && !view.active) act(node, 'emailServer', 'setDomain', { domain }); }; const create = (event: FormEvent) => { event.preventDefault(); const problem = newAccountProblem(username, password, accounts); if (problem !== undefined) { setNotice(t(`emailServer.${problem}`, { name: username })); return; } act(node, 'emailServer', 'addAccount', { username, password, ...accountNames(name, t) }); setNotice(t('emailServer.created', { name: username })); setUsername(''); setName(''); setPassword(''); }; return (
{(['newAccount', 'accounts', 'log'] as const).map((each) => ( ))}
{page === 'newAccount' ? (
{notice === null ? null : (

{notice}

)}
) : null} {page === 'accounts' ? (
{accounts.map((account) => ( setChosen(account.username)}> ))}
{t('emailServer.address')} {t('emailServer.mails')}
{`${account.username}@${view.domain}`} {account.mails}
{asking && chosenAccount !== undefined ? (

{t('emailServer.confirmDelete', { name: chosenAccount.username })}

) : ( )}
) : null} {page === 'log' ? (
          {text}
        
) : null}
); }