|
@@ -9,7 +9,7 @@
|
|
|
'flow-linking':linking || stickySockets,
|
|
|
'activity':dragging || pointerLink.active ,
|
|
|
'flow-detail': detailed,
|
|
|
- 'selecting': (selector)?true:false
|
|
|
+ 'selecting': !!selector
|
|
|
}"
|
|
|
@dragover.prevent
|
|
|
@drop="managerDrop"
|
|
@@ -58,7 +58,9 @@
|
|
|
<button @click="$emit('funcsPanelToggle')">Panel</button>
|
|
|
<button @click="stickySockets=!stickySockets"> {{ stickySockets? 'Hide':'Show' }} sockets </button>
|
|
|
<button @click="detailed=!detailed"> {{ detailed? 'Hide':'Show' }} detail </button>
|
|
|
- <button v-if="panzoom.x!=0 || panzoom.y!=0 || panzoom.zoom!=1" @click="panzoomReset">Reset view</button>
|
|
|
+ <button v-if="panzoom.x!=0 || panzoom.y!=0 || panzoom.zoom!=1" @click="panzoomReset">
|
|
|
+ Reset view
|
|
|
+ </button>
|
|
|
</div>
|
|
|
<div class="flow-container__info">
|
|
|
x:{{ panzoom.x.toFixed(2) }} y:{{ panzoom.y.toFixed(2) }} scale:{{ panzoom.zoom.toFixed(2) }}
|
|
@@ -73,12 +75,12 @@ import FlowNode from './node'
|
|
|
import FlowLink from './link'
|
|
|
import FlowPanZoom from './panzoom'
|
|
|
import SvgDefs from './svgdefwrapper.vue'
|
|
|
+import utils from '@/utils/utils'
|
|
|
|
|
|
export default {
|
|
|
name: 'FlowManager',
|
|
|
components: {FlowNode, FlowLink, FlowPanZoom, SvgDefs},
|
|
|
props: {
|
|
|
- // 'value': {type: Object, default: () => {}},
|
|
|
'registry': {type: Object, default: () => {}},
|
|
|
'width': {type: String, default: '800px'},
|
|
|
'height': {type: String, default: '600px'}
|
|
@@ -88,8 +90,8 @@ export default {
|
|
|
return {
|
|
|
panzoom: { x: 0, y: 0, zoom: 1 },
|
|
|
nodeData: { nodes: [], links: [] },
|
|
|
- dragging: null,
|
|
|
|
|
|
+ dragging: null,
|
|
|
linking: false,
|
|
|
stickySockets: false,
|
|
|
detailed: false,
|
|
@@ -117,7 +119,7 @@ export default {
|
|
|
inputs: nodeClass.inputs,
|
|
|
output: nodeClass.output,
|
|
|
match: match,
|
|
|
- dragging: this.dragging === node,
|
|
|
+ dragging: this.dragging && !!this.dragging[node.id],
|
|
|
nodeStyle: nodeClass.style
|
|
|
}
|
|
|
}
|
|
@@ -144,46 +146,28 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
- watch: {
|
|
|
- value: {
|
|
|
- handler (val) {
|
|
|
- const clone = JSON.parse(JSON.stringify(this.value)) // deepClone
|
|
|
- const nodes = []
|
|
|
- for (let n of clone.nodes) {
|
|
|
- if (this.dragging && this.dragging.id === n.id) {
|
|
|
- continue
|
|
|
- }
|
|
|
- nodes.push(n)
|
|
|
- }
|
|
|
- if (this.dragging) {
|
|
|
- nodes.push(this.dragging)
|
|
|
- }
|
|
|
- this.nodeData = {
|
|
|
- nodes: nodes,
|
|
|
- links: clone.links
|
|
|
- }
|
|
|
- this.$nextTick(() => {
|
|
|
- this.$forceUpdate()
|
|
|
- })
|
|
|
- },
|
|
|
- deep: true
|
|
|
- }
|
|
|
- },
|
|
|
mounted () {
|
|
|
- this.$parent.$on('sparta', () => {
|
|
|
- console.log('This is weird')
|
|
|
- })
|
|
|
this.$flowService.on('document', (v) => {
|
|
|
- if (!v.data) { return }
|
|
|
+ console.log('Updating document')
|
|
|
+ if (!v || !v.data || !v.data.nodes) { return }
|
|
|
const nodes = []
|
|
|
- for (let n of v.data.nodes) {
|
|
|
- if (this.dragging && this.dragging.id === n.id) {
|
|
|
+
|
|
|
+ for (let node of v.data.nodes) {
|
|
|
+ if (this.dragging && !!this.dragging[node.id]) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ let localNode = this.nodeData.nodes.find(n => n.id === node.id)
|
|
|
+ if (localNode) {
|
|
|
+ Object.assign(localNode, node)
|
|
|
+ nodes.push(localNode)
|
|
|
continue
|
|
|
}
|
|
|
- nodes.push(n)
|
|
|
+ nodes.push(node)
|
|
|
}
|
|
|
- if (this.dragging) {
|
|
|
- nodes.push(this.dragging)
|
|
|
+ if (this.dragging) { // Add our nodes
|
|
|
+ for (let nid in this.dragging) {
|
|
|
+ nodes.push(this.dragging[nid])
|
|
|
+ }
|
|
|
}
|
|
|
this.nodeData = {
|
|
|
nodes: nodes,
|
|
@@ -192,22 +176,20 @@ export default {
|
|
|
this.$nextTick(() => {
|
|
|
this.$forceUpdate()
|
|
|
})
|
|
|
-
|
|
|
- this.nodeData = {
|
|
|
- nodes: v.data.nodes || [],
|
|
|
- links: v.data.links || []
|
|
|
- }
|
|
|
})
|
|
|
this.$flowService.on('nodeUpdate', (v) => {
|
|
|
const nodes = v.data
|
|
|
const nd = this.nodeData
|
|
|
- for (let node of nodes) {
|
|
|
+ for (let nid in nodes) {
|
|
|
+ const node = nodes[nid]
|
|
|
const idx = nd.nodes.findIndex(n => n.id === node.id)
|
|
|
if (idx === -1) { // new node
|
|
|
- nd.nodes.push(node)
|
|
|
+ nd.nodes.push(nodes[nid])
|
|
|
continue
|
|
|
}
|
|
|
+ // if (!this.dragging || !this.dragging[node.id]) {
|
|
|
this.$set(nd.nodes, idx, node) // new Node
|
|
|
+ // }
|
|
|
}
|
|
|
})
|
|
|
|
|
@@ -217,7 +199,7 @@ export default {
|
|
|
document.addEventListener('keydown', this.keyDown)
|
|
|
document.addEventListener('keyup', this.keyUp)
|
|
|
},
|
|
|
- beforeDestroy () {
|
|
|
+ beoreDestroy () {
|
|
|
document.removeEventListener('keydown', this.keyDown)
|
|
|
document.removeEventListener('keyup', this.keyUp)
|
|
|
},
|
|
@@ -233,9 +215,7 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
panzoomReset () {
|
|
|
- this.panzoom.x = 0
|
|
|
- this.panzoom.y = 0
|
|
|
- this.panzoom.zoom = 1
|
|
|
+ this.panzoom = {x: 0, y: 0, zoom: 1}
|
|
|
},
|
|
|
// XXX: Shrink this function
|
|
|
// and create some LinkAdd method
|
|
@@ -244,7 +224,6 @@ export default {
|
|
|
|
|
|
const nodeRef = this.$refs.nodes.find(n => n.id === nodeId)
|
|
|
const node = this.nodeData.nodes.find(n => n.id === nodeId)
|
|
|
-
|
|
|
const isInput = socket.in !== undefined
|
|
|
const socketPos = isInput ? nodeRef.inputPos(socket.in) : nodeRef.outputPos(socket.out)
|
|
|
|
|
@@ -262,79 +241,67 @@ export default {
|
|
|
} else {
|
|
|
this.pointerLink.src = {nodeId: nodeId, type: this.registry[node.src].output, out: 0}
|
|
|
}
|
|
|
-
|
|
|
- // What socket is this
|
|
|
- // Create a temporary link
|
|
|
- const drag = (ev) => {
|
|
|
- const p = this.transformedPoint(ev.clientX, ev.clientY)
|
|
|
- if (isInput) {
|
|
|
- this.pointerLink.props.x1 = p.x
|
|
|
- this.pointerLink.props.y1 = p.y
|
|
|
- } else {
|
|
|
- this.pointerLink.props.x2 = p.x
|
|
|
- this.pointerLink.props.y2 = p.y
|
|
|
- }
|
|
|
- }
|
|
|
- const drop = (ev) => {
|
|
|
- document.removeEventListener('mousemove', drag)
|
|
|
- document.removeEventListener('mouseup', drop)
|
|
|
- this.pointerLink.active = false
|
|
|
-
|
|
|
- const targetNodeId = ev.target.getAttribute('data-nodeid')
|
|
|
- const targetIn = ev.target.getAttribute('data-in')
|
|
|
- const targetOut = ev.target.getAttribute('data-out')
|
|
|
-
|
|
|
- // Not a node or same node
|
|
|
- if (targetNodeId === undefined || targetNodeId === nodeId) {
|
|
|
- console.error('LINK: target is not a socket or is node')
|
|
|
- return
|
|
|
- }
|
|
|
- let link
|
|
|
- // target is input
|
|
|
- if (targetIn && !isInput) {
|
|
|
- link = {
|
|
|
- from: nodeId,
|
|
|
- to: targetNodeId,
|
|
|
- in: parseInt(targetIn)
|
|
|
+ utils.createDrag({
|
|
|
+ drag: (ev) => {
|
|
|
+ const p = this.transformedPoint(ev.clientX, ev.clientY)
|
|
|
+ if (isInput) {
|
|
|
+ this.pointerLink.props.x1 = p.x
|
|
|
+ this.pointerLink.props.y1 = p.y
|
|
|
+ } else {
|
|
|
+ this.pointerLink.props.x2 = p.x
|
|
|
+ this.pointerLink.props.y2 = p.y
|
|
|
}
|
|
|
- } else if (targetOut && isInput) {
|
|
|
- link = {
|
|
|
- from: targetNodeId,
|
|
|
- to: nodeId,
|
|
|
- in: socket.in
|
|
|
+ },
|
|
|
+ drop: (ev) => {
|
|
|
+ this.pointerLink.active = false
|
|
|
+
|
|
|
+ const targetNodeId = ev.target.getAttribute('data-nodeid')
|
|
|
+ const targetIn = ev.target.getAttribute('data-in')
|
|
|
+ const targetOut = ev.target.getAttribute('data-out')
|
|
|
+
|
|
|
+ // Not a node or same node
|
|
|
+ if (targetNodeId === undefined || targetNodeId === nodeId) {
|
|
|
+ console.error('LINK: target is not a socket')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let link
|
|
|
+ // target is input
|
|
|
+ if (targetIn && !isInput) {
|
|
|
+ link = {
|
|
|
+ from: nodeId,
|
|
|
+ to: targetNodeId,
|
|
|
+ in: parseInt(targetIn)
|
|
|
+ }
|
|
|
+ } else if (targetOut && isInput) {
|
|
|
+ link = {
|
|
|
+ from: targetNodeId,
|
|
|
+ to: nodeId,
|
|
|
+ in: socket.in
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // No link
|
|
|
+ if (!link) {
|
|
|
+ console.error('LINK: input same direction (in->in/out->out)')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const nodeFrom = this.nodeData.nodes.find(n => n.id === link.from)
|
|
|
+ const nodeTo = this.nodeData.nodes.find(n => n.id === link.to)
|
|
|
+
|
|
|
+ const output = this.registry[nodeFrom.src].output
|
|
|
+ const input = this.registry[nodeTo.src].inputs[link.in]
|
|
|
+ // Type checking
|
|
|
+ if (!(output === 'any' || output === input || input === 'any')) {
|
|
|
+ console.error('LINK: Invalid type')
|
|
|
+ return
|
|
|
}
|
|
|
- }
|
|
|
- // No link
|
|
|
- if (!link) {
|
|
|
- console.error('LINK: input same direction (in->in/out->out)')
|
|
|
- return
|
|
|
- }
|
|
|
- const nodeFrom = this.nodeData.nodes.find(n => n.id === link.from)
|
|
|
- const nodeTo = this.nodeData.nodes.find(n => n.id === link.to)
|
|
|
-
|
|
|
- const output = this.registry[nodeFrom.src].output
|
|
|
- const input = this.registry[nodeTo.src].inputs[link.in]
|
|
|
- // Type checking
|
|
|
- if (!(output === 'any' || output === input || input === 'any')) {
|
|
|
- console.error('LINK: Invalid type')
|
|
|
- return
|
|
|
- }
|
|
|
|
|
|
- // Input already exists
|
|
|
- const existingInputI = this.nodeData.links.findIndex(l => l.to === link.to && l.in === link.in)
|
|
|
- if (existingInputI !== -1) {
|
|
|
- this.nodeData.links.splice(existingInputI, 1)
|
|
|
- // console.error('LINK: already has input')
|
|
|
- // return
|
|
|
- }
|
|
|
- this.linkAdd(link)
|
|
|
- }
|
|
|
- document.addEventListener('mousemove', drag)
|
|
|
- document.addEventListener('mouseup', drop)
|
|
|
- },
|
|
|
- nodeDoubleClick (ev, i) {
|
|
|
- this.nodeModalTarget = i
|
|
|
- this.nodeModal = true
|
|
|
+ // Input already exists, replace
|
|
|
+ const existingInputI = this.nodeData.links.findIndex(l => l.to === link.to && l.in === link.in)
|
|
|
+ if (existingInputI !== -1) {
|
|
|
+ this.nodeData.links.splice(existingInputI, 1)
|
|
|
+ }
|
|
|
+ this.linkAdd(link)
|
|
|
+ }})
|
|
|
},
|
|
|
|
|
|
nodePointerDown (ev, i) {
|
|
@@ -357,66 +324,56 @@ export default {
|
|
|
if (!this.nodeSelection[tnode.id] && !ev.ctrlKey) this.nodeSelection = {}
|
|
|
this.nodeSelection[tnode.id] = tnode
|
|
|
// we can handle with nodeId and a search
|
|
|
- this.nodeData.nodes.splice(i, 1)
|
|
|
- this.nodeData.nodes.push(tnode) // put in last
|
|
|
|
|
|
- // transform CTM
|
|
|
- // delta.x -= tnode.x
|
|
|
- // delta.y -= tnode.y
|
|
|
-
|
|
|
- const nodeSel = this.nodeData.nodes.filter(n => {
|
|
|
- return !!this.nodeSelection[n.id]
|
|
|
- })
|
|
|
-
|
|
|
- let curP = this.transformedPoint(ev.x, ev.y)
|
|
|
- const drag = (ev) => {
|
|
|
- this.dragging = tnode
|
|
|
- const dragP = this.transformedPoint(ev.x, ev.y)
|
|
|
- for (let n of nodeSel) {
|
|
|
- n.x += dragP.x - curP.x
|
|
|
- n.y += dragP.y - curP.y
|
|
|
- }
|
|
|
- this.sendFlowEvent('nodeUpdate', nodeSel)
|
|
|
- curP = dragP
|
|
|
- // Bad possibly
|
|
|
- // this.$emit('input', this.nodeData)
|
|
|
+ // put the list of nodes in last
|
|
|
+ for (let nk in this.nodeSelection) {
|
|
|
+ let ni = this.nodeData.nodes.findIndex(n => n.id === this.nodeSelection[nk].id)
|
|
|
+ this.nodeData.nodes.splice(ni, 1)
|
|
|
+ this.nodeData.nodes.push(this.nodeSelection[nk]) // put in last
|
|
|
}
|
|
|
- const drop = (ev) => {
|
|
|
- document.removeEventListener('mousemove', drag)
|
|
|
- document.removeEventListener('mouseup', drop)
|
|
|
- if (!this.dragging) {
|
|
|
- if (!ev.ctrlKey) this.nodeSelection = {}
|
|
|
- this.nodeSelection[tnode.id] = tnode
|
|
|
- }
|
|
|
- this.dragging = null
|
|
|
+ let curP = this.transformedPoint(ev.x, ev.y)
|
|
|
|
|
|
- this.sendFlowEvent('nodeUpdate', nodeSel)
|
|
|
- this.sendDocumentUpdate()
|
|
|
- }
|
|
|
- document.addEventListener('mousemove', drag)
|
|
|
- document.addEventListener('mouseup', drop)
|
|
|
+ this.dragging = this.nodeSelection
|
|
|
+ utils.createDrag({
|
|
|
+ drag: (ev) => {
|
|
|
+ if (this.nodeSelection === undefined) {
|
|
|
+ console.error('Well something went wrong')
|
|
|
+ }
|
|
|
+ const dragP = this.transformedPoint(ev.x, ev.y)
|
|
|
+ for (let n in this.nodeSelection) {
|
|
|
+ this.nodeSelection[n].x += dragP.x - curP.x
|
|
|
+ this.nodeSelection[n].y += dragP.y - curP.y
|
|
|
+ }
|
|
|
+ this.sendFlowEvent('nodeUpdate', this.nodeSelection)
|
|
|
+ curP = dragP
|
|
|
+ },
|
|
|
+ drop: (ev) => {
|
|
|
+ this.dragging = null
|
|
|
+ this.sendDocumentUpdate()
|
|
|
+ }
|
|
|
+ })
|
|
|
},
|
|
|
nodeAdd (src, x = 100, y = 100) {
|
|
|
const newNode = {
|
|
|
- id: guid(),
|
|
|
+ id: utils.guid(),
|
|
|
x: x,
|
|
|
y: y,
|
|
|
label: src,
|
|
|
src: src
|
|
|
}
|
|
|
this.nodeData.nodes.push(newNode)
|
|
|
- this.sendFlowEvent('nodeUpdate', [newNode])
|
|
|
+ let nu = {}
|
|
|
+ this.sendFlowEvent('nodeUpdate', (nu[newNode.id] = newNode, nu))
|
|
|
this.sendDocumentUpdate()
|
|
|
},
|
|
|
linkAdd (link) {
|
|
|
this.nodeData.links.push(link)
|
|
|
- this.sendflowEvent('linkUpdate', link)
|
|
|
+ this.sendFlowEvent('linkUpdate', link)
|
|
|
this.sendDocumentUpdate()
|
|
|
},
|
|
|
linkRemove (link) {
|
|
|
const i = this.nodeData.links.findIndex(l => l === link)
|
|
|
if (i === -1) return
|
|
|
-
|
|
|
this.nodeData.links.splice(i, 1)
|
|
|
this.sendFlowEvent('linkRemove', link)
|
|
|
this.sendDocumentUpdate()
|
|
@@ -428,49 +385,44 @@ export default {
|
|
|
console.error('Registry: Drop src not found in registry')
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
const pt = this.transformedPoint(ev.x, ev.y)
|
|
|
this.nodeAdd(reg, pt.x, pt.y)
|
|
|
},
|
|
|
viewPointerDown (ev) {
|
|
|
- console.log('Starting selector')
|
|
|
+ if (ev.button !== 0) return
|
|
|
ev.preventDefault()
|
|
|
this.nodeSelection = {}
|
|
|
- const px = ev.x
|
|
|
- const py = ev.y
|
|
|
-
|
|
|
- const p = this.transformedPoint(px, py)
|
|
|
+ const p = this.transformedPoint(ev.x, ev.y)
|
|
|
this.selector = {x: p.x, y: p.y, width: 0, height: 0}
|
|
|
- const drag = (ev) => {
|
|
|
- const p = this.transformedPoint(px, py)
|
|
|
- const p2 = this.transformedPoint(ev.x, ev.y)
|
|
|
- const nwidth = p2.x - p.x
|
|
|
- const nheight = p2.y - p.y
|
|
|
- this.selector.x = p.x
|
|
|
- this.selector.y = p.y
|
|
|
- this.selector.width = nwidth
|
|
|
- this.selector.height = nheight
|
|
|
- if (nwidth < 0) {
|
|
|
- this.selector.x = p2.x
|
|
|
- this.selector.width = -nwidth
|
|
|
- }
|
|
|
- if (nheight < 0) {
|
|
|
- this.selector.y = p2.y
|
|
|
- this.selector.height = -nheight
|
|
|
- }
|
|
|
- }
|
|
|
- const drop = (ev) => {
|
|
|
- this.selector = null
|
|
|
- document.removeEventListener('mousemove', drag)
|
|
|
- document.removeEventListener('mouseup', drop)
|
|
|
- }
|
|
|
- document.addEventListener('mousemove', drag)
|
|
|
- document.addEventListener('mouseup', drop)
|
|
|
+ utils.createDrag({
|
|
|
+ drag: (evd) => {
|
|
|
+ // transform again in case we changed zoom/pan
|
|
|
+ const p = this.transformedPoint(ev.x, ev.y)
|
|
|
+ const p2 = this.transformedPoint(evd.x, evd.y)
|
|
|
+ const nwidth = p2.x - p.x
|
|
|
+ const nheight = p2.y - p.y
|
|
|
+ this.selector = {
|
|
|
+ x: nwidth < 0 ? p2.x : p.x,
|
|
|
+ y: nheight < 0 ? p2.y : p.y,
|
|
|
+ width: nwidth < 0 ? -nwidth : nwidth,
|
|
|
+ height: nheight < 0 ? -nheight : nheight
|
|
|
+ }
|
|
|
+ },
|
|
|
+ drop: (ev) => {
|
|
|
+ for (let n of this.nodeData.nodes) {
|
|
|
+ if (n.x > this.selector.x && n.x < (this.selector.x + this.selector.width) &&
|
|
|
+ n.y > this.selector.y && n.y < (this.selector.y + this.selector.height)
|
|
|
+ ) {
|
|
|
+ this.nodeSelection[n.id] = n
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.selector = null
|
|
|
+ }})
|
|
|
},
|
|
|
+ // service events
|
|
|
sendFlowEvent (type, param) {
|
|
|
this.$flowService[type](param)
|
|
|
},
|
|
|
-
|
|
|
sendDocumentUpdate (nodeData) {
|
|
|
this.$flowService.documentUpdate(this.nodeData, this.$route.params.sessId)
|
|
|
},
|
|
@@ -490,16 +442,7 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-// utils
|
|
|
-function guid () {
|
|
|
- function s4 () {
|
|
|
- return Math.floor((1 + Math.random()) * 0x10000)
|
|
|
- .toString(16)
|
|
|
- .substring(1)
|
|
|
- }
|
|
|
- return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
|
|
|
- s4() + '-' + s4() + s4() + s4()
|
|
|
-}
|
|
|
+
|
|
|
</script>
|
|
|
<style>
|
|
|
.flow-container {
|
|
@@ -515,8 +458,9 @@ function guid () {
|
|
|
display:flex;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
- color: #FFF;
|
|
|
+ color: #fff;
|
|
|
}
|
|
|
+
|
|
|
.flow-container__control button {
|
|
|
display:flex;
|
|
|
justify-content: center;
|
|
@@ -524,6 +468,7 @@ function guid () {
|
|
|
padding:14px;
|
|
|
color:#333;
|
|
|
}
|
|
|
+
|
|
|
.flow-container__info {
|
|
|
position:absolute;
|
|
|
bottom:10px;
|
|
@@ -531,6 +476,7 @@ function guid () {
|
|
|
padding:2px;
|
|
|
font-size:9px;
|
|
|
}
|
|
|
+
|
|
|
.flow-view {
|
|
|
border:none;
|
|
|
position:relative;
|
|
@@ -540,9 +486,6 @@ function guid () {
|
|
|
.flow-selector {
|
|
|
pointer-events:none;
|
|
|
opacity:0;
|
|
|
- /* TODO: theme */
|
|
|
- stroke: rgba(30,100,255,1);
|
|
|
- fill: rgba(30,100,255,0.3);
|
|
|
}
|
|
|
|
|
|
.flow-selector.flow-selector--selecting {
|