graflow: editable=>moving nodes
This commit is contained in:
141
bzGraflow.js
141
bzGraflow.js
@@ -34,6 +34,32 @@ class BZgraflow extends Buildoz{
|
||||
this.currentOrientation = null
|
||||
}
|
||||
|
||||
static _coreCssPromise = null
|
||||
|
||||
static async getCoreCss(){
|
||||
if(BZgraflow._coreCssPromise) return(await BZgraflow._coreCssPromise)
|
||||
BZgraflow._coreCssPromise = (async() => {
|
||||
const res = await fetch('/app/thirdparty/buildoz/buildoz.css')
|
||||
const css = await res.text()
|
||||
const m = css.match(/\/\*\s*BZGRAFLOW_CORE_START\s*\*\/([\s\S]*?)\/\*\s*BZGRAFLOW_CORE_END\s*\*\//)
|
||||
const core = m ? m[1] : ''
|
||||
return(core)
|
||||
})()
|
||||
return(await BZgraflow._coreCssPromise)
|
||||
}
|
||||
|
||||
async ensureIsolatedCoreStyles(){
|
||||
if(!this.hasAttribute('isolated')) return
|
||||
if(this._isolatedCoreInjected) return
|
||||
this._isolatedCoreInjected = true
|
||||
const core = await BZgraflow.getCoreCss()
|
||||
// Convert light-dom selectors (`bz-graflow ...`) to shadow-dom selectors (`:host ...`)
|
||||
const shadowCss = core.replaceAll('bz-graflow', ':host')
|
||||
const style = document.createElement('style')
|
||||
style.textContent = shadowCss
|
||||
this.mainContainer.appendChild(style)
|
||||
}
|
||||
|
||||
addIcon(el, name) {
|
||||
el.innerHTML = `<svg viewBox="0 0 1024 1024" width="20" height="20" fill="#000000"><g transform="rotate(90 512 512)"><path d="${this.btnIcons[name]}"/></g></svg>`
|
||||
}
|
||||
@@ -41,10 +67,7 @@ class BZgraflow extends Buildoz{
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
const flowUrl = this.getBZAttribute('flow')
|
||||
if(!flowUrl) {
|
||||
console.warn('BZgraflow: No flow URL !?')
|
||||
return
|
||||
}
|
||||
if(!flowUrl) return // Be tolerant: maybe injected later from JS above
|
||||
// If attribute "isolated" is present, render inside a shadow root.
|
||||
// Otherwise, render in light DOM (no shadow DOM).
|
||||
this.hostContainer = document.createElement('div')
|
||||
@@ -52,37 +75,7 @@ class BZgraflow extends Buildoz{
|
||||
this.mainContainer = this.hasAttribute('isolated')
|
||||
? this.hostContainer.attachShadow({ mode: 'open' })
|
||||
: this.hostContainer
|
||||
const style = document.createElement('style')
|
||||
//TODO kick this wart somewhere under a carpet
|
||||
style.textContent = `
|
||||
.bzgf-wires-container,
|
||||
.bzgf-nodes-container{ position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
.bzgf-nodes-container .bzgf-node{ position:absolute; }
|
||||
.bzgf-nodes-container .bzgf-fake-node{
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
backgrround: transparent;
|
||||
border-style: none;
|
||||
}
|
||||
.bzgf-nodes-container button.bzgf-zoom-in{
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
top: -0.5em;
|
||||
right: -1em;
|
||||
color: black;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
padding: 0;
|
||||
}
|
||||
bz-graflow button.bzgf-zoom-out{
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
`
|
||||
this.mainContainer.appendChild(style)
|
||||
this.ensureIsolatedCoreStyles()
|
||||
this.nodesContainer = document.createElement('div')
|
||||
this.nodesContainer.classList.add('bzgf-nodes-container')
|
||||
this.wiresContainer = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
@@ -91,9 +84,13 @@ class BZgraflow extends Buildoz{
|
||||
this.mainContainer.append(this.wiresContainer)
|
||||
this.mainContainer.append(this.nodesContainer)
|
||||
this.append(this.hostContainer)
|
||||
this.loadFlow(flowUrl) // Let it load async
|
||||
this.loadFlow(flowUrl).then(() => {
|
||||
if(this.hasAttribute('editable')){
|
||||
this.dnd = new MovingNodes(this)
|
||||
this.dnd.enableMovingNodes('.bzgf-node') }
|
||||
}) // Let it load async
|
||||
}
|
||||
|
||||
|
||||
error(msg, err){
|
||||
this.innerHTML = `<div style="background:red;color:black;margin: auto;width: fit-content;">${msg}</div>`
|
||||
if(err) console.error(msg, err)
|
||||
@@ -290,11 +287,7 @@ class BZgraflow extends Buildoz{
|
||||
childEl.addEventListener('transitionend', (e) => {
|
||||
if(e.propertyName !== 'transform') return
|
||||
this.hostContainer.style.visibility = 'hidden'
|
||||
// Important for nested subflows:
|
||||
// A non-'none' transform on this element creates a containing block, which would make
|
||||
// any nested `position:fixed` subflow overlay position relative to this element instead
|
||||
// of the viewport (showing up as an extra offset like 8px).
|
||||
childEl.style.transform = 'none'
|
||||
childEl.style.transform = 'none' // Important for nested subflows to position correctly
|
||||
childEl.style.willChange = ''
|
||||
this.dispatchEvent(new CustomEvent('subflowLoaded', {
|
||||
detail: { subflow: childEl },
|
||||
@@ -1031,7 +1024,69 @@ class BZgraflow extends Buildoz{
|
||||
}
|
||||
return(crossLayerLinks)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Buildoz.define('graflow', BZgraflow)
|
||||
|
||||
class MovingNodes{
|
||||
constructor(graflow){
|
||||
this.graflow = graflow
|
||||
this.nodesContainer = this.graflow.mainContainer.querySelector('.bzgf-nodes-container')
|
||||
this.state = null
|
||||
}
|
||||
|
||||
enableMovingNodes(itemSelector, handleSelector = itemSelector) {
|
||||
this.itemSelector = itemSelector
|
||||
this.handleSelector = handleSelector
|
||||
if(!this._handleCursorStyle){
|
||||
const style = document.createElement('style')
|
||||
style.textContent = `${handleSelector}{ cursor: move }`
|
||||
this.nodesContainer.appendChild(style)
|
||||
this._handleCursorStyle = style
|
||||
}
|
||||
|
||||
this.nodesContainer.addEventListener('pointerdown', this.pointerDown.bind(this))
|
||||
this.nodesContainer.addEventListener('pointermove', this.pointerMove.bind(this))
|
||||
this.nodesContainer.addEventListener('pointerup', this.pointerUp.bind(this))
|
||||
}
|
||||
|
||||
pointerDown(e){
|
||||
const node = (e.target.classList.contains(this.itemSelector)) ? e.target : e.target.closest(this.itemSelector)
|
||||
const handle = (node.classList.contains(this.handleSelector)) ? node : node.querySelector(this.handleSelector)
|
||||
|
||||
const rect = node.getBoundingClientRect()
|
||||
|
||||
this.state = {
|
||||
node,
|
||||
handle,
|
||||
startX: e.clientX,
|
||||
startY: e.clientY,
|
||||
offsetX: rect.left,
|
||||
offsetY: rect.top
|
||||
}
|
||||
const x = e.clientX - this.state.startX + this.state.offsetX
|
||||
const y = e.clientY - this.state.startY + this.state.offsetY
|
||||
node.setPointerCapture(e.pointerId)
|
||||
node.style.position = 'absolute'
|
||||
node.style.left = `${x}px`
|
||||
node.style.top = `${y}px`
|
||||
node.style.margin = '0'
|
||||
node.style.zIndex = '9999'
|
||||
}
|
||||
|
||||
pointerMove(e){
|
||||
if(!this.state) return
|
||||
const { node, startX, startY, offsetX, offsetY } = this.state
|
||||
const x = e.clientX - startX + offsetX
|
||||
const y = e.clientY - startY + offsetY
|
||||
node.style.left = `${x}px`
|
||||
node.style.top = `${y}px`
|
||||
this.graflow.updateWires(node.dataset.id, this.graflow.currentOrientation)
|
||||
}
|
||||
|
||||
pointerUp(e){
|
||||
if(!this.state) return
|
||||
this.state.node.releasePointerCapture(e.pointerId)
|
||||
this.state = null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user