UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

176 lines (175 loc) 6.74 kB
import { computed, signal } from "@preact/signals-core"; import cloneDeep from "lodash/cloneDeep"; import { Block } from "../../components/canvas/blocks/Block"; import { ESelectionStrategy } from "../../services/selection/types"; import { createAnchorPortId, createBlockPointPortId } from "./port/utils"; export const IS_CONNECTION_TYPE = "Connection"; export class ConnectionState { get id() { return this.$state.value.id; } get sourceBlockId() { return this.$state.value.sourceBlockId; } get sourceAnchorId() { return this.$state.value.sourceAnchorId; } get targetBlockId() { return this.$state.value.targetBlockId; } get targetAnchorId() { return this.$state.value.targetAnchorId; } static getConnectionId(connection) { if (connection.id) return connection.id; if (connection.sourceAnchorId && connection.targetAnchorId) { return [connection.sourceAnchorId, connection.targetAnchorId].join(":"); } return [connection.sourceBlockId, connection.targetBlockId].join(":"); } constructor(store, connectionState, connectionSelectionBucket) { this.store = store; this.connectionSelectionBucket = connectionSelectionBucket; this.$state = signal(undefined); this.isDestroyed = false; this.$sourcePortId = computed(() => { if (this.$state.value.sourcePortId) { return this.$state.value.sourcePortId; } if (this.$state.value.sourceAnchorId) { return createAnchorPortId(this.$state.value.sourceBlockId, this.$state.value.sourceAnchorId); } return createBlockPointPortId(this.$state.value.sourceBlockId, false); }); this.$targetPortId = computed(() => { if (this.$state.value.targetPortId) { return this.$state.value.targetPortId; } if (this.$state.value.targetAnchorId) { return createAnchorPortId(this.$state.value.targetBlockId, this.$state.value.targetAnchorId); } return createBlockPointPortId(this.$state.value.targetBlockId, true); }); this.$sourcePortState = computed(() => { const portId = this.$sourcePortId.value; let port = this.store.getPort(portId); if (!port) { port = this.store.observePort(portId, this); } else if (!port.observers.has(this)) { port.addObserver(this); } return port; }); this.$targetPortState = computed(() => { const portId = this.$targetPortId.value; let port = this.store.getPort(portId); if (!port) { port = this.store.observePort(portId, this); } else if (!port.observers.has(this)) { port.addObserver(this); } return port; }); this.$sourcePort = computed(() => { return this.$sourcePortState.value.$state.value; }); this.$targetPort = computed(() => { return this.$targetPortState.value.$state.value; }); /* @deprecated use $sourcePortState instead */ this.$sourceBlock = computed(() => { if (this.$sourcePortState.value.component && this.$sourcePortState.value.component instanceof Block) { return this.$sourcePortState.value.component.connectedState; } return undefined; }); /* @deprecated use $targetPortState instead */ this.$targetBlock = computed(() => { if (this.$targetPortState.value.component && this.$targetPortState.value.component instanceof Block) { return this.$targetPortState.value.component.connectedState; } return undefined; }); this.$geometry = computed(() => { if (!this.$sourcePort.value.lookup && !this.$targetPort.value.lookup) { return [this.$sourcePort.value, this.$targetPort.value]; } return undefined; }); /** * Computed signal that reactively determines if this connection is selected * by checking if its ID exists in the selection bucket */ this.$selected = computed(() => { return this.connectionSelectionBucket.$selected.value.has(this.id); }); const id = ConnectionState.getConnectionId(connectionState); this.$state.value = { ...connectionState, id }; } /** * Sets the view component for this connection state * @param viewComponent - The BaseConnection component instance * @returns {void} */ setViewComponent(viewComponent) { this.viewComponent = viewComponent; } /** * Gets the view component associated with this connection state. * @returns The BaseConnection view component or undefined if not set. */ getViewComponent() { return this.viewComponent; } /** * Checks if the connection is currently selected. * @returns True if the connection is selected, false otherwise. */ isSelected() { return this.$state.value.selected; } setSelection(selected, strategy = ESelectionStrategy.REPLACE) { this.store.setConnectionsSelection([this.id], selected, strategy); } /** * @deprecated Use `toJSON` instead. * @returns {TConnection} A deep copy of the connection data */ asTConnection() { return cloneDeep(this.$state.toJSON()); } /** * Converts the connection state to a plain JSON object * @returns {TConnection} A deep copy of the connection data */ toJSON() { return cloneDeep(this.$state.toJSON()); } /** * Updates the connection with new data * @param connection - Partial connection data to update * @returns {void} */ updateConnection(connection) { const { styles, ...newProps } = connection; const newStyles = Object.assign({}, this.$state.value.styles, styles); this.$state.value = Object.assign({}, this.$state.value, newProps, { styles: newStyles }); } /** * Clean up port observers when connection is destroyed * @returns {void} */ destroy() { // Stop observing source port if (this.$sourcePortId.value) { this.store.unobservePort(this.$sourcePortId.value, this); } // Stop observing target port if (this.$targetPortId.value) { this.store.unobservePort(this.$targetPortId.value, this); } } }