feat(editor): the simulation opens sheets, not windows in the window

A tap on a device in the simulation now only chooses it; its three-dot
button (or a long press) opens what Java's click opened: the desktop, and
newly a switch's SAT table and a home router's NAT table, beside each
card's data exchange.

The desktop, the tables and the data exchange are no windows in the
window any more but the simulation's sheet (SimulationSheet), after the
dashboard's AppSheet: below the toolbar to the bottom, a head with icon,
title, who else has it open, see-through and close. One at a time — it
covers the surface. It stays over a return to the design with the program
open on a desktop; a sheet whose device was removed closes. See-through
fades the body (0.55) and keeps the head; the choice is kept in the
browser. Java opens these as movable windows, several at once, with a
click; named in CHANGES.md.

Tests open through the menu (showFromMenu, showDesktop in editorPage.ts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 17:18:25 +00:00
co-authored by Claude Opus 5.5
parent 1593726447
commit 16d9cc2095
26 changed files with 585 additions and 497 deletions
+10 -15
View File
@@ -6,7 +6,7 @@
// list shows the dropped frames in Java's words.
import { expect, test, type Locator, type Page } from '@playwright/test';
import { drawing, onDevice, openEditor, serveEditor } from './editorPage.js';
import { drawing, openEditor, serveEditor, showDesktop, showFromMenu } from './editorPage.js';
let server: { readonly base: string; readonly close: () => Promise<void> };
@@ -18,11 +18,6 @@ test.afterAll(async () => {
await server.close();
});
async function tap(page: Page, id: string) {
const at = await onDevice(page, id);
await page.mouse.click(at.x, at.y);
}
async function install(desktop: Locator, program: string) {
await desktop.locator('[data-program="installation"]').click();
const installation = desktop.locator('[data-program-window="installation"]');
@@ -41,17 +36,17 @@ test('shows the tables of a home router and a switch, and drops while “Pakete
// The home router with the port forwarding: its entry, with Java's columns.
const gateway = idOf('Heimrouter Vincent');
await tap(page, gateway);
await showFromMenu(page, gateway, 'NAT-Tabelle anzeigen');
const nat = page.locator(`section[data-viewer="${gateway}"]`);
await expect(nat.locator('h2')).toHaveText('NAT-Tabelle Heimrouter Vincent');
await expect(nat.locator('th')).toHaveText(['Protokoll', 'Portfreigabe', 'WAN-Adresse', 'LAN-Adresse', 'LAN-Port', 'Letzte Aktualisierung']);
await expect(nat.locator('tbody tr')).toHaveCount(1);
await expect(nat.locator('tbody tr').first().locator('td')).toHaveText(['TCP', '80', '*', '192.168.0.10', '80', '-']);
await nat.locator('.desktop-close').click();
await nat.locator('.simulation-sheet__close').click();
// The web server behind it pings out.
const web = idOf('Webserver - 192.168.0.10');
await tap(page, web);
await showDesktop(page, web);
const desktop = page.locator(`section[data-desktop="${web}"]`);
await install(desktop, 'Befehlszeile');
await desktop.locator('[data-program="commandPrompt"]').click();
@@ -59,29 +54,29 @@ test('shows the tables of a home router and a switch, and drops while “Pakete
await input.fill('ping 141.99.4.10');
await input.press('Enter');
await expect(desktop.locator('.terminal-text')).toContainText('4 Paket(e) empfangen', { timeout: 30_000 });
await desktop.locator('.desktop-close').click();
await desktop.locator('.simulation-sheet__close').click();
await tap(page, gateway);
await showFromMenu(page, gateway, 'NAT-Tabelle anzeigen');
await expect(nat.locator('tbody tr')).toHaveCount(2);
await expect(nat.locator('tbody tr').nth(1).locator('td').nth(0)).toHaveText('ICMP');
await expect(nat.locator('tbody tr').nth(1).locator('td').nth(5)).toHaveText(/^\d\d:\d\d:\d\d$/);
await nat.getByRole('button', { name: 'Tabelle zurücksetzen' }).click();
await expect(nat.locator('tbody tr')).toHaveCount(1);
await nat.locator('.desktop-close').click();
await nat.locator('.simulation-sheet__close').click();
// The switch the web server hangs on: the addresses the ping taught it.
const link = (await drawing(page)).links.find((each) => each.sourceNode === web || each.targetNode === web)!;
const lanSwitch = link.sourceNode === web ? link.targetNode : link.sourceNode;
await tap(page, lanSwitch);
await showFromMenu(page, lanSwitch, 'SAT Tabelle anzeigen');
const sat = page.locator(`section[data-viewer="${lanSwitch}"]`);
await expect(sat.locator('th')).toHaveText(['MAC', 'Port', 'Letzte Aktualisierung']);
await expect(sat.locator('tbody tr').first().locator('td')).toHaveText([/^[0-9A-F:]{17}$/, /^Port \d+$/, /^\d\d:\d\d:\d\d$/]);
await sat.getByRole('button', { name: 'Tabelle löschen' }).click();
await expect(sat.locator('tbody tr')).toHaveCount(0);
await sat.locator('.desktop-close').click();
await sat.locator('.simulation-sheet__close').click();
// "Pakete verwerfen" held while a ping runs: answers lost, the frames listed.
await tap(page, web);
await showDesktop(page, web);
await desktop.locator('[data-program="commandPrompt"]').click();
await input.fill('ping 141.99.4.10');
await input.press('Enter');