@gravity-ui/graph
Version:
Modern graph editor component
124 lines (123 loc) • 4.57 kB
JavaScript
import { ESchedulerPriority } from "../../../lib";
import { ECameraScaleLevel } from "../../../services/camera/CameraService";
import { selectBlockAnchor } from "../../../store/block/selectors";
import { debounce } from "../../../utils/functions";
import { GraphComponent } from "../GraphComponent";
export class Anchor extends GraphComponent {
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.debouncedSetHitBox = debounce(() => {
const { x, y } = this.props.getPosition(this.props);
this.setHitBox(x - this.shift, y - this.shift, x + this.shift, y + this.shift);
}, {
priority: ESchedulerPriority.HIGHEST,
frameInterval: 4,
});
this.state = { size: props.size, raised: false, selected: false };
this.connectedState = selectBlockAnchor(this.context.graph, props.blockId, props.id);
this.connectedState.setViewComponent(this);
this.subscribeSignal(this.connectedState.$selected, (selected) => {
this.setState({ selected });
});
this.addEventListener("click", this);
this.addEventListener("mouseenter", this);
this.addEventListener("mousedown", this);
this.addEventListener("mouseleave", this);
this.computeRenderSize(this.props.size, this.state.raised);
this.shift = this.props.size / 2 + props.lineWidth;
}
getPosition() {
return this.props.getPosition(this.props);
}
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() {
super.unmount();
this.debouncedSetHitBox.cancel();
}
didIterate() {
const { x: poxX, y: posY } = this.props.getPosition(this.props);
const hash = `${poxX}/${posY}/${this.shift}`;
if (this.hitBoxHash !== hash) {
this.hitBoxHash = hash;
this.debouncedSetHitBox();
}
}
handleEvent(event) {
event.preventDefault();
event.stopPropagation();
switch (event.type) {
case "click": {
this.toggleSelected();
break;
}
case "mouseenter": {
this.setState({ raised: true });
this.computeRenderSize(this.props.size, true);
break;
}
case "mouseleave": {
this.setState({ raised: false });
this.computeRenderSize(this.props.size, false);
break;
}
}
}
computeRenderSize(size, raised) {
if (raised) {
this.setState({ size: size * 1.8 });
}
else {
this.setState({ size });
}
}
render() {
if (this.context.camera.getCameraBlockScaleLevel() === ECameraScaleLevel.Detailed) {
return;
}
const { x, y } = this.props.getPosition(this.props);
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();
}
}