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:
@@ -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;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { describe, expect, it } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
import { interfaceKey, NetworkRuntimeEngine, type Id } from '@mdg-filius/core';
|
||||
import { openProjectArchive, projectSnapshot, type ImportedProject } from '@mdg-filius/fls';
|
||||
import { addDevice, connect, createUndoManager, docSnapshot, moveDevice, newDeviceProject, readProject, removeDevice, removeLink, repair, ROOTS, topologyView, type NewDevice } from '../src/index.js';
|
||||
import { addDevice, connect, createUndoManager, docSnapshot, moveDevice, newDeviceProject, PALETTE, readProject, removeDevice, removeLink, removeLinksOf, repair, ROOTS, topologyView, type NewDevice } from '../src/index.js';
|
||||
|
||||
const fixture = join(dirname(fileURLToPath(import.meta.url)), 'fixtures/new-devices.fls');
|
||||
|
||||
@@ -75,6 +75,10 @@ describe('a new device', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("comes from a palette in Java's order", () => {
|
||||
expect(PALETTE).toEqual(['pc', 'notebook', 'networkSwitch', 'gateway', 'router', 'remoteLink']);
|
||||
});
|
||||
|
||||
it('keeps a router between 2 and 8 interfaces, 2 when not asked', () => {
|
||||
const count = (interfaces?: number) => newDeviceProject(device('router', interfaces === undefined ? {} : { interfaces })).graph.nodes[0]!.ports.length;
|
||||
expect([count(), count(1), count(5), count(12)]).toEqual([2, 2, 5, 8]);
|
||||
@@ -156,6 +160,16 @@ describe('a cable', () => {
|
||||
expect(topologyView(doc).links[0]!.wireless).toBe(true);
|
||||
});
|
||||
|
||||
it('is pulled with all of a device\'s cables, the device staying', () => {
|
||||
const { doc, pcA, pcB, hub, router, home } = network();
|
||||
connect(doc, { node: pcA.id }, { node: hub.id }, 'link-1', 'me');
|
||||
connect(doc, { node: pcB.id }, { node: hub.id }, 'link-2', 'me');
|
||||
expect(connect(doc, { node: router.id }, { node: home.id }, 'link-3', 'me')).toEqual({ link: 'link-3' });
|
||||
removeLinksOf(doc, hub.id, 'me');
|
||||
expect(readProject(doc).graph.links.map((link) => link.id)).toEqual(['link-3']);
|
||||
expect(readProject(doc).graph.nodes.some((node) => node.id === hub.id)).toBe(true);
|
||||
});
|
||||
|
||||
it('is pulled alone, and with the device it is plugged into', () => {
|
||||
const { doc, pcA, pcB, hub } = network();
|
||||
connect(doc, { node: pcA.id }, { node: hub.id }, 'link-1', 'me');
|
||||
|
||||
Reference in New Issue
Block a user