UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

167 lines (166 loc) 5.92 kB
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;