Переглянути джерело

frontend dev

* component organization, mousenode link started
luis 7 роки тому
батько
коміт
4109c8cdcc

+ 4 - 1
browser/vue-flow/.eslintrc.js

@@ -1,3 +1,6 @@
 module.exports = {
-  extends: [ 'standard', 'plugin:vue/recommended']
+  extends: [ 'standard', 'plugin:vue/recommended'],
+  rules: {
+    'vue/valid-v-model':'warning'
+  }
 }

+ 17 - 8
browser/vue-flow/src/App.vue

@@ -4,7 +4,6 @@
     <flow-manager
       width="100%"
       height="100%"/>
-
   </div>
 </template>
 
@@ -20,21 +19,31 @@ export default {
 </script>
 
 <style lang="scss">
+* {
+  box-sizing:border-box;
+}
+html,body {
+  padding:0;
+  margin:0;
+  height:100vh;
+  max-width:100%;
+  max-height:100%;
+}
+
 #app {
+  position:relative;
+  padding:0;
+  display:flex;
+  height:100%;
+  flex-flow:column;
   font-family: 'Avenir', Helvetica, Arial, sans-serif;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
-  text-align: center;
   color: #2c3e50;
-  margin-top: 60px;
 }
 
 #app .flow-container {
-  position:fixed;
-  top:0;
-  right:0;
-  left:0;
-  bottom:0;
+  flex:1;
 }
 
 </style>

+ 48 - 0
browser/vue-flow/src/components/flowlink.vue

@@ -0,0 +1,48 @@
+<template>
+  <g class="flow-link">
+    <path
+      class="flow-link__area"
+      d:="path"
+    />
+    <path
+      class="flow-link__visible"
+      :d="path"
+    />
+  </g>
+</template>
+<script>
+export default {
+  name: 'FlowLink',
+  props: {
+    x1: {type: Number, default: 0},
+    y1: {type: Number, default: 0},
+    x2: {type: Number, default: 0},
+    y2: {type: Number, default: 0}
+  },
+  computed: {
+    path () {
+      return `M${this.x1},${this.y1} C${this.x1 + 50},${this.y1} ${this.x2 - 50},${this.y2} ${this.x2},${this.y2}`
+    }
+  }
+}
+</script>
+<style>
+.flow-link {
+  cursor:pointer;
+}
+.flow-link .flow-link__area {
+  stroke-width:20;
+  stroke: transparent;
+  fill:none;
+}
+.flow-link .flow-link__visible{
+  stroke: rgba(0,0,0,0.5);
+  stroke-width:5;
+  stroke-linecap:round;
+  fill: transparent;
+}
+.flow-link:hover .flow-link__visible {
+  stroke: #F00;
+}
+
+</style>

+ 108 - 156
browser/vue-flow/src/components/flowmanager.vue

@@ -5,36 +5,17 @@
       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"
+      <flow-pan-zoom ref="panzoom" v-model="nodeData.panzoom">
+        <flow-link
           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 -->
+          v-bind="linkProps(l)"
+        />
+        <!-- mousel link-->
+        <flow-link
+          v-if="pointerLink.active"
+          v-bind="pointerLink"
+        />
         <flow-node
           ref="nodes"
           v-for="(n,i) of nodeData.nodes"
@@ -43,10 +24,9 @@
           :value="n"
           :transform="nodeTransform(i)"
           @nodePointerDown.prevent="nodeDragStart($event,i)"
-          @socketPointerDown.prevent="nodeDragStart($event,i)"
+          @socketPointerDown="socketPointerDown(n.id,...arguments)"
         />
-
-      </g>
+      </flow-pan-zoom>
     </svg>
     {{ nodeData }}
     <div v-for="(n,i) of nodeData.nodes">
@@ -57,142 +37,137 @@
 <script>
 import nodeData from './nodedata'
 import FlowNode from './flownode'
+import FlowLink from './flowlink'
+import FlowPanZoom from './flowpanzoom'
 
 export default {
   name: 'FlowManager',
-  components: {FlowNode},
+  components: {FlowNode, FlowLink, FlowPanZoom},
   props: {
     'width': {type: String, default: '800px'},
     'height': {type: String, default: '600px'}
   },
   data () {
     return {
-      nodeData: nodeData // shared?
+      nodeData: nodeData, // shared?
+      pointerLink: {active: false, x1: 0, y1: 0, x2: 0, y2: 0}
     }
   },
   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})`
+    },
+    // computed
+    linkProps () {
+      return (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)
+        return {x1, y1, x2, y2}
+      }
     }
+
   },
   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)
+    socketPointerDown (nodeId, e, socket) {
+      const node = this.$refs.nodes.find(n => n.value.id === nodeId)
+
+      const isInput = socket.in != undefined
+      console.log('Socket:', socket)
+      if (isInput) {
+        console.log('Socket is input')
+      }
+      const socketPos = isInput ? node.input(socket.in) : node.output(socket.out)
+
+      const p = this.transformedPoint(e.x, e.y)
+
+      if (isInput) {
+        this.pointerLink.x1 = node.value.x + socketPos.cx
+        this.pointerLink.y1 = node.value.y + socketPos.cy
+        this.pointerLink.x2 = p.x
+        this.pointerLink.y2 = p.y
+      } else {
+        this.pointerLink.x1 = p.x
+        this.pointerLink.y1 = p.y
+        this.pointerLink.x2 = node.value.x + socketPos.cx
+        this.pointerLink.y2 = node.value.y + socketPos.cy
+      }
+
+      this.pointerLink.active = true
+
+      // What socket is this
+      // Create a temporary link
+      const drag = (e) => {
+        const p = this.transformedPoint(e.x, e.y)
+        if (isInput) {
+          this.pointerLink.x1 = p.x
+          this.pointerLink.y1 = p.y
+        } else {
+          this.pointerLink.x2 = p.x
+          this.pointerLink.y2 = p.y
+        }
+      }
+      const drop = (e) => {
+        console.log('Dropping link in ', e.target)
+        document.removeEventListener('mousemove', drag)
+        document.removeEventListener('mouseup', drop)
+      }
+      document.addEventListener('mousemove', drag)
+      document.addEventListener('mouseup', drop)
     },
     nodeDragStart (e, i) {
+      if (e.button !== 0) return // first button
       // 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
+      // transform CTM
+      const delta = this.transformedPoint(e.clientX, e.clientY)
+      delta.x -= tnode.x
+      delta.y -= tnode.y
+
+      const drag = (e) => {
+        const point = this.transformedPoint(e.clientX, e.clientY)
+        tnode.x = point.x - delta.x
+        tnode.y = point.y - delta.y
       }
-      const mouseup = (e) => {
-        document.removeEventListener('mousemove', mousemove)
-        document.removeEventListener('mouseup', mouseup)
+      const drop = (e) => {
+        document.removeEventListener('mousemove', drag)
+        document.removeEventListener('mouseup', drop)
       }
-      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)
+      document.addEventListener('mousemove', drag)
+      document.addEventListener('mouseup', drop)
     },
-    drag (e) {
-      this.nodeData.pan.x += e.movementX
-      this.nodeData.pan.y += e.movementY
+    createSVGPoint (x, y) {
+      const p = this.$refs.svg.createSVGPoint()
+      p.x = x; p.y = y
+      return p
     },
-    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
+    transformedPoint (x, y, abs) {
+      const svgRect = this.$el.getBoundingClientRect()
+      if (!abs) {
+        x -= svgRect.x
+        y -= svgRect.y
+      }
+      return this.$refs.panzoom.transformedPoint(this.createSVGPoint(x, y))
     }
+    // helper
   }
 
 }
@@ -207,28 +182,5 @@ 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>

+ 24 - 16
browser/vue-flow/src/components/flownode.vue

@@ -1,31 +1,32 @@
 <template>
   <g
-    class="s-node"
-    :class="{'s-node--dragging':dragging}"
+    class="flow-node"
+    :class="{'flow-node--dragging':dragging}"
     @mousedown="$emit('nodePointerDown',$event)"
   >
     <rect
       ref="body"
-      class="s-node__body"
+      class="flow-node__body"
       v-bind="bodyProps"
     />
     <!-- input -->
     <circle
-      class="s-node__socket s-node__inputs"
+      class="flow-node__socket flow-node__inputs"
       v-for="i in value.inputs"
       v-bind="input(i-1)"
       :key="'in'+i"
-      @mousedown="$emit('socketPointerDown',$event, {in:i-1})"
+      @mousedown.stop.prevent="socketPointerDown($event, {in:i-1})"
     />
     <!-- output -->
     <circle
-      class="s-node__socket s-node__outputs"
+      class="flow-node__socket flow-node__outputs"
       :key="'out'+0"
       v-bind="output(0)"
+      @mousedown.stop.prevent="socketPointerDown($event, {out:0})"
     />
     <text
       ref="label"
-      class="s-node__label"
+      class="flow-node__label"
       v-bind="labelProps">
       {{ value.label }}
     </text>
@@ -92,11 +93,18 @@ export default {
     this.$nextTick(() => { // after mount we reupdate with new values
       this.$forceUpdate()
     })
+  },
+  methods: {
+    socketPointerDown (e, socket) {
+      console.log('Socket click:', e, socket)
+      this.$emit('socketPointerDown', e, socket)
+    }
+
   }
 }
 </script>
 <style lang="scss">
-.s-node__body {
+.flow-node__body {
   fill: #EFEFEF;
   stroke: #ddd;
   stroke-width:1;
@@ -105,8 +113,8 @@ export default {
           filter: drop-shadow(5px 5px 5px #000);
 
 }
-.s-node__body:hover,
-.s-node.snode--dragging s-node__body {
+.flow-node__body:hover,
+.flow-node.flow-node--dragging s-node__body {
   z-index:10;
   fill: #DFDFDF;
   cursor:move;
@@ -114,11 +122,11 @@ export default {
   //stroke-width:4;
   transition: all 1s;
 }
-.s-node {
+.flow-node {
 }
-.s-node.s-node--dragging {
+.flow-node.flow-node--dragging {
 }
-.s-node__socket {
+.flow-node__socket {
   fill: #AFAFAF;
   stroke:none;
 
@@ -126,12 +134,12 @@ export default {
   stroke-width:2;
   r:6;
 }
-.s-node__socket:hover {
+.flow-node__socket:hover {
   r:10;
 }
-.s-node__inputs {
+.flow-node__inputs {
 }
-.s-node__label {
+.flow-node__label {
   fill: #444;
   pointer-events:none;
   user-select:none;

+ 103 - 0
browser/vue-flow/src/components/flowpanzoom.vue

@@ -0,0 +1,103 @@
+<template>
+  <g>
+    <rect
+      ref="transformer"
+      class="transformer"
+      width="100%"
+      height="100%"
+      @mousedown="dragStart"/>
+
+    <g
+      ref="transform"
+      :transform="panzoomTransform">
+      <slot/>
+    </g>
+  </g>
+
+</template>
+<script>
+export default {
+  name: 'FlowPanZoom',
+  props: {
+    value: {type: Object, default: () => { return {x: 0, y: 0, zoom: 1} }}
+  },
+  data () {
+    return {
+      zoom: this.value.zoom,
+      x: this.value.x,
+      y: this.value.y
+    }
+  },
+  computed: {
+    panzoomTransform () {
+      const transString = 'matrix(' + [
+        this.zoom, 0, 0,
+        this.zoom, this.x, this.y
+      ].join(' ') + ')'
+      return transString
+    }
+
+  },
+  mounted () {
+    this.$el.addEventListener('wheel', this.wheel)
+  },
+  beforeDestroy () {
+    this.$el.removeEventListener('wheel', this.wheel)
+  },
+  methods: {
+    // panStart
+    dragStart (e) {
+      if (e.button !== 0) return // first button
+      if (e.target !== this.$refs.transformer) return
+
+      const drag = (e) => {
+        this.update(this.x + e.movementX, this.y + e.movementY)
+      }
+      const drop = (e) => {
+        console.log('Dropping')
+        document.removeEventListener('mousemove', drag)
+        document.removeEventListener('mouseup', drop)
+      }
+      document.addEventListener('mousemove', drag)
+      document.addEventListener('mouseup', drop)
+    },
+    wheel (e) {
+      e.preventDefault()
+      const svgRect = this.$el.getBoundingClientRect()
+      const oX = e.clientX - svgRect.x
+      const oY = e.clientY - svgRect.y
+      const z = this.zoom - (e.deltaY * 0.001 * this.zoom)
+
+      var curX = this.x
+      var curY = this.y
+      var scaleD = z / this.zoom // delta
+
+      var nx = scaleD * (curX - oX) + oX
+      var ny = scaleD * (curY - oY) + oY
+
+      this.update(nx, ny, z)
+    },
+    update (x, y, zoom) {
+      if (x) this.x = x
+      if (y) this.y = y
+      if (zoom) this.zoom = zoom
+
+      this.$emit('input', {x: this.x, y: this.y, zoom: this.zoom})
+    },
+    transformedPoint (point) {
+      const ctm = this.$refs.transform.getCTM()
+      return point.matrixTransform(ctm.inverse())
+    }
+  }
+}
+
+</script>
+
+<style>
+.transformer {
+  stroke: black;
+  stroke-width: 1;
+  fill:transparent;
+
+}
+</style>

+ 1 - 2
browser/vue-flow/src/components/nodedata.js

@@ -1,7 +1,6 @@
 // Document shared state
 export default {
-  pan: {x: 0, y: 0},
-  zoom: 1,
+  panzoom: {x: 0, y: 0, zoom: 1}, // or doc transform?
   defaults: {
     nodeHeight: 100
   },