graflow: entering subflow ok

This commit is contained in:
STEINNI
2026-02-22 20:58:35 +00:00
parent cee10fee4e
commit 17f150a930

View File

@@ -65,13 +65,6 @@ class BZgraflow extends Buildoz{
right: -1em;
color: #A00;
}
.bzgf-nodes-container .bzgf-node.scaler {
transform-origin: top left;
transform:
translate(var(--tx, 0), var(--ty, 0))
scale(var(--sx, 1), var(--sy, 1));
transition: transform 300ms ease-in-out;
}
`
this.mainContainer.appendChild(style)
this.nodesContainer = document.createElement('div')
@@ -159,18 +152,18 @@ class BZgraflow extends Buildoz{
this.stagedNodes[id].dataset.id = id
this.stagedNodes[id].ports = Object.fromEntries(Array.from(portEls).map(item => ([item.dataset.id, { ...item.dataset, el:item }])))
if(node.subflow) {
const btnZoomIn = document.createElement('button')
btnZoomIn.classList.add('bzgf-zoom-in', 'icon-copy')
btnZoomIn.addEventListener('click', () => {
this.zoomIn(id)
const btnEnterSubflow = document.createElement('button')
btnEnterSubflow.classList.add('bzgf-zoom-in', 'icon-copy')
btnEnterSubflow.addEventListener('click', () => {
this.EnterSubflow(id)
})
this.stagedNodes[id].appendChild(btnZoomIn)
this.stagedNodes[id].appendChild(btnEnterSubflow)
}
this.nodesContainer.append(this.stagedNodes[id])
return(this.stagedNodes[id])
}
zoomIn(id){
EnterSubflow(id){
const nodeEl = this.stagedNodes[id]
if(!nodeEl) return
@@ -185,10 +178,17 @@ class BZgraflow extends Buildoz{
const childEl = document.createElement('bz-graflow')
childEl.setAttribute('flow', flowUrl)
childEl.setAttribute('tension', this.getBZAttribute('tension') || '60')
childEl.style.zIndex = '9999'
// Match the clicked node's border so the transition feels like we're "expanding" it.
const nodeStyle = getComputedStyle(nodeEl)
childEl.style.border = nodeStyle.border
childEl.style.borderRadius = nodeStyle.borderRadius
// Put the child in the exact same viewport rect as the parent (fixed overlay)
this.Invade(this, childEl, { hideOld:false })
this.Invade(this, childEl)
// Fade out the current (host) graflow while the child scales up
this.hostContainer.style.opacity = '1'
this.hostContainer.style.transition = 'opacity 1000ms ease-in-out'
// Initial transform so the full-size child "fits" inside the node
const sx0 = nodeBB.width / parentBB.width
@@ -199,7 +199,7 @@ class BZgraflow extends Buildoz{
// Inline "scaler" (shadow styles don't apply to the child element)
childEl.style.transformOrigin = 'top left'
childEl.style.willChange = 'transform'
childEl.style.transition = 'transform 300ms ease-in-out'
childEl.style.transition = 'transform 1000ms ease-in-out'
childEl.style.transform = 'translate(var(--tx, 0px), var(--ty, 0px)) scale(var(--sx, 1), var(--sy, 1))'
childEl.style.setProperty('--tx', tx0 + 'px')
childEl.style.setProperty('--ty', ty0 + 'px')
@@ -213,24 +213,24 @@ class BZgraflow extends Buildoz{
childEl.style.setProperty('--ty', '0px')
childEl.style.setProperty('--sx', 1)
childEl.style.setProperty('--sy', 1)
this.hostContainer.style.opacity = '0'
})
childEl.addEventListener('transitionend', (e) => {
if(e.propertyName !== 'transform') return
this.style.visibility = 'hidden'
this.hostContainer.style.visibility = 'hidden'
}, { once:true })
}
Invade(oldEl, newEl, { hideOld=true } = {}){
Invade(oldEl, newEl){
const r = oldEl.getBoundingClientRect()
if(hideOld) oldEl.style.visibility = 'hidden'
newEl.style.position = 'fixed'
newEl.style.left = r.left + 'px'
newEl.style.top = r.top + 'px'
newEl.style.width = r.width + 'px'
newEl.style.height = r.height + 'px'
newEl.style.display = 'block'
oldEl.parentNode.appendChild(newEl)
oldEl.appendChild(newEl)
}
Evade(oldEl, newEl){
@@ -291,15 +291,39 @@ class BZgraflow extends Buildoz{
if(forceAutoplace){
const bb=this.getBoundingClientRect()
//TODO compute tensions from ports
if(bb.width > bb.height) this.autoPlace('horizontal', 80, 30, 500)
else this.autoPlace('vertical', 80, 30, 200)
//TODO compute tensions from ports, height and width
if(bb.width > bb.height) this.autoPlace('horizontal', 60, 60)
else this.autoPlace('vertical', 60, 60)
}
}
// Convert viewport (client) coordinates to this instance's SVG local coordinates.
// Required when the whole graflow is CSS-transformed (scale/translate), otherwise wire paths
// will be computed in the wrong coordinate space.
clientToSvg(x, y){
const svg = this.wiresContainer
const ctm = svg?.getScreenCTM?.()
if(ctm && ctm.inverse){
const inv = ctm.inverse()
if(svg?.createSVGPoint){
const pt = svg.createSVGPoint()
pt.x = x
pt.y = y
const p = pt.matrixTransform(inv)
return({ x: p.x, y: p.y })
}
if(typeof DOMPoint !== 'undefined'){
const p = new DOMPoint(x, y).matrixTransform(inv)
return({ x: p.x, y: p.y })
}
}
// Fallback: approximate using boundingClientRect (works only at scale=1)
const r = svg.getBoundingClientRect()
return({ x: x - r.left, y: y - r.top })
}
bezierNodes(idNode1, idPort1, idNode2, idPort2, tension=60) {
tension = parseInt(tension)
const svgRect = this.wiresContainer.getBoundingClientRect()
const node1 = this.stagedNodes[idNode1]
const port1 = node1.ports[idPort1]
const node2 = this.stagedNodes[idNode2]
@@ -310,10 +334,12 @@ class BZgraflow extends Buildoz{
}
const bb1 = port1.el.getBoundingClientRect()
const bb2 = port2.el.getBoundingClientRect()
const x1 = Math.floor(bb1.x + (bb1.width/2)) - svgRect.left
const y1 = Math.floor(bb1.y + (bb1.height/2)) - svgRect.top
const x2 = Math.floor(bb2.x + (bb2.width/2)) - svgRect.left
const y2 = Math.floor(bb2.y + (bb2.height/2)) - svgRect.top
const p1 = this.clientToSvg(bb1.x + (bb1.width/2), bb1.y + (bb1.height/2))
const p2 = this.clientToSvg(bb2.x + (bb2.width/2), bb2.y + (bb2.height/2))
const x1 = Math.floor(p1.x)
const y1 = Math.floor(p1.y)
const x2 = Math.floor(p2.x)
const y2 = Math.floor(p2.y)
const loop = (idNode1==idNode2) && (idPort1==idPort2)
const dist = Math.abs(x2 - x1) + Math.abs(y2 - y1)
@@ -336,7 +362,6 @@ class BZgraflow extends Buildoz{
bezierInterNodes(idNode1, idPort1, idNode2, idPort2, interNodes, orientation='horizontal', tension=60) {
tension = parseInt(tension)
const svgRect = this.wiresContainer.getBoundingClientRect()
const node1 = this.stagedNodes[idNode1]
let port1 = node1.ports[idPort1]
@@ -365,18 +390,23 @@ class BZgraflow extends Buildoz{
const endPath = directPath.substring(directPath.lastIndexOf(',')+1).trim()
let path = startPath
let [ , x1, y1] = startPath.split(' ')
x1 = parseInt(x1)
y1 = parseInt(y1)
x1 = parseFloat(x1)
y1 = parseFloat(y1)
for(const interNode of interNodes){
const bb = this.stagedNodes[interNode].getBoundingClientRect()
let x2; let y2;
// Entry/exit points on the placeholder box, converted to SVG coords (handles CSS transforms)
let entryClient; let exitClient
if(orientation=='horizontal'){
x2 = bb.x -svgRect.left
y2 =Math.floor(bb.y + (bb.height/2)) - svgRect.top
entryClient = { x: bb.left, y: bb.top + (bb.height/2) }
exitClient = { x: bb.right, y: bb.top + (bb.height/2) }
} else {
x2 = Math.floor(bb.x + (bb.width/2)) - svgRect.left
y2 = bb.y - svgRect.top
entryClient = { x: bb.left + (bb.width/2), y: bb.top }
exitClient = { x: bb.left + (bb.width/2), y: bb.bottom }
}
const entry = this.clientToSvg(entryClient.x, entryClient.y)
const exit = this.clientToSvg(exitClient.x, exitClient.y)
let x2 = Math.floor(entry.x)
let y2 = Math.floor(entry.y)
if(port1){
path += makeCubicBezier(x1, y1, x2, y2, ['w','e'].includes(port1.direction) ? 'horizontal' : 'vertical', orientation)
@@ -385,26 +415,21 @@ class BZgraflow extends Buildoz{
path += makeCubicBezier(x1, y1, x2, y2, orientation, orientation)
}
if(orientation=='horizontal'){
x2 += bb.width
path += ` L ${x2} ${y2} `
} else {
y2 += bb.height
path += ` L ${x2} ${y2} `
}
const x3 = Math.floor(exit.x)
const y3 = Math.floor(exit.y)
path += ` L ${x3} ${y3} `
x1 = x2
y1 = y2
x1 = x3
y1 = y3
}
let [x2, y2] = endPath.split(' ')
x2 = parseInt(x2)
y2 = parseInt(y2)
x2 = parseFloat(x2)
y2 = parseFloat(y2)
path += ' '+makeCubicBezier(x1, y1, x2, y2, orientation, orientation)
return(path)
}
autoPlace(orientation = 'horizontal', gapx = 80, gapy = 80, tween=1000, align='center'){
console.log('autoPlace', orientation, gapx, gapy, tween, align)
autoPlace(orientation = 'horizontal', gapx = 80, gapy = 80, tween=500, align='center'){
// Loops create infinite recursion in dfs for getting parents & adjacency lists: Remove them !
let linksWithoutBackEdges
if(this.hasAnyLoop(this.flow.nodes, this.flow.links)){
@@ -425,9 +450,12 @@ class BZgraflow extends Buildoz{
for(const layer of layers){
let totHeight = 0; let totWidth = 0
for(const [idx, nid] of layer.entries()){
// Use offset* (not impacted by CSS transforms) to keep autoPlace stable during zoom animations.
const bb = this.stagedNodes[nid].getBoundingClientRect()
totHeight += bb.height + gapy
totWidth += bb.width + gapx
const h = this.stagedNodes[nid].offsetHeight || bb.height
const w = this.stagedNodes[nid].offsetWidth || bb.width
totHeight += h + gapy
totWidth += w + gapx
indexes[nid] = { base: idx, ports: this.computePortOffsets(nid, orientation) }
}
if(totHeight>maxHeight) maxHeight = totHeight
@@ -490,7 +518,7 @@ class BZgraflow extends Buildoz{
if(!nid.startsWith('longLinkPlaceHolder_')) {
const bb = this.stagedNodes[nid].getBoundingClientRect()
this.moveNode(nid, x, y, orientation, tween)
y += gapy + bb.height
y += gapy + (this.stagedNodes[nid].offsetHeight || bb.height)
} else {
this.addFakeNode(nid, x, y, wMax*0.75, fakeNodeHeight)
this.moveNode(nid, x, y, orientation, tween)
@@ -509,7 +537,7 @@ class BZgraflow extends Buildoz{
if(!nid.startsWith('longLinkPlaceHolder_')){
const bb = this.stagedNodes[nid].getBoundingClientRect()
this.moveNode(nid, x, y, orientation, tween)
x += gapx + bb.width
x += gapx + (this.stagedNodes[nid].offsetWidth || bb.width)
} else {
this.addFakeNode(nid, x, y, fakeNodeWidth, hMax*0.75)
this.moveNode(nid, x, y, orientation, tween)
@@ -524,7 +552,8 @@ class BZgraflow extends Buildoz{
getMaxWidth(layer){
return(layer.filter(nid =>
!nid.startsWith('longLinkPlaceHolder_'))
.map(nid => this.stagedNodes[nid].getBoundingClientRect().width)
// Use offsetWidth (not impacted by CSS transforms) to keep autoPlace stable during zoom animations.
.map(nid => (this.stagedNodes[nid].offsetWidth || this.stagedNodes[nid].getBoundingClientRect().width))
.reduce((a, b) => a > b ? a : b, 0)
)
}
@@ -532,7 +561,8 @@ class BZgraflow extends Buildoz{
getMaxHeight(layer){
return(layer.filter(nid =>
!nid.startsWith('longLinkPlaceHolder_'))
.map(nid => this.stagedNodes[nid].getBoundingClientRect().height)
// Use offsetHeight (not impacted by CSS transforms) to keep autoPlace stable during zoom animations.
.map(nid => (this.stagedNodes[nid].offsetHeight || this.stagedNodes[nid].getBoundingClientRect().height))
.reduce((a, b) => a > b ? a : b, 0)
)
}