UNPKG

peepee

Version:

Visual Programming Language Where You Connect Ports Of One EventEmitter to Ports Of Another EventEmitter

165 lines (114 loc) 5.63 kB
import { Plugin } from 'plugin'; export class ConnectionRenderPlugin extends Plugin { app; subscriptions; constructor() { super(); this.subscriptions = new Set(); } init(app) { this.app = app; this.svg = this.app.svg; this.layers = this.app.layers; this.workbenchPlugin = this.app.plugins.get('WorkbenchPlugin'); this.engine = this.workbenchPlugin.engine; this.stationManager = app.plugins.get('StationManagerPlugin'); this.stationInstances = this.stationManager.stationInstances; this.portManager = app.plugins.get('PortManagerPlugin'); this.portInstances = this.portManager.portInstances; this.recordsManager = app.plugins.get('RecordsManagerPlugin'); this.recordInstances = this.recordsManager.recordInstances; this.colorManagerPlugin = app.plugins.get('ColorManagerPlugin'); this.primaryColorTransform = this.colorManagerPlugin.primaryColorTransform; this.database = app.plugins.get('DatabasePlugin'); this.app.on("connectionAdded", (connection) => this.renderConnection(connection)); this.app.on("connectionRestored", (connection) => this.renderConnection(connection)); this.app.on("connectionRemoved", (id) => this.removeConnection(id)); // this.app.on("stationRemoved", (id) => this.destroyPorts(id)); } stop() { for (const unsubscribe of this.subscriptions) unsubscribe(); this.subscriptions.clear(); } removeConnection(id) { const path = this.svg.querySelector(`.connection-path[data-connection-id="${id}"]`); const labels = this.svg.querySelectorAll(`.connection-label[data-connection-id="${id}"]`); if (path) path.remove(); labels.forEach((el) => el.remove()); } async renderConnection(connection) { const fromPort = this.portInstances.get(connection.fromPortId); const toPort = this.portInstances.get(connection.toPortId); if (!fromPort) throw new Error(`fromPort not found in portInstances (${connection.fromPortId})`); if (!toPort) throw new Error(`toPort not found in portInstances (${connection.toPortId})`); let record = this.recordInstances.get(connection.id); if(!record){ await this.app.until('recordAdded', connection.id); record = this.recordInstances.get(connection.id); } const pathId = `path-${connection.id}`; // Create path const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); this.tuneIn(record.get('color', 'green'), v=>path.style.stroke = this.primaryColorTransform(v) ) path.setAttribute("id", pathId); path.setAttribute("class", "connection-path"); path.setAttribute("data-connection-id", connection.id); const updatePath = () => { path.setAttribute("d", `M ${fromPort.x.value},${fromPort.y.value} L ${toPort.x.value},${toPort.y.value}`); }; updatePath(); // Create label elements const createDynamicLabel = (initial, anchor) => { const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); text.setAttribute("class", `connection-label connection-label-${anchor}`); text.setAttribute("data-connection-id", connection.id); text.setAttribute("text-anchor", anchor); const textPath = document.createElementNS("http://www.w3.org/2000/svg", "textPath"); textPath.setAttributeNS("http://www.w3.org/1999/xlink", "href", `#${pathId}`); textPath.textContent = initial; text.appendChild(textPath); return { text, textPath }; }; const { text: startText, textPath: startTP } = createDynamicLabel(connection.startLabel ?? "", "start"); const { text: midText, textPath: midTP } = createDynamicLabel(connection.centerLabel ?? "", "middle"); const { text: endText, textPath: endTP } = createDynamicLabel(connection.endLabel ?? "", "end"); endText.style.textAnchor = "end"; // Add to DOM this.layers.connections.appendChild(path); this.layers.labels.appendChild(startText); this.layers.labels.appendChild(midText); this.layers.labels.appendChild(endText); // Select connection this.listenTo(path, 'click', (e) => { e.stopPropagation(); this.app.emit("selectConnection", connection); }); const updateLabels = () => { requestAnimationFrame(() => { const totalLength = path.getTotalLength(); const aestheticSidePadding = 18; startTP.setAttribute("startOffset", `${Math.max(0, aestheticSidePadding)}px`); midTP.setAttribute("startOffset", `50%`); endTP.setAttribute("startOffset", `${Math.max(0, totalLength - aestheticSidePadding)}px`); }); }; const updateAll = () => { updatePath(); updateLabels(); }; // Subscribe position changes this.tuneIn(fromPort.x, updateAll); this.tuneIn(fromPort.y, updateAll); this.tuneIn(toPort.x, updateAll); this.tuneIn(toPort.y, updateAll); // Text updates // this.tuneIn(connection.signals.centerLabel, (text) => { midTP.textContent = text; updateLabels(); }); // this.tuneIn(connection.signals.startLabel, (text) => { startTP.textContent = text; updateLabels(); }); // this.tuneIn(connection.signals.endLabel, (text) => { endTP.textContent = text; updateLabels(); }); this.tuneIn(record.get('centerLabel', ''), (text) => { midTP.textContent = text; updateLabels(); }) this.tuneIn(record.get('startLabel', ''), (text) => { startTP.textContent = text; updateLabels(); }) this.tuneIn(record.get('endLabel', ''), (text) => { endTP.textContent = text; updateLabels(); }) // Initial offset position updateLabels(); } }