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