graflow 1.0

This commit is contained in:
STEINNI
2025-12-22 16:38:45 +00:00
parent b07aa583b5
commit 5b00367dd6
2 changed files with 175 additions and 1 deletions

View File

@@ -187,3 +187,18 @@ bz-slidepane[side="right"] div.handle {
transform: translateY(-50%); transform: translateY(-50%);
cursor: ew-resize; cursor: ew-resize;
} }
bz-graflow {
position: relative;
display: block;
width: 100vw;
height: 100vh;
}
bz-graflow .bzgf-wires-container,
bz-graflow .bzgf-nodes-container{
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
bz-graflow .bzgf-nodes-container{ z-index:10; }
bz-graflow .bzgf-wires-container{ z-index:9; }

159
bzGraflow.js Normal file
View File

@@ -0,0 +1,159 @@
class BZgraflow extends Buildoz{
constructor(){
super()
this.defaultAttrs = { }
this.stagedNodes = { }
this.stagedWires = { }
}
static _loadedNodeStyles = new Set() // Allow multi instances or re-loadNodes, but avoid reinjecting same styles !
connectedCallback() {
super.connectedCallback()
const flowUrl = this.getBZAttribute('flow')
if(!flowUrl) {
console.warn('BZgraflow: No flow URL !?')
return
}
this.loadFlow(flowUrl) // Let it load async while we coat
this.nodesContainer = document.createElement('div')
this.nodesContainer.classList.add('bzgf-nodes-container')
this.wiresContainer = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
this.wiresContainer.classList.add('bzgf-wires-container')
this.append(this.wiresContainer)
this.append(this.nodesContainer)
}
async loadFlow(url){
const res = await fetch(url+'?'+crypto.randomUUID())
const buf = await res.text()
let flowObj
try{
flowObj = JSON.parse(buf)
} catch(err){
console.error('Could not parse flow JSON!?', err)
return
}
if(!flowObj.nodesFile){
console.error('No nodesFile in JSON!?')
return
}
await this.loadNodes(flowObj.nodesFile)
this.flow = flowObj.flow
this.refresh()
}
async loadNodes(url) {
const res = await fetch(url+'?'+crypto.randomUUID())
const html = await res.text()
// Get nodes
const doc = new DOMParser().parseFromString(html, 'text/html')
this.nodesRegistry = {}
for(const tpl of doc.querySelectorAll('template')){
const rootEl = tpl.content.querySelector('.bzgf-node')
if(!rootEl) continue
this.nodesRegistry[rootEl.dataset.nodetype] = rootEl
}
// Now load styles (once)
if(!BZgraflow._loadedNodeStyles.has(url)) {
const styles = doc.querySelectorAll('style')
styles.forEach(styleEl => {
const style = document.createElement('style')
style.textContent = styleEl.textContent
document.head.appendChild(style)
})
BZgraflow._loadedNodeStyles.add(url)
}
}
addNode(type, id, x, y){
const nodeDef = this.nodesRegistry[type]
this.stagedNodes[id] = nodeDef.cloneNode(true)
this.stagedNodes[id].style.left = `${x}px`
this.stagedNodes[id].style.top = `${y}px`
this.stagedNodes[id].dataset.id = id
const portEls = this.stagedNodes[id].querySelectorAll('.port')
this.stagedNodes[id].ports = Object.fromEntries(Array.from(portEls).map(item => ([item.dataset.id, { ...item.dataset, el:item }])))
this.nodesContainer.append(this.stagedNodes[id])
return(this.stagedNodes[id])
}
addWire(idNode1, idPort1, idNode2, idPort2){
const node1 = this.stagedNodes[idNode1]
const port1 = node1.ports[idPort1]
const node2 = this.stagedNodes[idNode2]
const port2 = node2.ports[idPort2]
const id = `${node1.dataset.id}_${node2.dataset.id}`
const bb1 = port1.el.getBoundingClientRect()
const bb2 = port2.el.getBoundingClientRect()
const x1 = Math.floor(bb1.x + (bb1.width/2))
const y1 = Math.floor(bb1.y + (bb1.height/2))
const x2 = Math.floor(bb2.x + (bb2.width/2))
const y2 = Math.floor(bb2.y + (bb2.height/2))
console.log('====>', x1, y1, x2, y2)
this.stagedWires[id] = document.createElementNS('http://www.w3.org/2000/svg', 'path')
this.stagedWires[id].setAttribute('d', this.bezier(x1, y1, port1.direction , x2, y2, port2.direction, 60))
this.stagedWires[id].setAttribute('fill', 'none')
this.stagedWires[id].classList.add('bzgf-wire')
this.stagedWires[id].dataset.id = id
this.wiresContainer.append(this.stagedWires[id])
return(this.stagedWires[id])
}
refresh(){
let x = 0
let y = 0
for(const node of this.flow.nodes){
const nodeEl = this.addNode(node.nodeType, node.id , node.coords.x, node.coords.y)
}
for(const link of this.flow.links){
const [nodeId1, portId1] = link.from
const [nodeId2, portId2] = link.to
this.addWire(nodeId1, portId1, nodeId2, portId2)
}
}
bezier(x1, y1, dir1, x2, y2, dir2, tensionMin=60) {
const dirVect = {
n: { x: 0, y: -1 },
s: { x: 0, y: 1 },
e: { x: 1, y: 0 },
w: { x: -1, y: 0 },
}
const dist = Math.abs(x2 - x1) + Math.abs(y2 - y1)
let tension = dist * 0.4
if (tension < tensionMin) tension = tensionMin
const c1x = x1 + (dirVect[dir1].x * tension)
const c1y = y1 + (dirVect[dir1].y * tension)
const c2x = x2 + (dirVect[dir2].x * tension)
const c2y = y2 + (dirVect[dir2].y * tension)
return `M ${x1} ${y1} C ${c1x} ${c1y}, ${c2x} ${c2y}, ${x2} ${y2}`
}
/*
portPosition(nodeEl, portEl) {
const nodeRect = nodeEl.getBoundingClientRect()
const portRect = portEl.getBoundingClientRect()
const canvasRect = this.canvas.getBoundingClientRect()
return {
x: portRect.left - canvasRect.left + portRect.width / 2,
y: portRect.top - canvasRect.top + portRect.height / 2
}
}
*/
}
Buildoz.define('graflow', BZgraflow)