UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

109 lines (108 loc) 4.14 kB
import { Signal, computed } from "@preact/signals-core"; import { Layer } from "../../../services/Layer"; import { getUsableRectByBlockIds } from "../../../utils/functions"; import { Group } from "./Group"; export class BlockGroups extends Layer { static withBlockGrouping({ groupingFn, mapToGroups, }) { return class BlockGroupWithGrouping extends BlockGroups { constructor(props) { super(props); this.$groupsBlocksMap = computed(() => { const blocks = this.props.graph.rootStore.blocksList.$blocks.value; return groupingFn(blocks); }); this.unsubscribe.push(computed(() => { const groupedBlocks = this.$groupsBlocksMap.value; return Object.entries(groupedBlocks).map(([key, blocks]) => mapToGroups(key, { blocks, rect: getUsableRectByBlockIds(blocks) })); }).subscribe((groups) => { this.setGroups(groups); })); } }; } constructor(props) { super({ canvas: { zIndex: 1, classNames: ["no-user-select"], transformByCameraPosition: true, }, ...props, }); this.unsubscribe = []; this.$groupsBlocksMap = new Signal({}); this.$groupsSource = this.props.graph.rootStore.groupsList.$groups; this.updateBlocks = (groupId, { diffX, diffY }) => { if (this.props.updateBlocksOnDrag) { const blocks = this.$groupsBlocksMap.value[groupId]; if (blocks) { blocks.forEach((block) => { block.updateXY(block.x - diffX, block.y - diffY, true); }); } } }; const canvas = this.getCanvas(); this.setContext({ canvas, ctx: canvas.getContext("2d"), root: this.props.root, camera: this.props.camera, constants: this.props.graph.graphConstants, colors: this.props.graph.graphColors, graph: this.props.graph, ownerDocument: this.props.root, }); this.unsubscribe.push(this.$groupsSource.subscribe((groups) => { this.shouldUpdateChildren = true; this.shouldRenderChildren = true; this.setState({ groups }); })); this.performRender = this.performRender.bind(this); } /** * Called after initialization and when the layer is reattached. * This is where we set up event subscriptions to ensure they work properly * after the layer is unmounted and reattached. */ afterInit() { // Register event listener with the onGraphEvent method for automatic cleanup when unmounted this.onGraphEvent("camera-change", this.performRender); // Call parent afterInit to ensure proper initialization super.afterInit(); } getParent() { /* * Override parent to delegate click events to camera. * This allows camera movement when mouse button is held down. */ return this.props.graph.getGraphLayer().$.camera; } setGroups(groups) { this.props.graph.rootStore.groupsList.setGroups(groups); } updateGroups(groups) { this.props.graph.rootStore.groupsList.updateGroups(groups); } unmountLayer() { this.props.graph.rootStore.groupsList.reset(); } unmount() { this.unsubscribe.forEach((unsubscribe) => unsubscribe()); } getGroupComponent(group) { return group.$state.value.component || this.props.groupComponent || Group; } updateChildren() { return this.state.groups?.map((group) => { return this.getGroupComponent(group).create({ id: group.id, onDragUpdate: this.updateBlocks, draggable: this.props.draggable || false, }, { key: group.id }); }); } render() { this.resetTransform(); } }