UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

224 lines (223 loc) 8.15 kB
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(); } }