graflow 1.0
This commit is contained in:
17
buildoz.css
17
buildoz.css
@@ -186,4 +186,19 @@ bz-slidepane[side="right"] div.handle {
|
|||||||
background: repeating-linear-gradient( to right, rgba(255,255,255,1) 0, rgba(255,255,255,1) 2px, rgba(0,0,0,0.2) 3px, rgba(0,0,0,0.2) 4px );
|
background: repeating-linear-gradient( to right, rgba(255,255,255,1) 0, rgba(255,255,255,1) 2px, rgba(0,0,0,0.2) 3px, rgba(0,0,0,0.2) 4px );
|
||||||
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
159
bzGraflow.js
Normal 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)
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user