UNPKG

@selenite/graph-editor

Version:

A graph editor for visual programming, based on rete and svelte.

98 lines (97 loc) 3.88 kB
import { BaseAreaPlugin } from 'rete-area-plugin'; import { SocketsPositionsStorage } from './storage'; import { EventEmitter } from './utils'; /** * Abstract class for socket position calculation. It can be extended to implement custom socket position calculation. * @abstract * @listens render * @listens rendered * @listens unmount * @listens nodetranslated * @listens noderesized */ export class BaseSocketPosition { sockets = new SocketsPositionsStorage(); emitter = new EventEmitter(); area = null; /** * Attach the watcher to the area's child scope. * @param scope Scope of the watcher that should be a child of `BaseAreaPlugin` */ attach(scope) { if (this.area) return; if (!scope.hasParent()) return; this.area = scope.parentScope(BaseAreaPlugin); // eslint-disable-next-line max-statements, complexity this.area.addPipe(async (context) => { if (context.type === 'rendered' && context.data.type === 'socket') { const { nodeId, key, side, element } = context.data; const position = await this.calculatePosition(nodeId, side, key, element); if (position) { this.sockets.add({ nodeId, key, side, element, position }); this.emitter.emit({ nodeId, key, side }); } } else if (context.type === 'unmount') { this.sockets.remove(context.data.element); } else if (context.type === 'nodetranslated') { this.emitter.emit({ nodeId: context.data.id }); } else if (context.type === 'noderesized') { const { id: nodeId } = context.data; await Promise.all(this.sockets .snapshot() .filter((item) => item.nodeId === context.data.id) .map(async (item) => { const { side, key, element } = item; const position = await this.calculatePosition(nodeId, side, key, element); if (position) { item.position = position; } })); this.emitter.emit({ nodeId }); } else if (context.type === 'render' && context.data.type === 'connection') { const { source, target } = context.data.payload; const nodeId = source || target; this.emitter.emit({ nodeId }); } return context; }); } /** * Listen to socket position changes. Usually used by rendering plugins to update the start/end of the connection. * @internal * @param nodeId Node ID * @param side Side of the socket, 'input' or 'output' * @param key Socket key * @param change Callback function that is called when the socket position changes */ listen(nodeId, side, key, change) { const unlisten = this.emitter.listen((data) => { if (data.nodeId !== nodeId) return; if ((!data.key || data.side === side) && (!data.side || data.key === key)) { const position = this.sockets.getPosition({ side, nodeId, key }); if (!position) return; const { x, y } = position; const nodeView = this.area?.nodeViews.get(nodeId); if (nodeView) change({ x: x + nodeView.position.x, y: y + nodeView.position.y }); } }); this.sockets.snapshot().forEach((data) => { if (data.nodeId === nodeId) this.emitter.emit(data); }); return unlisten; } }