feat: added node-tooltips to the editor

This commit is contained in:
Gerla, J. (Justin)
2026-01-13 11:29:45 +00:00
committed by Björn Otgaar
parent 5385bd72b1
commit 566c4c18cc
16 changed files with 332 additions and 28 deletions

View File

@@ -3,7 +3,8 @@ import { useReactFlow, type XYPosition } from '@xyflow/react';
import { type ReactNode, useCallback, useRef, useState } from 'react';
import useFlowStore from '../VisProgStores';
import styles from '../../VisProg.module.css';
import { NodeDefaults, type NodeTypes } from '../NodeRegistry'
import { NodeDefaults, type NodeTypes} from '../NodeRegistry'
import {Tooltip} from "./NodeComponents.tsx";
/**
* Props for a draggable node within the drag-and-drop toolbar.
@@ -47,14 +48,17 @@ function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeP
});
return (
<div className={className}
ref={draggableRef}
id={`draggable-${nodeType}`}
data-testid={`draggable-${nodeType}`}
>
{children}
</div>
);
<Tooltip nodeType={nodeType}>
<div>
<div className={className}
ref={draggableRef}
id={`draggable-${nodeType}`}
data-testid={`draggable-${nodeType}`}
>
{children}
</div>
</div>
</Tooltip>)
}
/**
@@ -133,7 +137,7 @@ export function DndToolbar() {
}));
return (
<div className={`flex-col gap-lg padding-md ${styles.innerDndPanel}`}>
<div className={`flex-col gap-lg padding-md ${styles.innerDndPanel}`} id={"draggable-sidebar"}>
<div className="description">
You can drag these nodes to the pane to create new nodes.
</div>

View File

@@ -1,7 +1,12 @@
import { NodeToolbar } from '@xyflow/react';
import {NodeToolbar} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import {type JSX, useState} from "react";
import {createPortal} from "react-dom";
import styles from "../../VisProg.module.css";
import {NodeTooltips} from "../NodeRegistry.ts";
import useFlowStore from "../VisProgStores.tsx";
/**
* Props for the Toolbar component.
*
@@ -24,14 +29,94 @@ type ToolbarProps = {
* @constructor
*/
export function Toolbar({nodeId, allowDelete}: ToolbarProps) {
const {deleteNode} = useFlowStore();
const {nodes, deleteNode} = useFlowStore();
const deleteParentNode = ()=> {
const deleteParentNode = () => {
deleteNode(nodeId);
}
};
const nodeType = nodes.find((node) => node.id === nodeId)?.type as keyof typeof NodeTooltips;
return (
<NodeToolbar>
<NodeToolbar className={"flex-row align-center"}>
<button className="Node-toolbar__deletebutton" onClick={deleteParentNode} disabled={!allowDelete}>delete</button>
<Tooltip nodeType={nodeType}>
<div className={styles.nodeToolbarTooltip}>i</div>
</Tooltip>
</NodeToolbar>);
}
type TooltipProps = {
nodeType?: keyof typeof NodeTooltips;
children: JSX.Element;
};
/**
* A general tooltip component, that can be used as a wrapper for any component
* that has a nodeType and a corresponding nodeTooltip.
*
* currently used to show tooltips for draggable-nodes and nodes inside the editor
*
* @param {"start" | "end" | "phase" | "norm" | "goal" | "trigger" | "basic_belief" | undefined} nodeType
* @param {React.JSX.Element} children
* @returns {React.JSX.Element}
* @constructor
*/
export function Tooltip({ nodeType, children }: TooltipProps) {
const [showTooltip, setShowTooltip] = useState(false);
const [disabled , setDisabled] = useState(false);
const [coords, setCoords] = useState({ top: 0, left: 0 });
const updateTooltipPos = () => {
const rect = document.getElementById("draggable-sidebar")!.getBoundingClientRect();
setCoords({
// Position exactly below the bottom edge of the draggable sidebar (plus a small gap)
top: rect.bottom + 10,
left: rect.left + rect.width / 2, // Keep it horizontally centered
});
};
return nodeType ?
(<div>
<div
onMouseDown={() => {
updateTooltipPos();
setShowTooltip(false);
setDisabled(true);
}}
onMouseUp={() => {
setDisabled(false);
}}
onMouseOver={() => {
if (!disabled) {
updateTooltipPos();
setShowTooltip(true);
}
}}
onMouseLeave={ () => setShowTooltip(false)}
>
{children}
</div>
{showTooltip && createPortal(
<div
className={"flex-row"}
style={{
pointerEvents: 'none',
position: 'fixed',
top: `${coords.top}px`,
left: `${coords.left}px`,
transform: 'translateX(-50%)', // Center based on the midpoint
}}
>
<span className={styles.customTooltipHeader}>{nodeType}</span>
<span className={styles.customTooltip}>
{NodeTooltips[nodeType] || "Available for drag"}
</span>
</div>,
document.body
)}
</div>
) : children
}

View File

@@ -31,4 +31,13 @@
filter: drop-shadow(0 0 0.25rem green);
}
:global(.react-flow__handle) {
width: calc(8px / var(--flow-zoom, 1));
height: calc(8px / var(--flow-zoom, 1));
transition: width 0.1s ease, height 0.1s ease;
min-width: 8px;
min-height: 8px;
}