@gravity-ui/graph
Version:
Modern graph editor component
224 lines (223 loc) • 8.15 kB
JavaScript
import { computed, signal } from "@preact/signals-core";
import { BaseConnection } from "../../components/canvas/connections";
import { MultipleSelectionBucket } from "../../services/selection/MultipleSelectionBucket";
import { ESelectionStrategy } from "../../services/selection/types";
import { ConnectionState } from "./ConnectionState";
import { PortsStore } from "./port/PortList";
export class ConnectionsStore {
constructor(rootStore, graph) {
this.rootStore = rootStore;
this.graph = graph;
this.$connections = computed(() => {
return Array.from(this.$connectionsMap.value.values());
});
this.$connectionsMap = signal(new Map());
/**
* @deprecated Use connectionSelectionBucket.$selectedEntities instead
* Computed signal that returns selected connections as ConnectionState instances
*/
this.$selectedConnections = computed(() => {
return this.connectionSelectionBucket.$selectedEntities.value;
});
/**
* Computed signal that returns selected connections as BaseConnection GraphComponent instances
* Automatically resolves ConnectionState to BaseConnection components via getViewComponent()
* Use this when you need to work with rendered Connection components
*/
this.$selectedConnectionComponents = computed(() => {
// Use the built-in $selectedComponents from BaseSelectionBucket
return this.connectionSelectionBucket.$selectedComponents.value;
});
this.ports = new PortsStore(this.rootStore, this.graph);
// Create and register a selection bucket for connections
this.connectionSelectionBucket = new MultipleSelectionBucket("connection", (payload, defaultAction) => {
return this.graph.executеDefaultEventAction("connection-selection-change", payload, defaultAction);
}, (element) => element instanceof BaseConnection, (ids) => ids.map((id) => this.getConnectionState(id)).filter((conn) => conn !== undefined));
this.connectionSelectionBucket.attachToManager(this.rootStore.selectionService);
}
/**
* Claim ownership of a port (for Blocks, Anchors)
* @param id Port identifier
* @param owner Component that will own this port
* @returns The port state
*/
claimPort(id, owner) {
const port = this.ports.getOrCreatePort(id);
port.setOwner(owner);
return port;
}
/**
* Release ownership of a port (when Block/Anchor is destroyed)
* @param id Port identifier
* @param owner Component that currently owns this port
*/
releasePort(id, owner) {
const port = this.ports.getPort(id);
if (port?.owner === owner) {
port.removeOwner();
this.checkAndDeletePort(id);
}
}
/**
* Start observing a port (for Connections)
* @param id Port identifier
* @param observer The object that will observe this port
* @returns The port state
*/
observePort(id, observer) {
const port = this.ports.getOrCreatePort(id);
port.addObserver(observer);
return port;
}
/**
* Stop observing a port (when Connection is destroyed)
* @param id Port identifier
* @param observer The object that was observing this port
*/
unobservePort(id, observer) {
const port = this.ports.getPort(id);
if (port) {
port.removeObserver(observer);
this.checkAndDeletePort(id);
}
}
/**
* Get a port by its ID
* @param id Port identifier
* @returns The port state if it exists
*/
getPort(id) {
return this.ports.getPort(id);
}
/**
* Check if a port exists
* @param id Port identifier
* @returns true if port exists
*/
hasPort(id) {
return this.ports.getPort(id) !== undefined;
}
/**
* Check if a port can be deleted and delete it if possible
* @param id Port identifier
*/
checkAndDeletePort(id) {
const port = this.ports.getPort(id);
if (port && port.canBeDeleted()) {
this.ports.deletePort(id);
}
}
deletePorts(ports) {
this.ports.deletePorts(ports);
}
setSelection(connection, selected, params) {
const state = connection instanceof ConnectionState ? connection : this.$connectionsMap.value.get(connection);
if (state) {
if (selected !== Boolean(state.$state.value.selected)) {
if (!params?.ignoreChange) {
state.updateConnection({
selected,
});
}
return true;
}
}
return false;
}
updateConnections(connections) {
this.$connectionsMap.value = connections.reduce((acc, connection) => {
const c = this.getOrCreateConnection(connection);
acc.set(c.id, c);
return acc;
}, this.$connectionsMap.value);
}
setConnections(connections) {
this.$connectionsMap.value = new Map(connections.map((connection) => {
const c = this.getOrCreateConnection(connection);
return [c.id, c];
}));
}
getOrCreateConnection(connections) {
const id = ConnectionState.getConnectionId(connections);
if (this.$connectionsMap.value.has(id)) {
const c = this.$connectionsMap.value.get(id);
c.updateConnection(connections);
return c;
}
return new ConnectionState(this, connections, this.connectionSelectionBucket);
}
addConnection(connection) {
const newConnection = new ConnectionState(this, connection, this.connectionSelectionBucket);
this.$connectionsMap.value.set(newConnection.id, newConnection);
this.notifyConnectionMapChanged();
return newConnection.id;
}
notifyConnectionMapChanged() {
this.$connectionsMap.value = new Map(this.$connectionsMap.value);
}
deleteConnections(connections) {
connections.forEach((c) => {
c.destroy(); // Clean up port observers
this.$connectionsMap.value.delete(c.id);
});
this.notifyConnectionMapChanged();
}
deleteSelectedConnections() {
this.$connections.value.forEach((c) => {
if (c.$state.value.selected) {
c.destroy(); // Clean up port observers
this.$connectionsMap.value.delete(c.id);
}
});
this.notifyConnectionMapChanged();
}
/**
* Updates connection selection using the SelectionService
* @param ids Connection IDs to update selection for
* @param selected Whether to select or deselect
* @param strategy The selection strategy to apply
*/
setConnectionsSelection(ids, selected, strategy = ESelectionStrategy.REPLACE) {
if (selected) {
this.connectionSelectionBucket.select(ids, strategy);
}
else {
this.connectionSelectionBucket.deselect(ids);
}
}
/**
* Resets the selection for connections
*
* @returns {void}
*/
resetSelection() {
this.connectionSelectionBucket.reset();
}
getConnections(ids) {
if (!ids || !ids.length) {
return this.$connections.value;
}
const map = this.$connectionsMap.value;
return ids.map((id) => map.get(id)).filter(Boolean);
}
getConnectionState(id) {
return this.$connectionsMap.value.get(id);
}
getConnection(id) {
return this.getConnectionState(id)?.toJSON();
}
getConnectionStates(ids) {
return ids.map((id) => this.getConnectionState(id)).filter(Boolean);
}
toJSON() {
return this.$connections.value.map((c) => c.toJSON());
}
reset() {
// Clean up all connections first
this.$connections.value.forEach((c) => {
c.destroy();
});
this.setConnections([]);
this.ports.reset();
}
}