@gravity-ui/graph
Version:
Modern graph editor component
167 lines (166 loc) • 5.92 kB
JavaScript
import { ECameraScaleLevel } from "../../../services/camera/CameraService";
import { selectBlockAnchor } from "../../../store/block/selectors";
import { GraphComponent } from "../GraphComponent";
export class Anchor extends GraphComponent {
getEntityId() {
return this.props.id;
}
get zIndex() {
// @ts-ignore this.__comp.parent instanceOf Block
return this.__comp.parent.zIndex + 1;
}
constructor(props, parent) {
super(props, parent);
this.cursor = "pointer";
this.shift = 0;
this.onPositionChanged = () => {
const { x, y } = this.getPosition();
this.setHitBox(x - this.shift, y - this.shift, x + this.shift, y + this.shift);
};
this.state = { size: props.size, raised: false, selected: false };
this.connectedState = selectBlockAnchor(this.context.graph, props.blockId, props.id);
this.connectedState.setViewComponent(this);
this.addEventListener("click", this);
this.addEventListener("mouseenter", this);
this.addEventListener("mousedown", this);
this.addEventListener("mouseleave", this);
this.computeRenderSize(this.props.size, this.state.raised);
}
getHoverFactor() {
if (this.context.camera.getCameraBlockScaleLevel() === ECameraScaleLevel.Detailed) {
return Anchor.DETAILED_HOVER_FACTOR;
}
return Anchor.CANVAS_HOVER_FACTOR;
}
stateChanged(_nextState) {
if (this.state.size !== _nextState.size) {
this.computeShift(_nextState, this.props);
this.onPositionChanged();
}
if (this.state.raised !== _nextState.raised) {
this.computeRenderSize(this.props.size, _nextState.raised);
}
super.stateChanged(_nextState);
}
propsChanged(_nextProps) {
if (this.props.lineWidth !== _nextProps.lineWidth) {
this.computeShift(this.state, _nextProps);
this.onPositionChanged();
}
super.propsChanged(_nextProps);
}
willMount() {
this.props.port.setOwner(this);
this.subscribeSignal(this.connectedState.$selected, (selected) => {
this.setState({ selected });
});
this.subscribeSignal(this.props.port.$point, this.onPositionChanged);
this.computeShift(this.state, this.props);
this.onPositionChanged();
super.willMount();
}
computeShift(state = this.state, props = this.props) {
this.shift = state.size / 2 + props.lineWidth;
}
getPorts() {
return [this.props.port];
}
/**
* Get the position of the anchor.
* Returns the position of the anchor in the coordinate system of the graph(ABSOLUTE).
*
* Example:
* ```ts
* const pos = anchor.getPosition(); // { x: 100, y: 100 }
* ```
* port.getPoint is used port.$state.value so you can use this method in signals effect and compute.
* ```ts
* computed(() => {
* return anchor.getPosition().x + 10; // { x: 110, y: 100 }
* });
* ```
* @returns The position of the anchor in the coordinate system of the graph(ABSOLUTE).
*/
getPosition() {
return this.props.port.getPoint();
}
toggleSelected() {
this.connectedState.setSelection(!this.state.selected);
}
/**
* Anchor is draggable only when connection creation is disabled.
* When connections can be created via anchors, dragging is handled by ConnectionLayer.
*/
isDraggable() {
// If connection creation via anchors is enabled, anchor is not draggable
// (ConnectionLayer handles the interaction instead)
if (this.context.graph.rootStore.settings.getConfigFlag("canCreateNewConnections")) {
return false;
}
// Otherwise, delegate drag to parent block
return true;
}
handleDragStart(context) {
this.connectedState.block.getViewComponent()?.handleDragStart(context);
}
handleDrag(diff, context) {
this.connectedState.block.getViewComponent()?.handleDrag(diff, context);
}
handleDragEnd(context) {
this.connectedState.block.getViewComponent()?.handleDragEnd(context);
}
isVisible() {
const params = this.getHitBox();
return params ? this.context.camera.isRectVisible(...params) : true;
}
unmount() {
this.props.port.removeOwner();
this.connectedState.unsetViewComponent();
super.unmount();
}
handleEvent(event) {
event.preventDefault();
event.stopPropagation();
switch (event.type) {
case "click": {
this.toggleSelected();
break;
}
case "mouseenter": {
this.setState({ raised: true });
break;
}
case "mouseleave": {
this.setState({ raised: false });
break;
}
}
}
computeRenderSize(size, raised) {
if (raised) {
this.setState({ size: size * this.getHoverFactor() });
}
else {
this.setState({ size });
}
}
render() {
if (this.context.camera.getCameraBlockScaleLevel() === ECameraScaleLevel.Detailed) {
return;
}
const { x, y } = this.getPosition();
const ctx = this.context.ctx;
ctx.fillStyle = this.context.colors.anchor.background;
ctx.beginPath();
ctx.arc(x, y, this.state.size * 0.5, 0, 2 * Math.PI);
ctx.fill();
if (this.state.selected) {
ctx.strokeStyle = this.context.colors.anchor.selectedBorder;
ctx.lineWidth = this.props.lineWidth + 3;
ctx.stroke();
}
ctx.closePath();
}
}
Anchor.CANVAS_HOVER_FACTOR = 1.8;
Anchor.DETAILED_HOVER_FACTOR = 1.2;