tp-flow-nodes/tp-flow-node.js
2025-02-25 19:07:21 +01:00

199 lines
4.4 KiB
JavaScript

/**
@license
Copyright (c) 2024 trading_peter
This program is available under Apache License Version 2.0
*/
import './tp-flow-node-port.js';
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;
}
header {
display: flex;
align-items: center;
column-gap: 10px;
padding: 2px 5px;
}
.delete-btn {
cursor: pointer;
opacity: 0.7;
transition: opacity 0.2s;
}
.delete-btn:hover {
opacity: 1;
}
`
];
}
render() {
return html`
${this.renderNodeHeader()}
<div class="node-body">
<div class="input-ports">
${this.inputs.map((input, idx) => html`
<tp-flow-node-port class="port" exportparts="connectionPoint"
portType="input"
.portId=${idx}
.portName=${input.name}
.tagContent=${input.tagContent}
@mousedown="${this._handlePortClick}">
</tp-flow-node-port>
`)}
</div>
<div class="node-content" part="node-content">
${this.renderNodeContent()}
</div>
<div class="output-ports">
${this.outputs.map((output, idx) => html`
<tp-flow-node-port class="port" exportparts="connectionPoint"
portType="output"
.portId=${idx}
.portName=${output.name}
.tagContent=${output.tagContent}
@mousedown="${this._handlePortClick}">
</tp-flow-node-port>
`)}
</div>
</div>
`;
}
renderNodeHeader() {
return html`
<header drag-node>
A Node
<div class="delete-btn" @click="${this._handleDelete}">✕</div>
</header>
`;
}
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 = {};
}
updated(changes) {
super.updated(changes);
this.dispatchEvent(new CustomEvent('update-layout', { detail: this, bubbles: true, composed: true }));
}
_handlePortClick(e) {
e.stopPropagation(); // Prevents the event from getting caught by the panning action.
this.dispatchEvent(new CustomEvent('port-click', {
detail: { node: this, port: e.target },
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
}));
}
}