Explorar el Código

Introducing triggers

luis hace 7 años
padre
commit
13d27dd2af
Se han modificado 31 ficheros con 986 adiciones y 260 borrados
  1. 11 0
      readme.md
  2. 1 0
      browser/vue-flow/package.json
  3. 0 25
      browser/vue-flow/src/App.vue
  4. 4 0
      browser/vue-flow/src/assets/dark-theme.css
  5. 32 9
      browser/vue-flow/src/assets/default-theme.css
  6. 99 0
      browser/vue-flow/src/components/flow/link-trigger.vue
  7. 2 2
      browser/vue-flow/src/components/flow/link.vue
  8. 172 10
      browser/vue-flow/src/components/flow/manager.vue
  9. 7 5
      browser/vue-flow/src/components/flow/node-activity.vue
  10. 66 17
      browser/vue-flow/src/components/flow/node.vue
  11. 33 98
      browser/vue-flow/src/components/main.vue
  12. 43 19
      browser/vue-flow/src/components/panel-inspector.vue
  13. 1 0
      browser/vue-flow/src/services/flowservice.js
  14. 1 1
      browser/vue-flow/webpack.config.js
  15. 261 21
      browser/vue-flow/yarn.lock
  16. 1 1
      go/Makefile
  17. 12 2
      go/src/flow/operation.go
  18. 1 1
      go/src/flow/flowserver/chatroom.go
  19. 0 0
      go/src/flowserver/cmd/buildops/main.go
  20. 72 0
      go/src/flowserver/cmd/demo1/assets/assets.go
  21. 16 39
      go/src/flow/cmd/demo1/main.go
  22. 3 1
      go/src/flow/cmd/demo1/devops/devops.go
  23. 38 0
      go/src/flowserver/cmd/demo1/main.go
  24. 28 0
      go/src/flowserver/cmd/demo1/static/index.html
  25. 19 0
      go/src/flowserver/cmd/demo1/testops/testops.go
  26. 60 7
      go/src/flow/flowserver/flowbuilder.go
  27. 0 0
      go/src/flowserver/flowmsg/flowmessage.go
  28. 0 0
      go/src/flowserver/flowserver.go
  29. 2 1
      go/src/flow/flowserver/session.go
  30. 1 1
      go/src/flow/flowserver/sessionmgr.go
  31. 0 0
      go/src/flowserver/version.go

+ 11 - 0
readme.md

@@ -1,5 +1,16 @@
 # TODO
 
+## Ideas
+
+* [ ] triggers, vertical slots in nodes, handling events giving the possibility
+      to trigger execution of subsequent nodes based on links
+
+# Separate development
+
+* github.com/gohxs/flow
+* github.com/gohxs/flowserver
+* github.com/vue-hxs/flow-ui
+
 # Flow types
 
 * Graph

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

@@ -25,6 +25,7 @@
     "babel-loader": "^7.1.2",
     "babel-preset-env": "^1.6.0",
     "babel-preset-stage-3": "^6.24.1",
+    "copy-webpack-plugin": "^4.3.1",
     "cross-env": "^5.1.3",
     "css-loader": "^0.28.7",
     "eslint": "^4.16.0",

+ 0 - 25
browser/vue-flow/src/App.vue

@@ -4,31 +4,6 @@
   </div>
 </template>
 
-<script>
-import Vue from 'vue'
-import FlowService from './services/flowservice'
-export default {
-  name: 'App',
-  created () {
-    console.log(this.$route)
-    let ctx = this.$route.params.context
-    console.log('Context:', ctx)
-
-    let urlPath = [
-      window.location.host,
-      ctx,
-      'conn'
-    ]
-
-    let targetws = 'ws://' + urlPath.join('/')
-    if (window.location.protocol === 'https:') {
-      targetws = 'wss://' + urlPath.join('/')
-    }
-    Vue.use(FlowService, {location: targetws})
-  }
-}
-</script>
-
 <style lang="scss">
 * {
   box-sizing:border-box;

+ 4 - 0
browser/vue-flow/src/assets/dark-theme.css

@@ -48,3 +48,7 @@
 .dark .flow-node__activity {
   fill: var(--background-secondary);
 }
+
+.dark .app-info {
+  opacity: 0.1;
+}

+ 32 - 9
browser/vue-flow/src/assets/default-theme.css

@@ -205,6 +205,7 @@ h3 {
  * LINKS
  */
 .flow-link__visible {
+  opacity: 1;
   stroke: var(--normal);
 }
 
@@ -212,14 +213,26 @@ h3 {
 .flow-link:not(.flow-link--pointer):hover
 .flow-link__visible {
   stroke: var(--link-hover);
-
-  /*filter: url(#highlight-border);*/
 }
 
 .flow-link__head {
   fill: var(--normal) !important;
 }
 
+/*
+ * TRIGGERS
+ */
+.flow-trigger-link__visible {
+  opacity: 0.5;
+  stroke: var(--normal);
+}
+
+.flow-view:not(.activity)
+.flow-trigger-link:not(.flow-trigger-link--pointer):hover
+.flow-trigger-link__visible {
+  stroke: var(--link-hover);
+}
+
 /*
  * NODES
  */
@@ -227,6 +240,13 @@ h3 {
   /*filter: url(#highlight-border);*/
 }
 
+.flow-node__label {
+  font-family: RobotoMono, monospace;
+  letter-spacing: -0.05em;
+  font-size: 14px;
+  fill: var(--node-label);
+}
+
 .flow-node__selection {
   transition: all var(--transition-speed);
 }
@@ -252,13 +272,6 @@ h3 {
   fill: #2f2 !important;
 }
 
-.flow-node__label {
-  font-family: RobotoMono, monospace;
-  letter-spacing: -0.05em;
-  font-size: 14px;
-  fill: var(--node-label);
-}
-
 .flow-node__socket-detail {
   font-size: 12px;
   font-weight: 100;
@@ -267,6 +280,16 @@ h3 {
   filter: url(#solid-white);
 }
 
+.flow-node__trigger {
+  fill: var(--node-socket);
+  stroke: var(--node-socket);
+}
+
+.flow-node__trigger--match {
+  stroke: #2f2 !important;
+  fill: #2f2 !important;
+}
+
 /*
  * NODE ACTIVITY
  */

+ 99 - 0
browser/vue-flow/src/components/flow/link-trigger.vue

@@ -0,0 +1,99 @@
+<template>
+  <g
+    class="flow-trigger-link"
+    :class="{'flow-trigger-link--pointer':pointer}"
+    @click="$emit('click',$event)">
+    <path
+      class="flow-trigger-link__area"
+      :d="path"
+    />
+
+    <path
+      class="flow-trigger-link__visible"
+      :d="path"
+    />
+    <text
+      class="flow-trigger-link__label"
+      v-if="label"
+      :x="5+x1+(x2-x1)/2"
+      :y="y1+(y2-y1)/2"
+    >
+      on: {{ label }}
+    </text>
+  </g>
+</template>
+<script>
+export default {
+  name: 'FlowTriggerLink',
+  props: {
+    x1: {type: Number, default: 0},
+    y1: {type: Number, default: 0},
+    x2: {type: Number, default: 0},
+    y2: {type: Number, default: 0},
+    pointer: {type: Boolean, default: false},
+    label: {type: String, default: null}
+  },
+  computed: {
+    path () {
+      const x1 = this.x1
+      const y1 = this.y1
+      const x2 = this.x2
+      const y2 = this.y2 - (this.pointer ? 6 : 8)
+
+      let ox1, oy1, ox2, oy2
+      ox1 = 0
+      oy1 = 50
+      ox2 = 0
+      oy2 = -50
+      return `
+      M${x1},${y1} 
+      C${x1 + ox1},${y1 + oy1} 
+      ${x2 + ox2},${y2 + oy2} 
+      ${x2},${y2 - 1}
+      L${x2} ${y2}
+      `
+    }
+  }
+}
+</script>
+<style>
+.flow-trigger-link {
+  user-select:none;
+}
+
+.flow-view:not(.activity) .flow-trigger-link {
+  cursor:pointer;
+}
+
+.flow-trigger-link__head{
+  fill:#333;
+}
+
+.flow-trigger-link {
+  stroke:#333;
+}
+
+.flow-trigger-link__area {
+  stroke-width:20;
+  stroke: transparent;
+  fill: transparent;
+}
+
+.flow-trigger-link__visible{
+  stroke-dasharray: 10,4;
+  stroke-width:2;
+  fill: transparent;
+  marker-end:url(#head);
+}
+
+.flow-trigger-link--pointer {
+  pointer-events:none;
+}
+
+.flow-trigger-link__label {
+  font-size:10px;
+  fill: #777;
+  stroke:none;
+}
+
+</style>

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

@@ -94,13 +94,13 @@ export default {
   stroke:#333;
 }
 
-.flow-link .flow-link__area {
+.flow-link__area {
   stroke-width:20;
   stroke: transparent;
   fill: transparent;
 }
 
-.flow-link .flow-link__visible{
+.flow-link__visible{
   stroke-width:2;
   fill: transparent;
   marker-end:url(#head);

+ 172 - 10
browser/vue-flow/src/components/flow/manager.vue

@@ -7,6 +7,8 @@
       class="flow-view view"
       :class="{
         'flow-linking':linking || stickySockets,
+        'flow-triggers':triggerLinking || stickyTriggers,
+
         'activity':dragging || pointerLink.active ,
         'flow-node--activity':nodeActivity,
         'selecting': !!selector
@@ -25,10 +27,18 @@
         <!-- links below nodes -->
         <flow-link
           v-for="(link,i) in nodeData.links"
-          :key="i"
+          :key="'link' + i"
           v-bind="linkProps(link)"
           @click="linkPointerClick($event,link)"
         />
+        <!-- trigger links -->
+        <flow-trigger-link
+          v-for="(trigger,i) in nodeData.triggers"
+          :key="'trigger'+i"
+          label="WIP"
+          v-bind="triggerProps(trigger)"
+          @click="triggerRemove(trigger)"
+        />
         <!-- nodes -->
         <flow-node
           ref="nodes"
@@ -39,6 +49,7 @@
           :selected="nodeSelection[n.id]?true:false"
           @nodePointerDown.prevent="nodePointerDown($event,i)"
           @socketPointerDown="socketPointerDown(n.id,...arguments)"
+          @triggerPointerDown="triggerPointerDown(n.id,...arguments)"
           @nodeDoubleClick="$emit('nodeDblClick',n)"
           @nodeRightClick="$refs.menu.open($event,n)"
 
@@ -49,6 +60,11 @@
           v-if="pointerLink.active"
           v-bind="pointerLink.props"
         />
+        <flow-trigger-link
+          :pointer="true"
+          v-if="pointerTriggerLink.active"
+          v-bind="pointerTriggerLink.props"
+        />
         <rect
           class="flow-selector"
           :class="{'flow-selector--selecting':(selector)?true:false}"
@@ -56,8 +72,8 @@
       </flow-pan-zoom>
     </svg>
     <div class="flow-container__control">
-      <button @click="$emit('funcsPanelToggle')">Panel</button>
       <button @click="stickySockets=!stickySockets"> {{ stickySockets? 'Hide':'Show' }} sockets </button>
+      <button @click="stickyTriggers=!stickyTriggers"> {{ stickyTriggers? 'Hide':'Show' }} triggers </button>
       <button @click="nodeActivity=!nodeActivity"> {{ nodeActivity? 'Hide':'Show' }} activity </button>
       <button @click="$emit('documentSave')"> Save </button> <!-- should disable until confirmation -->
       <button v-if="panzoom.x!=0 || panzoom.y!=0 || panzoom.zoom!=1" @click="panzoomReset">
@@ -68,6 +84,7 @@
       x:{{ panzoom.x.toFixed(2) }} y:{{ panzoom.y.toFixed(2) }} scale:{{ panzoom.zoom.toFixed(2) }}
       nodes: {{ nodeData.nodes.length }}
       links: {{ nodeData.links.length }}
+      triggers: {{ nodeData.triggers.length }}
     </div>
     <hx-context-menu ref="menu">
       <template slot-scope="d" >
@@ -84,6 +101,8 @@
 <script>
 import FlowNode from './node'
 import FlowLink from './link'
+import FlowTriggerLink from './link-trigger'
+
 import FlowPanZoom from './panzoom'
 import HxContextMenu from '@/components/shared/hx-contextmenu'
 import SvgDefs from './svgdefswrapper'
@@ -91,7 +110,7 @@ import utils from '@/utils/utils'
 
 export default {
   name: 'FlowManager',
-  components: {FlowNode, FlowLink, FlowPanZoom, HxContextMenu, SvgDefs},
+  components: {FlowNode, FlowLink, FlowTriggerLink, FlowPanZoom, HxContextMenu, SvgDefs},
   props: {
     'activity': {type: Object, default: () => {}},
     'registry': {type: Object, default: () => {}},
@@ -102,41 +121,52 @@ export default {
     // const cloned = JSON.parse(JSON.stringify(this.value)) // initial?
     return {
       panzoom: { x: 0, y: 0, zoom: 1 },
-      nodeData: { nodes: [], links: [] },
+      nodeData: { nodes: [], links: [], triggers: [] },
 
       dragging: null,
       linking: false,
+      triggerLinking: false,
+
       pointerLink: {active: false, props: {}, src: {}},
+      pointerTriggerLink: {active: false, props: {}, src: {}},
+
       selector: null,
       nodeSelection: {},
 
       stickySockets: false,
+      stickyTriggers: false,
       nodeActivity: true
     }
   },
   computed: {
     nodeProps () {
       return (node) => {
-        let match = {}
+        let highlight = {}
         if (this.pointerLink.active && this.pointerLink.src.nodeId !== node.id) {
           if (this.pointerLink.src.in !== undefined) {
-            match = {out: this.pointerLink.src.type}
+            highlight = {type: 'socket-out', dtype: this.pointerLink.src.type}
           } else {
-            match = {in: this.pointerLink.src.type}
+            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.nodeRemove(node)
           return
         }
+        console.log('Updating nodeProp based on activity')
         return {
           transform: `translate(${node.x} ${node.y})`,
           id: node.id,
           label: node.label,
           inputs: nodeClass.inputs,
           output: nodeClass.output,
-          match: match,
+          // Combine this into one
+          match: highlight,
           dragging: this.dragging && !!this.dragging[node.id],
           activity: this.activity[node.id],
           nodeStyle: nodeClass.style
@@ -159,6 +189,37 @@ export default {
         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[nodeFrom.id] && this.activity[nodeFrom.id].status
+        }
+      }
+    },
+    triggerProps () {
+      return (trigger) => {
+        if (!this.$refs.nodes) return
+        // For size .x .y
+        const nodeFrom = this.nodeData.nodes.find(n => n.id === trigger.from)
+        const nodeTo = this.nodeData.nodes.find(n => n.id === trigger.to)
+
+        const refFrom = this.$refs.nodes.find(n => n.id === trigger.from)
+        const refTo = this.$refs.nodes.find(n => n.id === trigger.to)
+        if (!refFrom || !refTo) { // delete trigger
+          return {}
+        }
+
+        const fromOutput = {
+          x: 0,
+          y: refFrom.bodyProps.height / 2
+        }
+        const toInput = {
+          x: 0,
+          y: -refTo.bodyProps.height / 2
+        }
+
         return {
           x1: nodeFrom.x + fromOutput.x,
           y1: nodeFrom.y + fromOutput.y,
@@ -168,6 +229,7 @@ export default {
         }
       }
     }
+
   },
   mounted () {
     this.$flowService.on('document', (v) => {
@@ -193,13 +255,15 @@ export default {
       }
       this.nodeData = {
         nodes: nodes,
-        links: v.data.links
+        links: v.data.links,
+        triggers: v.data.triggers
       }
       this.$nextTick(() => {
         this.$forceUpdate()
       })
     })
     this.$flowService.on('nodeUpdate', (v) => {
+      console.log('Incoming node update')
       const nodes = v.data
       const nd = this.nodeData
       for (let nid in nodes) {
@@ -209,8 +273,12 @@ export default {
           nd.nodes.push(nodes[nid])
           continue
         }
+        let lnode = nd.nodes[idx]
+
+        Object.assign(lnode, node)
+
         // if (!this.dragging || !this.dragging[node.id]) {
-        this.$set(nd.nodes, idx, node) // new Node
+        // this.$set(nd.nodes, idx, node) // new Node
         // }
       }
     })
@@ -229,6 +297,7 @@ export default {
       if (document.activeElement && document.activeElement.matches('input,textarea')) { return }
       if (ev.shiftKey) {
         this.linking = true
+        this.triggerLinking = true
       }
 
       let single = null
@@ -262,6 +331,7 @@ export default {
     keyUp (ev) {
       if (!ev.shiftKey) {
         this.linking = false
+        this.triggerLinking = false
       }
     },
     panzoomReset () {
@@ -361,6 +431,76 @@ export default {
           this.linkAdd(link)
         }})
     },
+    triggerPointerDown (nodeId, ev, dir) {
+      if (ev.button !== 0) return
+
+      const nodeRef = this.$refs.nodes.find(n => n.id === nodeId)
+      const node = this.nodeData.nodes.find(n => n.id === nodeId)
+
+      const isInput = (dir === 'in')
+      const triggerPos = {
+        x: 0,
+        y: nodeRef.bodyProps.height / 2 * (isInput ? -1 : 1)
+      }
+      this.pointerTriggerLink.src = {nodeId: node.id, dir: dir}
+
+      this.pointerTriggerLink.props = {
+        x1: node.x + triggerPos.x,
+        y1: node.y + triggerPos.y,
+        x2: node.x + triggerPos.x,
+        y2: node.y + triggerPos.y
+      }
+
+      this.pointerTriggerLink.active = true
+      utils.createDrag({
+        drag: (ev) => {
+          const p = this.transformedPoint(ev.clientX, ev.clientY)
+          if (isInput) {
+            this.pointerTriggerLink.props.x1 = p.x
+            this.pointerTriggerLink.props.y1 = p.y
+          } else {
+            this.pointerTriggerLink.props.x2 = p.x
+            this.pointerTriggerLink.props.y2 = p.y
+          }
+        },
+        drop: (ev) => {
+          this.pointerTriggerLink.active = false
+
+          // find Parent
+          var curTarget = ev.target
+          for (; curTarget.hasAttribute !== undefined && curTarget !== document.body; curTarget = curTarget.parentNode) {
+            if (curTarget.hasAttribute('data-nodeid')) {
+              break
+            }
+          }
+          if (!curTarget.hasAttribute || curTarget === document.body) {
+            console.error('TRIGGER: target is not a socket')
+            return
+          }
+          const targetNodeId = curTarget.getAttribute('data-nodeid')
+          const targetDir = curTarget.getAttribute('data-dir')
+          if (targetNodeId === node.id) {
+            console.error('TRIGGER: cannot link to self')
+            return
+          }
+
+          let trigger
+          // target is input
+          if (targetDir === 'in') {
+            trigger = {
+              from: nodeId,
+              to: targetNodeId
+            }
+          } else {
+            trigger = {
+              from: targetNodeId,
+              to: nodeId
+            }
+          }
+          this.triggerAdd(trigger)
+        }
+      })
+    },
 
     nodeInspect (tnode, force) {
       this.$emit('nodeInspect', tnode, force)
@@ -414,19 +554,28 @@ export default {
         }
       })
     },
+
+    // STORE
     nodeRemove (node) {
       const i = this.nodeData.nodes.indexOf(node)
       if (i === -1) return
       this.nodeData.links = this.nodeData.links.filter(l => l.from !== node.id && l.to !== node.id)
+      this.nodeData.triggers = this.nodeData.triggers.filter(l => l.from !== node.id && l.to !== node.id)
+
       this.nodeData.nodes.splice(i, 1)
       this.sendFlowEvent('nodeRemove', node)
       this.sendDocumentUpdate()
     },
+    /// ////////////
+    // NODE CREATOR FUNC
+    //
+    // STORE
     nodeAdd (src, x = 100, y = 100) {
       const newNode = {
         id: utils.guid(),
         x: x,
         y: y,
+        defaultInputs: {},
         label: src,
         src: src
       }
@@ -463,6 +612,19 @@ export default {
       this.sendFlowEvent('linkRemove', link)
       this.sendDocumentUpdate()
     },
+
+    triggerAdd (trigger) {
+      this.nodeData.triggers.push(trigger)
+      this.sendDocumentUpdate()
+    },
+    triggerRemove (trigger) {
+      const i = this.nodeData.triggers.findIndex(l => l === trigger)
+      if (i === -1) return
+      this.nodeData.triggers.splice(i, 1)
+      this.sendFlowEvent('triggerRemove', trigger)
+      this.sendDocumentUpdate()
+    },
+
     managerDrop (ev) {
       ev.preventDefault()
       const reg = ev.dataTransfer.getData('text')

+ 7 - 5
browser/vue-flow/src/components/flow/node-activity.vue

@@ -3,11 +3,6 @@
     class="flow-node__activity"
     :status="activity.status"
   >
-    <!--<circle
-      class="flow-node__activity-background"
-      rx="0"
-      ry="0"
-      r="17"/>-->
     <rect
       class="flow-node__activity-background"
       x="-12"
@@ -67,6 +62,13 @@ export default {
       return utils.padStart(min.toFixed(0), 2, '0') + ':' + utils.padStart(sec.toFixed(0), 2, '0')
     }
   },
+  watcher: {
+    activity () {
+      clearTimeout(this._timeOut)
+      this.finishTime = null
+      this.updateTime()
+    }
+  },
   mounted () {
     this._timeOut = setTimeout(this.updateTime, 999)
   },

+ 66 - 17
browser/vue-flow/src/components/flow/node.vue

@@ -75,7 +75,7 @@
       v-bind="inputProps(i)"
       @mousedown.stop.prevent="socketPointerDown($event, {in:i})"
       class="flow-node__socket flow-node__socket--inputs"
-      :class="{'flow-node__socket--match': match.in && (inp.type == match.in || match.in == 'interface {}' || inp.type=='interface {}')}"
+      :class="{'flow-node__socket--match': match.type == 'socket-in' && (inp.type == match.dtype || match.dtype == 'interface {}' || inp.type=='interface {}')}"
     >
       <circle r="5" />
       <text
@@ -95,7 +95,7 @@
       :key="'out'+0"
       @mousedown.stop.prevent="socketPointerDown($event, {out:0})"
       class="flow-node__socket flow-node__socket--outputs"
-      :class="{ 'flow-node__socket--match': match.out && (output.type == match.out || match.out == 'interface {}' || output.type == 'interface {}'), }"
+      :class="{ 'flow-node__socket--match': match.type =='socket-out' && (output.type == match.dtype || match.dtype == 'interface {}' || output.type == 'interface {}'), }"
     >
       <circle r="5" />
       <text
@@ -106,7 +106,29 @@
       </text>
     </g>
 
-    <!-- socket labels -->
+    <!-- TRIGGER SOCKETS -->
+    <rect
+      class="flow-node__trigger flow-node__socket--trigger"
+      :class="{'flow-node__trigger--match': match.type == 'trigger-out'}"
+      :data-nodeid="id"
+      data-dir="in"
+      :x="-5"
+      :y="-bodyProps.height/2-5"
+      width="10"
+      height="10"
+      @mousedown.stop.prevent="triggerPointerDown($event, 'in')"
+    />
+    <rect
+      class="flow-node__trigger flow-node__socket--trigger"
+      :class="{'flow-node__trigger--match': match.type == 'trigger-in'}"
+      :data-nodeid="id"
+      data-dir="out"
+      :x="-5"
+      :y="bodyProps.height/2 -5"
+      width="10"
+      height="10"
+      @mousedown.stop.prevent="triggerPointerDown($event, 'out')"
+    />
 
     <flow-node-activity
       v-if="activity"
@@ -145,8 +167,8 @@ export default {
   data () {
     return {
       // maintain reference here?
-      labelRect: {x: 0, y: 0, width: 0, height: 0},
-      bodyRect: {x: 0, y: 0, width: 0, height: 0}
+      labelRect: {x: 0, y: 0, width: 0, height: 0}
+      // bodyRect: {x: 0, y: 0, width: 0, height: 0}
     }
   },
   computed: {
@@ -265,6 +287,9 @@ export default {
     },
     socketPointerDown (ev, socket) {
       this.$emit('socketPointerDown', ev, socket)
+    },
+    triggerPointerDown (ev, dir) {
+      this.$emit('triggerPointerDown', ev, dir)
     }
 
   }
@@ -277,13 +302,6 @@ export default {
   cursor:move;
 }
 
-.flow-node__socket {
-  pointer-events: none;
-  stroke-width:1;
-  opacity:0;
-  transition: all var(--transition-speed);
-}
-
 .flow-node__body {
   opacity:0.9;
   transition: all var(--transition-speed);
@@ -301,6 +319,14 @@ export default {
   stroke: green !important;
 }
 
+/* sockets */
+.flow-node__socket {
+  pointer-events: none;
+  stroke-width:1;
+  opacity:0;
+  transition: all var(--transition-speed);
+}
+
 .flow-view:not(.activity) .flow-node__socket:hover {
   stroke-width:10;
   cursor:pointer;
@@ -318,21 +344,44 @@ export default {
   stroke-width:10;
 }
 
-/*
-Override flow-node
-for hidden
- */
 .flow-linking .flow-node__socket {
   opacity:1;
   pointer-events: inherit;
 }
 
-.flow-linking .flow-node__socket--match,
 .flow-node__socket--match {
   opacity:1;
   pointer-events: inherit;
 }
 
+/* triggers */
+.flow-node__trigger {
+  pointer-events:none;
+  opacity:0;
+  transition: all var(--transition-speed);
+}
+
+.flow-triggers .flow-node__trigger {
+  pointer-events:inherit;
+  opacity:1;
+}
+
+.flow-view:not(.activity) .flow-node__trigger:hover {
+  stroke-width:10;
+  cursor:pointer;
+}
+
+.flow-node__trigger--match {
+  stroke-width:10;
+  opacity:1;
+  pointer-events: inherit;
+}
+
+/*
+Override flow-node
+for hidden
+ */
+
 .flow-node__label {
   stroke:none;
   pointer-events:none;

+ 33 - 98
browser/vue-flow/src/components/main.vue

@@ -43,15 +43,15 @@
         @input="documentUpdate"-->
         <hx-split
           dir="horizontal"
-          :resizeable="funcsActive"
-          :split="funcsActive?funcsSize:'0px'"
+          :resizeable="true"
+          :split="funcsSize"
           @onSplitResize="funcsSizeUpdate"
 
         >
           <div class="flow-panel__container">
             <div class="flow-panel__selector">
               <button :class="{active:panel=='palette'}" @click="panel='palette'">Funcs</button>
-              <button :class="{active:panel=='inspector'}" @click="panel='inspector';nodeInspect = nodeActive">Inspector</button>
+              <button :class="{active:panel=='inspector'}" @click="panel='inspector';">Inspector</button>
             </div>
             <transition name="fade">
               <flow-funcs
@@ -67,7 +67,8 @@
                 v-show="panel=='inspector'"
                 :registry="registry"
                 :activity="activity"
-                :node-inspect="nodeInspect"
+                :node-inspect="nodeActive"
+                @update="nodeInspectorChange"
                 @nodeProcess="nodeProcess($event)"
               />
             </transition>
@@ -76,7 +77,6 @@
             ref="flowManager"
             :activity="activity"
             :registry="registry"
-            @funcsPanelToggle="funcsActive=!funcsActive"
             @nodeInspect="nodeInspectStart(...arguments)"
             @nodeProcess="nodeProcess(...arguments)"
             @nodeDblClick="nodeInspectStart(...arguments,true)"
@@ -89,82 +89,12 @@
       <div class="app-chat">
         <app-chat/>
       </div>
-      <!-- Node inspector -->
-      <!-- Move this to a different place -->
-      <!-- And rename it to inspector -->
-      <!--<hx-modal
-        class="flow-modal"
-        v-if="nodeInspect"
-        @close="nodeInspect=null"
-        @keydown.esc="nodeInspect=null"
-      >
-        <div slot="header">Node inspector:</div>
-        <div slot="body" class="flow-modal__body">
-          <div class="flow-modal__info">
-            <svg class="flow-view preview activity flow-node--detail flow-node--activity flow-linking" width="100%" height="100%" viewBox="0 0 300 200">
-              <flow-panzoom>
-                <flow-node
-                  style="pointer-events:none"
-                  ref="modalPreviewNode"
-                  :id="nodeInspect.id"
-                  transform="translate(150,100)"
-                  :match="{}"
-                  :label="nodeInspect.label"
-                  :inputs= "registry[nodeInspect.src].inputs"
-                  :output= "registry[nodeInspect.src].output"
-                  :activity= "activity[nodeInspect.id]"
-                  :nodeStyle= "registry[nodeInspect.src].style"
-                />
-              </flow-panzoom>
-            </svg>
-            <div class="flow-modal__params" v-if="nodeInspect.prop" >
-              <h3>Node Parameters</h3>
-              <div class="flow-modal__param" v-for="(v,k) in nodeInspect.prop">
-                <label>{{ k }}</label>
-                <input
-                  ref="nodeInspectProp"
-                  type="text"
-                  @keydown.prevent.stop.enter="nodeInspect=null;"
-                  @keydown.esc="nodeInspect=null"
-                  v-model="nodeInspect.prop[k]">
-              </div>
-            </div>
-            <div class="flow-modal__properties">
-
-              <h3>Node Properties</h3>
-              <label>Description</label>
-              <div class="property">Bogus description</div>
-              <label>Help</label>
-              <div class="property">Connect to input a thing and goes to output another thing</div>
-              <div v-if="activity && activity[nodeInspect.id] && activity[nodeInspect.id].data" class="flow-modal__properties-result">
-                <label>Result</label>
-                <div class="property">{{ activity && activity[nodeInspect.id] && activity[nodeInspect.id].data }}</div>
-              </div>
-              <div v-if="activity && activity[nodeInspect.id] && activity[nodeInspect.id].error" class="flow-modal__properties-error">
-                <label>Error</label>
-                <div class="property">{{ activity && activity[nodeInspect.id] && activity[nodeInspect.id].error }}</div>
-              </div>
-              <div style="flex: 1 0 100%;">&nbsp;</div>
-              <button class="flow-modal__button-run primary-inverse" @click="nodeProcess(nodeInspect)">Run</button>
-            </div>
-          </div>
-          <label>label</label>
-          <input
-            ref="modalInput"
-            type="text"
-            @keydown.enter="nodeInspect=null;"
-            @keydown.esc="nodeInspect=null"
-            v-model="nodeInspect.label" >
-        </div>
-        <div class="flow-modal__footer" slot="footer">
-          <button class="primary" @click="nodeInspect=false">OK</button>
-        </div>
-      </hx-modal>-->
       <hx-notify/>
     </div>
   </div>
 </template>
 <script>
+import Vue from 'vue'
 import AppChat from '@/components/chat'
 import FlowManager from '@/components/flow/manager'
 import FlowNode from '@/components/flow/node'
@@ -174,6 +104,7 @@ import FlowInspector from './panel-inspector'
 import HxSplit from '@/components/shared/hx-split'
 import HxModal from '@/components/shared/hx-modal'
 import HxNotify from '@/components/shared/hx-notify'
+import FlowService from '@/services/flowservice'
 import defRegistry from './defregistry'
 import 'reset-css/reset.css'
 
@@ -201,27 +132,27 @@ export default {
       panel: 'palette',
 
       nodeActive: null,
-      nodeInspect: null,
+      // nodeInspect: null,
 
       funcsSize: '250px',
-      funcsActive: true,
       funcsResizeable: false,
 
       dark: false
     }
   },
-  watch: {
-    nodeInspect: {
-      handler (val, oldVal) {
-        if (!val === null && !oldVal) { return }
-        if (!val) {
-          this.$refs.flowManager.sendDocumentUpdate()
-          return
-        }
-        this.$refs.flowManager.sendFlowEvent('nodeUpdate', [this.nodeInspect])
-      },
-      deep: true
+  created () {
+    let ctx = this.$route.params.context
+    let urlPath = [
+      window.location.host,
+      ctx,
+      'conn'
+    ]
+
+    let targetws = 'ws://' + urlPath.join('/')
+    if (window.location.protocol === 'https:') {
+      targetws = 'wss://' + urlPath.join('/')
     }
+    Vue.use(FlowService, {location: targetws})
   },
 
   mounted () {
@@ -252,8 +183,6 @@ export default {
     })
     this.$flowService.on('sessionNotify', (v) => {
       this.$notify(v.data)
-      // Make this elsewhere
-      // console.log(v.data)
     })
     this.$flowService.on('sessionLog', (v) => {
       console.log(v.data) // Temporary
@@ -271,27 +200,33 @@ export default {
     })
   },
   methods: {
+    nodeInspectorChange (node) {
+      this.$refs.flowManager.sendFlowEvent('nodeUpdate', [node])
+    },
     nodeInspectStart (node, changePane) { // node
       this.nodeActive = node
       if (changePane) {
-        this.funcsActive = true
         this.panel = 'inspector'
       }
       if (this.panel !== 'inspector') {
         return
       }
-      this.nodeInspect = node
+      // this.nodeInspect = node
 
       // if (!changePane) { return }
       this.$nextTick(() => {
         // panel input
-        if (!this.$refs.inspector) { return }
+        if (!this.$refs.inspector) { }
         const insp = this.$refs.inspector
-        let targetInput = this.$refs.inspector.$refs.labelInput
-        if (insp.$refs.props && insp.$refs.props.length > 0) {
+        let targetInput = insp.$refs.label
+        if (insp.$refs.inputs && insp.$refs.inputs.length > 0) {
+          targetInput = insp.$refs.inputs[0]
+        } else if (insp.$refs.propss && insp.$refs.props.length > 0) {
           targetInput = insp.$refs.props[0]
         }
-
+        if (!targetInput) {
+          return
+        }
         targetInput.setSelectionRange(0, targetInput.value.length)
         targetInput.focus()
       })
@@ -339,7 +274,7 @@ export default {
   align-items: center;
 }
 
-.flow-main  .app-info {
+.app-info {
   opacity:0.5;
   color: #aaa;
   font-size:10px;

+ 43 - 19
browser/vue-flow/src/components/panel-inspector.vue

@@ -3,7 +3,7 @@
     <template v-if="nodeInspect">
       <!-- VIEWER -->
       <div class="flow-inspector__container">
-        <svg
+        <!--<svg
           class="flow-view preview activity flow-node--detail flow-node--activity flow-linking flow-inspector__area flow-inspector--view "
           width="100%"
           height="100%"
@@ -22,19 +22,31 @@
               :nodeStyle= "registry[nodeInspect.src].style"
             />
           </flow-panzoom>
-        </svg>
+        </svg>-->
 
         <!-- DESCRIPTIONS -->
         <div class="flow-inspector__area flow-inspector--properties ">
-
           <label>ID</label>
           <div class="property">[{{ nodeInspect.id }}]</div>
+          <label>src</label>
+          <div class="property">{{ nodeInspect.src }}</div>
           <label>Description</label>
           <div class="property">Bogus description</div>
           <label>Help</label>
           <div class="property">Connect to input a thing and goes to output another thing</div>
         </div>
-        <div class="flow-inspector__activity flow-inspector__area">
+        <div class="flow-inspector__area flow-inspector--inputs">
+          <h3>Inputs defaults:</h3>
+          <div
+            class="flow-inspector__param"
+            v-for="(n,i) in registry[nodeInspect.src].inputs"
+            :key="i">
+            <label>{{ i }}:{{ n.type }}</label>
+            <input ref="inputs" type="text" v-model="nodeInspect.defaultInputs[i]" @input="localChange">
+          </div>
+        </div>
+
+        <div class="flow-inspector__area flow-inspector--activity ">
           <div
             v-if="activity && activity[nodeInspect.id] && activity[nodeInspect.id].data"
             class="flow-inspector--properties-result">
@@ -55,14 +67,15 @@
             <input
               ref="props"
               type="text"
-              v-model="nodeInspect.prop[k]">
+              v-model="nodeInspect.prop[k]" @input="localChange">
           </div>
+
         </div>
 
         <!-- STATIC PARAM -->
         <div class="flow-inspector__area  flow-inspector--static">
           <label>label</label>
-          <input ref="labelInput" type="text" v-model="nodeInspect.label">
+          <input ref="label" type="text" v-model="nodeInspect.label" @input="localChange">
         </div>
       </div><!-- /container -->
 
@@ -90,9 +103,19 @@ export default {
     activity: {type: Object, default: null},
     registry: {type: Object, default: null}
   },
-  watch: {
-    nodeInspect () {
-      this.$nextTick(() => this.$forceUpdate())
+  data () {
+    return {
+      // nodeInspect: this.value
+      // JSON.parse(JSON.stringify(this.value))
+    }
+  },
+  methods: {
+    localChange () {
+      // Seems that there might be browsers triggering the input before the v-model
+      // so we defer the execution until we have nodeInspect updated
+      this.$nextTick(() => {
+        this.$emit('update', this.nodeInspect)
+      })
     }
   }
 }
@@ -127,21 +150,22 @@ export default {
 
 .flow-inspector__area{
   flex:1;
+  padding: 10px 0;
+  border-bottom: solid 1px rgba(150,150,150,0.3);
+}
+
+.flow-inspector__area h3{
+  font-size:14px;
+  color: var(--primary-lighter);
+  padding-bottom:4px;
 }
 
 .flow-inspector__area label{
   display:block;
+  font-weight:bold;
   padding:5px 0;
-  color: var(--primary);
-}
-
-.flow-inspector__area h3{
-  display:none;
-  background: var(--background);
-  font-size:14px;
-  color: var(--primary);
-  padding:4px 4px;
-  border-bottom: solid 1px rgba(150,150,150,0.2);
+  margin:0;
+  color: var(--primary-darker);
 }
 
 .flow-inspector--view {

+ 1 - 0
browser/vue-flow/src/services/flowservice.js

@@ -41,6 +41,7 @@ export default {
       'documentUpdate', 'documentRun', 'documentSave', // DOCUMENT
       'chatEvent', 'chatJoin', 'chatRename', // CHAT
       'linkAdd', 'linkUpdate', 'linkRemove', // LINK
+      'triggerAdd', 'triggerUpdate', 'triggerRemove',
       'nodeUpdate', 'nodeAdd', 'nodeRemove', 'nodeProcess' // NODE
     ].forEach(ftyp => {
       service[ftyp] = (param, id) => {

+ 1 - 1
browser/vue-flow/webpack.config.js

@@ -1,6 +1,7 @@
 var path = require('path')
 var webpack = require('webpack')
 var HtmlWebpackPlugin = require('html-webpack-plugin')
+var CopyWebpackPlugin = require('copy-webpack-plugin')
 
 var outfile = 'index.js'
 
@@ -52,7 +53,6 @@ module.exports = {
               'css-loader',
               'sass-loader?indentedSyntax'
             ]
-
           },
           postLoaders: {
             html: 'babel-loader'

+ 261 - 21
browser/vue-flow/yarn.lock

@@ -211,7 +211,7 @@ anymatch@^1.3.0:
     micromatch "^2.1.5"
     normalize-path "^2.0.0"
 
-aproba@^1.0.3:
+aproba@^1.0.3, aproba@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
 
@@ -271,7 +271,7 @@ array-unique@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
 
-arrify@^1.0.0:
+arrify@^1.0.0, arrify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
 
@@ -911,7 +911,7 @@ block-stream@*:
   dependencies:
     inherits "~2.0.0"
 
-bluebird@^3.1.1, bluebird@^3.4.7:
+bluebird@^3.1.1, bluebird@^3.4.7, bluebird@^3.5.0:
   version "3.5.1"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
 
@@ -1080,6 +1080,24 @@ bytes@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
 
+cacache@^10.0.1:
+  version "10.0.2"
+  resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.2.tgz#105a93a162bbedf3a25da42e1939ed99ffb145f8"
+  dependencies:
+    bluebird "^3.5.0"
+    chownr "^1.0.1"
+    glob "^7.1.2"
+    graceful-fs "^4.1.11"
+    lru-cache "^4.1.1"
+    mississippi "^1.3.0"
+    mkdirp "^0.5.1"
+    move-concurrently "^1.0.1"
+    promise-inflight "^1.0.1"
+    rimraf "^2.6.1"
+    ssri "^5.0.0"
+    unique-filename "^1.1.0"
+    y18n "^3.2.1"
+
 caller-path@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
@@ -1189,6 +1207,10 @@ chokidar@^1.6.0, chokidar@^1.7.0:
   optionalDependencies:
     fsevents "^1.0.0"
 
+chownr@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
+
 cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -1337,7 +1359,7 @@ concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
 
-concat-stream@^1.6.0:
+concat-stream@^1.5.0, concat-stream@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
   dependencies:
@@ -1393,6 +1415,32 @@ cookie@0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
 
+copy-concurrently@^1.0.0:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
+  dependencies:
+    aproba "^1.1.1"
+    fs-write-stream-atomic "^1.0.8"
+    iferr "^0.1.5"
+    mkdirp "^0.5.1"
+    rimraf "^2.5.4"
+    run-queue "^1.0.0"
+
+copy-webpack-plugin@^4.3.1:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.3.1.tgz#19ba6370bf6f8e263cbd66185a2b79f2321a9302"
+  dependencies:
+    cacache "^10.0.1"
+    find-cache-dir "^1.0.0"
+    globby "^7.1.1"
+    is-glob "^4.0.0"
+    loader-utils "^0.2.15"
+    lodash "^4.3.0"
+    minimatch "^3.0.4"
+    p-limit "^1.0.0"
+    pify "^3.0.0"
+    serialize-javascript "^1.4.0"
+
 core-js@^2.4.0, core-js@^2.5.0:
   version "2.5.3"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
@@ -1588,6 +1636,10 @@ currently-unhandled@^0.4.1:
   dependencies:
     array-find-index "^1.0.1"
 
+cyclist@~0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
+
 d@1:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
@@ -1715,6 +1767,13 @@ diffie-hellman@^5.0.0:
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
+dir-glob@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034"
+  dependencies:
+    arrify "^1.0.1"
+    path-type "^3.0.0"
+
 dns-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@@ -1795,6 +1854,15 @@ domutils@1.5.1:
     dom-serializer "0"
     domelementtype "1"
 
+duplexify@^3.4.2, duplexify@^3.5.3:
+  version "3.5.3"
+  resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.3.tgz#8b5818800df92fd0125b27ab896491912858243e"
+  dependencies:
+    end-of-stream "^1.0.0"
+    inherits "^2.0.1"
+    readable-stream "^2.0.0"
+    stream-shift "^1.0.0"
+
 ecc-jsbn@~0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
@@ -1835,6 +1903,12 @@ encodeurl@~1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
 
+end-of-stream@^1.0.0, end-of-stream@^1.1.0:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
+  dependencies:
+    once "^1.4.0"
+
 enhanced-resolve@^3.4.0:
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e"
@@ -2387,6 +2461,13 @@ flow@^0.2.3:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/flow/-/flow-0.2.3.tgz#f8da65efa249127ec99376a28896572a9795d1af"
 
+flush-write-stream@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417"
+  dependencies:
+    inherits "^2.0.1"
+    readable-stream "^2.0.4"
+
 for-in@^0.1.3:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
@@ -2439,6 +2520,22 @@ fresh@0.5.2:
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
 
+from2@^2.1.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
+  dependencies:
+    inherits "^2.0.1"
+    readable-stream "^2.0.0"
+
+fs-write-stream-atomic@^1.0.8:
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
+  dependencies:
+    graceful-fs "^4.1.2"
+    iferr "^0.1.5"
+    imurmurhash "^0.1.4"
+    readable-stream "1 || 2"
+
 fs.realpath@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2589,6 +2686,17 @@ globby@^6.1.0:
     pify "^2.0.0"
     pinkie-promise "^2.0.0"
 
+globby@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680"
+  dependencies:
+    array-union "^1.0.1"
+    dir-glob "^2.0.0"
+    glob "^7.1.2"
+    ignore "^3.3.5"
+    pify "^3.0.0"
+    slash "^1.0.0"
+
 globule@^1.0.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09"
@@ -2597,7 +2705,7 @@ globule@^1.0.0:
     lodash "~4.17.4"
     minimatch "~3.0.2"
 
-graceful-fs@^4.1.2:
+graceful-fs@^4.1.11, graceful-fs@^4.1.2:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
 
@@ -2854,7 +2962,11 @@ ieee754@^1.1.4:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
 
-ignore@^3.3.3, ignore@^3.3.6:
+iferr@^0.1.5:
+  version "0.1.5"
+  resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
+
+ignore@^3.3.3, ignore@^3.3.5, ignore@^3.3.6:
   version "3.3.7"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"
 
@@ -3007,7 +3119,7 @@ is-extglob@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
 
-is-extglob@^2.1.0:
+is-extglob@^2.1.0, is-extglob@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
 
@@ -3039,6 +3151,12 @@ is-glob@^3.1.0:
   dependencies:
     is-extglob "^2.1.0"
 
+is-glob@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
+  dependencies:
+    is-extglob "^2.1.1"
+
 is-my-json-valid@^2.12.4:
   version "2.17.1"
   resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz#3da98914a70a22f0a8563ef1511a246c6fc55471"
@@ -3319,7 +3437,7 @@ loader-runner@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
 
-loader-utils@^0.2.16:
+loader-utils@^0.2.15, loader-utils@^0.2.16:
   version "0.2.17"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
   dependencies:
@@ -3549,6 +3667,21 @@ minimist@^1.1.3, minimist@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
 
+mississippi@^1.3.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-1.3.1.tgz#2a8bb465e86550ac8b36a7b6f45599171d78671e"
+  dependencies:
+    concat-stream "^1.5.0"
+    duplexify "^3.4.2"
+    end-of-stream "^1.1.0"
+    flush-write-stream "^1.0.0"
+    from2 "^2.1.0"
+    parallel-transform "^1.1.0"
+    pump "^1.0.0"
+    pumpify "^1.3.3"
+    stream-each "^1.1.0"
+    through2 "^2.0.0"
+
 mixin-object@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e"
@@ -3562,6 +3695,17 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkd
   dependencies:
     minimist "0.0.8"
 
+move-concurrently@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
+  dependencies:
+    aproba "^1.1.1"
+    copy-concurrently "^1.0.0"
+    fs-write-stream-atomic "^1.0.8"
+    mkdirp "^0.5.1"
+    rimraf "^2.5.4"
+    run-queue "^1.0.3"
+
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -3798,7 +3942,7 @@ on-headers@~1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
 
-once@^1.3.0, once@^1.3.3:
+once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
   dependencies:
@@ -3870,6 +4014,12 @@ p-finally@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
 
+p-limit@^1.0.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
+  dependencies:
+    p-try "^1.0.0"
+
 p-limit@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc"
@@ -3884,10 +4034,22 @@ p-map@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
 
+p-try@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
+
 pako@~1.0.5:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
 
+parallel-transform@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
+  dependencies:
+    cyclist "~0.2.2"
+    inherits "^2.0.3"
+    readable-stream "^2.1.5"
+
 param-case@2.1.x:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247"
@@ -3971,6 +4133,12 @@ path-type@^2.0.0:
   dependencies:
     pify "^2.0.0"
 
+path-type@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
+  dependencies:
+    pify "^3.0.0"
+
 pbkdf2@^3.0.3:
   version "3.0.14"
   resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade"
@@ -4339,6 +4507,10 @@ progress@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
 
+promise-inflight@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
+
 proxy-addr@~2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
@@ -4364,6 +4536,28 @@ public-encrypt@^4.0.0:
     parse-asn1 "^5.0.0"
     randombytes "^2.0.1"
 
+pump@^1.0.0:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954"
+  dependencies:
+    end-of-stream "^1.1.0"
+    once "^1.3.1"
+
+pump@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
+  dependencies:
+    end-of-stream "^1.1.0"
+    once "^1.3.1"
+
+pumpify@^1.3.3:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb"
+  dependencies:
+    duplexify "^3.5.3"
+    inherits "^2.0.3"
+    pump "^2.0.0"
+
 punycode@1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
@@ -4483,16 +4677,7 @@ read-pkg@^2.0.0:
     normalize-package-data "^2.3.2"
     path-type "^2.0.0"
 
-readable-stream@1.0:
-  version "1.0.34"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.1"
-    isarray "0.0.1"
-    string_decoder "~0.10.x"
-
-readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.2.9, readable-stream@^2.3.3:
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.2.9, readable-stream@^2.3.3:
   version "2.3.3"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
   dependencies:
@@ -4504,6 +4689,15 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable
     string_decoder "~1.0.3"
     util-deprecate "~1.0.1"
 
+readable-stream@1.0:
+  version "1.0.34"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.1"
+    isarray "0.0.1"
+    string_decoder "~0.10.x"
+
 readdirp@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
@@ -4753,7 +4947,7 @@ right-align@^0.1.1:
   dependencies:
     align-text "^0.1.1"
 
-rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1:
+rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
   dependencies:
@@ -4772,6 +4966,12 @@ run-async@^2.2.0:
   dependencies:
     is-promise "^2.1.0"
 
+run-queue@^1.0.0, run-queue@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
+  dependencies:
+    aproba "^1.1.1"
+
 rx-lite-aggregates@^4.0.8:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
@@ -4858,6 +5058,10 @@ send@0.16.1:
     range-parser "~1.2.0"
     statuses "~1.3.1"
 
+serialize-javascript@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005"
+
 serve-index@^1.7.2:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
@@ -5054,6 +5258,12 @@ sshpk@^1.7.0:
     jsbn "~0.1.0"
     tweetnacl "~0.14.0"
 
+ssri@^5.0.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.1.0.tgz#2cbf1df36b74d0fc91fcf89640a4b3e1d10b1899"
+  dependencies:
+    safe-buffer "^5.1.0"
+
 "statuses@>= 1.3.1 < 2":
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
@@ -5075,6 +5285,13 @@ stream-browserify@^2.0.1:
     inherits "~2.0.1"
     readable-stream "^2.0.2"
 
+stream-each@^1.1.0:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd"
+  dependencies:
+    end-of-stream "^1.1.0"
+    stream-shift "^1.0.0"
+
 stream-http@^2.7.2:
   version "2.7.2"
   resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad"
@@ -5085,6 +5302,10 @@ stream-http@^2.7.2:
     to-arraybuffer "^1.0.0"
     xtend "^4.0.0"
 
+stream-shift@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
+
 strict-uri-encode@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
@@ -5222,6 +5443,13 @@ text-table@~0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
 
+through2@^2.0.0:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
+  dependencies:
+    readable-stream "^2.1.5"
+    xtend "~4.0.1"
+
 through@^2.3.6:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
@@ -5363,6 +5591,18 @@ uniqs@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
 
+unique-filename@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3"
+  dependencies:
+    unique-slug "^2.0.0"
+
+unique-slug@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab"
+  dependencies:
+    imurmurhash "^0.1.4"
+
 unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -5675,7 +5915,7 @@ xml-char-classes@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d"
 
-xtend@^4.0.0:
+xtend@^4.0.0, xtend@~4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
 

+ 1 - 1
go/Makefile

@@ -11,7 +11,7 @@ GETENV=
 # What packages to build
 
 # CLI Packages
-CLI=flow/cmd/demo1
+CLI=flowserver/cmd/demo1
 BIN=$(addprefix $(DIST)/, $(notdir $(CLI)))
 
 # Windows build

+ 12 - 2
go/src/flow/operation.go

@@ -8,6 +8,7 @@ package flow
 import (
 	"errors"
 	"fmt"
+	"log"
 	"reflect"
 	"sync"
 )
@@ -166,9 +167,16 @@ func opFunc(f *Flow, id string) *operation {
 				go func(i int, in *operation) {
 					defer wg.Done()
 					fr, err := in.processWithCtx(ctx, params...)
+					if fr == nil {
+						log.Println("Creating a new nil value?")
+						callParam[i] = reflect.Zero(fnval.Type().In(i))
+						return
+					}
 					if err != nil {
+						//callParam[i] = reflect.ValueOf(err)
 						return
 					}
+
 					callParam[i] = reflect.ValueOf(fr)
 				}(i, in)
 			}
@@ -176,8 +184,10 @@ func opFunc(f *Flow, id string) *operation {
 			// Return type checking
 			errMsg := ""
 			for i, p := range callParam {
-				// TypeChecking checking
-				if !p.IsValid() {
+				if p.Interface() == nil {
+					// do nothing just passthrough
+				} else if !p.IsValid() {
+					//callParam[i] = reflect.Zero(fnval.Type().In(i))
 					errMsg += fmt.Sprintf("Input %d invalid\n", i)
 				} else if !p.Type().AssignableTo(fnval.Type().In(i)) {
 					errMsg += fmt.Sprintf("Input %d type mismatch expected: %v got :%v\n", i, fnval.Type().In(i), p.Type())

+ 1 - 1
go/src/flow/flowserver/chatroom.go

@@ -2,7 +2,7 @@ package flowserver
 
 import (
 	"errors"
-	"flow/flowserver/flowmsg"
+	"flowserver/flowmsg"
 	"log"
 	"sync"
 

go/src/flow/cmd/buildops/main.go → go/src/flowserver/cmd/buildops/main.go


+ 72 - 0
go/src/flowserver/cmd/demo1/assets/assets.go

@@ -0,0 +1,72 @@
+// Package assets -- Generated by folder2go (http://github.com/gohxs/folder2go)
+package assets
+import (
+	"mime"
+	"net/http"
+	"path/filepath"
+	"strings"
+)
+type fs map[string][]byte
+
+var (
+	// Data contains the binarized folder
+	Data = fs{ 
+		"index.html": []byte{ 
+			0x3C, 0x21, 0x44, 0x4F, 0x43, 0x54, 0x59, 0x50, 0x45, 0x20, 0x68, 0x74, 0x6D, 0x6C, 0x3E, 0x0A, 0x3C, 0x68, 0x74, 0x6D, 
+			0x6C, 0x3E, 0x0A, 0x09, 0x3C, 0x68, 0x65, 0x61, 0x64, 0x3E, 0x0A, 0x09, 0x09, 0x3C, 0x74, 0x69, 0x74, 0x6C, 0x65, 0x3E, 
+			0x66, 0x6C, 0x6F, 0x77, 0x3C, 0x2F, 0x74, 0x69, 0x74, 0x6C, 0x65, 0x3E, 0x0A, 0x09, 0x09, 0x3C, 0x73, 0x74, 0x79, 0x6C, 
+			0x65, 0x3E, 0x0A, 0x2E, 0x61, 0x70, 0x70, 0x2D, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x6F, 0x72, 0x20, 0x7B, 0x0A, 0x20, 
+			0x20, 0x64, 0x69, 0x73, 0x70, 0x6C, 0x61, 0x79, 0x3A, 0x66, 0x6C, 0x65, 0x78, 0x3B, 0x0A, 0x20, 0x20, 0x66, 0x6C, 0x65, 
+			0x78, 0x2D, 0x66, 0x6C, 0x6F, 0x77, 0x3A, 0x63, 0x6F, 0x6C, 0x75, 0x6D, 0x6E, 0x3B, 0x0A, 0x20, 0x20, 0x6A, 0x75, 0x73, 
+			0x74, 0x69, 0x66, 0x79, 0x2D, 0x63, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x3A, 0x20, 0x63, 0x65, 0x6E, 0x74, 0x65, 0x72, 
+			0x3B, 0x0A, 0x20, 0x20, 0x61, 0x6C, 0x69, 0x67, 0x6E, 0x2D, 0x69, 0x74, 0x65, 0x6D, 0x73, 0x3A, 0x20, 0x63, 0x65, 0x6E, 
+			0x74, 0x65, 0x72, 0x3B, 0x0A, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3A, 0x31, 0x30, 0x30, 0x25, 0x3B, 0x0A, 
+			0x7D, 0x0A, 0x2E, 0x61, 0x70, 0x70, 0x2D, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x6F, 0x72, 0x20, 0x3E, 0x2A, 0x20, 0x7B, 
+			0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6E, 0x67, 0x3A, 0x31, 0x30, 0x70, 0x78, 0x3B, 0x20, 0x7D, 0x0A, 0x2E, 0x61, 0x70, 
+			0x70, 0x2D, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x6F, 0x72, 0x2D, 0x69, 0x74, 0x65, 0x6D, 0x73, 0x20, 0x2A, 0x3A, 0x66, 
+			0x69, 0x72, 0x73, 0x74, 0x2D, 0x63, 0x68, 0x69, 0x6C, 0x64, 0x20, 0x7B, 0x20, 0x62, 0x6F, 0x72, 0x64, 0x65, 0x72, 0x2D, 
+			0x72, 0x69, 0x67, 0x68, 0x74, 0x3A, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3B, 0x20, 0x7D, 0x0A, 0x09, 0x09, 0x3C, 0x2F, 0x73, 
+			0x74, 0x79, 0x6C, 0x65, 0x3E, 0x0A, 0x09, 0x3C, 0x2F, 0x68, 0x65, 0x61, 0x64, 0x3E, 0x0A, 0x09, 0x3C, 0x62, 0x6F, 0x64, 
+			0x79, 0x3E, 0x0A, 0x09, 0x09, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x61, 0x70, 0x70, 
+			0x2D, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x6F, 0x72, 0x22, 0x3E, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x68, 0x33, 0x3E, 
+			0x20, 0x53, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x20, 0x69, 0x64, 0x65, 0x61, 0x73, 0x20, 0x75, 0x73, 0x69, 0x6E, 0x67, 0x20, 
+			0x66, 0x6C, 0x6F, 0x77, 0x3A, 0x20, 0x3C, 0x2F, 0x68, 0x33, 0x3E, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, 
+			0x20, 0x63, 0x6C, 0x61, 0x73, 0x73, 0x3D, 0x22, 0x61, 0x70, 0x70, 0x2D, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x6F, 0x72, 
+			0x2D, 0x69, 0x74, 0x65, 0x6D, 0x73, 0x22, 0x3E, 0x0A, 0x09, 0x09, 0x09, 0x3C, 0x61, 0x20, 0x68, 0x72, 0x65, 0x66, 0x3D, 
+			0x22, 0x2F, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x22, 0x3E, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x20, 0x74, 
+			0x68, 0x69, 0x6E, 0x67, 0x3C, 0x2F, 0x61, 0x3E, 0x0A, 0x09, 0x09, 0x09, 0x3C, 0x61, 0x20, 0x68, 0x72, 0x65, 0x66, 0x3D, 
+			0x22, 0x2F, 0x64, 0x65, 0x76, 0x6F, 0x70, 0x73, 0x22, 0x3E, 0x44, 0x65, 0x76, 0x6F, 0x70, 0x73, 0x20, 0x73, 0x61, 0x6D, 
+			0x70, 0x6C, 0x65, 0x3C, 0x2F, 0x61, 0x3E, 0x0A, 0x09, 0x09, 0x09, 0x3C, 0x61, 0x20, 0x68, 0x72, 0x65, 0x66, 0x3D, 0x22, 
+			0x2F, 0x74, 0x65, 0x73, 0x74, 0x6F, 0x70, 0x73, 0x22, 0x3E, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67, 0x20, 0x72, 0x65, 
+			0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x3C, 0x2F, 0x61, 0x3E, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, 0x76, 
+			0x3E, 0x0A, 0x09, 0x3C, 0x2F, 0x64, 0x69, 0x76, 0x3E, 0x0A, 0x09, 0x3C, 0x2F, 0x62, 0x6F, 0x64, 0x79, 0x0A, 0x3C, 0x2F, 
+			0x68, 0x74, 0x6D, 0x6C, 0x3E, 0x0A, 0x0A, 			 
+		},
+	
+	}
+)
+
+
+// AssetHandleFunc Http handler
+func AssetHandleFunc(w http.ResponseWriter, r *http.Request) {
+	urlPath := ""
+
+	// func that handles mux
+	server := r.Context().Value(http.ServerContextKey).(*http.Server)
+	mux, ok := server.Handler.(*http.ServeMux)
+	if ok {
+		_, handlerPath := mux.Handler(r)
+		urlPath = strings.TrimPrefix(r.URL.String(), handlerPath)
+	}
+	if urlPath == "" { // Auto index
+		urlPath = "index.html"
+	}
+	data, ok := Data[urlPath]
+	if !ok {
+		w.WriteHeader(404)
+	}
+
+	w.Header().Set("Content-type", mime.TypeByExtension(filepath.Ext(urlPath)))
+	w.Write(data)
+}
+

+ 16 - 39
go/src/flow/cmd/demo1/main.go

@@ -1,45 +1,36 @@
-package main
+package defaultops
 
 import (
 	"errors"
 	"flow"
-	"flow/cmd/demo1/devops"
-	"flow/flowserver"
 	"flow/registry"
 	"fmt"
-	"log"
 	"math"
 	"math/rand"
-	"net/http"
 	"strings"
 	"time"
-
-	"github.com/gohxs/prettylog"
-	"github.com/gohxs/webu"
-	"github.com/gohxs/webu/chain"
 )
 
-func main() {
-	prettylog.Global()
-	log.Println("Running version:", flowserver.Version)
-
+// New create a registry
+func New() *registry.R {
+	r := registry.New()
 	// String functions
 	registry.Describer(
-		registry.Add(strings.Split).Inputs("string", "separator"),
-		registry.Add(strings.Join).Inputs("", "sep"),
-		registry.Add(strings.Compare, strings.Contains),
-		registry.Register("Cat", func(a, b string) string { return a + " " + b }),
-		registry.Register("ToString", func(a interface{}) string { return fmt.Sprint(a) }),
+		r.Add(strings.Split).Inputs("string", "separator"),
+		r.Add(strings.Join).Inputs("", "sep"),
+		r.Add(strings.Compare, strings.Contains),
+		r.Register("Cat", func(a, b string) string { return a + " " + b }),
+		r.Register("ToString", func(a interface{}) string { return fmt.Sprint(a) }),
 	).Tags("string").Extra("style", registry.M{"color": "#839"})
 
 	// Math functions
-	registry.Add(
+	r.Add(
 		math.Abs, math.Cos, math.Sin, math.Exp, math.Exp2, math.Tanh, math.Max, math.Min,
 	).Tags("math").Extra("style", registry.M{"color": "#386"})
 
 	registry.Describer(
-		registry.Add(rand.Int, rand.Intn, rand.Float64),
-		registry.Register("Perm", func(n int) []int {
+		r.Add(rand.Int, rand.Intn, rand.Float64),
+		r.Register("Perm", func(n int) []int {
 			if n > 10 { // Limiter for safety
 				n = 10
 			}
@@ -48,28 +39,14 @@ func main() {
 	).Tags("rand").Extra("style", registry.M{"color": "#486"})
 
 	// Test functions
-	registry.Add(testErrorPanic, testErrorDelayed, testRandomError).
+	r.Add(testErrorPanic, testErrorDelayed, testRandomError).
 		Tags("testing-errors")
 
 	registry.Describer(
-		registry.Register("wait", wait),
-		registry.Register("waitRandom", waitRandom),
+		r.Register("wait", wait),
+		r.Register("waitRandom", waitRandom),
 	).Tags("testing-time").Extra("style", map[string]string{"color": "#8a5"})
-
-	addr := ":2015"
-	log.Println("Starting server  at:", addr)
-
-	c := chain.New(webu.ChainLogger(prettylog.New("req")))
-
-	mux := http.NewServeMux()
-
-	mux.Handle("/", c.Build(http.RedirectHandler("/default/", 302).ServeHTTP))
-	mux.Handle("/default/", c.Build(flowserver.New(registry.Global, "default").ServeHTTP))
-
-	mux.Handle("/devops/", c.Build(flowserver.New(devops.New(), "devops").ServeHTTP))
-
-	// Context registry
-	http.ListenAndServe(addr, mux)
+	return r
 }
 
 func wait(data flow.Data, n int) flow.Data {

+ 3 - 1
go/src/flow/cmd/demo1/devops/devops.go

@@ -133,7 +133,9 @@ make: Leaving directory '/home/stdio/coding/Projects/Flow'`
 
 	for scanner.Scan() {
 		time.Sleep(time.Duration(rand.Intn(3000)) * time.Millisecond)
-		handler.out.Write([]byte(scanner.Text()))
+		if handler.out != nil {
+			handler.out.Write([]byte(scanner.Text()))
+		}
 	}
 
 	return handler

+ 38 - 0
go/src/flowserver/cmd/demo1/main.go

@@ -0,0 +1,38 @@
+package main
+
+import (
+	"flowserver"
+	"flowserver/cmd/demo1/assets"
+	"flowserver/cmd/demo1/defaultops"
+	"flowserver/cmd/demo1/devops"
+	"flowserver/cmd/demo1/testops"
+	"log"
+	"net/http"
+
+	"github.com/gohxs/prettylog"
+	"github.com/gohxs/webu"
+	"github.com/gohxs/webu/chain"
+)
+
+//go:generate go get github.com/gohxs/folder2go
+//go:generate folder2go -handler -nobackup static assets assets
+
+func main() {
+	prettylog.Global()
+	log.Println("Running version:", flowserver.Version)
+
+	addr := ":2015"
+	log.Println("Starting server  at:", addr)
+
+	c := chain.New(webu.ChainLogger(prettylog.New("req")))
+
+	mux := http.NewServeMux()
+	mux.HandleFunc("/", assets.AssetHandleFunc)
+
+	mux.Handle("/default/", c.Build(flowserver.New(defaultops.New(), "default").ServeHTTP))
+	mux.Handle("/devops/", c.Build(flowserver.New(devops.New(), "devops").ServeHTTP))
+	mux.Handle("/testops/", c.Build(flowserver.New(testops.New(), "tests").ServeHTTP))
+
+	// Context registry
+	http.ListenAndServe(addr, mux)
+}

+ 28 - 0
go/src/flowserver/cmd/demo1/static/index.html

@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>flow</title>
+		<style>
+.app-selector {
+  display:flex;
+  flex-flow:column;
+  justify-content: center;
+  align-items: center;
+  height:100%;
+}
+.app-selector >* { padding:10px; }
+.app-selector-items *:first-child { border-right: #000; }
+		</style>
+	</head>
+	<body>
+		<div class="app-selector">
+    <h3> Sample ideas using flow: </h3>
+    <div class="app-selector-items">
+			<a href="/default">Default thing</a>
+			<a href="/devops">Devops sample</a>
+			<a href="/testops">testing registry</a>
+    </div>
+	</div>
+	</body
+</html>
+

+ 19 - 0
go/src/flowserver/cmd/demo1/testops/testops.go

@@ -0,0 +1,19 @@
+package testops
+
+import "flow/registry"
+
+func New() *registry.R {
+
+	r := registry.New()
+
+	r.Register("myfunc", testLabels)
+
+	return r
+}
+
+type Bignamedtypetotestthelabelsinthesockets int
+
+func testLabels(a Bignamedtypetotestthelabelsinthesockets, b int) Bignamedtypetotestthelabelsinthesockets {
+	return Bignamedtypetotestthelabelsinthesockets(1)
+
+}

+ 60 - 7
go/src/flow/flowserver/flowbuilder.go

@@ -7,14 +7,16 @@ import (
 	"log"
 	"reflect"
 	"strings"
+	"time"
 )
 
 // Node that will contain registry src
 type Node struct {
-	ID    string            `json:"id"`
-	Src   string            `json:"src"`
-	Label string            `json:"label"`
-	Prop  map[string]string `json:"prop"`
+	ID            string            `json:"id"`
+	Src           string            `json:"src"`
+	Label         string            `json:"label"`
+	DefaultInputs map[int]string    `json:"defaultInputs"`
+	Prop          map[string]string `json:"prop"`
 }
 
 // Link that joins two nodes
@@ -24,15 +26,23 @@ type Link struct {
 	In   int    `json:"in"`
 }
 
+// Trigger that join two nodes on state change
+type Trigger struct {
+	From string   `json:"from"`
+	To   string   `json:"to"`
+	On   []string `json:"on"`
+}
+
 // FlowDocument flow document
 type FlowDocument struct {
-	Nodes []Node `json:"nodes"`
-	Links []Link `json:"links"`
+	Nodes    []Node    `json:"nodes"`
+	Links    []Link    `json:"links"`
+	Triggers []Trigger `json:"triggers"`
 }
 
 // FlowBuild build a flowGraph from incoming web ui json
 func FlowBuild(rawData []byte, r *registry.R) (*flow.Flow, error) {
-	doc := FlowDocument{[]Node{}, []Link{}}
+	doc := FlowDocument{[]Node{}, []Link{}, []Trigger{}}
 	err := json.Unmarshal(rawData, &doc)
 	if err != nil {
 		return nil, err
@@ -59,6 +69,14 @@ func FlowBuild(rawData []byte, r *registry.R) (*flow.Flow, error) {
 		}
 
 		param := make([]flow.Data, len(entry.Inputs))
+
+		// Default inputs
+		for i := range param {
+			if v, ok := n.DefaultInputs[i]; ok {
+				param[i] = v
+			}
+		}
+
 		// Find links
 		for _, l := range doc.Links {
 			if l.To != n.ID {
@@ -105,5 +123,40 @@ func FlowBuild(rawData []byte, r *registry.R) (*flow.Flow, error) {
 		}
 		f.DefOp(n.ID, n.Src, param...)
 	}
+	//Trigger testing
+	for _, t := range doc.Triggers {
+		log.Println("Trigger:", t)
+		log.Println("Attaching an hook per trigger test")
+
+		f.Hook(flow.Hook{
+			Any: func(name string, ID string, triggerTime time.Time, extra ...interface{}) {
+				if name == "waiting" {
+					return
+				}
+				if ID != t.From {
+					return
+				}
+				exec := false
+				for _, o := range t.On {
+					if name == o {
+						exec = true
+						break
+					}
+				}
+				log.Println("Should trigger on:", name, t.On)
+				if !exec {
+					log.Println("Mismatching trigger, but its a test")
+				}
+
+				log.Println("Grabbing an executor:", t.To)
+				op := f.Res(t.To)
+				log.Println("Processing this operator")
+				go op.Process() // Background
+				log.Println("Returning from operator")
+			},
+		})
+
+	}
+
 	return f, nil
 }

go/src/flow/flowserver/flowmsg/flowmessage.go → go/src/flowserver/flowmsg/flowmessage.go


go/src/flow/flowserver/flowserver.go → go/src/flowserver/flowserver.go


+ 2 - 1
go/src/flow/flowserver/session.go

@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"errors"
 	"flow"
-	"flow/flowserver/flowmsg"
+	"flowserver/flowmsg"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -194,6 +194,7 @@ func (s *FlowSession) NodeRun(c *websocket.Conn, data []byte) error {
 
 		s.flow, err = FlowBuild(s.RawDoc, localr)
 
+		log.Println("Flow:", s.flow)
 		if err != nil {
 			log.Println("Flow error:", err)
 			return

+ 1 - 1
go/src/flow/flowserver/sessionmgr.go

@@ -4,8 +4,8 @@ import (
 	"encoding/json"
 	"errors"
 	"flow"
-	"flow/flowserver/flowmsg"
 	"flow/registry"
+	"flowserver/flowmsg"
 	"log"
 	"net/http"
 	"os"

go/src/flow/flowserver/version.go → go/src/flowserver/version.go