@gravity-ui/graph
Version:
Modern graph editor component
126 lines (125 loc) • 4.31 kB
JavaScript
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;
}