UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

124 lines (123 loc) 4.57 kB
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(); } }