소스 검색

Performance changes on the frontend

luis 7 년 전
부모
커밋
af5c7fad77

+ 3 - 1
browser/vue-flow/README.md

@@ -2,7 +2,9 @@
 
 ## Things to do
 
-* Move selection to central state
+* Performance:
+  Try to cache as most of getBBox as possible as it seems a bottleneck
+* ~~Move selection to central state~~
 
 ### UI
 

+ 1 - 0
browser/vue-flow/package.json

@@ -49,6 +49,7 @@
     "highlight-loader": "^0.7.2",
     "html-loader": "^0.5.5",
     "html-webpack-plugin": "^2.30.1",
+    "lodash.debounce": "^4.0.8",
     "markdown-loader": "^2.0.2",
     "marked": "^0.3.12",
     "node-sass": "^4.7.2",

+ 9 - 0
browser/vue-flow/src/assets/doc/readme.md

@@ -289,6 +289,15 @@ would be good to have the same by right clicking in other subjects:
 
 ## Changelog
 
+15/02/2018
+
+* **Selection**: Added clone using ctrl + drag will clone selected nodes
+* **Selection**: Added drag threshold, only start dragging if pointer goes over a threshold value
+* **Selection**: Moved selection to vuex state
+* **Styling**: Few changes in css for secondary button
+* **Inspector**: Added result button to inspector
+* **Activity**: Added new activity icon if the node has data
+
 13/02/2018
 
 * **Selection**: Animated selection areas to improve visibility

+ 1 - 1
browser/vue-flow/src/assets/lines-theme.css

@@ -86,7 +86,7 @@
 }
 
 .lines .flow-node--selected .flow-node__label {
-  fill: white !important;
+  fill: var(--normal) !important;
 }
 
 .lines .flow-panel__selector {

+ 8 - 2
browser/vue-flow/src/components/app-flow.vue

@@ -46,6 +46,7 @@
               @nodeInspect="nodeInspectStart(...arguments)"
               @nodeProcess="nodeProcess(...arguments)"
               @nodeDoubleClick="nodeInspectStart(...arguments,true)"
+              @activityPointerDown="nodeInspectStart($event,true,true)"
               @documentSave="documentSave"
 
               width="100%"
@@ -135,14 +136,19 @@ export default {
   },
   methods: {
     ...mapActions('flow', ['NODE_INSPECT', 'NOTIFICATION_ADD']),
-    nodeInspectStart (node, changePane) { // node
-      this.NODE_INSPECT(node.id)
+    nodeInspectStart (nodeId, changePane, showData) { // node
+      this.NODE_INSPECT(nodeId)
+
+      if (showData) {
+        this.modalData = true
+      }
       if (changePane) {
         this.panel = 'inspector'
       }
       if (this.panel !== 'inspector') {
         return
       }
+
       // this.nodeInspect = node
 
       // if (!changePane) { return }

+ 183 - 85
browser/vue-flow/src/components/flow/editor.js

@@ -7,6 +7,7 @@ import FlowModalData from './modal-data' // NEW 15/02/2018
 import HxContextMenu from '@/components/shared/hx-contextmenu'
 import SvgDefs from './svgdefswrapper'
 import utils from '@/utils/utils'
+import _debounce from 'lodash.debounce'
 
 export default {
   name: 'FlowManager',
@@ -29,14 +30,16 @@ export default {
 
       stickySockets: false,
       stickyTriggers: false,
-      nodeActivity: true
+      nodeActivity: true,
+      nodeProps: [],
+      linkProps: []
+      // cacheNodeProps: [],
     }
   },
   computed: {
-    ...mapGetters('flow', ['registry', 'activity', 'nodeData', 'nodeById', 'nodeSelection']),
+    ...mapGetters('flow', ['registry', 'activity', 'nodeData', 'nodeById', 'nodeSelection', 'nodeIdxCache']),
     outputNode () {
       const n = this.nodeData.nodes.find(n => n.src === 'Output')
-
       return !!n
     },
     viewClasses () {
@@ -48,60 +51,11 @@ export default {
         'selecting': !!this.selector
       }
     },
-    nodeProps () {
+    /* nodeProps () {
       return (node) => {
-        let highlight = {}
-        if (this.pointerLink.active && this.pointerLink.src.nodeId !== node.id) {
-          if (this.pointerLink.src.in !== undefined) {
-            highlight = {type: 'socket-out', dtype: this.pointerLink.src.type}
-          } else {
-            highlight = {type: 'socket-in', dtype: this.pointerLink.src.type}
-          }
-        }
-        if (this.pointerTriggerLink.active && this.pointerTriggerLink.src.nodeId !== node.id) {
-          highlight = {type: (this.pointerTriggerLink.src.dir === 'in') ? 'trigger-in' : 'trigger-out'}
-        }
-
-        const nodeClass = this.registry[node.src]
-        if (!nodeClass) {
-          this.NODE_REMOVE([node])
-          return
-        }
-        return {
-          transform: `translate(${node.x} ${node.y})`,
-          id: node.id,
-          // Combine this into one
-          match: highlight,
-          dragging: this.dragging && !!this.dragging[node.id],
-          activity: this.activity && this.activity.nodes && this.activity.nodes[node.id]
-        }
+        return this.nodePropsBuild(node)
       }
-    },
-    linkProps () {
-      return (link) => {
-        if (!this.$refs.nodes) return
-        // For size .x .y
-        const nodeFrom = this.nodeById(link.from)
-        const nodeTo = this.nodeById(link.to)
-
-        const refFrom = this.$refs.nodes.find(n => n.id === link.from)
-        const refTo = this.$refs.nodes.find(n => n.id === link.to)
-        if (!refFrom || !refTo) { // delete link
-          return {}
-        }
-
-        const fromOutput = refFrom.outputPos(0) // only 1 output
-        const toInput = refTo.inputPos(link.in)
-
-        return {
-          x1: nodeFrom.x + fromOutput.x,
-          y1: nodeFrom.y + fromOutput.y,
-          x2: nodeTo.x + toInput.x,
-          y2: nodeTo.y + toInput.y,
-          status: this.activity.nodes[nodeFrom.id] && this.activity.nodes[nodeFrom.id].status
-        }
-      }
-    },
+    }, */
     triggerProps () {
       return (trigger) => {
         if (!this.$refs.nodes) return
@@ -133,11 +87,28 @@ export default {
         }
       }
     }
-
+  },
+  watch: {
+    'nodeData.nodes' (val, oldVal) {
+      console.log('Nodes updated')
+      this.nodePropsUpdate()
+    },
+    'nodeData.links' () {
+      console.log('Updating links')
+      this.linkPropsUpdate()
+    },
+    'activity' () {
+      _debounce(this.activityUpdate, 1500)
+    }
+  },
+  created () {
+    this.nodePropsUpdate()
   },
   mounted () {
+    console.log('Mounted')
     this.$nextTick(() => {
       this.$forceUpdate()
+      this.$nextTick(() => { this.linkPropsUpdate() })
     })
     document.addEventListener('keydown', this.keyDown)
     document.addEventListener('keyup', this.keyUp)
@@ -155,6 +126,99 @@ export default {
       'LINK_ADD', 'LINK_REMOVE',
       'TRIGGER_ADD', 'TRIGGER_REMOVE' ]),
 
+    activityUpdate () {
+      console.log('Activity updated')
+      this.linkPropsUpdate()
+    },
+    nodePropsBuild (node) {
+      let highlight = {}
+      if (this.pointerLink.active && this.pointerLink.src.nodeId !== node.id) {
+        if (this.pointerLink.src.in !== undefined) {
+          highlight = {type: 'socket-out', dtype: this.pointerLink.src.type}
+        } else {
+          highlight = {type: 'socket-in', dtype: this.pointerLink.src.type}
+        }
+      }
+      if (this.pointerTriggerLink.active && this.pointerTriggerLink.src.nodeId !== node.id) {
+        highlight = {type: (this.pointerTriggerLink.src.dir === 'in') ? 'trigger-in' : 'trigger-out'}
+      }
+
+      const nodeClass = this.registry[node.src]
+      if (!nodeClass) {
+        this.NODE_REMOVE([node])
+        return
+      }
+      return {
+        transform: `translate(${node.x} ${node.y})`,
+        id: node.id,
+        node: node,
+        // Combine this into one
+        match: highlight,
+        selected: !!this.nodeSelection[node.id],
+        dragging: this.dragging && !!this.dragging[node.id],
+        activity: this.activity && this.activity.nodes && this.activity.nodes[node.id]
+      }
+    },
+
+    nodePropsUpdate (n) {
+      // Find node ID and update it only
+      if (n) {
+        const idx = this.nodeIdxCache[n.id]
+        this.$set(this.nodeProps, idx, this.nodePropsBuild(n))
+        return
+      }
+
+      const ret = []
+      for (let node of this.nodeData.nodes) {
+        ret.push(this.nodePropsBuild(node))
+      }
+      this.nodeProps = ret
+    },
+    // propCaches
+    linkPropsUpdate (link, idx) {
+      if (!this.$refs.nodes) {
+        setTimeout(() => {
+          this.linkPropsUpdate() // Retry until we have refs
+        }, 200)
+        return
+      }
+      const linkProps = (link) => {
+        if (!this.$refs.nodes) return [] // empty we need $refs.nodes
+        // For size .x .y
+        const nodeFrom = this.nodeById(link.from)
+        const nodeTo = this.nodeById(link.to)
+
+        const refFrom = this.$refs.nodes.find(n => n.node.id === link.from)
+        const refTo = this.$refs.nodes.find(n => n.node.id === link.to)
+        if (!refFrom || !refTo) { // delete link
+          // No ref yet
+          return {}
+        }
+
+        const fromOutput = refFrom.outputPos(0) // only 1 output
+        const toInput = refTo.inputPos(link.in)
+
+        return {
+          x1: nodeFrom.x + fromOutput.x,
+          y1: nodeFrom.y + fromOutput.y,
+          x2: nodeTo.x + toInput.x,
+          y2: nodeTo.y + toInput.y,
+          status: this.activity.nodes[nodeFrom.id] && this.activity.nodes[nodeFrom.id].status
+        }
+      }
+      if (link) {
+        if (idx === undefined)idx = this.linkProps.findIndex(l => l === link)
+        this.$set(this.linkProps, idx, linkProps(link))
+        return
+      }
+      const ret = []
+      for (let link of this.nodeData.links) {
+        const p = linkProps(link)
+        ret.push(p)
+      }
+      this.linkProps = ret
+    },
+
     keyDown (ev) {
       if (document.activeElement && document.activeElement.matches('input,textarea')) { return }
       if (ev.shiftKey) {
@@ -162,23 +226,22 @@ export default {
         this.triggerLinking = true
       }
 
-      let single = null
-      if (Object.keys(this.nodeSelection).length === 1) { single = this.nodeSelection[Object.keys(this.nodeSelection)[0]] }
+      let singleId = null
+      if (Object.keys(this.nodeSelection).length === 1) { singleId = Object.keys(this.nodeSelection)[0] }
       switch (ev.key) {
         case 'Enter':
-          if (!single) { return }
-          this.nodeInspect(single, true)
+          if (!singleId) { return }
+          this.nodeInspect(singleId, true)
           break
         case 'Delete':
           if (Object.keys(this.nodeSelection).length === 0) { return }
-          console.log('Removing nodes:', this.nodeSelection)
           this.NODE_REMOVE(this.nodeSelection)
           break
         case 'a':
           if (ev.ctrlKey) {
             ev.preventDefault()
             ev.stopPropagation()
-            this.NODE_SELECTION_ADD(this.nodeData.nodes)
+            this.select(this.nodeData.nodes)
           }
           break
       }
@@ -196,8 +259,7 @@ export default {
     // and create some LinkAdd method
     socketPointerDown (nodeId, ev, socket) {
       if (ev.button !== 0) return
-
-      const nodeRef = this.$refs.nodes.find(n => n.id === nodeId)
+      const nodeRef = this.$refs.nodes.find(n => n.node.id === nodeId)
       const node = this.nodeById(nodeId)
       const isInput = socket.in !== undefined
       const socketPos = isInput ? nodeRef.inputPos(socket.in) : nodeRef.outputPos(socket.out)
@@ -210,13 +272,17 @@ export default {
         y2: node.y + socketPos.y
       }
 
-      this.pointerLink.active = true
       if (isInput) {
         this.pointerLink.src = {nodeId: nodeId, type: this.registry[node.src].inputs[socket.in].type, in: socket.in}
       } else {
         this.pointerLink.src = {nodeId: nodeId, type: this.registry[node.src].output.type, out: 0}
       }
+
       utils.createDrag({
+        dragStart: (ev) => {
+          this.pointerLink.active = true
+          this.nodePropsUpdate()
+        },
         drag: (ev) => {
           const p = this.transformedPoint(ev.clientX, ev.clientY)
           if (isInput) {
@@ -229,6 +295,7 @@ export default {
         },
         drop: (ev) => {
           this.pointerLink.active = false
+          this.nodePropsUpdate()
           if (ev.target.matches('.flow-pan-zoom__transformer')) {
             if (isInput) {
               console.error('LINK: Invalid target')
@@ -369,20 +436,21 @@ export default {
         }
       })
     },
-
+    nodeRemove (nodeId) {
+      const node = this.nodeById(nodeId)
+      this.NODE_REMOVE([node])
+    },
     // Is this used?
-    nodeInspect (tnode, force) {
-      this.$emit('nodeInspect', tnode, force)
+    nodeInspect (nodeId, force) {
+      this.$emit('nodeInspect', nodeId, force)
     },
 
     // HERE
-    nodePointerDown (ev, i) {
+    nodePointerDown (ev, nodeId) {
       document.activeElement && document.activeElement.blur()
-      const tnode = this.nodeData.nodes[i]
+      const tnode = this.nodeById(nodeId)
       if (ev.button === 1) {
         this.NODE_REMOVE([tnode])
-        // this.nodeRemove(tnode)
-        // remove related links
         return
       }
       if (ev.button !== 0) return // first button
@@ -392,13 +460,14 @@ export default {
         }
         return
       }
-      this.nodeInspect(tnode)
+      this.nodeInspect(tnode.id)
 
+      let selectionAdd = true
       // Switch selection
       if (!this.nodeSelection[tnode.id] && !ev.ctrlKey) {
-        this.NODE_SELECTION_CLEAR()
+        selectionAdd = false
       }
-      this.NODE_SELECTION_ADD([tnode])
+      this.select([tnode], selectionAdd)
       this.NODE_RAISE(this.nodeSelection)
 
       let curP = this.transformedPoint(ev.x, ev.y)
@@ -406,6 +475,7 @@ export default {
       if (ev.ctrlKey && Object.keys(this.nodeSelection).length > 0) clone = true
       utils.createDrag({
         drag: (ev) => {
+          if (!ev.ctrlKey) clone = false
           /// /////////// IMPORTANT NEW ////////////////
           // logic: we analyse selection, create new nodes based on same src
           // with same things, and checkout the inner links, nodes between our nodes
@@ -442,23 +512,29 @@ export default {
                 }
               }
             }
-
             // Check inner links
-
-            this.NODE_SELECTION_CLEAR()
-            this.NODE_SELECTION_ADD(newNodes)
+            this.select(newNodes)
           }
+          // DRAG operation
           this.dragging = this.nodeSelection
           const dragP = this.transformedPoint(ev.x, ev.y)
           const nodeUpdate = []
           for (let k in this.nodeSelection) {
             const n = this.nodeById(k)
-            // create new nodes
-            nodeUpdate.push({
+            const cloneNode = {
               ...n,
               x: n.x + dragP.x - curP.x,
               y: n.y + dragP.y - curP.y
+            }
+            // XXX: temporary code
+            this.nodePropsUpdate(cloneNode)
+            // Find links too
+            this.nodeData.links.forEach((link, idx) => {
+              if (link.from !== k && link.to !== k) return true
+              this.$nextTick(() => { this.linkPropsUpdate(link, idx) })
             })
+
+            nodeUpdate.push(cloneNode)
           }
           this.NODE_UPDATE(nodeUpdate)
           curP = dragP
@@ -472,14 +548,23 @@ export default {
           for (let k in this.nodeSelection) {
             const n = this.nodeById(k)
             // create new nodes
-            nodeUpdate.push({
+            const cloneNode = {
               ...n,
               x: n.x + dragP.x - curP.x,
               y: n.y + dragP.y - curP.y
               // snapping
               // x: Math.round((n.x + dragP.x - curP.x) / 10) * 10,
               // y: Math.round((n.y + dragP.y - curP.y) / 10) * 10
+            }
+            // XXX: temporary code
+            this.nodePropsUpdate(cloneNode)
+            // Place this in the nodeProps itself
+            this.nodeData.links.forEach((link, idx) => {
+              if (link.from !== k && link.to !== k) return true
+              this.$nextTick(() => { this.linkPropsUpdate(link, idx) })
             })
+
+            nodeUpdate.push(cloneNode)
           }
           // Updating nodes
           this.NODE_UPDATE(nodeUpdate)
@@ -490,6 +575,7 @@ export default {
         }
       })
     },
+
     /// ////////////
     // NODE CREATOR FUNC
     //
@@ -551,7 +637,8 @@ export default {
           }
         },
         drop: (ev) => {
-          if (!ev.shiftKey) this.NODE_SELECTION_CLEAR()
+          let selectionAdd = false
+          if (ev.shiftKey) selectionAdd = true
           const nodesToSelect = []
           for (let n in this.nodeData.nodes) {
             const node = this.nodeData.nodes[n]
@@ -562,14 +649,16 @@ export default {
               // Add to selection
             }
           }
-          this.NODE_SELECTION_ADD(nodesToSelect)
+          this.select(nodesToSelect, selectionAdd)
           this.selector = null
+          this.nodePropsUpdate()
         },
         noDrag: (ev) => {
-          if (!ev.shiftKey) this.NODE_SELECTION_CLEAR()
+          if (!ev.shiftKey) this.select()
         }
       })
     },
+
     documentProcess () {
       const n = this.nodeData.nodes.find(n => n.src === 'Output')
       this.NODE_PROCESS(n.id)
@@ -604,6 +693,15 @@ export default {
 
       this.NODE_ADD([portalNode])
     },
+    select (nodes, add) {
+      if (!add) {
+        this.NODE_SELECTION_CLEAR()
+      }
+      if (nodes) {
+        this.NODE_SELECTION_ADD(nodes)
+      }
+      this.nodePropsUpdate()
+    },
     // HELPERS depending on svg ref
     createSVGPoint (x, y) {
       const p = this.$refs.svg.createSVGPoint()

+ 21 - 24
browser/vue-flow/src/components/flow/editor.vue

@@ -18,14 +18,12 @@
         ref="panzoom"
         v-model="panzoom">
 
-        <!-- links below nodes -->
         <flow-link
-          v-for="(link,i) in nodeData.links"
+          v-for="(l,i) in linkProps"
           :key="'link' + i"
-          v-bind="linkProps(link)"
-          @mousedown.middle="LINK_REMOVE(link)"
+          v-bind="l"
+          @mousedown.middle="LINK_REMOVE(nodeData.links[i])"
         />
-        <!-- trigger links -->
         <flow-trigger-link
           v-for="(trigger,i) in nodeData.triggers"
           :key="'trigger'+i"
@@ -33,21 +31,20 @@
           v-bind="triggerProps(trigger)"
           @mousedown.middle="TRIGGER_REMOVE(trigger)"
         />
-        <!-- nodes -->
         <flow-node
           ref="nodes"
-          v-for="(n,i) of nodeData.nodes"
-          v-bind="nodeProps(n)"
-          :key="'node' + n.id"
-          :id="n.id"
-          :selected="!!nodeSelection[n.id]"
-          @nodePointerDown.prevent="nodePointerDown($event,i)"
+          v-for="n of nodeProps"
+          v-bind="n"
+          :key="n.id"
+          @nodePointerDown.prevent="nodePointerDown($event,n.id)"
           @socketPointerDown="socketPointerDown(n.id,...arguments)"
           @triggerPointerDown="triggerPointerDown(n.id,...arguments)"
-          @nodeDoubleClick="$emit('nodeDoubleClick',n)"
-          @nodeRightClick="$refs.menu.open($event,n)"
+          @nodeDoubleClick="$emit('nodeDoubleClick',n.id)"
+          @nodeRightClick="$refs.menu.open($event,n.id)"
+          @activityPointerDown="$emit('activityPointerDown',n.id)"
         />
-        <!-- mouse link-->
+
+        <!-- mouse links-->
         <flow-link
           :pointer="true"
           v-if="pointerLink.active"
@@ -76,21 +73,21 @@
       <button v-if="outputNode" @click="documentProcess" class="primary-inverse">RUN</button>
     </div>
     <div class="flow-container__info">
-      x:{{ panzoom.x.toFixed(2) }} y:{{ panzoom.y.toFixed(2) }} scale:{{ panzoom.zoom.toFixed(2) }};
-      {{ nodeData.nodes.length }} nodes;
-      {{ nodeData.links.length }} links;
-      {{ nodeData.triggers.length }} triggers;
-      {{ nodeSelection.length }} selected;
+      x:{{ panzoom.x.toFixed(2) }} y:{{ panzoom.y.toFixed(2) }} scale:{{ panzoom.zoom.toFixed(2) }} |
+      {{ nodeData.nodes.length }} nodes
+      {{ nodeData.links.length }} links
+      {{ nodeData.triggers.length }} triggers
+      {{ Object.keys(nodeSelection).length }} selected
     </div>
     <hx-context-menu ref="menu">
       <template slot-scope="d" >
         <div class="flow-node__context-menu">
-          <div class="hover" @click="NODE_PROCESS(d.userData.id)">Run</div>
-          <div class="hover" @click="createPortal(d.userData.id)">Create Portal</div>
+          <div class="hover" @click="NODE_PROCESS(d.userData)">Run</div>
+          <div class="hover" @click="createPortal(d.userData)">Create Portal</div>
           <hr>
-          <div class="hover" @click="NODE_REMOVE([d.userData])">Delete</div>
+          <div class="hover" @click="nodeRemove(d.userData)">Delete</div>
           <hr>
-          <div class="hover" @click="NODE_TRAIN(d.userData.id)">Train(temporary)</div>
+          <div class="hover" @click="NODE_TRAIN(d.userData)">Train(temporary)</div>
           <hr>
           <div class="hover" @click="nodeInspect(d.userData,true)">Inspect</div>
         </div>

+ 1 - 1
browser/vue-flow/src/components/flow/link.vue

@@ -21,7 +21,7 @@ export default {
     path () {
       const x1 = this.x1 + 7
       const y1 = this.y1
-      const x2 = this.x2 - (this.pointer ? 4 : 19.5)
+      const x2 = this.x2 - (this.pointer ? 19.5 : 19.5)
       const y2 = this.y2
 
       const dx = x2 - x1

+ 1 - 1
browser/vue-flow/src/components/flow/modal-data.vue

@@ -1,6 +1,6 @@
 <template>
   <div slot="body">
-    canvas maybe?
+    WIP: canvas maybe?
     <p> {{ nodeInspect }}</p>
     <hr>
     <p> {{ nodeActivity }} </p>

+ 35 - 13
browser/vue-flow/src/components/flow/node-activity.vue

@@ -2,6 +2,14 @@
   <g
     class="flow-node__activity"
     :status="nodeActivity.status"
+    :class="{
+      'flow-node__activity--running': nodeActivity.status == 'running',
+      'flow-node__activity--waiting': nodeActivity.status == 'waiting',
+      'flow-node__activity--finish-data': nodeActivity.status == 'finish' && nodeActivity.data,
+      'flow-node__activity--finish': nodeActivity.status == 'finish' && !nodeActivity.data,
+      'flow-node__activity--error': nodeActivity.status == 'error'
+    }"
+    @mousedown.stop.prevent="activityPointerDown"
   >
     <rect
       class="flow-node__activity-background"
@@ -15,23 +23,23 @@
     <icon-refresh
       v-if="nodeActivity.status=='running'"
       v-bind="iconProps"
-      class="flow-node__activity-icon flow-node__activity--running" />
+      class="flow-node__activity-icon" />
     <icon-wait
       v-else-if="nodeActivity.status=='waiting'"
       v-bind="iconProps"
-      class="flow-node__activity-icon flow-node__activity--waiting"/>
+      class="flow-node__activity-icon"/>
     <icon-data
       v-else-if="nodeActivity.status=='finish' && nodeActivity.data"
       v-bind="iconProps"
-      class="flow-node__activity-icon flow-node__activity--finish-data"/>
+      class="flow-node__activity-icon"/>
     <icon-ok
       v-else-if="nodeActivity.status=='finish'"
       v-bind="iconProps"
-      class="flow-node__activity-icon flow-node__activity--finish"/>
+      class="flow-node__activity-icon"/>
     <icon-fail
       v-else-if="nodeActivity.status=='error'"
       v-bind="iconProps"
-      class="flow-node__activity-icon flow-node__activity-error"/>
+      class="flow-node__activity-icon"/>
     <icon-question
       v-else
       v-bind="iconProps"
@@ -93,6 +101,7 @@ export default {
   },
   watch: {
     nodeActivity (val, oldVal) {
+      console.log('Activity updated')
       this.recalcStartTime()
     }
   },
@@ -105,6 +114,9 @@ export default {
     clearTimeout(this._timeOut)
   },
   methods: {
+    activityPointerDown () {
+      this.$emit('activityPointerDown')
+    },
     recalcStartTime () {
       this.startTime = null
       this.finishTime = null
@@ -146,7 +158,7 @@ export default {
   font-size:12px;
   opacity:0;
   user-select: none;
-  pointer-event:none;
+  pointer-events:none;
   transition: all var(--transition-speed);
 }
 
@@ -174,35 +186,45 @@ export default {
   opacity:1;
 }
 
-.flow-node__activity-icon  >* {
+.flow-node__activity-icon > * {
   transform-origin: 32px 32px;
   stroke-width: 6px;
   stroke: inherits;
 }
 
-.flow-node__activity--running >* {
+.flow-node__activity--running
+.flow-node__activity-icon > * {
   -webkit-animation: spin 1s infinite linear;
   -moz-animation: spin 1s infinite linear;
   animation: spin 1s infinite linear;
   stroke: #aa0;
 }
 
-.flow-node__activity--error > * {
+.flow-node__activity--error
+.flow-node__activity-icon  >* {
   stroke: #f22;
 }
 
-.flow-node__activity--waiting > * {
+.flow-node__activity--waiting
+.flow-node__activity-icon > * {
   -webkit-animation: shake 1s infinite linear;
   -moz-animation: shake 1s infinite linear;
   animation: shake 1s infinite linear;
 }
 
-.flow-node__activity--finish-data > * {
-  stroke: #5c5;
+.flow-node__activity--finish-data {
+  pointer-events: unset;
+  cursor:pointer;
+}
+
+.flow-node__activity--finish-data
+.flow-node__activity-icon > * {
   transform: scale(0.9) translateY(-3px);
+  stroke: #4c4;
 }
 
-.flow-node__activity--finish > * {
+.flow-node__activity--finish
+.flow-node__activity-icon > * {
   stroke: #2c2;
 }
 

+ 65 - 26
browser/vue-flow/src/components/flow/node.vue

@@ -6,9 +6,9 @@
       'flow-node--selected': selected
     }"
     :status="status"
-    @mousedown.stop.prevent="$emit('nodePointerDown',$event)"
-    @contextmenu.capture.prevent="$emit('nodeRightClick', $event)"
-    @dblclick="$emit('nodeDoubleClick',$event)"
+    @mousedown.stop.prevent="nodePointerDown"
+    @contextmenu.capture.prevent="nodeRightClick"
+    @dblclick="nodeDoubleClick"
   >
 
     <!-- shape -->
@@ -71,14 +71,13 @@
     <g
       v-for="(inp,i) in inputs"
       :key="'in'+i"
-      :data-nodeid="id"
+      :data-nodeid="node.id"
       :data-in="i"
-      v-bind="inputProps(i)"
+      v-bind="inputProps[i]"
       @mousedown.stop.prevent="socketPointerDown($event, {in:i})"
       class="flow-node__socket flow-node__socket--inputs"
     >
       <circle r="5" />
-      <!--<rect :x="-16-inputLabel(i).length*7 " :y="-10" :width="inputLabel(i).length*7" :height="20" fill="red" stroke-width="0"/>-->
       <rect
         v-bind="inputLabelBGProps(i)"
         class="flow-node__socket-detail--background"
@@ -89,14 +88,14 @@
         :x="-14"
         :y="4"
         class="flow-node__socket-detail"
-      >{{ inputLabel(i) }}</text>
+      >{{ inputLabel[i] }}</text>
 
     </g>
     <!-- output -->
     <g
       v-if="output"
       v-bind="outputProps(0)"
-      :data-nodeid="id"
+      :data-nodeid="node.id"
       :data-out="0"
       :key="'out'+0"
       @mousedown.stop.prevent="socketPointerDown($event, {out:0})"
@@ -121,7 +120,7 @@
     <rect
       class="flow-node__trigger flow-node__socket--trigger"
       :class="{'flow-node__trigger--match': match.type == 'trigger-out'}"
-      :data-nodeid="id"
+      :data-nodeid="node.id"
       data-dir="in"
       :x="-5"
       :y="-bodyProps.height/2-5"
@@ -132,7 +131,7 @@
     <rect
       class="flow-node__trigger flow-node__socket--trigger"
       :class="{'flow-node__trigger--match': match.type == 'trigger-in'}"
-      :data-nodeid="id"
+      :data-nodeid="node.id"
       data-dir="out"
       :x="-5"
       :y="bodyProps.height/2 -5"
@@ -149,8 +148,9 @@
     </text>
 
     <flow-node-activity
+      @activityPointerDown="activityPointerDown"
       v-if="nodeActivity"
-      :node-id="id"
+      :node-id="node.id"
       :transform="'translate('+bodyProps.width/2 +','+ -bodyProps.height/2 +')'"/>
   </g>
 </template>
@@ -172,7 +172,7 @@ export default {
   name: 'FlowNode',
   components: {FlowNodeActivity},
   props: {
-    'id': {type: String, required: true},
+    'node': {type: Object, required: true},
     'match': {type: Object, default: () => {}},
     'dragging': {type: Boolean, default: false},
     'selected': {type: Boolean, default: false}
@@ -182,15 +182,18 @@ export default {
   data () {
     return {
       // maintain reference here?
+      inputLabelRect: [],
+      outputLabelRect: [],
       labelRect: {x: 0, y: 0, width: 0, height: 0}
       // bodyRect: {x: 0, y: 0, width: 0, height: 0}
     }
   },
   computed: {
-    ...mapGetters('flow', ['nodeData', 'registry', 'activity']),
-    node () {
-      return this.nodeData.nodes.find(n => n.id === this.id)
-    },
+    ...mapGetters('flow', ['nodeData', 'registry', 'activity', 'nodeById']),
+    /* node () {
+      // cached
+      return this.nodeById(this.id)
+    }, */
     inputs () {
       return this.registry[this.node.src].inputs || []
     },
@@ -198,7 +201,7 @@ export default {
       return this.registry[this.node.src].output
     },
     nodeActivity () {
-      return this.activity && this.activity.nodes && this.activity.nodes[this.id]
+      return this.activity && this.activity.nodes && this.activity.nodes[this.node.id]
     },
     color () {
       return this.node.color ||
@@ -225,6 +228,7 @@ export default {
         transform: `translate(0,${-this.labelRect.height / 2})`
       }
     },
+    // Store this locally
     bodyProps () {
       let width = this.labelRect.width + 46
       let height = Math.max(this.labelRect.height + 20, 60, this.inputs.length * 25)
@@ -252,7 +256,28 @@ export default {
       return rect
     },
     inputProps () {
-      return (i) => {
+      const ret = []
+      for (var i in this.inputs) {
+        // console.log('Recalc input props')
+        // console.log('Rebuild input WHY')
+
+        let defaultInput = this.node.defaultInputs[i]
+
+        const inp = this.inputs[i]
+        const match = this.match.type === 'socket-in' && (inp.type === this.match.dtype || this.match.dtype === 'interface {}' || inp.type === 'interface {}')
+
+        const {x, y} = this.inputPos(i)
+        ret.push({
+          class: {
+            'flow-node__socket--match': match,
+            'flow-node__socket--withvalue': !!defaultInput
+          },
+          transform: `translate(${x} ${y})`
+        })
+      }
+      return ret
+
+      /* return (i) => {
         let defaultInput = this.node.defaultInputs[i]
 
         const inp = this.inputs[i]
@@ -266,7 +291,7 @@ export default {
           },
           transform: `translate(${x} ${y})`
         }
-      }
+      } */
     },
     outputProps () {
       return (i) => {
@@ -282,14 +307,17 @@ export default {
       }
     },
     inputLabel () {
-      return (i) => {
+      const ret = []
+      for (var i in this.inputs) {
+        // console.log('Recalulating input label')
         let input = ''
         if (this.inputs[i].name) {
           input += this.inputs[i].name + ':'
         }
         input += this.inputs[i].type
-        return input
+        ret.push(input)
       }
+      return ret
     },
     defInput () {
       return (i) => {
@@ -301,7 +329,6 @@ export default {
           this.$nextTick(this.$forceUpdate)
           return {x: 0, y: 0, width: 0, height: 0}
         }
-
         let bbox = this.$refs.inputLabel[i].getBBox()
 
         return {x: bbox.x - 5, y: bbox.y - 2, width: bbox.width + 10, height: bbox.height + 4}
@@ -324,17 +351,18 @@ export default {
           this.$nextTick(this.$forceUpdate)
           return {x: 0, y: 0, width: 0, height: 0}
         }
-
+        // console.log('Recalc label')
         let bbox = this.$refs.outputLabel.getBBox()
 
         return {x: bbox.x - 5, y: bbox.y - 2, width: bbox.width + 10, height: bbox.height + 4}
       }
     }
-
   },
   watch: {
     // the only thing now that affects geometry
-    'node.label' () {
+    'node.label' (val, oldVal) {
+      if (val === oldVal) { return }
+      console.log('Label changed')
       this.$nextTick(() => {
         this.labelRect = this.$refs.label.getBBox()
       })
@@ -366,12 +394,23 @@ export default {
         y: 0
       }
     },
-
+    nodePointerDown (ev) {
+      this.$emit('nodePointerDown', ev)
+    },
+    nodeRightClick (ev) {
+      this.$emit('nodeRightClick', ev)
+    },
+    nodeDoubleClick (ev) {
+      this.$emit('nodeDoubleClick', ev)
+    },
     socketPointerDown (ev, socket) {
       this.$emit('socketPointerDown', ev, socket)
     },
     triggerPointerDown (ev, dir) {
       this.$emit('triggerPointerDown', ev, dir)
+    },
+    activityPointerDown (ev) {
+      this.$emit('activityPointerDown', ev)
     }
 
   }

+ 1 - 0
browser/vue-flow/src/components/flow/panel-funcs.vue

@@ -99,6 +99,7 @@ export default {
   watch: {
     search () {
       for (let g of this.$refs.funcGroup) {
+        // Hack?
         g.state.active = true
       }
     }

+ 1 - 5
browser/vue-flow/src/components/flow/panel-inspector.vue

@@ -72,7 +72,7 @@
           </div>
         </div>
         <div v-if="registry[nodeInspect.src].inputs" class="flow-inspector__area flow-inspector--inputs">
-          <h3>Inputs constants</h3>
+          <h3>Constants</h3>
           <div
             class="flow-inspector__param"
             v-for="(n,i) in registry[nodeInspect.src].inputs"
@@ -209,7 +209,6 @@ export default {
 
 .flow-inspector__area h3{
   font-size:14px;
-  color: var(--primary-lighter);
   padding-bottom:4px;
 }
 
@@ -235,9 +234,6 @@ export default {
   word-wrap: break-word;
 }
 
-.flow-inspector--activity {
-}
-
 .flow-inspector--properties-error {
   color: red;
 }

+ 1 - 1
browser/vue-flow/src/components/shared/hx-collapsible.vue

@@ -22,7 +22,7 @@ import HxToggleArrow from './hx-toggle-arrow'
 export default {
   components: {HxToggleArrow},
   props: {
-    active: {type: Boolean, default: true},
+    active: {type: Boolean, default: false},
     icon: {type: Boolean, default: true}
   },
   data () {

+ 3 - 1
browser/vue-flow/src/store/flow/getters.js

@@ -1,7 +1,9 @@
 export default {
+  nodeIdxCache: state => state.nodeIdxCache,
   nodeData: state => state.nodeData,
   nodeById: state => id => {
-    return state.nodeData.nodes.find(n => n.id === id)
+    const idx = state.nodeIdxCache[id]
+    return state.nodeData.nodes[idx]
   },
   registry: state => state.registry,
   activity: state => state.activity,

+ 24 - 2
browser/vue-flow/src/store/flow/mutations.js

@@ -11,6 +11,10 @@ export default {
   },
   [m.DOCUMENT_UPDATE] (state, nodeData) {
     state.nodeData = nodeData
+    // Rebuild cache
+    for (let k in state.nodeData.nodes) {
+      state.nodeIdxCache[state.nodeData.nodes[k].id] = k
+    }
   },
   [m.ACTIVITY_UPDATE] (state, activity) {
     state.activity = activity
@@ -18,18 +22,27 @@ export default {
   [m.NODE_RAISE] (state, nodes) {
     for (let k in nodes) {
       const sn = nodes[k]
+      // Need to search by ID since its manipulating the list?
       let ni = state.nodeData.nodes.findIndex(n => n.id === sn.id)
       const node = state.nodeData.nodes[ni]
       state.nodeData.nodes.splice(ni, 1)
       state.nodeData.nodes.push(node) // put in last
     }
+    // Rebuild cache
+    //
+    for (let k in state.nodeData.nodes) {
+      state.nodeIdxCache[state.nodeData.nodes[k].id] = k
+    }
   },
   [m.NODE_UPDATE] (state, nodes) {
     // If array
     for (let k in nodes) {
       const node = nodes[k]
-      const ni = state.nodeData.nodes.findIndex(n => n.id === node.id)
-      Vue.set(state.nodeData.nodes, ni, node)
+      const ni = state.nodeIdxCache[node.id]
+      if (ni === null) continue
+
+      Object.assign(state.nodeData.nodes[ni], node)
+      // Vue.set(state.nodeData.nodes, ni, node)
       if (node.id === state.nodeInspect.id) {
         // Update node inspect
         state.nodeInspect = node
@@ -38,10 +51,15 @@ export default {
   },
   [m.NODE_ADD] (state, nodes) {
     state.nodeData.nodes.push(...nodes)
+    // Rebuild cache
+    for (let k in state.nodeData.nodes) {
+      state.nodeIdxCache[state.nodeData.nodes[k].id] = k
+    }
   },
   [m.NODE_REMOVE] (state, nodes) {
     for (let k in nodes) {
       const node = nodes[k]
+      // Need to search by ID since its manipulating the list
       const ni = state.nodeData.nodes.findIndex(n => n.id === node.id)
       state.nodeData.links = state.nodeData.links
         .filter(l => l.from !== node.id && l.to !== node.id)
@@ -49,6 +67,10 @@ export default {
         .filter(l => l.from !== node.id && l.to !== node.id)
       state.nodeData.nodes.splice(ni, 1)
     }
+    // Rebuild cache
+    for (let k in state.nodeData.nodes) {
+      state.nodeIdxCache[state.nodeData.nodes[k].id] = k
+    }
   },
   [m.NODE_INSPECT] (state, nodeId) {
     const node = state.nodeData.nodes.find(n => n.id === nodeId)

+ 3 - 1
browser/vue-flow/src/store/flow/state.js

@@ -1,5 +1,7 @@
 export default {
   sessId: null,
+
+  nodeIdxCache: {}, // Cache node by ids to indexes
   // document
   nodeData: {
     nodes: [],
@@ -8,8 +10,8 @@ export default {
   },
   nodeInspect: {},
   nodeSelection: [],
-
   registry: {},
+
   activity: {nodes: []},
 
   notifications: []

+ 2 - 0
browser/vue-flow/src/utils/utils.js

@@ -42,6 +42,8 @@ module.exports = {
         Math.abs(y - ev.y) > 10) {
         inDrag = (obj && obj.drag) || function () {}
         inDrop = (obj && obj.drop) || function () {}
+
+        obj && obj.dragStart && obj.dragStart(ev)
       }
     }
 

+ 4 - 0
browser/vue-flow/yarn.lock

@@ -3572,6 +3572,10 @@ lodash.cond@^4.3.0:
   version "4.5.2"
   resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
 
+lodash.debounce@^4.0.8:
+  version "4.0.8"
+  resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
+
 lodash.memoize@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"