@gravity-ui/graph
Version:
Modern graph editor component
145 lines (144 loc) • 4.99 kB
JavaScript
import { ESelectionStrategy } from "../../../services/selection/types";
import { isAllowDrag, isMetaKeyEvent } from "../../../utils/functions";
import { layoutText } from "../../../utils/renderers/text";
import { GraphComponent } from "../GraphComponent";
const defaultStyle = {
background: "rgba(100, 100, 100, 0.1)",
border: "rgba(100, 100, 100, 0.3)",
borderWidth: 2,
selectedBackground: "rgba(100, 100, 100, 1)",
selectedBorder: "rgba(100, 100, 100, 1)",
};
const defaultGeometry = {
padding: [20, 20, 20, 20],
};
export class Group extends GraphComponent {
static define(config) {
return class SpecificGroup extends Group {
constructor(props, parent) {
super({
...props,
style: {
...defaultStyle,
...config.style,
...props.style,
},
geometry: {
...defaultGeometry,
...config.geometry,
...props.geometry,
},
}, parent);
}
};
}
get zIndex() {
return 0;
}
constructor(props, parent) {
super(props, parent);
this.blocks = [];
this.cursor = "pointer";
this.style = {
...defaultStyle,
...props.style,
};
this.geometry = {
...defaultGeometry,
...props.geometry,
};
this.subscribeToGroup();
this.addEventListener("click", (event) => {
event.stopPropagation();
this.groupState.setSelection(true, !isMetaKeyEvent(event) ? ESelectionStrategy.REPLACE : ESelectionStrategy.APPEND);
});
}
getEntityId() {
return this.props.id;
}
/**
* Check if group can be dragged based on props.draggable and canDrag setting
*/
isDraggable() {
const canDrag = this.context.graph.rootStore.settings.$canDrag.value;
return Boolean(this.props.draggable) && isAllowDrag(canDrag, this.state.selected);
}
/**
* Handle drag start - nothing special needed, DragService handles autopanning and cursor
*/
handleDragStart(_context) {
// DragService handles autopanning and cursor locking
}
/**
* Handle drag update - update group rect and notify via callback
*/
handleDrag(diff, _context) {
const rect = {
x: this.state.rect.x + diff.deltaX,
y: this.state.rect.y + diff.deltaY,
width: this.state.rect.width,
height: this.state.rect.height,
};
this.setState({
rect,
});
this.updateHitBox(rect);
this.props.onDragUpdate(this.props.id, { deltaX: diff.deltaX, deltaY: diff.deltaY });
}
/**
* Handle drag end - nothing special needed, DragService handles cleanup
*/
handleDragEnd(_context) {
// DragService handles autopanning disable and cursor unlock
}
getRect(rect = this.state.rect) {
const [paddingTop, paddingRight, paddingBottom, paddingLeft] = this.geometry.padding;
return {
x: rect.x - paddingLeft,
y: rect.y - paddingTop,
width: rect.width + paddingLeft + paddingRight,
height: rect.height + paddingTop + paddingBottom,
};
}
subscribeToGroup() {
this.groupState = this.context.graph.rootStore.groupsList.getGroupState(this.props.id);
this.subscribeSignal(this.groupState.$selected, (selected) => {
this.setState({
selected,
});
});
return this.subscribeSignal(this.groupState.$state, (group) => {
if (group) {
this.setState({
...this.state,
...group,
});
this.updateHitBox(this.getRect(group.rect));
}
});
}
updateHitBox(rect) {
this.setHitBox(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
}
layoutText(text, textParams) {
const currentRect = this.getRect();
return layoutText(text, this.context.ctx, currentRect, {
maxWidth: currentRect.width,
maxHeight: currentRect.height,
...textParams,
});
}
renderBody(ctx, rect = this.getRect()) {
ctx.strokeStyle = this.state.selected ? this.style.selectedBorder : this.style.border;
ctx.fillStyle = this.state.selected ? this.style.selectedBackground : this.style.background;
ctx.lineWidth = this.style.borderWidth;
// Рисуем прямоугольник группы
ctx.beginPath();
ctx.roundRect(rect.x, rect.y, rect.width, rect.height, 8);
ctx.fill();
ctx.stroke();
}
render() {
this.renderBody(this.context.ctx, this.getRect());
}
}