|
@@ -0,0 +1,234 @@
|
|
|
+<template>
|
|
|
+ <div class="flow-container">
|
|
|
+ <svg
|
|
|
+ ref="svg"
|
|
|
+ class="view"
|
|
|
+ :width="width"
|
|
|
+ :height="height">
|
|
|
+
|
|
|
+ <!-- static dragger -->
|
|
|
+ <rect
|
|
|
+ ref="transformer"
|
|
|
+ class="transformer"
|
|
|
+ width="100%"
|
|
|
+ height="100%"
|
|
|
+ @mousedown="dragStart"/>
|
|
|
+
|
|
|
+ <g
|
|
|
+ ref="transform"
|
|
|
+ :transform="panzoomTransform">
|
|
|
+
|
|
|
+ <!-- links -->
|
|
|
+ <g
|
|
|
+ class="connection"
|
|
|
+ v-for="(l,i) in nodeData.links"
|
|
|
+ :key="i"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ class="area"
|
|
|
+ v-bind="link(l)"
|
|
|
+ />
|
|
|
+ <path
|
|
|
+ class="visible"
|
|
|
+ v-bind="link(l)"
|
|
|
+ />
|
|
|
+ </g>
|
|
|
+
|
|
|
+ <!-- nodes -->
|
|
|
+ <flow-node
|
|
|
+ ref="nodes"
|
|
|
+ v-for="(n,i) of nodeData.nodes"
|
|
|
+ :key="'node' + n.id"
|
|
|
+ :id="n.id"
|
|
|
+ :value="n"
|
|
|
+ :transform="nodeTransform(i)"
|
|
|
+ @nodePointerDown.prevent="nodeDragStart($event,i)"
|
|
|
+ @socketPointerDown.prevent="nodeDragStart($event,i)"
|
|
|
+ />
|
|
|
+
|
|
|
+ </g>
|
|
|
+ </svg>
|
|
|
+ {{ nodeData }}
|
|
|
+ <div v-for="(n,i) of nodeData.nodes">
|
|
|
+ <input type="text" v-model="n.label">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<script>
|
|
|
+import nodeData from './nodedata'
|
|
|
+import FlowNode from './flownode'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'FlowManager',
|
|
|
+ components: {FlowNode},
|
|
|
+ props: {
|
|
|
+ 'width': {type: String, default: '800px'},
|
|
|
+ 'height': {type: String, default: '600px'}
|
|
|
+ },
|
|
|
+ data () {
|
|
|
+ return {
|
|
|
+ nodeData: nodeData // shared?
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ panzoomTransform () {
|
|
|
+ const transString = 'matrix(' + [
|
|
|
+ this.nodeData.zoom, 0, 0,
|
|
|
+ this.nodeData.zoom, this.nodeData.pan.x, this.nodeData.pan.y
|
|
|
+ ].join(' ') + ')'
|
|
|
+ return transString
|
|
|
+ },
|
|
|
+ nodeTransform () {
|
|
|
+ return (i) => `translate(${this.nodeData.nodes[i].x} ${this.nodeData.nodes[i].y})`
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted () {
|
|
|
+ document.addEventListener('mousewheel', this.wheel)
|
|
|
+ document.addEventListener('mousemove', this.mousemove)
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.$forceUpdate()
|
|
|
+ })
|
|
|
+ },
|
|
|
+ beforeDestroy () {
|
|
|
+ document.removeEventListener('mousewheel', this.wheel)
|
|
|
+ document.removeEventListener('mousemove', this.mousemove)
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ socketPointerDown (e, i, ...params) {
|
|
|
+ console.log('Socket down', e, i, params)
|
|
|
+ },
|
|
|
+ nodeDragStart (e, i) {
|
|
|
+ // we can handle with nodeId and a search
|
|
|
+ var tnode = this.nodeData.nodes[i]
|
|
|
+ this.nodeData.nodes.splice(i, 1)
|
|
|
+ this.nodeData.nodes.push(tnode) // put in last
|
|
|
+
|
|
|
+ const mousemove = (e) => {
|
|
|
+ tnode.x += e.movementX / this.nodeData.zoom
|
|
|
+ tnode.y += e.movementY / this.nodeData.zoom
|
|
|
+ }
|
|
|
+ const mouseup = (e) => {
|
|
|
+ document.removeEventListener('mousemove', mousemove)
|
|
|
+ document.removeEventListener('mouseup', mouseup)
|
|
|
+ }
|
|
|
+ document.addEventListener('mousemove', mousemove)
|
|
|
+ document.addEventListener('mouseup', mouseup)
|
|
|
+ },
|
|
|
+ link (link) {
|
|
|
+ if (!this.$refs.nodes) return
|
|
|
+ const refFrom = this.$refs.nodes.find(n => n.value.id === link.from)
|
|
|
+ const refTo = this.$refs.nodes.find(n => n.value.id === link.to)
|
|
|
+
|
|
|
+ const fromOutput = refFrom.output(0) // only 1 output
|
|
|
+ const toInput = refTo.input(link.in)
|
|
|
+
|
|
|
+ const x1 = refFrom.value.x + fromOutput.cx // + inputPos.x
|
|
|
+ const y1 = refFrom.value.y + fromOutput.cy // + inputPos.y
|
|
|
+
|
|
|
+ const x2 = refTo.value.x + toInput.cx
|
|
|
+ const y2 = refTo.value.y + toInput.cy
|
|
|
+ // console.log('Thing:', x1, y1, x2, y2)
|
|
|
+
|
|
|
+ const linkProps = {d: `M${x1},${y1} C${x1 + 50},${y1} ${x2 - 50},${y2} ${x2},${y2}`}
|
|
|
+ return linkProps
|
|
|
+ },
|
|
|
+ /*
|
|
|
+ nodeDragStart (idx) {
|
|
|
+ console.log('Start drag')
|
|
|
+ var tnode = this.nodes[idx]
|
|
|
+ this.nodes.splice(idx, 1)
|
|
|
+ this.nodes.push(tnode) // put in last
|
|
|
+ },
|
|
|
+ nodeDrag (idx) {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ // manipulate links
|
|
|
+ var tnode = this.nodes[idx]
|
|
|
+ const foundLinks = this.links.filter(l => l.from === tnode.id || l.to === tnode.id)
|
|
|
+ for (let l of foundLinks) {
|
|
|
+ const fromNode = this.nodes.find(n => n.id === l.from)
|
|
|
+ const toNode = this.nodes.find(n => n.id === l.to)
|
|
|
+
|
|
|
+ let {cx: x1, cy: y1} = fromNode.node.output[0]
|
|
|
+ let {cx: x2, cy: y2} = toNode.node.input[l.in]
|
|
|
+
|
|
|
+ x1 += fromNode.node.movePos.x
|
|
|
+ y1 += fromNode.node.movePos.y
|
|
|
+
|
|
|
+ x2 += toNode.node.movePos.x
|
|
|
+ y2 += toNode.node.movePos.y
|
|
|
+
|
|
|
+ l.link.d = `M${x1},${y1} C${x1 + 50},${y1} ${x2 - 50},${y2} ${x2},${y2}`
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }, */
|
|
|
+
|
|
|
+ // panStart
|
|
|
+ dragStart (e) {
|
|
|
+ console.log(e.target)
|
|
|
+ if (e.target !== this.$refs.transformer) return
|
|
|
+ document.addEventListener('mousemove', this.drag)
|
|
|
+ document.addEventListener('mouseup', this.dragStop)
|
|
|
+ },
|
|
|
+ drag (e) {
|
|
|
+ this.nodeData.pan.x += e.movementX
|
|
|
+ this.nodeData.pan.y += e.movementY
|
|
|
+ },
|
|
|
+ dragStop (e) {
|
|
|
+ document.removeEventListener('mousemove', this.drag)
|
|
|
+ document.removeEventListener('mouseup', this.dragStop)
|
|
|
+ },
|
|
|
+ wheel (e) {
|
|
|
+ var oX = e.clientX
|
|
|
+ var oY = e.clientY
|
|
|
+ const z = this.nodeData.zoom - (e.deltaY * 0.001 * this.nodeData.zoom)
|
|
|
+
|
|
|
+ var curX = this.nodeData.pan.x
|
|
|
+ var curY = this.nodeData.pan.y
|
|
|
+ var scaleD = z / this.nodeData.zoom // delta
|
|
|
+
|
|
|
+ var nx = scaleD * (curX - oX) + oX
|
|
|
+ var ny = scaleD * (curY - oY) + oY
|
|
|
+
|
|
|
+ this.nodeData.pan.x = nx
|
|
|
+ this.nodeData.pan.y = ny
|
|
|
+ this.nodeData.zoom = z
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+</script>
|
|
|
+<style>
|
|
|
+.flow-container {
|
|
|
+ display:flex;
|
|
|
+ flex-flow:column;
|
|
|
+}
|
|
|
+svg.view {
|
|
|
+ border: solid 1px #F00;
|
|
|
+ position:relative;
|
|
|
+}
|
|
|
+.transformer {
|
|
|
+ stroke: black;
|
|
|
+ stroke-width: 1;
|
|
|
+ fill:transparent;
|
|
|
+
|
|
|
+}
|
|
|
+.connection {
|
|
|
+ cursor:pointer;
|
|
|
+}
|
|
|
+.connection .area {
|
|
|
+ stroke-width:20;
|
|
|
+ stroke: transparent;
|
|
|
+ fill:none;
|
|
|
+}
|
|
|
+.connection .visible{
|
|
|
+ stroke: rgba(0,0,0,0.5);
|
|
|
+ stroke-width:5;
|
|
|
+ stroke-linecap:round;
|
|
|
+ fill: transparent;
|
|
|
+}
|
|
|
+.connection:hover .visible {
|
|
|
+ stroke: #F00;
|
|
|
+}
|
|
|
+
|
|
|
+</style>
|