graflow: editable=>moving nodes
This commit is contained in:
@@ -8,10 +8,11 @@
|
||||
|
||||
<body>
|
||||
<ul>
|
||||
<li><a href="./test1.html">test1 (P42 graph + 2 depths subflows)</a></li>
|
||||
<li><a href="./test2.html">test2 (organigram)</a></li>
|
||||
<li><a href="./test3.html">test3 (EIC simple + subflow)</a></li>
|
||||
<li><a href="./test4.html">test4 (EIC-ICMP)</a></li>
|
||||
<li><a href="./test1.html">test1 (P42 graph + ISOLATED + 2 depths subflows)</a></li>
|
||||
<li><a href="./test2.html">test2 (organigram) + ISOLATED</a></li>
|
||||
<li><a href="./test3.html">test3 (EIC simple + subflow + NON-ISOLATED)</a></li>
|
||||
<li><a href="./test4.html">test4 (EIC-ICMP + NON-ISOLATED)</a></li>
|
||||
<li><a href="./test5.html">test5 (P42 graph + ISOLATED + EDITABLE)</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<bz-graflow class="compunet" flow="/app/assets/json/bzGraflow/testFlow1.json" tension="60" isolated>
|
||||
<bz-graflow class="compunet" flow="/app/assets/json/bzGraflow/testFlow1.1.json" tension="60" isolated>
|
||||
<div class="demooptions"> <!-- just for demo purposes -->
|
||||
<button data-trigger="onAutoplace1H">Auto-place Horizontal</button>
|
||||
<button data-trigger="onAutoplace1V">Auto-place Vertical</button>
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>graflow</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<link type="text/css" rel="stylesheet" href="/app/thirdparty/eicui/eicui-2.0.css">
|
||||
<link type="text/css" rel="stylesheet" href="../../thirdparty/buildoz/buildoz.css">
|
||||
<script src="../../thirdparty/buildoz/buildoz.js"></script>
|
||||
<script src="../../thirdparty/buildoz/bzGraflow.js"></script>
|
||||
<style>
|
||||
@font-face { /*FF does not indirectly load if inside a shawdow-dom */
|
||||
font-family: 'glyphs';
|
||||
src: url('/app/assets/styles/fonts/glyphs.eot');
|
||||
src: url('/app/assets/styles/fonts/glyphs.eot') format('embedded-opentype'),
|
||||
url('/app/assets/styles/fonts/glyphs.ttf') format('truetype'),
|
||||
url('/app/assets/styles/fonts/glyphs.woff') format('woff'),
|
||||
url('/app/assets/styles/fonts/glyphs.svg') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
body{
|
||||
display: grid;
|
||||
grid-gap: 5px;
|
||||
background:#888;
|
||||
font-size: 16px;
|
||||
}
|
||||
.demooptions{
|
||||
padding: 2px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
width: 10em;
|
||||
background: #FFFB;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
z-index:99;
|
||||
font-size: .7em;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
.demooptions button{
|
||||
text-transform: none;
|
||||
margin: 2px;
|
||||
font-size: 1em;
|
||||
}
|
||||
bz-graflow{
|
||||
overflow: scroll;
|
||||
border: 2px solid black;
|
||||
}
|
||||
bz-graflow.compunet{ grid-column: 1 / -1; width: 100vw; height: 60vh; background:black; }
|
||||
</style>
|
||||
<script>
|
||||
window.addEventListener('load',()=>{
|
||||
let grflw1 = document.querySelector('bz-graflow.compunet')
|
||||
grflw1.addEventListener('subflowLoaded',
|
||||
(evt) => { grflw1 = evt.detail.subflow }
|
||||
)
|
||||
grflw1.addEventListener('subflowExited',
|
||||
(evt) => { grflw1 = evt.target }
|
||||
)
|
||||
document.querySelector('[data-trigger="onAutoplace1H"]').addEventListener('click',
|
||||
(evt) => { grflw1.autoPlace('horizontal', 80, 30, 1000, document.querySelector('[data-id="compunet"]').value) }
|
||||
)
|
||||
document.querySelector('[data-trigger="onAutoplace1V"]').addEventListener('click',
|
||||
(evt) => { grflw1.autoPlace('vertical', 80, 30, 1000, document.querySelector('[data-id="compunet"]').value) }
|
||||
)
|
||||
|
||||
document.querySelector('input[data-id="compunet"]').addEventListener('change',
|
||||
(evt) => { grflw1.setAttribute('tension', evt.target.value); grflw1.refresh() }
|
||||
)
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<bz-graflow class="compunet" flow="/app/assets/json/bzGraflow/testFlow1.json" tension="60" isolated editable>
|
||||
<div class="demooptions"> <!-- just for demo purposes -->
|
||||
<button data-trigger="onAutoplace1H">Auto-place Horizontal</button>
|
||||
<button data-trigger="onAutoplace1V">Auto-place Vertical</button>
|
||||
<select name="align" data-id="compunet">
|
||||
<option value="center">Center</option>
|
||||
<option value="first">First</option>
|
||||
<option value="last">Last</option>
|
||||
<option value="auto">Auto</option>
|
||||
</select>
|
||||
<div class-"cols-2"=""><label>tension</label><input data-id="compunet" type="number" size="2" value="60"></div>
|
||||
</div>
|
||||
</bz-graflow>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"nodesFile": "/app/assets/html/bzGraflow/nodesTest1.html",
|
||||
"flow": {
|
||||
"nodes":[
|
||||
{ "nodeType": "inc",
|
||||
"id": "aze",
|
||||
"coords": { "x": 220, "y": 120}
|
||||
},
|
||||
{ "nodeType": "inc",
|
||||
"subflow": {
|
||||
"url": "/app/assets/json/bzGraflow/testFlowEic.json",
|
||||
"portLinks": [
|
||||
{ "refNodeType": "refnodein", "refnodePort": "out1",
|
||||
"parentPort": "in1",
|
||||
"subflowNode":"aze2", "subflowPort": "in1",
|
||||
"direction": "in"
|
||||
},
|
||||
{ "refNodeType": "refnodeout", "refnodePort": "in1",
|
||||
"parentPort": "out1",
|
||||
"subflowNode":"aze5", "subflowPort": "out1",
|
||||
"direction": "out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "aze2",
|
||||
"coords": { "x": 220, "y": 10}
|
||||
},
|
||||
{ "nodeType": "factor",
|
||||
"id": "qsd",
|
||||
"coords": { "x": 470, "y": 170}
|
||||
},
|
||||
{ "nodeType": "factor",
|
||||
"id": "qsd2",
|
||||
"coords": { "x": 470, "y": 50}
|
||||
},
|
||||
{ "nodeType": "wadder",
|
||||
"id": "wcx",
|
||||
"coords": { "x": 720, "y": 50}
|
||||
},
|
||||
{ "nodeType": "multiplier",
|
||||
"id": "ert",
|
||||
"coords": { "x": 550, "y": 350}
|
||||
},
|
||||
{ "nodeType": "input",
|
||||
"id": "0000",
|
||||
"coords": { "x": 20, "y": 350}
|
||||
},
|
||||
{ "nodeType": "console",
|
||||
"id": "9999",
|
||||
"coords": { "x": 800, "y": 350}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{ "from": ["0000", "out1"], "to": ["aze", "inp1"] },
|
||||
{ "from": ["aze2", "out1"], "to": ["qsd2", "inp1"] },
|
||||
{ "from": ["aze", "out1"], "to": ["qsd", "inp1"] },
|
||||
{ "from": ["qsd2", "out1"], "to": ["ert", "inp2"] },
|
||||
{ "from": ["0000", "out1"], "to": ["aze2", "inp1"] },
|
||||
{ "from": ["qsd2", "out1"], "to": ["wcx", "inp2"] },
|
||||
{ "from": ["wcx", "out1"], "to": ["ert", "inp1"] },
|
||||
{ "from": ["qsd", "out1"], "to": ["wcx", "inp1"] },
|
||||
{ "from": ["ert", "out1"], "to": ["9999", "inp1"] }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -7,21 +7,6 @@
|
||||
"coords": { "x": 220, "y": 120}
|
||||
},
|
||||
{ "nodeType": "inc",
|
||||
"subflow": {
|
||||
"url": "/app/assets/json/bzGraflow/testFlowEic.json",
|
||||
"portLinks": [
|
||||
{ "refNodeType": "refnodein", "refnodePort": "out1",
|
||||
"parentPort": "in1",
|
||||
"subflowNode":"aze2", "subflowPort": "in1",
|
||||
"direction": "in"
|
||||
},
|
||||
{ "refNodeType": "refnodeout", "refnodePort": "in1",
|
||||
"parentPort": "out1",
|
||||
"subflowNode":"aze5", "subflowPort": "out1",
|
||||
"direction": "out"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "aze2",
|
||||
"coords": { "x": 220, "y": 10}
|
||||
},
|
||||
|
||||
Vendored
+35
@@ -204,3 +204,38 @@ bz-graflow .bzgf-main-container{
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* BZGRAFLOW_CORE_START */
|
||||
/* bz-graflow internal layout rules (used in light DOM, and injected into shadow DOM when isolated) */
|
||||
bz-graflow .bzgf-wires-container,
|
||||
bz-graflow .bzgf-nodes-container{
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
bz-graflow .bzgf-nodes-container .bzgf-node{ position:absolute; }
|
||||
bz-graflow .bzgf-nodes-container .bzgf-fake-node{
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: transparent;
|
||||
border-style: none;
|
||||
}
|
||||
bz-graflow .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;
|
||||
}
|
||||
/* BZGRAFLOW_CORE_END */
|
||||
|
||||
Vendored
+98
-43
@@ -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