183 lines
5.5 KiB
JavaScript
183 lines
5.5 KiB
JavaScript
|
import { html, css, svg } from 'lit';
|
||
|
|
||
|
export const connectionStyles = css`
|
||
|
.connections {
|
||
|
pointer-events: none;
|
||
|
}
|
||
|
|
||
|
.connections path {
|
||
|
transition: stroke 0.3s ease, stroke-width 0.3s ease;
|
||
|
}
|
||
|
|
||
|
.connections path:hover {
|
||
|
stroke: #999;
|
||
|
stroke-width: 3;
|
||
|
}
|
||
|
`;
|
||
|
|
||
|
export const connections = function(superClass) {
|
||
|
return class extends superClass {
|
||
|
constructor() {
|
||
|
super();
|
||
|
this.connections = [];
|
||
|
this.draggingConnection = null;
|
||
|
this.mousePosition = { x: 0, y: 0 };
|
||
|
}
|
||
|
|
||
|
firstUpdated() {
|
||
|
this.addEventListener('port-click', this._handlePortClick);
|
||
|
document.addEventListener('mousemove', this._updatePreviewConnection.bind(this));
|
||
|
super.firstUpdated();
|
||
|
}
|
||
|
|
||
|
_updatePreviewConnection(e) {
|
||
|
if (this.draggingConnection) {
|
||
|
this.mousePosition = {
|
||
|
x: e.clientX,
|
||
|
y: e.clientY
|
||
|
};
|
||
|
this.requestUpdate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_renderConnections() {
|
||
|
return html`
|
||
|
<svg class="connections" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;">
|
||
|
${this.connections.map(conn => this._renderConnection(conn))}
|
||
|
${this.draggingConnection ? this._renderPreviewConnection() : null}
|
||
|
</svg>
|
||
|
`;
|
||
|
}
|
||
|
|
||
|
_renderPreviewConnection() {
|
||
|
const sourceNode = this.nodes.find(node => node.id === this.draggingConnection.sourceNodeId);
|
||
|
if (!sourceNode) return '';
|
||
|
|
||
|
const sourcePort = sourceNode.shadowRoot.querySelector(
|
||
|
`.output-ports [data-port-id="${this.draggingConnection.sourcePortId}"]`
|
||
|
);
|
||
|
if (!sourcePort) return '';
|
||
|
|
||
|
const sourceRect = sourcePort.getBoundingClientRect();
|
||
|
const canvasRect = this.canvas.getBoundingClientRect();
|
||
|
|
||
|
const start = {
|
||
|
x: sourceRect.left + sourceRect.width/2 - canvasRect.left,
|
||
|
y: sourceRect.top + sourceRect.height/2 - canvasRect.top
|
||
|
};
|
||
|
|
||
|
const end = {
|
||
|
x: this.mousePosition.x - canvasRect.left,
|
||
|
y: this.mousePosition.y - canvasRect.top
|
||
|
};
|
||
|
|
||
|
const controlPoint1 = {
|
||
|
x: start.x + Math.min(100, Math.abs(end.x - start.x) / 2),
|
||
|
y: start.y
|
||
|
};
|
||
|
|
||
|
const controlPoint2 = {
|
||
|
x: end.x - Math.min(100, Math.abs(end.x - start.x) / 2),
|
||
|
y: end.y
|
||
|
};
|
||
|
|
||
|
const path = `M${start.x},${start.y} C${controlPoint1.x},${controlPoint1.y} ${controlPoint2.x},${controlPoint2.y} ${end.x},${end.y}`;
|
||
|
|
||
|
return svg`
|
||
|
<path
|
||
|
d="${path}"
|
||
|
fill="none"
|
||
|
stroke="#666"
|
||
|
stroke-width="2"
|
||
|
stroke-dasharray="5,5"
|
||
|
id="preview-connection"
|
||
|
/>
|
||
|
`;
|
||
|
}
|
||
|
|
||
|
_renderConnection(conn) {
|
||
|
const sourceNode = this.nodes.find(node => node.id === conn.sourceNodeId);
|
||
|
const targetNode = this.nodes.find(node => node.id === conn.targetNodeId);
|
||
|
|
||
|
if (!sourceNode || !targetNode) return '';
|
||
|
|
||
|
const sourcePort = sourceNode.shadowRoot.querySelector(`.output-ports [data-port-id="${conn.sourcePortId}"]`);
|
||
|
const targetPort = targetNode.shadowRoot.querySelector(`.input-ports [data-port-id="${conn.targetPortId}"]`);
|
||
|
|
||
|
if (!sourcePort || !targetPort) return '';
|
||
|
|
||
|
const sourceRect = sourcePort.getBoundingClientRect();
|
||
|
const targetRect = targetPort.getBoundingClientRect();
|
||
|
const canvasRect = this.canvas.getBoundingClientRect();
|
||
|
|
||
|
const start = {
|
||
|
x: sourceRect.left + sourceRect.width/2 - canvasRect.left,
|
||
|
y: sourceRect.top + sourceRect.height/2 - canvasRect.top
|
||
|
};
|
||
|
|
||
|
const end = {
|
||
|
x: targetRect.left + targetRect.width/2 - canvasRect.left,
|
||
|
y: targetRect.top + targetRect.height/2 - canvasRect.top
|
||
|
};
|
||
|
|
||
|
const controlPoint1 = {
|
||
|
x: start.x + Math.min(100, Math.abs(end.x - start.x) / 2),
|
||
|
y: start.y
|
||
|
};
|
||
|
|
||
|
const controlPoint2 = {
|
||
|
x: end.x - Math.min(100, Math.abs(end.x - start.x) / 2),
|
||
|
y: end.y
|
||
|
};
|
||
|
|
||
|
const path = `M${start.x},${start.y} C${controlPoint1.x},${controlPoint1.y} ${controlPoint2.x},${controlPoint2.y} ${end.x},${end.y}`;
|
||
|
|
||
|
return svg`
|
||
|
<path
|
||
|
d="${path}"
|
||
|
fill="none"
|
||
|
stroke="#666"
|
||
|
stroke-width="2"
|
||
|
id="${conn.id}"
|
||
|
/>
|
||
|
`;
|
||
|
}
|
||
|
|
||
|
_handlePortClick(e) {
|
||
|
const { nodeId, portType, portId, portName } = e.detail;
|
||
|
|
||
|
if (!this.draggingConnection) {
|
||
|
if (portType === 'output') {
|
||
|
this.draggingConnection = {
|
||
|
sourceNodeId: nodeId,
|
||
|
sourcePortId: portId,
|
||
|
sourcePortType: portType
|
||
|
};
|
||
|
}
|
||
|
} else {
|
||
|
if (portType === 'input' && nodeId !== this.draggingConnection.sourceNodeId) {
|
||
|
const connectionExists = this.connections.some(conn =>
|
||
|
conn.sourceNodeId === this.draggingConnection.sourceNodeId &&
|
||
|
conn.sourcePortId === this.draggingConnection.sourcePortId &&
|
||
|
conn.targetNodeId === nodeId &&
|
||
|
conn.targetPortId === portId
|
||
|
);
|
||
|
|
||
|
if (!connectionExists) {
|
||
|
const connection = {
|
||
|
id: `conn_${Date.now()}`,
|
||
|
sourceNodeId: this.draggingConnection.sourceNodeId,
|
||
|
sourcePortId: this.draggingConnection.sourcePortId,
|
||
|
targetNodeId: nodeId,
|
||
|
targetPortId: portId
|
||
|
};
|
||
|
|
||
|
this.connections = [...this.connections, connection];
|
||
|
}
|
||
|
}
|
||
|
this.draggingConnection = null;
|
||
|
this.requestUpdate();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|