Jelajahi Sumber

Adding sessions

luis 7 tahun lalu
induk
melakukan
19c66c4d4c

TEMPAT SAMPAH
DIST/flowserver


+ 10 - 0
DIST/web/index.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Flow</title>
+  </head>
+  <body>
+    <div id="app"></div>
+  <script type="text/javascript" src="/index.js"></script></body>
+</html>

File diff ditekan karena terlalu besar
+ 7 - 0
DIST/web/index.js


File diff ditekan karena terlalu besar
+ 1 - 0
DIST/web/index.js.map


+ 20 - 0
Makefile

@@ -0,0 +1,20 @@
+
+.PHONY: all backend frontend clean
+
+all:frontend backend
+	mkdir -p DIST
+
+clean:
+	rm -rf DIST
+
+backend:
+	cd go;make
+	cp go/DIST/* DIST
+
+frontend:
+	mkdir -p DIST/web
+	cd browser/vue-flow;yarn build
+	cp -r browser/vue-flow/dist/* DIST/web
+
+
+

+ 7 - 0
browser/vue-flow/docker/Caddyfile

@@ -0,0 +1,7 @@
+flow.hexasoftware.com {
+	tls off
+	rewrite / {
+		regexp ^[^.]*$
+		to /index.html
+	}
+}

+ 6 - 0
browser/vue-flow/docker/Dockerfile

@@ -0,0 +1,6 @@
+FROM joshix/caddy
+COPY dist/ /var/www/html
+COPY docker/Caddyfile /var/www/html
+WORKDIR /var/www/html
+EXPOSE 2015
+CMD ["/bin/caddy"]

+ 2 - 0
browser/vue-flow/docker/docker.sh

@@ -0,0 +1,2 @@
+docker build --rm -t hexasoftware.com:5000/flow-proto -f ./docker/Dockerfile ..
+docker push hexasoftware.com:5000/flow-proto

+ 0 - 1
browser/vue-flow/index.html

@@ -6,6 +6,5 @@
   </head>
   <body>
     <div id="app"></div>
-    <script src="/dist/build.js"></script>
   </body>
 </html>

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

@@ -6,6 +6,8 @@
   "license": "MIT",
   "private": true,
   "scripts": {
+    "docker": "docker build --rm -t hexasoftware.com:5000/flow-proto -f ./docker/Dockerfile .",
+    "docker-pull": "docker push hexasoftware.com:5000/flow-proto",
     "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
     "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
   },
@@ -19,6 +21,7 @@
   ],
   "devDependencies": {
     "babel-core": "^6.26.0",
+    "babel-eslint": "^8.2.1",
     "babel-loader": "^7.1.2",
     "babel-preset-env": "^1.6.0",
     "babel-preset-stage-3": "^6.24.1",
@@ -27,6 +30,8 @@
     "eslint": "^4.14.0",
     "eslint-config-node": "^2.0.0",
     "eslint-config-standard": "^11.0.0-beta.0",
+    "eslint-plugin-babel": "^4.1.2",
+    "eslint-plugin-import": "^2.8.0",
     "eslint-plugin-node": "^5.2.1",
     "eslint-plugin-promise": "^3.6.0",
     "eslint-plugin-standard": "^3.0.1",
@@ -36,8 +41,10 @@
     "flow-bin": "^0.62.0",
     "html-webpack-plugin": "^2.30.1",
     "node-sass": "^4.5.3",
+    "reset-css": "^2.2.1",
     "sass-loader": "^6.0.6",
     "vue-loader": "^13.0.5",
+    "vue-router": "^3.0.1",
     "vue-template-compiler": "^2.4.4",
     "webpack": "^3.6.0",
     "webpack-dev-server": "^2.9.1"

+ 3 - 16
browser/vue-flow/src/App.vue

@@ -1,20 +1,12 @@
 <template>
   <div id="app">
-    <h3>An app with svg</h3>
-    <flow-manager
-      width="100%"
-      height="100%"/>
+    <router-view/>
   </div>
 </template>
 
 <script>
-import FlowManager from '@/components/flowmanager'
-
 export default {
-  name: 'App',
-  components: {
-    'FlowManager': FlowManager
-  }
+  name: 'App'
 }
 </script>
 
@@ -29,8 +21,8 @@ html,body {
   max-width:100%;
   max-height:100%;
 }
-
 #app {
+  background: #f4f4f4;
   position:relative;
   padding:0;
   display:flex;
@@ -41,9 +33,4 @@ html,body {
   -moz-osx-font-smoothing: grayscale;
   color: #2c3e50;
 }
-
-#app .flow-container {
-  flex:1;
-}
-
 </style>

+ 25 - 0
browser/vue-flow/src/assets/style.css

@@ -0,0 +1,25 @@
+h4 {
+  margin: 2px 0 8px 0;
+  font-weight: bold;
+}
+
+button {
+  outline: none;
+  cursor: pointer;
+  background: #ccc;
+  border: none;
+  color: #333;
+  margin: 2px 0 2px 2px;
+  padding: 10px;
+  transition: all 0.3s;
+}
+
+button:hover {
+  background: #b1b1b1;
+}
+
+ul {
+  margin-left: 10px;
+  padding-left: 15px;
+  list-style: disc;
+}

+ 6 - 5
browser/vue-flow/src/components/flowlink.vue

@@ -1,8 +1,8 @@
 <template>
-  <g class="flow-link">
+  <g class="flow-link" @click="$emit('click',$event)">
     <path
       class="flow-link__area"
-      d:="path"
+      :d="path"
     />
     <path
       class="flow-link__visible"
@@ -11,6 +11,7 @@
   </g>
 </template>
 <script>
+const s = 60
 export default {
   name: 'FlowLink',
   props: {
@@ -21,7 +22,7 @@ export default {
   },
   computed: {
     path () {
-      return `M${this.x1},${this.y1} C${this.x1 + 50},${this.y1} ${this.x2 - 50},${this.y2} ${this.x2},${this.y2}`
+      return `M${this.x1},${this.y1} C${this.x1 + s},${this.y1} ${this.x2 - s},${this.y2} ${this.x2},${this.y2}`
     }
   }
 }
@@ -33,11 +34,11 @@ export default {
 .flow-link .flow-link__area {
   stroke-width:20;
   stroke: transparent;
-  fill:none;
+  fill: transparent;
 }
 .flow-link .flow-link__visible{
   stroke: rgba(0,0,0,0.5);
-  stroke-width:5;
+  stroke-width:4;
   stroke-linecap:round;
   fill: transparent;
 }

+ 297 - 0
browser/vue-flow/src/components/flow/manager.vue

@@ -0,0 +1,297 @@
+<template>
+  <div class="flow-container">
+    <div>
+      fn:
+      <button
+        :key="i"
+        v-for="(r,k,i) of nodeData.registry"
+        @click="nodeAdd(r)">
+        {{ k }}
+      </button>
+    </div>
+    <svg
+      ref="svg"
+      class="view"
+      :width="width"
+      :height="height">
+      <flow-pan-zoom
+        ref="panzoom"
+        v-model="nodeData.panzoom">
+        <flow-link
+          v-for="(l,i) in nodeData.links"
+          :key="i"
+          v-bind="linkProps(l)"
+          @click="removeLink(i)"
+        />
+        <!-- mouse link-->
+        <flow-link
+          v-if="pointerLink.active"
+          v-bind="pointerLink.props"
+        />
+        <flow-node
+          ref="nodes"
+          v-for="(n,i) of nodeData.nodes"
+          v-bind="nodeProps(n)"
+          :key="'node' + n.id"
+          :id="n.id"
+          @nodePointerDown.prevent="nodeDragStart($event,i)"
+          @socketPointerDown="socketPointerDown(n.id,...arguments)"
+        />
+      </flow-pan-zoom>
+    </svg>
+  </div>
+</template>
+<script>
+import nodeData from '../nodedata'
+import FlowNode from './node'
+import FlowLink from './link'
+import FlowPanZoom from './panzoom'
+
+export default {
+  name: 'FlowManager',
+  components: {FlowNode, FlowLink, FlowPanZoom},
+  props: {
+    'width': {type: String, default: '800px'},
+    'height': {type: String, default: '600px'}
+  },
+  data () {
+    return {
+      nodeData: nodeData,
+      pointerLink: {active: false, props: {}, src: {}}
+    }
+  },
+  computed: {
+    nodeProps () {
+      return (node) => {
+        let match = {}
+        if (this.pointerLink.active && this.pointerLink.src.nodeId !== node.id) {
+          if (this.pointerLink.src.in !== undefined) {
+            match = {out: this.pointerLink.src.type}
+          } else {
+            match = {in: this.pointerLink.src.type}
+          }
+        }
+        const nodeClass = this.nodeData.registry[node.src]
+        return {
+          transform: `translate(${node.x} ${node.y})`,
+          id: node.id,
+          label: node.label,
+          type: nodeClass.type,
+          inputs: nodeClass.inputs,
+          output: nodeClass.output,
+          match: match,
+          color: nodeClass.style && nodeClass.style.color,
+          textColor: nodeClass.style && nodeClass.style.textColor
+        }
+      }
+    },
+    linkProps () {
+      return (link) => {
+        if (!this.$refs.nodes) return
+        // For size .x .y
+        const nodeFrom = this.nodeData.nodes.find(n => n.id === link.from)
+        const nodeTo = this.nodeData.nodes.find(n => n.id === link.to)
+
+        const refFrom = this.$refs.nodes.find(n => n.id === link.from)
+        const refTo = this.$refs.nodes.find(n => n.id === link.to)
+
+        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
+        }
+      }
+    }
+
+  },
+  mounted () {
+    this.$nextTick(() => {
+      this.$forceUpdate()
+    })
+  },
+  methods: {
+    // XXX: Shrink this function
+    socketPointerDown (nodeId, e, socket) {
+      const nodeRef = this.$refs.nodes.find(n => n.id === nodeId)
+      const node = this.nodeData.nodes.find(n => n.id === nodeId)
+
+      const isInput = socket.in !== undefined
+      const socketPos = isInput ? nodeRef.inputPos(socket.in) : nodeRef.outputPos(socket.out)
+
+      const p = this.transformedPoint(e.clientX, e.clientY)
+      if (isInput) {
+        this.pointerLink.props = {
+          x1: node.x + socketPos.x,
+          y1: node.y + socketPos.y,
+          x2: p.x,
+          y2: p.y
+        }
+      } else {
+        this.pointerLink.props = {
+          x1: p.x,
+          y1: p.y,
+          x2: node.x + socketPos.x,
+          y2: node.y + socketPos.y
+        }
+      }
+
+      this.pointerLink.active = true
+      if (isInput) {
+        this.pointerLink.src = {nodeId: nodeId, type: this.nodeData.registry[node.src].inputs[socket.in], in: socket.in}
+      } else {
+        this.pointerLink.src = {nodeId: nodeId, type: this.nodeData.registry[node.src].output, out: 0}
+      }
+
+      // What socket is this
+      // Create a temporary link
+      const drag = (e) => {
+        const p = this.transformedPoint(e.clientX, e.clientY)
+        if (isInput) {
+          this.pointerLink.props.x1 = p.x
+          this.pointerLink.props.y1 = p.y
+        } else {
+          this.pointerLink.props.x2 = p.x
+          this.pointerLink.props.y2 = p.y
+        }
+      }
+      const drop = (e) => {
+        document.removeEventListener('mousemove', drag)
+        document.removeEventListener('mouseup', drop)
+        this.pointerLink.active = false
+
+        const targetNodeId = e.target.getAttribute('data-nodeid')
+        const targetIn = e.target.getAttribute('data-in')
+        const targetOut = e.target.getAttribute('data-out')
+        // Not a node or same node
+        if (targetNodeId === undefined || targetNodeId === nodeId) {
+          console.error('LINK: target is not a socket/ same socket')
+          return
+        }
+        let link
+        // target is input
+        if (targetIn && !isInput) {
+          link = {
+            from: nodeId,
+            to: targetNodeId,
+            in: parseInt(targetIn)
+          }
+        } else if (targetOut && isInput) {
+          link = {
+            from: targetNodeId,
+            to: nodeId,
+            in: socket.in
+          }
+        }
+        // No link
+        if (!link) {
+          console.error('LINK: input same direction (in->in/out->out)')
+          return
+        }
+        const nodeFrom = this.nodeData.nodes.find(n => n.id === link.from)
+        const nodeTo = this.nodeData.nodes.find(n => n.id === link.to)
+        // Type checking
+        if (this.nodeData.registry[ nodeFrom.src ].output !==
+          this.nodeData.registry[ nodeTo.src ].inputs[link.in]) {
+          console.error('LINK: Invalid type')
+          return
+        }
+
+        // Input already exists
+        const existingInputI = this.nodeData.links.findIndex(l => l.to === link.to && l.in === link.in)
+        if (existingInputI !== -1) {
+          this.nodeData.links.splice(existingInputI, 1)
+          // console.error('LINK: already has input')
+          // return
+        }
+
+        this.nodeData.links.push(link)
+      }
+      document.addEventListener('mousemove', drag)
+      document.addEventListener('mouseup', drop)
+    },
+    nodeDragStart (e, i) {
+      e.preventDefault()
+      var tnode = this.nodeData.nodes[i]
+      if (e.button === 1) {
+        // remove related links
+        this.nodeData.links = this.nodeData.links.filter(l => l.from !== tnode.id && l.to !== tnode.id)
+        this.nodeData.nodes.splice(i, 1)
+        return
+      }
+      if (e.button !== 0) return // first button
+      // we can handle with nodeId and a search
+      this.nodeData.nodes.splice(i, 1)
+      this.nodeData.nodes.push(tnode) // put in last
+
+      // transform CTM
+      const delta = this.transformedPoint(e.clientX, e.clientY)
+      delta.x -= tnode.x
+      delta.y -= tnode.y
+
+      const drag = (e) => {
+        const point = this.transformedPoint(e.clientX, e.clientY)
+        tnode.x = point.x - delta.x
+        tnode.y = point.y - delta.y
+      }
+      const drop = (e) => {
+        document.removeEventListener('mousemove', drag)
+        document.removeEventListener('mouseup', drop)
+      }
+      document.addEventListener('mousemove', drag)
+      document.addEventListener('mouseup', drop)
+    },
+    nodeAdd (k) {
+      console.log('Adding:', k)
+      this.nodeData.nodes.push({
+        id: guid(),
+        x: 100,
+        y: 100,
+        label: k,
+        src: k
+      })
+    },
+    removeLink (i) {
+      this.nodeData.links.splice(i, 1)
+    },
+    createSVGPoint (x, y) {
+      const p = this.$refs.svg.createSVGPoint()
+      p.x = x; p.y = y
+      return p
+    },
+    transformedPoint (x, y, abs) {
+      const svgRect = this.$refs.svg.getBoundingClientRect()
+      if (!abs) {
+        x -= svgRect.x
+        y -= svgRect.y
+      }
+      return this.$refs.panzoom.transformedPoint(this.createSVGPoint(x, y))
+    }
+    // helper
+  }
+
+}
+function guid () {
+  function s4 () {
+    return Math.floor((1 + Math.random()) * 0x10000)
+      .toString(16)
+      .substring(1)
+  }
+  return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+    s4() + '-' + s4() + s4() + s4()
+}
+</script>
+<style>
+.flow-container {
+  display:flex;
+  flex-flow:column;
+}
+svg.view {
+  border:none;
+  position:relative;
+}
+
+</style>

+ 65 - 33
browser/vue-flow/src/components/flownode.vue

@@ -13,7 +13,9 @@
     <circle
       class="flow-node__socket flow-node__socket--inputs"
       v-for="(inp,i) in inputs"
-      v-bind="inputPos(i)"
+      v-bind="inputProps(i)"
+      :data-nodeid="id"
+      :data-in="i"
       :class="{'flow-node__socket--match': inp == match.in}"
       :key="'in'+i"
       @mousedown.stop.prevent="socketPointerDown($event, {in:i})"
@@ -21,9 +23,11 @@
     <!-- output -->
     <circle
       class="flow-node__socket flow-node__socket--outputs"
+      :data-nodeid="id"
+      :data-out="0"
       :key="'out'+0"
       :class="{ 'flow-node__socket--match': output == match.out}"
-      v-bind="outputPos(0)"
+      v-bind="outputProps(0)"
       @mousedown.stop.prevent="socketPointerDown($event, {out:0})"
     />
     <text
@@ -45,7 +49,10 @@ export default {
     'type': {type: String, default: ''},
     'inputs': {type: Array, default: () => []},
     'output': {type: String, default: ''},
-    'match': {type: Object, default: () => {}}
+    'match': {type: Object, default: () => {}},
+
+    'color': {type: String, default: '#444'},
+    'textColor': {type: String, default: '#fff'}
     // 'value': {type: Object, default: () => {}}
   },
   data () {
@@ -58,35 +65,52 @@ export default {
   },
   computed: {
     labelProps () {
-      console.log('match:', this.match)
-      return {x: -this.labelRect.width / 2, y: 5}
+      return {
+        x: -this.labelRect.width / 2,
+        y: 5,
+        fill: this.textColor
+      }
     },
     bodyProps () {
       const width = this.labelRect.width + 20
-      const rect = {x: -width / 2, y: -50, width: width, height: 100}
+      const rect = {
+        x: -width / 2,
+        y: -50,
+        width: width,
+        height: 100,
+        fill: this.color
+      }
       if (this.type === 'circle') {
         rect.rx = width
         rect.ry = width
       }
       return rect
     },
-
-    // Do this else where
-    inputPos () {
+    inputProps () {
       return (i) => {
-        const ilen = this.inputs.length
-        if (ilen === 0) return {}
-        const d = this.bodyProps.height / (ilen * 2)
+        const {x, y} = this.inputPos(i)
         return {
-          cx: this.bodyProps.x,
-          cy: this.bodyProps.y + d + (i * 2 * d)
+          transform: `translate(${x} ${y})`,
+          r: 6
+        }
+      }
+    },
+    outputProps () {
+      return (i) => {
+        const {x, y} = this.outputPos(i)
+        return {
+          transform: `translate(${x} ${y})`,
+          r: 6
         }
       }
     },
     outputPos () {
       return (i) => {
         const rect = this.bodyProps
-        return {cx: rect.x + rect.width, cy: 0, r: 10}
+        return {
+          x: rect.x + rect.width,
+          y: 0
+        }
       }
     }
 
@@ -108,6 +132,15 @@ export default {
     })
   },
   methods: {
+    inputPos (i) {
+      const ilen = this.inputs.length
+      if (ilen === 0) return {}
+      const d = this.bodyProps.height / (ilen * 2)
+      return {
+        x: this.bodyProps.x,
+        y: this.bodyProps.y + d + (i * 2 * d)
+      }
+    },
     socketPointerDown (e, socket) {
       this.$emit('socketPointerDown', e, socket)
     }
@@ -116,45 +149,44 @@ export default {
 }
 </script>
 <style lang="scss">
+.flow-node {
+  position:relative;
+}
 .flow-node__body {
-  fill: #EFEFEF;
-  stroke: #ddd;
+  // fill:#ddd;
+  stroke: rgba(190,0,0,0);
   stroke-width:1;
 
-  -webkit-filter: drop-shadow(5px 5px 5px #000);
-          filter: drop-shadow(5px 5px 5px #000);
-
 }
 .flow-node__body:hover,
 .flow-node.flow-node--dragging s-node__body {
-  z-index:10;
-  fill: #DFDFDF;
   cursor:move;
-  //stroke:red;
-  //stroke-width:4;
-  transition: all 1s;
+  stroke:rgba(190,0,0,1);
+  stroke-width:1;
+  transition: all .3s;
 }
 .flow-node.flow-node--dragging {
 }
 .flow-node__socket {
   fill: #AFAFAF;
-  stroke:none;
+  stroke: #AFAFAF;
+  stroke-width:2;
 
   cursor:pointer;
-  stroke-width:2;
   r:6;
 }
+
 .flow-node__socket:hover {
-  r:10;
+  stroke: #5F5FFF;
+  stroke-width:2;
 }
-.flow-node__inputs {
+.flow-node__socket--match {
+  fill:green;
+  stroke:green;
 }
+
 .flow-node__label {
-  fill: #444;
   pointer-events:none;
   user-select:none;
 }
-.flownode__socket--match {
-  fill:#0F0;
-}
 </style>

+ 18 - 12
browser/vue-flow/src/components/flowpanzoom.vue

@@ -2,14 +2,14 @@
   <g>
     <rect
       ref="transformer"
-      class="transformer"
+      class="flow-pan-zoom__transformer"
       width="100%"
       height="100%"
-      @mousedown="dragStart"/>
+      @mousedown.stop.prevent="dragStart"/>
 
     <g
       ref="transform"
-      :transform="panzoomTransform">
+      v-bind="transformProps">
       <slot/>
     </g>
   </g>
@@ -25,16 +25,21 @@ export default {
     return {
       zoom: this.value.zoom,
       x: this.value.x,
-      y: this.value.y
+      y: this.value.y,
+      moving: false
     }
   },
   computed: {
-    panzoomTransform () {
+    transformProps () {
       const transString = 'matrix(' + [
         this.zoom, 0, 0,
         this.zoom, this.x, this.y
       ].join(' ') + ')'
-      return transString
+
+      return {
+        transform: transString,
+        class: 'flow-pan-zoom__transform ' + this.moving ? 'moving' : ''
+      }
     }
 
   },
@@ -51,10 +56,11 @@ export default {
       if (e.target !== this.$refs.transformer) return
 
       const drag = (e) => {
+        this.moving = true
         this.update(this.x + e.movementX, this.y + e.movementY)
       }
       const drop = (e) => {
-        console.log('Dropping')
+        this.moving = false
         document.removeEventListener('mousemove', drag)
         document.removeEventListener('mouseup', drop)
       }
@@ -63,10 +69,12 @@ export default {
     },
     wheel (e) {
       e.preventDefault()
+      let deltaY = (e.deltaY > 0) ? 1 : -1
+      deltaY *= (e.shiftKey) ? 0.01 : 0.07
       const svgRect = this.$refs.transformer.getBoundingClientRect()
       const oX = e.clientX - svgRect.x
       const oY = e.clientY - svgRect.y
-      const z = this.zoom - (e.deltaY * 0.001 * this.zoom)
+      const z = this.zoom - (deltaY * this.zoom)
 
       var curX = this.x
       var curY = this.y
@@ -94,10 +102,8 @@ export default {
 </script>
 
 <style>
-.transformer {
-  stroke: black;
-  stroke-width: 1;
+.flow-pan-zoom__transformer {
   fill:transparent;
-
 }
+
 </style>

+ 78 - 0
browser/vue-flow/src/components/flowmain.vue

@@ -0,0 +1,78 @@
+<template>
+  <div class="flow-main">
+    <div class="app-header">
+      Flow
+    </div>
+    <div class="app-flow-container">
+      <div class="app-info">
+        <h4>TODO:</h4>
+        <ul>
+          <li>Load the functions from server registry</li>
+          <li>Create session based collaboration</li>
+          <li>Build the graph on the server</li>
+          <li>Create training mechanism</li>
+          <li>matrix pooling</li>
+          <li>Group nodes into a single box, exposing inputs and outputs</li>
+        </ul>
+
+      </div>
+      <flow-manager
+        width="100%"
+        height="100%"/>
+    </div>
+  </div>
+</template>
+<script>
+import FlowManager from '@/components/flow/manager'
+import 'reset-css/reset.css'
+import '@/assets/style.css'
+
+export default {
+  components: {FlowManager},
+
+  mounted () {
+    if (this.$route.params.sessID === undefined) {
+      console.log('Requesting new session')
+      this.$ws.send({op: 'newSession'})
+    }
+  }
+
+}
+
+</script>
+<style>
+.flow-main {
+  height:100%;
+  display:flex;
+  flex-direction: column;
+}
+.flow-main .app-flow-container {
+  position:relative;
+  flex:1;
+}
+.flow-main .flow-container {
+  position:absolute;
+  top:0;
+  right:0;
+  bottom:0;
+  left:0;
+}
+
+.flow-main .app-header {
+  background:#113244;
+  color:#EEE;
+  padding:14px;
+  font-weight:bold;
+}
+.flow-main  .app-info {
+  box-shadow:0px 1px 1px rgba(150,150,0,0.2);
+  color: #999;
+  background: #FfFfdd;
+  margin:20px;
+  padding:20px;
+  position:absolute;
+  left:0;
+  bottom:10%;
+}
+
+</style>

+ 0 - 197
browser/vue-flow/src/components/flowmanager.vue

@@ -1,197 +0,0 @@
-<template>
-  <div class="flow-container">
-    <svg
-      ref="svg"
-      class="view"
-      :width="width"
-      :height="height">
-      <flow-pan-zoom ref="panzoom" v-model="nodeData.panzoom">
-        <flow-link
-          v-for="(l,i) in nodeData.links"
-          :key="i"
-          v-bind="linkProps(l)"
-        />
-        <!-- mouse link-->
-        <flow-link
-          v-if="pointerLink.active"
-          v-bind="pointerLink"
-        />
-        <flow-node
-          ref="nodes"
-          v-for="(n,i) of nodeData.nodes"
-          :key="'node' + n.id"
-          :id="n.id"
-          v-bind="nodeProps(n)"
-          @nodePointerDown.prevent="nodeDragStart($event,i)"
-          @socketPointerDown="socketPointerDown(n.id,...arguments)"
-        />
-      </flow-pan-zoom>
-    </svg>
-    {{ nodeData }}
-    <div v-for="(n,i) of nodeData.nodes">
-      <input type="text" v-model="n.label">
-    </div>
-  </div>
-</template>
-<script>
-import nodeData from './nodedata'
-import FlowNode from './flownode'
-import FlowLink from './flowlink'
-import FlowPanZoom from './flowpanzoom'
-
-export default {
-  name: 'FlowManager',
-  components: {FlowNode, FlowLink, FlowPanZoom},
-  props: {
-    'width': {type: String, default: '800px'},
-    'height': {type: String, default: '600px'}
-  },
-  data () {
-    return {
-      nodeData: nodeData,
-      pointerLink: {active: false, x1: 0, y1: 0, x2: 0, y2: 0},
-      hlType: ''
-    }
-  },
-  computed: {
-    nodeProps () {
-      return (n) => {
-        return {
-          transform: `translate(${n.x} ${n.y})`,
-          id: n.id,
-          label: n.label,
-          type: this.nodeData.registry[n.src].type,
-          inputs: this.nodeData.registry[n.src].inputs,
-          output: this.nodeData.registry[n.src].output,
-          match: {}
-        }
-      }
-    },
-    // computed
-    linkProps () {
-      return (link) => {
-        if (!this.$refs.nodes) return
-        // find Index perhaps
-        const nodeFrom = this.nodeData.nodes.find(n => n.id === link.from)
-        const nodeTo = this.nodeData.nodes.find(n => n.id === link.to)
-
-        const refFrom = this.$refs.nodes.find(n => n.id === link.from)
-        const refTo = this.$refs.nodes.find(n => n.id === link.to)
-
-        const fromOutput = refFrom.outputPos(0) // only 1 output
-        const toInput = refTo.inputPos(link.in)
-
-        const x1 = nodeFrom.x + fromOutput.cx // + inputPos.x
-        const y1 = nodeFrom.y + fromOutput.cy // + inputPos.y
-
-        const x2 = nodeTo.x + toInput.cx
-        const y2 = nodeTo.y + toInput.cy
-        // console.log('Thing:', x1, y1, x2, y2)
-        return {x1, y1, x2, y2}
-      }
-    }
-
-  },
-  mounted () {
-    this.$nextTick(() => {
-      this.$forceUpdate()
-    })
-  },
-  methods: {
-    socketPointerDown (nodeId, e, socket) {
-      const nodeRef = this.$refs.nodes.find(n => n.id === nodeId)
-      const node = this.nodeData.nodes.find(n => n.id === nodeId)
-
-      const isInput = socket.in !== undefined
-      const socketPos = isInput ? nodeRef.inputPos(socket.in) : nodeRef.outputPos(socket.out)
-
-      const p = this.transformedPoint(e.x, e.y)
-      if (isInput) {
-        this.pointerLink.x1 = node.x + socketPos.cx
-        this.pointerLink.y1 = node.y + socketPos.cy
-        this.pointerLink.x2 = p.x
-        this.pointerLink.y2 = p.y
-      } else {
-        this.pointerLink.x1 = p.x
-        this.pointerLink.y1 = p.y
-        this.pointerLink.x2 = node.x + socketPos.cx
-        this.pointerLink.y2 = node.y + socketPos.cy
-      }
-
-      this.pointerLink.active = true
-
-      // What socket is this
-      // Create a temporary link
-      const drag = (e) => {
-        const p = this.transformedPoint(e.x, e.y)
-        if (isInput) {
-          this.pointerLink.x1 = p.x
-          this.pointerLink.y1 = p.y
-        } else {
-          this.pointerLink.x2 = p.x
-          this.pointerLink.y2 = p.y
-        }
-      }
-      const drop = (e) => {
-        console.log('Dropping link in ', e.target)
-        document.removeEventListener('mousemove', drag)
-        document.removeEventListener('mouseup', drop)
-      }
-      document.addEventListener('mousemove', drag)
-      document.addEventListener('mouseup', drop)
-    },
-    nodeDragStart (e, i) {
-      if (e.button !== 0) return // first button
-      // we can handle with nodeId and a search
-      var tnode = this.nodeData.nodes[i]
-      this.nodeData.nodes.splice(i, 1)
-      this.nodeData.nodes.push(tnode) // put in last
-
-      // transform CTM
-      const delta = this.transformedPoint(e.clientX, e.clientY)
-      delta.x -= tnode.x
-      delta.y -= tnode.y
-
-      const drag = (e) => {
-        const point = this.transformedPoint(e.clientX, e.clientY)
-        tnode.x = point.x - delta.x
-        tnode.y = point.y - delta.y
-      }
-      const drop = (e) => {
-        document.removeEventListener('mousemove', drag)
-        document.removeEventListener('mouseup', drop)
-      }
-      document.addEventListener('mousemove', drag)
-      document.addEventListener('mouseup', drop)
-    },
-
-    createSVGPoint (x, y) {
-      const p = this.$refs.svg.createSVGPoint()
-      p.x = x; p.y = y
-      return p
-    },
-    transformedPoint (x, y, abs) {
-      const svgRect = this.$el.getBoundingClientRect()
-      if (!abs) {
-        x -= svgRect.x
-        y -= svgRect.y
-      }
-      return this.$refs.panzoom.transformedPoint(this.createSVGPoint(x, y))
-    }
-    // helper
-  }
-
-}
-
-</script>
-<style>
-.flow-container {
-  display:flex;
-  flex-flow:column;
-}
-svg.view {
-  border: solid 1px #F00;
-  position:relative;
-}
-
-</style>

+ 40 - 6
browser/vue-flow/src/components/nodedata.js

@@ -1,20 +1,22 @@
 // Document shared state
-export default{
+/* export default{
   panzoom: { x: 0, y: 0, zoom: 1 },
   defaults: { nodeHeight: 100 },
   registry: {
-    'MatMul': { inputs: [ '[]float32', '[]float32' ], output: '[]float32' },
+    'MatMul': { inputs: [ '[]float32', '[]float32' ], output: '[]float32', style: {color: '#789', textColor: '#fff'} },
     'Weights': { inputs: [], output: '[]float32' },
-    'Input': { inputs: [], output: '[]float32' },
-    'Activator': { inputs: [ '[]float32' ], output: '[]float32', type: 'circle' },
-    'R': { inputs: [ '[]float32', '[]float32' ], output: 'string ' }
+    'Input': { inputs: [], output: '[]float32', style: {color: '#686', textColor: '#fff'}},
+    'Activator': { inputs: [ '[]float32' ], output: '[]float32', type: 'circle', style: {color: '#a44', textColor: 'white'}},
+    'R': { inputs: [ '[]float32', 'string' ], output: 'string' },
+    'reverse': { inputs: [ 'string' ], output: 'string' }
   },
   nodes: [
     { id: '1', x: 976, y: 163, label: 'Test input random', src: 'R' },
     { id: '2', x: 702, y: 190, label: 'Sigmoid', src: 'Activator' },
     { id: '3', x: 541, y: 195, label: 'MatMul', src: 'MatMul' },
     { id: '4', x: 320, y: 249, label: 'Input', src: 'Input' },
-    { id: '5', x: 347, y: 90, label: 'Weights', src: 'Weights' }
+    { id: '5', x: 347, y: 90, label: 'Weights', src: 'Weights' },
+    { id: '6', x: 976, y: 263, label: 'reverse', src: 'reverse' }
   ],
   links: [
     { from: '4', to: '3', in: 1 },
@@ -22,4 +24,36 @@ export default{
     { from: '3', to: '2', in: 0 },
     { from: '2', to: '1', in: 0 }
   ]
+} /**/
+
+export default {
+  panzoom: { x: 0, y: 0, zoom: 1 },
+  defaults: { 'nodeHeight': 100 },
+  registry: {
+    'MatMul': { inputs: [ '[]float32', '[]float32' ], output: '[]float32', style: { 'color': '#789', 'textColor': '#fff' } },
+    'Weights': { inputs: [], output: '[]float32' },
+    'Input': { inputs: [], output: '[]float32', style: { 'color': '#686', 'textColor': '#fff' } },
+    'Activator': { inputs: [ '[]float32' ], output: '[]float32', 'type': 'circle', style: { 'color': '#a44', 'textColor': 'white' } },
+    'test': { inputs: [ '[]float32', 'string' ], output: 'string' },
+    'reverse': { inputs: [ 'string' ], output: 'string' }
+  },
+  nodes: [
+    { id: '4', x: 175, y: 85, label: 'Input', src: 'Input' },
+    { id: '3', x: 400, y: 111, label: 'MatMul', src: 'MatMul' },
+    { id: '1', x: 1041, y: 137, label: 'Testing', src: 'test' },
+    { id: '6', x: 735, y: 115, label: 'MatMul', src: 'MatMul' },
+    { id: '8', x: 908, y: 113, label: 'Activator', src: 'Activator' },
+    { id: '2', x: 556, y: 114, label: 'Sigmoid', src: 'Activator' },
+    { id: '7', x: 615, y: 283, label: 'Weights', src: 'Weights' },
+    { id: '5', x: 256, y: 269, label: 'Weights', src: 'Weights' }
+  ],
+  links: [
+    { from: '3', to: '2', in: 0 },
+    { from: '2', to: '6', in: 0 },
+    { from: '5', to: '3', in: 1 },
+    { from: '4', to: '3', in: 0 },
+    { from: '7', to: '6', in: 1 },
+    { from: '6', to: '8', in: 0 },
+    { from: '8', to: '1', in: 0 }
+  ]
 }

+ 5 - 0
browser/vue-flow/src/main.js

@@ -1,7 +1,12 @@
 import Vue from 'vue'
 import App from './App.vue'
+import WsConn from './services/wsconn'
+import router from './router'
+
+Vue.use(WsConn, {location: 'ws://' + window.location.host + '/conn'})
 
 window.app = new Vue({
   el: '#app',
+  router,
   render: h => h(App)
 })

+ 13 - 0
browser/vue-flow/src/router/index.js

@@ -0,0 +1,13 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import FlowMain from '@/components/flowmain'
+
+Vue.use(Router)
+
+export default new Router({
+  mode: 'history',
+  routes: [
+    { path: '/:sessId', component: FlowMain }
+  ]
+
+})

+ 47 - 0
browser/vue-flow/src/services/wsconn.js

@@ -0,0 +1,47 @@
+export default {
+
+  install (Vue, options) {
+    let ws
+    let connected = false
+
+    const eventBus = new Vue({
+      methods: {
+        send (msg) {
+          if (connected === false) {
+            console.log('No connection, scheduling message')
+            this.$on('open', () => {
+              this.$emit('send', msg)
+            })
+            return
+          }
+          this.$emit('send', msg)
+        }
+      }
+    })
+
+    const connect = (loc) => {
+      ws = new window.WebSocket(loc)
+      ws.onopen = () => { this.connected = true; console.log('Connected'); eventBus.$emit('open') }
+      ws.onmessage = (e) => {}
+      ws.onerror = (e) => { this.connected = false; console.log('Error:', e) }
+      ws.onclose = () => {
+        if (connected === true) { } // emit close
+        connected = false
+        setTimeout(() => connect(loc), 3000) // Reconnect
+      }
+      ws.onmessage = (e) => { // receiving message
+        console.log('Message received')
+        eventBus.$emit('message', JSON.parse(e.data))
+      }
+    }
+    connect(options.location)
+
+    eventBus.$on('send', (msg) => {
+      console.log('Sending message', msg)
+      ws.send(JSON.stringify(msg))
+    })
+
+    Vue.prototype.$ws = eventBus
+  }
+
+}

+ 5 - 4
browser/vue-flow/webpack.config.js

@@ -2,13 +2,13 @@ var path = require('path')
 var webpack = require('webpack')
 var HtmlWebpackPlugin = require('html-webpack-plugin')
 
-var outfile = 'vue-edi-table.js'
+var outfile = 'index.js'
 
 module.exports = {
   entry: './src/main.js',
   output: {
     path: path.resolve(__dirname, './dist'),
-    publicPath: '/dist',
+    publicPath: '/',
     filename: outfile
     // libraryTarget: 'commonjs2'
   },
@@ -105,7 +105,8 @@ if (process.env.NODE_ENV === 'production') {
     }),
     new webpack.LoaderOptionsPlugin({
       minimize: true
-    })
+    }),
+    new HtmlWebpackPlugin({filename: 'index.html', template: 'index.html'})
   ])
-  module.exports.externals = { 'vue': 'vue' }
+  // module.exports.externals = { 'vue': 'vue' }
 }

+ 76 - 1
browser/vue-flow/yarn.lock

@@ -10,6 +10,14 @@
     esutils "^2.0.2"
     js-tokens "^3.0.0"
 
+"@babel/code-frame@7.0.0-beta.36":
+  version "7.0.0-beta.36"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.36.tgz#2349d7ec04b3a06945ae173280ef8579b63728e4"
+  dependencies:
+    chalk "^2.0.0"
+    esutils "^2.0.2"
+    js-tokens "^3.0.0"
+
 "@babel/helper-function-name@7.0.0-beta.31":
   version "7.0.0-beta.31"
   resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.31.tgz#afe63ad799209989348b1109b44feb66aa245f57"
@@ -19,12 +27,26 @@
     "@babel/traverse" "7.0.0-beta.31"
     "@babel/types" "7.0.0-beta.31"
 
+"@babel/helper-function-name@7.0.0-beta.36":
+  version "7.0.0-beta.36"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.36.tgz#366e3bc35147721b69009f803907c4d53212e88d"
+  dependencies:
+    "@babel/helper-get-function-arity" "7.0.0-beta.36"
+    "@babel/template" "7.0.0-beta.36"
+    "@babel/types" "7.0.0-beta.36"
+
 "@babel/helper-get-function-arity@7.0.0-beta.31":
   version "7.0.0-beta.31"
   resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.31.tgz#1176d79252741218e0aec872ada07efb2b37a493"
   dependencies:
     "@babel/types" "7.0.0-beta.31"
 
+"@babel/helper-get-function-arity@7.0.0-beta.36":
+  version "7.0.0-beta.36"
+  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.36.tgz#f5383bac9a96b274828b10d98900e84ee43e32b8"
+  dependencies:
+    "@babel/types" "7.0.0-beta.36"
+
 "@babel/template@7.0.0-beta.31":
   version "7.0.0-beta.31"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.31.tgz#577bb29389f6c497c3e7d014617e7d6713f68bda"
@@ -34,6 +56,15 @@
     babylon "7.0.0-beta.31"
     lodash "^4.2.0"
 
+"@babel/template@7.0.0-beta.36":
+  version "7.0.0-beta.36"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.36.tgz#02e903de5d68bd7899bce3c5b5447e59529abb00"
+  dependencies:
+    "@babel/code-frame" "7.0.0-beta.36"
+    "@babel/types" "7.0.0-beta.36"
+    babylon "7.0.0-beta.36"
+    lodash "^4.2.0"
+
 "@babel/traverse@7.0.0-beta.31":
   version "7.0.0-beta.31"
   resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.31.tgz#db399499ad74aefda014f0c10321ab255134b1df"
@@ -47,6 +78,19 @@
     invariant "^2.2.0"
     lodash "^4.2.0"
 
+"@babel/traverse@7.0.0-beta.36":
+  version "7.0.0-beta.36"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.36.tgz#1dc6f8750e89b6b979de5fe44aa993b1a2192261"
+  dependencies:
+    "@babel/code-frame" "7.0.0-beta.36"
+    "@babel/helper-function-name" "7.0.0-beta.36"
+    "@babel/types" "7.0.0-beta.36"
+    babylon "7.0.0-beta.36"
+    debug "^3.0.1"
+    globals "^11.1.0"
+    invariant "^2.2.0"
+    lodash "^4.2.0"
+
 "@babel/types@7.0.0-beta.31":
   version "7.0.0-beta.31"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.31.tgz#42c9c86784f674c173fb21882ca9643334029de4"
@@ -55,6 +99,14 @@
     lodash "^4.2.0"
     to-fast-properties "^2.0.0"
 
+"@babel/types@7.0.0-beta.36":
+  version "7.0.0-beta.36"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.36.tgz#64f2004353de42adb72f9ebb4665fc35b5499d23"
+  dependencies:
+    esutils "^2.0.2"
+    lodash "^4.2.0"
+    to-fast-properties "^2.0.0"
+
 abbrev@1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@@ -337,6 +389,17 @@ babel-eslint@^8.0.2:
     eslint-scope "~3.7.1"
     eslint-visitor-keys "^1.0.0"
 
+babel-eslint@^8.2.1:
+  version "8.2.1"
+  resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.2.1.tgz#136888f3c109edc65376c23ebf494f36a3e03951"
+  dependencies:
+    "@babel/code-frame" "7.0.0-beta.36"
+    "@babel/traverse" "7.0.0-beta.36"
+    "@babel/types" "7.0.0-beta.36"
+    babylon "7.0.0-beta.36"
+    eslint-scope "~3.7.1"
+    eslint-visitor-keys "^1.0.0"
+
 babel-generator@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5"
@@ -804,6 +867,10 @@ babylon@7.0.0-beta.31:
   version "7.0.0-beta.31"
   resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.31.tgz#7ec10f81e0e456fd0f855ad60fa30c2ac454283f"
 
+babylon@7.0.0-beta.36:
+  version "7.0.0-beta.36"
+  resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.36.tgz#3a3683ba6a9a1e02b0aa507c8e63435e39305b9e"
+
 babylon@^6.18.0:
   version "6.18.0"
   resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
@@ -2446,7 +2513,7 @@ globals@^10.0.0:
   version "10.4.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-10.4.0.tgz#5c477388b128a9e4c5c5d01c7a2aca68c68b2da7"
 
-globals@^11.0.1:
+globals@^11.0.1, globals@^11.1.0:
   version "11.1.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-11.1.0.tgz#632644457f5f0e3ae711807183700ebf2e4633e4"
 
@@ -4606,6 +4673,10 @@ requires-port@1.0.x, requires-port@1.x.x, requires-port@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
 
+reset-css@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/reset-css/-/reset-css-2.2.1.tgz#fe3204c0c4831979fbb4967c90a992f0164635ed"
+
 resolve-cwd@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -5370,6 +5441,10 @@ vue-loader@^13.0.5:
     vue-style-loader "^3.0.0"
     vue-template-es2015-compiler "^1.6.0"
 
+vue-router@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9"
+
 vue-style-loader@^3.0.0:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.0.3.tgz#623658f81506aef9d121cdc113a4f5c9cac32df7"

TEMPAT SAMPAH
go/DIST/flowserver


+ 55 - 0
go/Makefile

@@ -0,0 +1,55 @@
+# Luis Figueiredo (luisf@casemyway.com)
+# 
+
+GOPATH=$(CURDIR)/deps:$(CURDIR)
+DIST=./DIST
+ENV=
+
+# Source in packages names
+# What packages to build
+
+# CLI Packages
+CLI=flowserver/cmd/flowserver
+BIN=$(addprefix $(DIST)/, $(notdir $(CLI)))
+
+# Windows build
+ifeq ($(GOOS),windows)
+	ENV+=CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH)
+	BIN:=$(addsuffix .exe, $(BIN))
+endif
+
+.PHONY: all deps clean dist-clean $(BIN)
+
+all: $(BIN)
+	@$(ENV) echo -e "\e[32;01mBuilt for OS: `go env GOOS`, ARCH: `go env GOARCH`\e[0m"
+	
+$(BIN):  deps
+	$(ENV) GOPATH="$(GOPATH)" go build -o $@ $(CLI)
+
+# generate
+generate:
+	GOPATH="$(GOPATH)" go generate ./src/...
+
+test:
+	$(ENV) go test ./src/...
+
+#$(BIN): $(addprefix src/,$(SOURCE))
+#	echo $<
+
+deps:
+	$(ENV) GOPATH="$(GOPATH)" go get -v ./src/... # everything from source
+	# test package 
+	$(ENV) GOPATH="$(GOPATH)" go get -v -t ./src/...
+
+clean:
+	rm -rf $(DIST)
+
+dist-clean: clean
+	rm -rf bin
+	rm -rf pkg
+	rm -rf deps
+
+$(DIST):
+	mkdir -p $(DIST)
+
+

+ 15 - 0
go/src/flowserver/cmd/flowserver/main.go

@@ -0,0 +1,15 @@
+package main
+
+import (
+	"flowserver"
+
+	"github.com/gohxs/prettylog"
+)
+
+func main() {
+	prettylog.Global()
+
+	f := flowserver.FlowServer{}
+	f.ListenAndServe()
+
+}

+ 56 - 0
go/src/flowserver/flowserver.go

@@ -0,0 +1,56 @@
+package flowserver
+
+import (
+	"fmt"
+	"log"
+	"net"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+
+	"github.com/gohxs/prettylog"
+	"github.com/gohxs/webu"
+	"github.com/gohxs/webu/chain"
+)
+
+//go:generate go get dev.hexasoftware.com/hxs/genversion
+//go:generate genversion -package flowserver -out version.go
+
+// FlowServer structure
+type FlowServer struct{}
+
+//ListenAndServe starts the httpserver
+func (f *FlowServer) ListenAndServe() error {
+
+	fsm := FlowSessionManager{}
+
+	// HttpPart
+	c := chain.New(webu.ChainLogger(prettylog.New("req")))
+	mux := http.NewServeMux()
+	mux.Handle("/conn", c.Build(fsm.ServeHTTP))
+
+	proxyURL, err := url.Parse("http://localhost:8081")
+	if err != nil {
+		return err
+	}
+
+	//mux.Handle("/", c.Build(webu.StaticHandler("web", "index.html")))
+	mux.Handle("/", c.Build(httputil.NewSingleHostReverseProxy(proxyURL).ServeHTTP))
+
+	port := 4000
+	for {
+		addr := fmt.Sprintf(":%d", port)
+		s, err := net.Listen("tcp", addr)
+		if err != nil {
+			log.Println("Listen error:", err)
+			port++
+			continue
+		}
+		log.Println("Listening at:", addr)
+
+		err = http.Serve(s, mux)
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+}

+ 1 - 0
go/src/flowserver/handlers.go

@@ -0,0 +1 @@
+package flowserver

+ 13 - 0
go/src/flowserver/session.go

@@ -0,0 +1,13 @@
+package flowserver
+
+import (
+	"github.com/gorilla/websocket"
+)
+
+// FlowSession Create a session and link clients
+//
+type FlowSession struct {
+	ID string // Random handle for sessionID
+	// List of clients on this session
+	clients []*websocket.Conn
+}

+ 108 - 0
go/src/flowserver/sessionmanager.go

@@ -0,0 +1,108 @@
+package flowserver
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+	"sync"
+
+	"github.com/gorilla/websocket"
+)
+
+//FlowSessionManager or FlowServerCore
+type FlowSessionManager struct {
+	// List of flow sessions?
+	sessions map[string]*FlowSession
+
+	sync.Mutex
+}
+
+//New creates a New initialized FlowSessionManager
+func New() *FlowSessionManager {
+	return &FlowSessionManager{
+		sessions: map[string]*FlowSession{},
+	}
+}
+func (fsm *FlowSessionManager) CreateSession() *FlowSession {
+	fsm.Lock()
+	defer fsm.Unlock()
+	for {
+		ID := RandString(10)
+		sess, ok := fsm.sessions[ID]
+		if !ok {
+			fsm.sessions[ID] = sess
+			return sess
+		}
+	}
+
+}
+
+//LoadSession loads or creates a new Session
+func (fsm *FlowSessionManager) LoadSession(ID string) *FlowSession {
+	fsm.Lock()
+	defer fsm.Unlock()
+
+	log.Println("Fetching session:", ID)
+	sess, ok := fsm.sessions[ID]
+	if !ok {
+		sess = &FlowSession{ID: ID}
+		fsm.sessions[ID] = sess
+	}
+	return sess
+
+}
+
+// FlowMessage Main message structure
+type FlowMessage struct {
+	OP   string          `json:"op"`
+	Data json.RawMessage `json:"data"`
+}
+
+var upgrader = websocket.Upgrader{}
+
+func (fsm *FlowSessionManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	log.Println("Receiving ws connection")
+	c, err := upgrader.Upgrade(w, r, nil)
+	if err != nil {
+		log.Println("upgrade:", err)
+	}
+	defer c.Close()
+
+	/////////////////
+	// Message handling
+	// ////////
+
+	for {
+		mt, data, err := c.ReadMessage()
+		if err != nil {
+			log.Println("Err:", err)
+			break
+		}
+		if mt != websocket.TextMessage {
+			log.Println("Not a text message?")
+			break
+		}
+
+		fmsg := FlowMessage{}
+		err = json.Unmarshal(data, &fmsg)
+		if err != nil {
+			log.Println("Err parsing message:", err)
+			c.WriteJSON("bye")
+		}
+		switch fmsg.OP {
+		case "newSession":
+			log.Println("We want a new session so")
+			sess := fsm.LoadSession("")
+			sess.clients = append(sess.clients, c)
+		}
+		// Create a Session, assign us to session
+
+		// Switch kind of mesasge etc here
+		// Acquire session ID, and send all the info
+		// Write msg
+		// Create or Load a session
+
+	}
+	log.Println("ws Is disconnecting")
+
+}

+ 30 - 0
go/src/flowserver/util.go

@@ -0,0 +1,30 @@
+package flowserver
+
+import "math/rand"
+
+const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+const (
+	letterIdxBits = 6                    // 6 bits to represent a letter index
+	letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
+	letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
+)
+
+// RandString from stackoverflow
+// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang
+func RandString(n int) string {
+	b := make([]byte, n)
+	// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
+	for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
+		if remain == 0 {
+			cache, remain = rand.Int63(), letterIdxMax
+		}
+		if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
+			b[i] = letterBytes[idx]
+			i--
+		}
+		cache >>= letterIdxBits
+		remain--
+	}
+
+	return string(b)
+}