tp-flow-nodes/tp-flow-node.js
2024-12-20 14:05:35 +01:00

199 lines
4.3 KiB
JavaScript

/**
@license
Copyright (c) 2024 trading_peter
This program is available under Apache License Version 2.0
*/
import { LitElement, html, css } from 'lit';
// tp-flow-node.js
export class TpFlowNode extends LitElement {
static get styles() {
return [
css`
:host {
display: block;
position: absolute;
background: #2b2b2b;
border-radius: 4px;
color: #fff;
min-width: 150px;
}
.node-body {
display: grid;
grid-template-columns: 20px 1fr 20px;
}
.node-content {
padding: 20px;
}
.node-ports {
justify-content: space-between;
padding: 8px;
}
.input-ports, .output-ports {
display: flex;
flex-direction: column;
gap: 8px;
align-items: flex-start;
justify-content: space-evenly;
}
.output-ports {
align-items: flex-end;
}
.port {
width: 12px;
height: 12px;
background: #666;
border-radius: 50%;
cursor: pointer;
}
.port:hover {
background: #888;
}
.delete-btn {
position: absolute;
top: 5px;
right: 5px;
width: 16px;
height: 16px;
cursor: pointer;
opacity: 0.7;
transition: opacity 0.2s;
}
.delete-btn:hover {
opacity: 1;
}
`
];
}
render() {
return html`
<div class="node-body">
<div class="delete-btn" @click="${this._handleDelete}">✕</div>
<div class="input-ports">
${this.inputs.map((input, idx) => html`
<div class="port"
data-port-type="input"
data-port-id="${idx}"
data-port-name="${input.name}"
@mousedown="${this._handlePortClick}">
</div>
`)}
</div>
<div class="node-content" part="node-content">
${this.renderNodeContent()}
</div>
<div class="output-ports">
${this.outputs.map((output, idx) => html`
<div class="port"
data-port-type="output"
data-port-id="${idx}"
data-port-name="${output.name}"
@mousedown="${this._handlePortClick}">
</div>
`)}
</div>
</div>
`;
}
renderNodeContent() {
console.warn('Your node should override the renderNodeContent method.');
return null;
}
static get properties() {
return {
inputs: { type: Array },
outputs: { type: Array },
x: { type: Number },
y: { type: Number },
data: { type: Object }
};
}
constructor() {
super();
this.inputs = [];
this.outputs = [];
this.x = 0;
this.y = 0;
this.data = {};
}
_handlePortClick(e) {
e.stopPropagation(); // Prevents the event from getting caught by the panning action.
const portEl = e.target;
const detail = {
nodeId: this.id,
portType: portEl.dataset.portType,
portId: portEl.dataset.portId,
portName: portEl.dataset.portName
};
this.dispatchEvent(new CustomEvent('port-click', {
detail,
bubbles: true,
composed: true
}));
}
_handleDelete(e) {
e.stopPropagation(); // Prevent event from triggering other handlers
this.dispatchEvent(new CustomEvent('node-delete-requested', {
detail: { nodeId: this.id },
bubbles: true,
composed: true
}));
}
exportData() {
// Get current transform
const transform = window.getComputedStyle(this).transform;
const matrix = new DOMMatrix(transform);
return {
id: this.id,
type: super.tagName.toLowerCase(),
position: {
x: matrix.m41,
y: matrix.m42
},
data: this.data
};
}
importData(data) {
this.id = data.id;
this.data = data.data;
// Apply position
if (data.position) {
this.style.transform = `translate(${data.position.x}px, ${data.position.y}px)`;
}
}
dispatchDataUpdate() {
this.dispatchEvent(new CustomEvent('flow-changed', {
detail: {
type: 'node-data-changed',
data: this.exportData()
},
bubbles: true,
composed: true
}));
}
}