import * as React from 'react'; import { DiagramEngine, LinkWidget, PointModel } from '@projectstorm/react-diagrams-core'; import { RightAngleLinkFactory } from './RightAngleLinkFactory'; import { DefaultLinkModel, DefaultLinkSegmentWidget } from '@projectstorm/react-diagrams-defaults'; import { Point } from '@projectstorm/geometry'; import { MouseEvent } from 'react'; import { RightAngleLinkModel } from './RightAngleLinkModel'; export interface RightAngleLinkProps { color?: string; width?: number; smooth?: boolean; link: RightAngleLinkModel; diagramEngine: DiagramEngine; factory: RightAngleLinkFactory; } export interface RightAngleLinkState { selected: boolean; canDrag: boolean; } export class RightAngleLinkWidget extends React.Component { public static defaultProps: RightAngleLinkProps = { color: 'red', width: 3, link: null, smooth: false, diagramEngine: null, factory: null }; refPaths: React.RefObject[]; // DOM references to the label and paths (if label is given), used to calculate dynamic positioning refLabels: { [id: string]: HTMLElement }; dragging_index: number; constructor(props: RightAngleLinkProps) { super(props); this.refPaths = []; this.state = { selected: false, canDrag: false }; this.dragging_index = 0; } componentDidUpdate(): void { this.props.link.setRenderedPaths( this.refPaths.map((ref) => { return ref.current; }) ); } componentDidMount(): void { this.props.link.setRenderedPaths( this.refPaths.map((ref) => { return ref.current; }) ); } componentWillUnmount(): void { this.props.link.setRenderedPaths([]); } generateLink(path: string, extraProps: any, id: string | number): JSX.Element { const ref = React.createRef(); this.refPaths.push(ref); return ( { this.setState({ selected: selected }); }} extras={extraProps} /> ); } calculatePositions(points: PointModel[], event: MouseEvent, index: number, coordinate: string) { // If path is first or last add another point to keep node port on its position if (index === 0) { let point = new PointModel({ link: this.props.link, position: new Point(points[index].getX(), points[index].getY()) }); this.props.link.addPoint(point, index); this.dragging_index++; return; } else if (index === points.length - 2) { let point = new PointModel({ link: this.props.link, position: new Point(points[index + 1].getX(), points[index + 1].getY()) }); this.props.link.addPoint(point, index + 1); return; } // Merge two points if it is not close to node port and close to each other if (index - 2 > 0) { let _points = { [index - 2]: points[index - 2].getPosition(), [index + 1]: points[index + 1].getPosition(), [index - 1]: points[index - 1].getPosition() }; if (Math.abs(_points[index - 1][coordinate] - _points[index + 1][coordinate]) < 5) { _points[index - 2][coordinate] = this.props.diagramEngine.getRelativeMousePoint(event)[coordinate]; _points[index + 1][coordinate] = this.props.diagramEngine.getRelativeMousePoint(event)[coordinate]; points[index - 2].setPosition(_points[index - 2]); points[index + 1].setPosition(_points[index + 1]); points[index - 1].remove(); points[index - 1].remove(); this.dragging_index--; this.dragging_index--; return; } } // Merge two points if it is not close to node port if (index + 2 < points.length - 2) { let _points = { [index + 3]: points[index + 3].getPosition(), [index + 2]: points[index + 2].getPosition(), [index + 1]: points[index + 1].getPosition(), [index]: points[index].getPosition() }; if (Math.abs(_points[index + 1][coordinate] - _points[index + 2][coordinate]) < 5) { _points[index][coordinate] = this.props.diagramEngine.getRelativeMousePoint(event)[coordinate]; _points[index + 3][coordinate] = this.props.diagramEngine.getRelativeMousePoint(event)[coordinate]; points[index].setPosition(_points[index]); points[index + 3].setPosition(_points[index + 3]); points[index + 1].remove(); points[index + 1].remove(); return; } } // If no condition above handled then just update path points position let _points = { [index]: points[index].getPosition(), [index + 1]: points[index + 1].getPosition() }; _points[index][coordinate] = this.props.diagramEngine.getRelativeMousePoint(event)[coordinate]; _points[index + 1][coordinate] = this.props.diagramEngine.getRelativeMousePoint(event)[coordinate]; points[index].setPosition(_points[index]); points[index + 1].setPosition(_points[index + 1]); } draggingEvent(event: MouseEvent, index: number) { let points = this.props.link.getPoints(); // get moving difference. Index + 1 will work because links indexes has // length = points.lenght - 1 let dx = Math.abs(points[index].getX() - points[index + 1].getX()); let dy = Math.abs(points[index].getY() - points[index + 1].getY()); // moving with y direction if (dx === 0) { this.calculatePositions(points, event, index, 'x'); } else if (dy === 0) { this.calculatePositions(points, event, index, 'y'); } this.props.link.setFirstAndLastPathsDirection(); } handleMove = function (event: MouseEvent) { this.draggingEvent(event, this.dragging_index); }.bind(this); handleUp = function (event: MouseEvent) { // Unregister handlers to avoid multiple event handlers for other links this.setState({ canDrag: false, selected: false }); window.removeEventListener('mousemove', this.handleMove); window.removeEventListener('mouseup', this.handleUp); }.bind(this); render() { //ensure id is present for all points on the path let points = this.props.link.getPoints(); let paths = []; // Get points based on link orientation let pointLeft = points[0]; let pointRight = points[points.length - 1]; let hadToSwitch = false; if (pointLeft.getX() > pointRight.getX()) { pointLeft = points[points.length - 1]; pointRight = points[0]; hadToSwitch = true; } let dy = Math.abs(points[0].getY() - points[points.length - 1].getY()); // When new link add one middle point to get everywhere 90° angle if (this.props.link.getTargetPort() === null && points.length === 2) { [...Array(2)].forEach((item) => { this.props.link.addPoint( new PointModel({ link: this.props.link, position: new Point(pointLeft.getX(), pointRight.getY()) }), 1 ); }); this.props.link.setManuallyFirstAndLastPathsDirection(true, true); } // When new link is moving and not connected to target port move with middle point // TODO: @DanielLazarLDAPPS This will be better to update in DragNewLinkState // in function fireMouseMoved to avoid calling this unexpectedly e.g. after Deserialize else if (this.props.link.getTargetPort() === null && this.props.link.getSourcePort() !== null) { points[1].setPosition( pointRight.getX() + (pointLeft.getX() - pointRight.getX()) / 2, !hadToSwitch ? pointLeft.getY() : pointRight.getY() ); points[2].setPosition( pointRight.getX() + (pointLeft.getX() - pointRight.getX()) / 2, !hadToSwitch ? pointRight.getY() : pointLeft.getY() ); } // Render was called but link is not moved but user. // Node is moved and in this case fix coordinates to get 90° angle. // For loop just for first and last path else if (!this.state.canDrag && points.length > 2) { // Those points and its position only will be moved for (let i = 1; i < points.length; i += points.length - 2) { if (i - 1 === 0) { if (this.props.link.getFirstPathXdirection()) { points[i].setPosition(points[i].getX(), points[i - 1].getY()); } else { points[i].setPosition(points[i - 1].getX(), points[i].getY()); } } else { if (this.props.link.getLastPathXdirection()) { points[i - 1].setPosition(points[i - 1].getX(), points[i].getY()); } else { points[i - 1].setPosition(points[i].getX(), points[i - 1].getY()); } } } } // If there is existing link which has two points add one // NOTE: It doesn't matter if check is for dy or dx if (points.length === 2 && dy !== 0 && !this.state.canDrag) { this.props.link.addPoint( new PointModel({ link: this.props.link, position: new Point(pointLeft.getX(), pointRight.getY()) }) ); } for (let j = 0; j < points.length - 1; j++) { paths.push( this.generateLink( LinkWidget.generateLinePath(points[j], points[j + 1]), { 'data-linkid': this.props.link.getID(), 'data-point': j, onMouseDown: (event: MouseEvent) => { if (event.button === 0) { this.setState({ canDrag: true }); this.dragging_index = j; // Register mouse move event to track mouse position // On mouse up these events are unregistered check "this.handleUp" window.addEventListener('mousemove', this.handleMove); window.addEventListener('mouseup', this.handleUp); } }, onMouseEnter: (event: MouseEvent) => { this.setState({ selected: true }); this.props.link.lastHoverIndexOfPath = j; } }, j ) ); } this.refPaths = []; return {paths}; } }