Files
rio/frontend/code/components/graphEditor/draggingConnectionStrategy.ts
2024-11-17 12:26:40 +01:00

141 lines
4.7 KiB
TypeScript

import { componentsByElement, componentsById } from "../../componentManagement";
import { ComponentId } from "../../dataModels";
import { GraphEditorComponent } from "./graphEditor";
import { NodeInputComponent } from "../nodeInput";
import { NodeOutputComponent } from "../nodeOutput";
import {
getNodeFromPort,
getPortFromCircle,
getPortViewportPosition,
makeConnectionElement,
updateConnectionFromCoordinates,
updateConnectionFromObject,
} from "./utils";
import { AugmentedConnectionState } from "./graphStore";
/// The user is currently dragging a connection from a port. This means the
/// connection is already connected to one port, with the other end dangling at
/// the pointer.
export class DraggingConnectionStrategy {
fixedNodeId: ComponentId;
fixedPortId: ComponentId;
element: SVGPathElement;
constructor(
fixedNodeId: ComponentId,
fixedPortId: ComponentId,
element: SVGPathElement
) {
this.fixedNodeId = fixedNodeId;
this.fixedPortId = fixedPortId;
this.element = element;
}
onDragMove(ge: GraphEditorComponent, event: PointerEvent): void {
// Update the latent connection
const fixedPortComponent = componentsById[this.fixedPortId] as
| NodeInputComponent
| NodeOutputComponent
| undefined;
// The element could've been deleted in the meantime
if (fixedPortComponent === undefined) {
return;
}
let [x1, y1] = getPortViewportPosition(fixedPortComponent);
let [x2, y2] = [event.clientX, event.clientY];
// Convert the coordinates to the editor's coordinate system
const editorRect = ge.element.getBoundingClientRect();
x1 = x1 - editorRect.left;
y1 = y1 - editorRect.top;
x2 = x2 - editorRect.left;
y2 = y2 - editorRect.top;
// If dragging from an input port, flip the coordinates
if (fixedPortComponent instanceof NodeInputComponent) {
[x1, y1, x2, y2] = [x2, y2, x1, y1];
}
// Update the SVG path
updateConnectionFromCoordinates(this.element, x1, y1, x2, y2);
}
onDragEnd(ge: GraphEditorComponent, event: PointerEvent): void {
// Remove the SVG path
this.element.remove();
// Was the pointer released on a port?
let dropElement = event.target as HTMLElement;
if (!dropElement.classList.contains("rio-graph-editor-port-circle")) {
return;
}
let dropPortComponent = getPortFromCircle(dropElement);
// Get the fixed port
let fixedPortComponent = componentsById[this.fixedPortId] as
| NodeInputComponent
| NodeOutputComponent
| undefined;
// The element could've been deleted in the meantime
if (fixedPortComponent === undefined) {
return;
}
// Each connection must connect exactly one input and one output
let fromPortComponent: NodeOutputComponent;
let toPortComponent: NodeInputComponent;
if (fixedPortComponent instanceof NodeOutputComponent) {
if (dropPortComponent instanceof NodeOutputComponent) {
return;
}
fromPortComponent = fixedPortComponent;
toPortComponent = dropPortComponent as NodeInputComponent;
} else {
if (dropPortComponent instanceof NodeInputComponent) {
return;
}
fromPortComponent = dropPortComponent as NodeOutputComponent;
toPortComponent = fixedPortComponent;
}
// Prepare all data needed to make the connection
const fromNodeComponent = getNodeFromPort(fromPortComponent);
const toNodeComponent = getNodeFromPort(toPortComponent);
// Input nodes can only have one connection at a time. If the target
// port already has a connection, stop here.
let existingConnections = ge.graphStore.getConnectionsForPort(
toNodeComponent.id,
toPortComponent.id
);
if (existingConnections.length > 0) {
return;
}
// Create a real connection between the two ports
let connectionElement = makeConnectionElement();
ge.svgChild.appendChild(connectionElement);
let augmentedConn: AugmentedConnectionState = {
fromNode: fromNodeComponent.id,
fromPort: fromPortComponent.id,
toNode: toNodeComponent.id,
toPort: toPortComponent.id,
element: connectionElement,
};
ge.graphStore.addConnection(augmentedConn);
updateConnectionFromObject(ge, augmentedConn);
}
}