UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

126 lines (125 loc) 4.31 kB
import { computed, signal } from "@preact/signals-core"; import cloneDeep from "lodash/cloneDeep"; import { ESelectionStrategy } from "../../utils/types/types"; import { AnchorState } from "../anchor/Anchor"; export const IS_BLOCK_TYPE = "Block"; export class BlockState { static fromTBlock(store, block) { return new BlockState(store, block); } get id() { return this.$state.value.id; } get x() { return this.$state.value.x; } get y() { return this.$state.value.y; } get width() { return this.$state.value.width; } get height() { return this.$state.value.height; } get selected() { return this.$state.value.selected; } constructor(store, block) { this.store = store; this.$state = signal(undefined); this.$anchorStates = signal([]); this.$geometry = computed(() => { const state = this.$state.value; return { x: state.x | 0, y: state.y | 0, width: state.width, height: state.height, }; }); this.$anchorIndexs = computed(() => { const typeIndex = {}; return new Map(this.$anchorStates.value ?.sort((a, b) => (a.state.index || 0) - (b.state.index || 0)) .map((anchorState) => { if (!typeIndex[anchorState.state.type]) { typeIndex[anchorState.state.type] = 0; } return [anchorState.id, typeIndex[anchorState.state.type]++]; }) || []); }); this.$anchors = computed(() => { return this.$anchorStates.value?.map((anchorState) => anchorState.asTAnchor()) || []; }); this.$selectedAnchors = computed(() => { return this.$anchorStates.value?.filter((anchorState) => anchorState.$selected.value) || []; }); this.$state.value = block; this.$anchorStates.value = block.anchors?.map((anchor) => new AnchorState(this, anchor)) ?? []; } onAnchorSelected(anchorId, selected) { this.store.setAnchorSelection(this.id, anchorId, selected); } getSelectedAnchor() { return this.$selectedAnchors.value[0]; } getAnchorState(id) { return this.$anchorStates.value.find((state) => state.id === id); } updateXY(x, y, forceUpdate = false) { this.store.updatePosition(this.id, { x, y }); if (forceUpdate) { this.blockView.updatePosition(x, y, true); } } setViewComponent(blockComponent) { this.blockView = blockComponent; } getViewComponent() { return this.blockView; } getConnections() { return this.store.getBlockConnections(this.id); } setSelection(selected, strategy = ESelectionStrategy.REPLACE) { this.store.updateBlocksSelection([this.id], selected, strategy); } clearAnchorsSelection() { this.$anchorStates.value.forEach((anchor) => anchor.setSelection(false)); } setName(newName) { this.$state.value.name = newName; } updateAnchors(anchors) { const anchorsMap = new Map(this.$anchorStates.value.map((a) => [a.id, a])); this.$anchorStates.value = anchors.map((anchor) => { if (anchorsMap.has(anchor.id)) { const anchorState = anchorsMap.get(anchor.id); anchorState.update(anchor); return anchorState; } return new AnchorState(this, anchor); }); } updateBlock(block) { // Update anchors first to ensure they have correct state when geometry changes if (block.anchors) { this.updateAnchors(block.anchors); } this.$state.value = Object.assign({}, this.$state.value, block); this.getViewComponent()?.updateHitBox(this.$geometry.value, true); } getAnchorById(anchorId) { return this.$anchorStates.value.find((anchor) => anchor.id === anchorId); } asTBlock() { return cloneDeep(this.$state.toJSON()); } } export function mapToTBlock(blockState) { return blockState.asTBlock(); } export function mapToBlockId(blockState) { return blockState.id; }