Skip to content

Commit aba19cb

Browse files
authored
Flow Graph Editor: Phase 2 — Context Menus, Port Tooltips, Toasts (#18309)
## Summary Editor UX improvements for the Flow Graph Editor (Phase 2, PR 5). ### Changes - **Right-click context menus** — Canvas, node, link, and frame context menus using a shared `ContextMenu` primitive - **Port-level hover tooltips** — Shows port name, type, and description on hover - **Toast/notification system** — Visual feedback for save, load, validation, and other operations - **Fix tsconfig.build.json paths** — Corrected for TS6 migration (`baseUrl` removal) - **Fix `disconnectFrom()` → `disconnectFromAll()`** — API change from master merge ### Scope All changes are within `packages/tools/flowGraphEditor/` — no shared-ui-components or core changes. Zero risk to other editors. ### Manual Testing Tested via `npm run serve -w @tools/flow-graph-editor` with CDN build.
1 parent 0ff0557 commit aba19cb

12 files changed

Lines changed: 762 additions & 9 deletions

File tree

packages/tools/flowGraphEditor/ISSUES-Phase-2.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ When done with an issue, update the MANUAL.md to reflect the new feature or fix,
2424

2525
- [x] **No variables panel** — There is no way to see all graph variables at a glance, set initial values, rename them globally, or inspect current runtime values. Users must create `GetVariable`/`SetVariable` blocks and hope the variable names match across the graph. A typo in a variable name silently creates a separate variable. **Expected:** A "Variables" panel listing all variables defined in the graph with their name, type, and default value. Users should be able to add, rename, and delete variables from this panel. Renaming should propagate to all `GetVariable`/`SetVariable` blocks referencing that name.
2626

27-
- [ ] **No right-click context menu** — No context menu exists on the canvas, nodes, or links. The shared UI library has a `ContextMenu` primitive (`sharedUiComponents/src/fluent/primitives/contextMenu.tsx`) but the flow graph editor doesn't use it. Common operations require memorizing keyboard shortcuts. **Expected:** Right-click context menus for:
27+
- [x] **No right-click context menu** — No context menu exists on the canvas, nodes, or links. The shared UI library has a `ContextMenu` primitive (`sharedUiComponents/src/fluent/primitives/contextMenu.tsx`) but the flow graph editor doesn't use it. Common operations require memorizing keyboard shortcuts. **Expected:** Right-click context menus for:
2828
- **Canvas:** Add block (opens search), Paste, Create sticky note, Create frame, Select all
2929
- **Node:** Delete, Duplicate, Add breakpoint/Remove breakpoint, Create frame from selection, Disconnect all ports
3030
- **Link:** Delete connection, Insert block on connection
@@ -55,11 +55,11 @@ When done with an issue, update the MANUAL.md to reflect the new feature or fix,
5555

5656
## Medium (quality-of-life improvements)
5757

58-
- [ ] **No port-level tooltips or descriptions** — Block-level tooltips in the palette are comprehensive (every block has a description). However, individual ports on nodes have no documentation. A user seeing ports named `a`, `b`, `val`, `res` gets no hint about what each expects or produces. **Expected:** Hover over a port to see a tooltip with the port name, data type, and a brief description (e.g., "a (Number): The left operand"). Port descriptions could come from the block's `_registerInput`/`_registerOutput` metadata.
58+
- [x] **No port-level tooltips or descriptions** — Block-level tooltips in the palette are comprehensive (every block has a description). However, individual ports on nodes have no documentation. A user seeing ports named `a`, `b`, `val`, `res` gets no hint about what each expects or produces. **Expected:** Hover over a port to see a tooltip with the port name, data type, and a brief description (e.g., "a (Number): The left operand"). Port descriptions could come from the block's `_registerInput`/`_registerOutput` metadata.
5959

6060
- [x] **No undo/redo buttons in the toolbar** — Undo/redo works via Ctrl+Z / Ctrl+Shift+Z, but there are no visible toolbar buttons. New users may not discover the feature exists. **Expected:** Undo and Redo icon buttons in the toolbar with disabled state when at the beginning/end of the history stack.
6161

62-
- [ ] **No toast/notification system for editor operations** — Errors and validation results only appear in the log panel at the bottom. Operations like "Graph saved", "Snippet loaded", "Validation passed (0 errors)" give no immediate visual feedback in the main workspace. Users must look at the log panel to know if an action succeeded. **Expected:** Brief toast notifications (auto-dismissing after 3–5 seconds) for key operations: save/load success/failure, validation summary, snippet ID copied, etc.
62+
- [x] **No toast/notification system for editor operations** — Errors and validation results only appear in the log panel at the bottom. Operations like "Graph saved", "Snippet loaded", "Validation passed (0 errors)" give no immediate visual feedback in the main workspace. Users must look at the log panel to know if an action succeeded. **Expected:** Brief toast notifications (auto-dismissing after 3–5 seconds) for key operations: save/load success/failure, validation summary, snippet ID copied, etc.
6363

6464
## Low (nice to have)
6565

packages/tools/flowGraphEditor/MANUAL.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,77 @@ The editor includes blocks for controlling audio playback using the Babylon.js A
484484

485485
---
486486

487+
## Right-Click Context Menus
488+
489+
Right-click on the canvas, a node, a link, or a frame to open a context menu with relevant actions. The menu items change depending on what was clicked.
490+
491+
### Canvas (Background)
492+
493+
| Action | Shortcut | Description |
494+
| ------------------- | ----------- | ------------------------------------------------- |
495+
| **Add Block...** | Space | Opens the block search box to add a new block |
496+
| **Paste** | Ctrl+V | Pastes previously copied blocks |
497+
| **Create Sticky Note** | Ctrl+M | Adds a sticky note at the click position |
498+
| **Select All** | Ctrl+A | Selects all nodes and frames |
499+
| **Zoom to Fit** || Zooms to fit the entire graph |
500+
| **Reorganize** || Auto-layouts the graph |
501+
502+
### Node(s)
503+
504+
| Action | Shortcut | Description |
505+
| ------------------------ | ----------- | --------------------------------------------------------- |
506+
| **Delete** | Del | Deletes the selected node(s) |
507+
| **Duplicate** | Ctrl+C/V | Copies and pastes the selected node(s) |
508+
| **Add/Remove Breakpoint** | F9 | Toggles a breakpoint (single execution block only) |
509+
| **Create Smart Group** | Ctrl+G | Groups 2+ selected blocks into a frame (when applicable) |
510+
| **Disconnect All Ports** || Disconnects all input and output ports from the selection |
511+
512+
### Link
513+
514+
| Action | Shortcut | Description |
515+
| --------------------- | -------- | ------------------------------------ |
516+
| **Delete Connection** | Del | Removes the selected connection line |
517+
518+
### Frame
519+
520+
| Action | Shortcut | Description |
521+
| ---------------- | -------- | ------------------------------------------ |
522+
| **Delete Frame** | Del | Removes the frame (keeps contained blocks) |
523+
| **Collapse/Expand** || Toggles the frame between collapsed and expanded states |
524+
525+
---
526+
527+
## Port Tooltips
528+
529+
Hover over any port icon (the colored dot or shape on a node) to see a tooltip showing:
530+
531+
- **Port name** — the connection point's label (e.g., "a", "result", "in")
532+
- **Data type** — for data ports, the rich type (e.g., Number, Vector3, Boolean)
533+
- **Direction** — whether it's an Input or Output port
534+
535+
Signal ports (execution flow) show "Signal Input" or "Signal Output".
536+
537+
---
538+
539+
## Toast Notifications
540+
541+
Brief notifications appear in the bottom-right corner of the editor for key operations. Toasts auto-dismiss after 4 seconds and can be closed early by clicking the ✕ button.
542+
543+
### Operations That Trigger Toasts
544+
545+
| Event | Severity | Message |
546+
| ------------------------------ | -------- | ----------------------------------------- |
547+
| File load success | Success | "Flow graph loaded from file" |
548+
| File save | Success | "Flow graph saved to file" |
549+
| Snippet save | Success | "Graph saved — ID: ... (copied to clipboard)" |
550+
| Snippet load success | Success | "Flow graph loaded from snippet ..." |
551+
| Snippet save/load failure | Error | Error description |
552+
| Disconnect all ports | Info | "Disconnected all ports" |
553+
554+
All toast messages are also logged to the Log panel for reference.
555+
556+
---
557+
487558
## Block Property Panel
488559

489560
Select a block on the canvas to view and edit its properties in the right-hand panel. The panel has up to four sections:
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
.fge-context-menu-overlay {
2+
position: fixed;
3+
top: 0;
4+
left: 0;
5+
width: 100%;
6+
height: 100%;
7+
z-index: 10000;
8+
}
9+
10+
.fge-context-menu {
11+
position: fixed;
12+
z-index: 10001;
13+
background: #2a2a2a;
14+
border: 1px solid #555;
15+
border-radius: 4px;
16+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
17+
min-width: 180px;
18+
padding: 4px 0;
19+
font:
20+
13px "acumin-pro",
21+
sans-serif;
22+
color: #ccc;
23+
24+
.fge-ctx-item {
25+
display: flex;
26+
align-items: center;
27+
gap: 8px;
28+
padding: 6px 16px;
29+
cursor: pointer;
30+
white-space: nowrap;
31+
user-select: none;
32+
33+
&:hover {
34+
background: #3a5a8a;
35+
color: white;
36+
}
37+
38+
&.disabled {
39+
opacity: 0.35;
40+
cursor: not-allowed;
41+
&:hover {
42+
background: transparent;
43+
color: #ccc;
44+
}
45+
}
46+
47+
.fge-ctx-shortcut {
48+
margin-left: auto;
49+
font-size: 11px;
50+
opacity: 0.6;
51+
}
52+
}
53+
54+
.fge-ctx-separator {
55+
height: 1px;
56+
background: #555;
57+
margin: 4px 0;
58+
}
59+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import * as React from "react";
2+
import "./contextMenu.scss";
3+
4+
/**
5+
* Describes one item in a context menu.
6+
*/
7+
export interface IContextMenuItem {
8+
/** Display label */
9+
label: string;
10+
/** Callback when clicked */
11+
action: () => void;
12+
/** Keyboard shortcut hint (display only) */
13+
shortcut?: string;
14+
/** Whether the item is disabled */
15+
disabled?: boolean;
16+
/** Accessible description */
17+
ariaLabel?: string;
18+
}
19+
20+
/**
21+
* A separator entry in a context menu.
22+
*/
23+
export interface IContextMenuSeparator {
24+
/** Marker to distinguish separator from action items */
25+
isSeparator: true;
26+
}
27+
28+
export type ContextMenuEntry = IContextMenuItem | IContextMenuSeparator;
29+
30+
function IsSeparator(entry: ContextMenuEntry): entry is IContextMenuSeparator {
31+
return "isSeparator" in entry && entry.isSeparator === true;
32+
}
33+
34+
interface IContextMenuComponentProps {
35+
/** Screen X position */
36+
x: number;
37+
/** Screen Y position */
38+
y: number;
39+
/** Menu entries */
40+
items: ContextMenuEntry[];
41+
/** Called when the menu should close */
42+
onClose: () => void;
43+
}
44+
45+
/**
46+
* Generic right-click context menu shown as a fixed overlay.
47+
*/
48+
export class ContextMenuComponent extends React.Component<IContextMenuComponentProps> {
49+
private _menuRef = React.createRef<HTMLDivElement>();
50+
51+
/** @internal */
52+
override componentDidMount() {
53+
this._clampToViewport();
54+
// Close on Escape
55+
this._onKeyDown = (e: KeyboardEvent) => {
56+
if (e.key === "Escape") {
57+
this.props.onClose();
58+
}
59+
};
60+
document.addEventListener("keydown", this._onKeyDown);
61+
}
62+
63+
/** @internal */
64+
override componentDidUpdate() {
65+
this._clampToViewport();
66+
}
67+
68+
/** @internal */
69+
override componentWillUnmount() {
70+
if (this._onKeyDown) {
71+
document.removeEventListener("keydown", this._onKeyDown);
72+
}
73+
}
74+
75+
private _onKeyDown: ((e: KeyboardEvent) => void) | null = null;
76+
77+
private _clampToViewport() {
78+
const el = this._menuRef.current;
79+
if (!el) {
80+
return;
81+
}
82+
const rect = el.getBoundingClientRect();
83+
const vw = window.innerWidth;
84+
const vh = window.innerHeight;
85+
if (rect.right > vw) {
86+
el.style.left = `${Math.max(0, vw - rect.width - 4)}px`;
87+
}
88+
if (rect.bottom > vh) {
89+
el.style.top = `${Math.max(0, vh - rect.height - 4)}px`;
90+
}
91+
}
92+
93+
/** @internal */
94+
override render() {
95+
return (
96+
<div className="fge-context-menu-overlay" onPointerDown={() => this.props.onClose()} onContextMenu={(e) => e.preventDefault()}>
97+
<div ref={this._menuRef} className="fge-context-menu" role="menu" style={{ left: this.props.x, top: this.props.y }} onPointerDown={(e) => e.stopPropagation()}>
98+
{this.props.items.map((entry, idx) => {
99+
if (IsSeparator(entry)) {
100+
return <div key={`sep-${idx}`} className="fge-ctx-separator" role="separator" />;
101+
}
102+
const item = entry;
103+
return (
104+
<div
105+
key={`item-${idx}`}
106+
className={`fge-ctx-item${item.disabled ? " disabled" : ""}`}
107+
role="menuitem"
108+
tabIndex={item.disabled ? -1 : 0}
109+
aria-label={item.ariaLabel ?? item.label}
110+
aria-disabled={item.disabled}
111+
onClick={() => {
112+
if (!item.disabled) {
113+
item.action();
114+
this.props.onClose();
115+
}
116+
}}
117+
onKeyDown={(e) => {
118+
if (e.key === "Enter" || e.key === " ") {
119+
e.preventDefault();
120+
if (!item.disabled) {
121+
item.action();
122+
this.props.onClose();
123+
}
124+
}
125+
}}
126+
>
127+
<span>{item.label}</span>
128+
{item.shortcut && <span className="fge-ctx-shortcut">{item.shortcut}</span>}
129+
</div>
130+
);
131+
})}
132+
</div>
133+
</div>
134+
);
135+
}
136+
}

packages/tools/flowGraphEditor/src/components/help/helpContent.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export type HelpTopicId =
1414
| "smart-groups"
1515
| "keyboard-shortcuts"
1616
| "block-properties"
17+
| "context-menus"
18+
| "port-tooltips"
19+
| "toast-notifications"
1720
| "how-to-use"
1821
| "variables"
1922
| "gltf-import-export"
@@ -356,6 +359,65 @@ export const HelpTopics: IHelpTopic[] = [
356359
},
357360
],
358361
},
362+
{
363+
id: "context-menus",
364+
title: "Right-Click Context Menus",
365+
sections: [
366+
{
367+
html: `<p>Right-click on the canvas, a node, a link, or a frame to open a context menu with common actions.</p>`,
368+
},
369+
{
370+
heading: "Canvas Menu",
371+
html: `<ul>
372+
<li><b>Add Block...</b> — opens the block search box</li>
373+
<li><b>Paste</b> — pastes copied blocks</li>
374+
<li><b>Create Sticky Note</b> — adds a note at the click position</li>
375+
<li><b>Select All</b> — selects all nodes and frames</li>
376+
<li><b>Zoom to Fit</b> / <b>Reorganize</b></li>
377+
</ul>`,
378+
},
379+
{
380+
heading: "Node Menu",
381+
html: `<ul>
382+
<li><b>Delete</b> — removes selected block(s)</li>
383+
<li><b>Duplicate</b> — copies and pastes the selection</li>
384+
<li><b>Add/Remove Breakpoint</b> — toggles breakpoint (execution blocks only)</li>
385+
<li><b>Create Smart Group</b> — groups 2+ blocks into a frame</li>
386+
<li><b>Disconnect All Ports</b> — removes all connections</li>
387+
</ul>`,
388+
},
389+
{
390+
heading: "Link & Frame Menus",
391+
html: `<p><b>Link</b>: Delete Connection. <b>Frame</b>: Delete Frame, Collapse/Expand.</p>`,
392+
},
393+
],
394+
},
395+
{
396+
id: "port-tooltips",
397+
title: "Port Tooltips",
398+
sections: [
399+
{
400+
html: `<p>Hover over any port icon on a node to see a tooltip with the port's <b>name</b>, <b>data type</b>, and <b>direction</b> (Input/Output).</p>
401+
<p>Signal ports show "Signal Input" or "Signal Output". Data ports show the rich type name (e.g., Number, Vector3, Boolean).</p>`,
402+
},
403+
],
404+
},
405+
{
406+
id: "toast-notifications",
407+
title: "Toast Notifications",
408+
sections: [
409+
{
410+
html: `<p>Brief notifications appear in the bottom-right corner for save/load and other operations. They auto-dismiss after 4 seconds.</p>
411+
<ul>
412+
<li><b>Success</b> (green) — file/snippet saved or loaded</li>
413+
<li><b>Error</b> (red) — save/load failure</li>
414+
<li><b>Info</b> (blue) — general information</li>
415+
<li><b>Warning</b> (amber) — caution notices</li>
416+
</ul>
417+
<p>All toast messages are also logged to the Log panel.</p>`,
418+
},
419+
],
420+
},
359421
{
360422
id: "how-to-use",
361423
title: "How to Use (Embed Code)",

0 commit comments

Comments
 (0)