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
+22 -21
View File
@@ -11,7 +11,7 @@ import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { expect, test, type Page } from '@playwright/test';
import { drawing, onDevice, serveEditor } from './editorPage.js';
import { drawing, onDevice, serveEditor, showDesktop } from './editorPage.js';
let server: { readonly base: string; readonly close: () => Promise<void> };
@@ -62,20 +62,18 @@ test("shows a card's frames as Java's data exchange window does, with their deta
await expect(window.getByRole('tab')).toHaveText(['Browser - 192.168.0.10']);
await expect(window.locator('th')).toHaveText(['Nr.', 'Zeit', 'Quelle', 'Ziel', 'Protokoll', 'Schicht', 'Bemerkungen / Details']);
await expect(window.locator('tbody tr')).toHaveCount(0);
// Closed, it keeps its tab (Java hides its dialog); it lies over the devices.
await window.locator('.desktop-close').click();
// Closed, it keeps its tab (Java hides its dialog).
await window.locator('.simulation-sheet__close').click();
await expect(window).toHaveCount(0);
// The browser fetches the server's page: a row per frame, its highest layer's, in its colour.
const at = await onDevice(page, await idOf(page, 'Browser'));
await page.mouse.click(at.x, at.y);
const desktop = page.locator('section[data-desktop]');
const desktop = await showDesktop(page, await idOf(page, 'Browser'));
await desktop.locator('[data-program="webBrowser"]').click();
const address = desktop.getByRole('textbox', { name: 'Adresse' });
await address.fill('192.168.0.20');
await address.press('Enter');
await expect(desktop.locator('.program-titlebar h3')).toHaveText('Seiten & Formulare', { timeout: 30_000 });
await desktop.locator('.desktop-close').click();
await desktop.locator('.simulation-sheet__close').click();
await (await menuOf(page, 'Browser')).filter({ hasText: 'Datenaustausch' }).click();
await expect(window.getByRole('tab')).toHaveText(['Browser - 192.168.0.10']);
const rows = window.locator('tbody tr');
@@ -135,7 +133,7 @@ test("shows a card's frames as Java's data exchange window does, with their deta
expect(text.match(/^\+-/gm)?.length).toBe(await rows.count());
// A router's cards: one tab each, the one chosen in front.
await window.locator('.desktop-close').click();
await window.locator('.simulation-sheet__close').click();
await expect(await menuOf(page, 'Router')).toHaveText(['Datenaustausch anzeigen (192.168.0.1)', 'Datenaustausch anzeigen (10.0.0.1)']);
await page.getByRole('menuitem', { name: 'Datenaustausch anzeigen (192.168.0.1)' }).click();
await expect(window.getByRole('tab')).toHaveText(['Browser - 192.168.0.10', 'Router - 192.168.0.1']);
@@ -153,13 +151,12 @@ test("shows a card's frames as Java's data exchange window does, with their deta
// Without "Automatisches Scrollen", fetched again (its tab kept over the window closed): the table stays at its start, nothing chosen.
await window.getByLabel('Automatisches Scrollen').uncheck();
await window.locator('.desktop-close').click();
await page.mouse.click(at.x, at.y);
await showDesktop(page, await idOf(page, 'Browser'));
await desktop.locator('[data-program="webBrowser"]').click();
await address.fill('192.168.0.20');
await address.press('Enter');
await page.waitForTimeout(6_000);
await desktop.locator('.desktop-close').click();
await desktop.locator('.simulation-sheet__close').click();
await (await menuOf(page, 'Browser')).filter({ hasText: 'Datenaustausch' }).click();
await expect(window.getByLabel('Automatisches Scrollen')).not.toBeChecked();
await expect.poll(() => rows.count()).toBeGreaterThan(15);
@@ -168,7 +165,7 @@ test("shows a card's frames as Java's data exchange window does, with their deta
expect(await scrolled()).toMatchObject({ overflows: true, top: 0 });
// A third tab; a tab closed beside the chosen one leaves it chosen.
await window.locator('.desktop-close').click();
await window.locator('.simulation-sheet__close').click();
await (await menuOf(page, 'Server')).filter({ hasText: 'Datenaustausch' }).click();
await expect(window.getByRole('tab')).toHaveText(['Browser - 192.168.0.10', 'Router - 192.168.0.1', 'Server - 192.168.0.20']);
await window.getByRole('tab', { name: 'Browser - 192.168.0.10' }).click();
@@ -190,12 +187,12 @@ test("shows a card's frames as Java's data exchange window does, with their deta
await expect(window).toHaveCount(0);
});
test('chooses a device with a tap in the simulation, its three-dot button as in the design — only where it has a menu', async ({ page }) => {
test('chooses a device with a tap in the simulation, its three-dot button as in the design, and opens nothing by itself', async ({ page }) => {
await openPages(page);
await start(page);
const button = page.locator('.device-menu-button');
const sheet = page.locator('aside[aria-label="Einstellungen"]');
// A router: Java's tap does nothing else; the button opens the same menu as the long press.
// A router: the button opens the same menu as the long press.
const router = await onDevice(page, await idOf(page, 'Router'));
await page.mouse.click(router.x, router.y);
await expect(page.locator(`g.device[data-node="${await idOf(page, 'Router')}"]`)).toHaveAttribute('data-selected', 'true');
@@ -203,25 +200,29 @@ test('chooses a device with a tap in the simulation, its three-dot button as in
await expect(page.getByRole('menuitem')).toHaveText(['Datenaustausch anzeigen (192.168.0.1)', 'Datenaustausch anzeigen (10.0.0.1)']);
await page.getByRole('menuitem', { name: 'Datenaustausch anzeigen (10.0.0.1)' }).click();
await expect(page.locator('section[data-capture]').getByRole('tab', { selected: true })).toHaveText('Router - 10.0.0.1');
await page.locator('section[data-capture] .desktop-close').click();
// A computer: the tap still opens its desktop (Java), and chooses it; its settings stay closed, the design is locked.
await page.locator('section[data-capture] .simulation-sheet__close').click();
// A computer: the tap opens no desktop (Java's click does), it only chooses; its settings stay closed, the design is locked.
const browser = await idOf(page, 'Browser');
const at = await onDevice(page, browser);
await page.mouse.click(at.x, at.y);
await expect(page.locator(`section[data-desktop="${browser}"]`)).toBeVisible();
await expect(button).toBeVisible();
await expect(page.locator('section[data-desktop]')).toHaveCount(0);
await expect(sheet).toHaveCount(0);
await page.locator(`section[data-desktop="${browser}"] .desktop-close`).click();
await button.click();
await expect(page.getByRole('menuitem')).toHaveText(['Desktop anzeigen', 'Datenaustausch anzeigen (192.168.0.10)']);
await page.keyboard.press('Escape');
// A switch has no menu in the simulation, so no button; a tap beside lets go.
// A switch: the tap opens no table either; its menu does.
const lanSwitch = (await drawing(page)).nodes.find((node) => node.kind === 'networkSwitch')!.id;
const hub = await onDevice(page, lanSwitch);
await page.mouse.click(hub.x, hub.y);
await expect(page.locator(`g.device[data-node="${lanSwitch}"]`)).toHaveAttribute('data-selected', 'true');
await expect(button).toHaveCount(0);
await page.locator(`section[data-viewer="${lanSwitch}"] .desktop-close`).click();
await expect(page.locator('section[data-viewer]')).toHaveCount(0);
await button.click();
await expect(page.getByRole('menuitem')).toHaveText(['SAT Tabelle anzeigen']);
await page.getByRole('menuitem', { name: 'SAT Tabelle anzeigen' }).click();
await expect(page.locator(`section[data-viewer="${lanSwitch}"]`)).toBeVisible();
await page.locator('.simulation-sheet__close').click();
// A tap beside lets go.
await page.mouse.click(at.x, at.y);
await expect(button).toBeVisible();
const surface = (await page.locator('svg.filius-canvas').boundingBox())!;