feat(editor): F4.4b the design tools: palette, moving, cables, menus, undo

The palette in Java's order with Java's icons and names: a device dragged
onto the surface lands with its icon at the drop point, a tapped one in the
middle of the view; a new router asks for 2 to 8 interfaces as Java does.
A tap selects, a drag moves (divided by the scale), a long press or the
three-dot button opens Java's menu. The cable tool plugs by a drag or by
two taps, shows WAN or LAN on a home router's halves and Java's messages
when refused. Delete removes the selection; undo and redo by button and
Ctrl+Z/Ctrl+Y, each editor with its own origin. Browser tests with mouse
and touch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 09:37:49 +00:00
co-authored by Claude Opus 5.5
parent 27ab7a1f49
commit 806df6baca
16 changed files with 1218 additions and 74 deletions
+9 -2
View File
@@ -29,8 +29,8 @@ import {
import type { ImportedProject } from '@mdg-filius/fls';
import { emptyProject, inOrder, nodeEntry, ROOTS } from './projectDoc.js';
/** The kinds of the palette, in Java's order (GUIDesignSidebar). */
export const PALETTE: readonly Exclude<NodeKind, 'unsupported'>[] = ['pc', 'notebook', 'networkSwitch', 'router', 'gateway', 'remoteLink'];
/** The kinds of the palette, in Java's order (GUIDesignSidebar.addComponentItemsToSidebar; the cable comes first, a tool). */
export const PALETTE: readonly Exclude<NodeKind, 'unsupported'>[] = ['pc', 'notebook', 'networkSwitch', 'gateway', 'router', 'remoteLink'];
/** A router's interfaces, as Java's dialog offers them (2 to 8, 2 when it is closed). */
export const ROUTER_INTERFACES = { minimum: 2, maximum: 8, standard: 2 } as const;
@@ -141,6 +141,13 @@ export function removeLink(doc: Y.Doc, id: Id, origin: unknown): void {
doc.transact(() => links(doc).delete(id), origin);
}
/** Java's "remove all cables" of a device (its context menu). */
export function removeLinksOf(doc: Y.Doc, id: Id, origin: unknown): void {
doc.transact(() => {
for (const [linkId, link] of [...links(doc).entries()]) if (link.get('sourceNode') === id || link.get('targetNode') === id) links(doc).delete(linkId);
}, origin);
}
/** A cable's end: a device, and the port when one was asked for. */
export interface CableEnd {
readonly node: Id;