114 lines
3.4 KiB
JavaScript
114 lines
3.4 KiB
JavaScript
import {
|
|
DiagramComponent,
|
|
pickAttributes,
|
|
validateOptionalHandler
|
|
} from "./diagram-component.js";
|
|
import { DrawingLink, DrawingSurface, DrawingTriple } from "./drawing-core.js";
|
|
import { DiagramNode } from "./diagram-node.js";
|
|
|
|
const LINK_ATTRIBUTES = [
|
|
"content",
|
|
"contentType",
|
|
"cx",
|
|
"cy",
|
|
"width",
|
|
"height",
|
|
"backgroundColor",
|
|
"borderColor",
|
|
"borderWidth",
|
|
"textColor",
|
|
"sourceX",
|
|
"sourceY",
|
|
"targetX",
|
|
"targetY",
|
|
"lineColor",
|
|
"lineWidth",
|
|
"hasArrow"
|
|
];
|
|
|
|
/**
|
|
* Public rendering handle for one diagram link.
|
|
*
|
|
* A link owns only visual and interaction state. Its source and target are
|
|
* DiagramNode instances from the same DiagramEngine.
|
|
*/
|
|
export class DiagramLink extends DiagramComponent {
|
|
constructor(engine, attributes) {
|
|
const component = new DrawingLink(pickAttributes(attributes, LINK_ATTRIBUTES));
|
|
super(engine, component, LINK_ATTRIBUTES);
|
|
}
|
|
|
|
/** Read or replace the source node. Pass null to disconnect it. */
|
|
sourceNode(node) {
|
|
return this.connectNode(DrawingSurface.CONNECTION_TYPE_SOURCE, node);
|
|
}
|
|
|
|
/** Read or replace the target node. Pass null to disconnect it. */
|
|
targetNode(node) {
|
|
return this.connectNode(DrawingSurface.CONNECTION_TYPE_TARGET, node);
|
|
}
|
|
|
|
/** Register a callback for a source or target connection change. */
|
|
onConnectionChange(handler) {
|
|
validateOptionalHandler(handler, "connection-change");
|
|
this.component.connectionChangeHandler = handler ? (type, node, event) => {
|
|
handler(this, type, node ? this.engine.handleFor(node) : null, event);
|
|
} : null;
|
|
return this;
|
|
}
|
|
|
|
/** Straighten both link segments between their current endpoints. */
|
|
straighten() {
|
|
const relation = this.component.relations()
|
|
.find((candidate) => candidate instanceof DrawingTriple);
|
|
const sourceNode = relation ? relation.sourceNode() : null;
|
|
const targetNode = relation ? relation.targetNode() : null;
|
|
|
|
if (!sourceNode || !targetNode) {
|
|
this.component.straighten();
|
|
return this;
|
|
}
|
|
|
|
const sourcePoint = relation.connectedPoint(
|
|
sourceNode, targetNode.cx(), targetNode.cy());
|
|
const targetPoint = relation.connectedPoint(
|
|
targetNode, sourceNode.cx(), sourceNode.cy());
|
|
this.component.straighten(
|
|
sourcePoint.x, sourcePoint.y, targetPoint.x, targetPoint.y);
|
|
return this;
|
|
}
|
|
|
|
/** Read or update one connected endpoint. */
|
|
connectNode(type, node) {
|
|
const connectedComponent = this.engine.drawingSurface
|
|
.connectedNode(type, this.component);
|
|
|
|
if (node === undefined) {
|
|
return connectedComponent ? this.engine.handleFor(connectedComponent) : null;
|
|
}
|
|
|
|
if (node !== null && !this.validateNode(type, node)) return this;
|
|
if (connectedComponent) {
|
|
this.engine.drawingSurface.disconnect(type, connectedComponent, this.component);
|
|
}
|
|
if (node !== null) {
|
|
this.engine.drawingSurface.connect(type, node.component, this.component);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/** Ensure an endpoint belongs to this engine and is not used at both ends. */
|
|
validateNode(type, node) {
|
|
if (!(node instanceof DiagramNode) || node.engine !== this.engine) {
|
|
throw new TypeError("Invalid diagram node");
|
|
}
|
|
|
|
const otherType = DrawingSurface.anotherConnectionType(type);
|
|
const otherNode = this.engine.drawingSurface.connectedNode(otherType, this.component);
|
|
if (otherNode === node.component) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|