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
+18 -3
View File
@@ -1,12 +1,13 @@
// The editor's page. Embedded in the dashboard (F6) it gets its document and
// language over the bridge; on its own — for development and the browser
// tests — it opens one of FILIUS' examples or a file (`?example=<name>`
// opens that example at once, `?lang=en` and `?theme=dark` set the rest).
// tests — it opens one of FILIUS' examples, an empty project or a file
// (`?example=<name>` opens that example at once, `?example=new` an empty
// project, `?lang=en` and `?theme=dark` set the rest).
import { StrictMode, useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import * as Y from 'yjs';
import { writeProject } from '@mdg-filius/doc';
import { emptyProject, topologyView, writeProject } from '@mdg-filius/doc';
import { openProjectArchive } from '@mdg-filius/fls';
import { startBridge } from './bridge/dashboardBridge.js';
import { Editor } from './Editor.js';
@@ -14,6 +15,8 @@ import { LanguageProvider, translate, type Language } from './i18n/i18n.js';
import './theme/tokens.css';
const EXAMPLES = import.meta.glob('../../../corpus/java-examples/*.fls', { query: '?url', import: 'default', eager: true }) as Record<string, string>;
/** `?example=new`: an empty project. */
const NEW_PROJECT = 'new';
const exampleUrl = (name: string) => Object.entries(EXAMPLES).find(([path]) => path.endsWith(`/${name}`))?.[1];
function docOf(bytes: Uint8Array): Y.Doc {
@@ -44,6 +47,12 @@ function App() {
}, []);
useEffect(() => {
if (example === NEW_PROJECT) {
const doc = new Y.Doc();
writeProject(doc, emptyProject());
setDoc(doc);
return;
}
const url = exampleUrl(example);
if (url === undefined) return;
void fetch(url)
@@ -51,6 +60,11 @@ function App() {
.then((buffer) => setDoc(docOf(new Uint8Array(buffer))));
}, [example]);
// The development page lets its browser tests read the document's drawing (never the editor embedded in the dashboard).
useEffect(() => {
(window as unknown as { filiusDev?: unknown }).filiusDev = doc === null ? undefined : { view: () => topologyView(doc) };
}, [doc]);
const t = (key: string) => translate(language, key);
return (
<LanguageProvider language={language}>
@@ -61,6 +75,7 @@ function App() {
{t('dev.example')}{' '}
<select value={example} onChange={(event) => setExample(event.target.value)}>
<option value="" />
<option value={NEW_PROJECT}>{t('dev.newProject')}</option>
{Object.keys(EXAMPLES)
.map((path) => path.slice(path.lastIndexOf('/') + 1))
.sort()