瀏覽代碼

Finish vuex, improved flowbuilder to build current nodes

luis 7 年之前
父節點
當前提交
e1bbc6c301

+ 9 - 0
browser/vue-flow/src/assets/icons/eye.svg

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
+<path fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" d="M1,32c0,0,11,15,31,15s31-15,31-15S52,17,32,17
+	S1,32,1,32z"/>
+<circle fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" cx="32" cy="32" r="7"/>
+</svg>

+ 29 - 47
browser/vue-flow/src/components/chat.vue

@@ -4,7 +4,12 @@
     <div class="flow-chat__area">
       <div class="flow-chat__container">
 
-        <input tabindex="-1" class="handle" type="text" v-model="handle" @blur="sendChatRename" @keyup.enter="sendChatRename">
+        <input
+          class="handle"
+          type="text"
+          :value="handle"
+          @blur="CHAT_RENAME($event.target.value)"
+          @keyup.enter="CHAT_RENAME($event.target.value)">
         <div ref="messages" class="flow-chat__messages">
           <div v-for="m in events" class="message">
             <div class="handle">
@@ -30,13 +35,9 @@
 </template>
 
 <script>
+import {mapGetters, mapActions} from 'vuex'
 import IconUser from '@/assets/icons/user.svg'
-
-// init
-let storedHandle = localStorage.getItem('handle')
-if (!storedHandle || storedHandle === '') {
-  storedHandle = 'someone'
-}
+import utils from '@/utils/utils'
 
 // Load handle from storage
 export default {
@@ -44,8 +45,8 @@ export default {
   filters: {
     time (value) {
       const d = new Date(value)
-      const hours = pad(d.getHours(), 2)
-      const minutes = pad(d.getMinutes(), 2)
+      const hours = utils.padStart(d.getHours(), '0', 2)
+      const minutes = utils.padStart(d.getMinutes(), '0', 2)
       let msg = `${hours}:${minutes}`
       return msg
     }
@@ -53,12 +54,16 @@ export default {
   data () {
     return {
       active: false,
-      handle: storedHandle,
-      events: [],
-      userList: [],
+      // handle: storedHandle,
+      // events: [],
+      // userList: [],
       input: ''
     }
   },
+  computed: {
+    ...mapGetters('chat', ['userList', 'events', 'handle'])
+  },
+
   watch: {
     active (val, oldVal) {
       if (val === true && oldVal === false) {
@@ -79,54 +84,31 @@ export default {
       } */
     }
   },
-  mounted () {
-    // this.$flowService.on('chatEvent', this.addChatEvent)
-    this.$flowService.on('chatEvent', this.recvChatEvent)
-    this.$flowService.on('chatUserList', this.recvChatUserList)
-    this.$flowService.on('sessionJoin', this.sendChatJoin)
-    // we might not be joined in a sess
-    // off
-    this.$flowService.connected(this.sendChatJoin)
+  created () {
+    this.subscription = this.$store.subscribe(mut => {
+      if (mut.type === 'chat/EVENT_ADD' && mut.payload.type === 'msg') {
+        // if (this.active) { return }
+        this.NOTIFICATION_ADD(`<b>${mut.payload.handle}:</b> ${mut.payload.message}`)
+      }
+    })
   },
   beforeDestroy () {
-    this.$flowService.off('chatEvent', this.recvChatEvent)
-    this.$flowService.off('chatUserList', this.recvChatUserList)
-    this.$flowService.off('sessionJoin', this.sendChatJoin)
+    this.subscription()
   },
+
   methods: {
+    ...mapActions('flow', ['NOTIFICATION_ADD']),
+    ...mapActions('chat', ['EVENT_SEND', 'CHAT_RENAME', 'CHAT_JOIN']),
     send () {
       const msg = this.input
       if (msg.trim() === '') { return }
       this.input = ''
       const msgEvent = {type: 'msg', handle: this.handle, message: msg, time: new Date()}
-      this.$flowService.chatEvent(msgEvent)
-    },
-    recvChatUserList (v) {
-      this.userList = v.data
-    },
-    recvChatEvent (v) {
-      if (!this.active) this.$notify(`<b>${v.data.handle}:</b> ${v.data.message}`)
-      this.events.push(v.data)
-    },
-    sendChatJoin () {
-      // Clear messages here
-      this.events = []
-      this.$flowService.chatJoin(this.handle, this.$route.params.sessId)
-    },
-    sendChatRename () {
-      const oldHandle = localStorage.getItem('handle')
-      if (this.handle === oldHandle) return
-      localStorage.setItem('handle', this.handle)
-      this.$flowService.chatRename(this.handle)
+      this.EVENT_SEND(msgEvent)
     }
   }
 }
 
-function pad (n, width, z) {
-  z = z || '0'
-  n = n + ''
-  return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n
-}
 </script>
 <style>
 .flow-chat {

+ 2 - 2
browser/vue-flow/src/components/flow/editor.js

@@ -39,7 +39,7 @@ export default {
     }
   },
   computed: {
-    ...mapGetters(['registry', 'activity', 'nodeData', 'nodeById']),
+    ...mapGetters('flow', ['registry', 'activity', 'nodeData', 'nodeById']),
     viewClasses () {
       return {
         'flow-linking': this.linking || this.stickySockets,
@@ -152,7 +152,7 @@ export default {
     document.removeEventListener('keyup', this.keyUp)
   },
   methods: {
-    ...mapActions([
+    ...mapActions('flow', [
       'DOCUMENT_SYNC',
       'NODE_RAISE', 'NODE_UPDATE', 'NODE_ADD', 'NODE_REMOVE', 'NODE_INSPECT',
       'LINK_ADD', 'LINK_REMOVE',

+ 109 - 0
browser/vue-flow/src/components/flow/notifications.vue

@@ -0,0 +1,109 @@
+<template>
+  <div
+    :class="{active: msgs.length>0 && active}"
+    class="flow-notification"
+  >
+    <icon-eye
+      width="16"
+      height="16"
+      class="flow-notification__eye"/>
+    <p
+      :key = "i"
+      v-for="(m,i) of msgs"
+      v-html="m"/>
+
+  </div>
+</template>
+<script>
+import {mapActions} from 'vuex'
+import IconEye from '@/assets/icons/eye.svg'
+export default {
+  name: 'FlowNotification',
+  components: {IconEye},
+  data () {
+    return {
+      msgs: [],
+      active: false
+    }
+  },
+  watch: {
+    '$store.state.flow.notifications' (val) {
+      if (val.length === 0) {
+        this.active = false
+        return
+      }
+      let msgs = val
+      if (msgs.length > 5) {
+        msgs = msgs.slice(msgs.length - 5)
+      }
+      this.msgs = msgs
+
+      this.active = true
+      /* clearTimeout(this.timeout)
+      this.timeout = setTimeout(() => {
+        this.active = false
+      }, 5000) */
+    }
+  },
+  methods: {
+    ...mapActions('flow', ['NOTIFICATION_CLEAR'])
+    /* transitionend () {
+      if (!this.active) { // send Clear notifications
+        // this.msgs = []
+        // this.NOTIFICATION_CLEAR()
+      }
+    } */
+
+  }
+}
+</script>
+<style>
+/* notifications */
+.flow-notification {
+  position:relative;
+  pointer-events:none;
+  user-select:none;
+  opacity:0;
+  position: fixed;
+  z-index: 1000;
+  padding: 20px;
+  width: 50%;
+  left: 50%;
+  transform: translateX(-50%);
+  bottom: 10%;
+  background: var(--normal);
+  color: var(--background);
+  border: solid 1px rgba(150, 150, 150, 0.3);
+  transition: opacity var(--transition-speed);
+}
+
+.flow-notification b {
+  color: var(--primary-lighter);
+}
+
+/* trick to maintain under mouse */
+.flow-notification:hover {
+  border: solid 1px var(--primary);
+  pointer-events:initial !important;
+  user-select:initial !important;
+  opacity:0.8 !important;
+}
+
+.flow-notification.active {
+  /*pointer-events:initial;
+  user-select:initial;*/
+  opacity:0.6;
+}
+
+.flow-notification__eye {
+  pointer-events:initial;
+  position:absolute;
+  right:5px;
+  top:5px;
+}
+
+.flow-notification__eye > *{
+  stroke: var(--background);
+}
+
+</style>

+ 13 - 15
browser/vue-flow/src/components/main.vue

@@ -82,7 +82,7 @@
       <div class="app-chat">
         <app-chat/>
       </div>
-      <hx-notify/>
+      <flow-notifications/>
     </div>
   </div>
 </template>
@@ -91,11 +91,11 @@ import {mapGetters, mapActions} from 'vuex'
 import AppChat from '@/components/chat'
 import FlowEditor from '@/components/flow/editor'
 import FlowPanzoom from '@/components/flow/panzoom'
+import FlowNotifications from '@/components/flow/notifications'
 import FlowFuncs from './panel-funcs'
 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 'reset-css/reset.css'
 
 import '@/assets/dark-theme.css'
@@ -106,11 +106,11 @@ export default {
   components: {
     FlowEditor,
     FlowPanzoom,
+    FlowNotifications,
     FlowInspector,
     FlowFuncs,
     HxSplit,
     HxModal,
-    HxNotify,
     AppChat
   },
   data () {
@@ -123,8 +123,12 @@ export default {
       dark: false
     }
   },
+  computed: {
+    ...mapGetters(['registry', 'activity'])
+  },
+
   created () {
-    let ctx = this.$route.params.context
+    /* let ctx = this.$route.params.context
     let urlPath = [
       window.location.host,
       ctx,
@@ -134,7 +138,7 @@ export default {
     if (window.location.protocol === 'https:') {
       targetws = 'wss://' + urlPath.join('/')
     }
-    this.$flowService.connect(targetws)
+    this.$flowService.connect(targetws) */
     // Vue.use(FlowService, {location: targetws})
   },
 
@@ -145,16 +149,13 @@ export default {
         this.$router.push('/' + this.$route.params.context + '/' + v.id) // Swap to ID
       }
     })
-    this.$flowService.on('sessionNotify', (v) => {
-      this.$notify(v.data)
-    })
     this.$flowService.on('sessionLog', (v) => {
       console.log(v.data) // Temporary
     })
 
     // Connected
-    this.$flowService.connected(() => {
-      this.$notify('Connected')
+    /* this.$flowService.connected(() => {
+      this.NOTIFICATION_ADD('Connected')
       // Make this in a service
       if (this.$route.params.sessId === undefined) {
         console.log('Creating new session')
@@ -162,13 +163,10 @@ export default {
         return
       }
       this.$flowService.sessionLoad(undefined, this.$route.params.sessId)
-    })
-  },
-  computed: {
-    ...mapGetters(['registry', 'activity'])
+    }) */
   },
   methods: {
-    ...mapActions(['NODE_INSPECT']),
+    ...mapActions('flow', ['NODE_INSPECT', 'NOTIFICATION_ADD']),
     nodeInspectStart (node, changePane) { // node
       this.NODE_INSPECT(node.id)
       if (changePane) {

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

@@ -61,7 +61,7 @@ export default {
     }
   },
   computed: {
-    ...mapGetters(['registry']),
+    ...mapGetters('flow', ['registry']),
     funcsGroups () {
       // Set
       let group = new Set()

+ 3 - 3
browser/vue-flow/src/components/panel-inspector.vue

@@ -121,15 +121,15 @@ export default {
     }
   },
   computed: {
-    ...mapGetters(['registry', 'activity'])
+    ...mapGetters('flow', ['registry', 'activity'])
   },
   watch: {
-    '$store.state.nodeInspect' (node) {
+    '$store.state.flow.nodeInspect' (node) {
       this.nodeInspect = JSON.parse(JSON.stringify(node))
     }
   },
   methods: {
-    ...mapActions(['NODE_UPDATE', 'DOCUMENT_SYNC']),
+    ...mapActions('flow', ['NODE_UPDATE', 'DOCUMENT_SYNC']),
     localChange () {
       this.NODE_UPDATE([JSON.parse(JSON.stringify(this.nodeInspect))])
       this.DOCUMENT_SYNC()

browser/vue-flow/src/components/shared/hx-notify.vue → browser/vue-flow/src/components/shared/hx-notify.vue.bak


+ 21 - 0
browser/vue-flow/src/store/chat/actions.js

@@ -0,0 +1,21 @@
+import m from './mutation-types'
+import flowService from '@/services/flowservice'
+
+export default {
+  [m.EVENT_SEND] (ctx, msg) {
+    flowService.chatEvent(msg)
+  },
+  [m.USERLIST_UPDATE] ({commit}, userlist) {
+    commit(m.USERLIST_UPDATE, userlist)
+  },
+  [m.EVENT_ADD] ({commit}, event) {
+    commit(m.EVENT_ADD, event)
+  },
+  [m.HANDLE_UPDATE] (ctx, name) {
+    ctx.commit(m.HANDLE_UPDATE, name)
+    flowService.chatRename(name)
+  },
+  [m.CHAT_JOIN] (ctx, v) {
+    flowService.chatJoin(v.handle, v.sessId)
+  }
+}

+ 6 - 0
browser/vue-flow/src/store/chat/getters.js

@@ -0,0 +1,6 @@
+export default {
+  userList: state => state.userList,
+  events: state => state.events,
+  handle: state => state.handle
+  // messages: state => state.events.filter(e => e.type === 'msg')
+}

+ 11 - 0
browser/vue-flow/src/store/chat/index.js

@@ -0,0 +1,11 @@
+import state from './state'
+import mutations from './mutations'
+import actions from './actions'
+import getters from './getters'
+
+export default {
+  state,
+  actions,
+  mutations,
+  getters
+}

+ 14 - 0
browser/vue-flow/src/store/chat/mutation-types.js

@@ -0,0 +1,14 @@
+var types = [
+  'USERLIST_UPDATE',
+  'EVENT_ADD',
+  'EVENT_SEND',
+  'HANDLE_UPDATE',
+  'CHAT_JOIN'
+]
+
+const obj = {}
+for (let k of types) {
+  obj[k] = k
+}
+
+module.exports = obj

+ 14 - 0
browser/vue-flow/src/store/chat/mutations.js

@@ -0,0 +1,14 @@
+import m from './mutation-types'
+
+export default {
+  [m.USERLIST_UPDATE] (state, userList) {
+    state.userList = userList
+  },
+  [m.EVENT_ADD] (state, event) {
+    state.events.push(event)
+  },
+  [m.HANDLE_UPDATE] (state, handle) {
+    state.handle = handle
+    localStorage.setItem('handle', handle)
+  }
+}

+ 11 - 0
browser/vue-flow/src/store/chat/state.js

@@ -0,0 +1,11 @@
+// init
+let storedHandle = localStorage.getItem('handle')
+if (!storedHandle || storedHandle === '') {
+  storedHandle = 'someone'
+}
+
+export default {
+  userList: [],
+  events: [],
+  handle: storedHandle
+}

+ 0 - 54
browser/vue-flow/src/store/default-registry.js

@@ -1,54 +0,0 @@
-export default{
-  'Input': {
-    categories: ['core'],
-    output: {type: 'interface {}'},
-    style: { color: '#686', shape: 'circle' },
-    props: {} // should be sent in the node
-  },
-  'Variable': {
-    categories: ['core'],
-    output: {type: 'interface {}'},
-    style: { color: '#88a', shape: 'circle' },
-    props: {init: ''}
-  },
-  'Const': {
-    categories: ['core'],
-    output: {type: 'interface {}'},
-    style: { color: '#555' }
-    // , props: {value: ''}
-  },
-  'Notify': {
-    categories: ['flow-web'],
-    inputs: [{type: 'interface {}'}, {type: 'string', name: 'msg'}],
-    output: {type: 'interface {}'},
-    style: {color: '#665'}
-  },
-  'Log': {
-    categories: ['flow-web'],
-    output: {type: 'io.Writer'},
-    style: {color: '#665'}
-  }
-}
-
-/* {
-        // Fixed default stuff
-        'Test': {
-          group: 'Generic',
-          output: 'any',
-          style: {
-            shape: 'thing'
-          }
-        },
-
-        'MatMul': { group: 'Machine learning', inputs: [ '[]float32', '[]float32' ], output: '[]float32', style: { color: '#a44', textColor: 'white' } },
-        'Activator': { group: 'Machine learning', inputs: [ '[]float32' ], output: '[]float32', style: { color: '#a44', textColor: 'white', shape: 'circle' } },
-
-        'test': { group: 'Text', inputs: [ '[]float32', 'string' ], output: 'string', style: {'color': '#a93'} },
-        'reverse': { group: 'Text', inputs: [ 'string' ], output: 'string', style: {'color': '#a93'} },
-
-        'fetch': { group: 'json', output: 'json', style: {'color': '#99a'} },
-        'jsonExtract': { group: 'json', inputs: ['json'], output: 'string', style: {'color': '#99a'} },
-
-        'string': { group: 'Visualization', inputs: ['string'], style: {'color': '#9a9'} },
-        'lineGraph': { group: 'Visualization', inputs: ['[]float32', '[]float32'], style: {'color': '#9a9'} }
-      }, */

+ 12 - 4
browser/vue-flow/src/store/actions.js

@@ -1,11 +1,9 @@
 import m from './mutation-types'
-import log from './log'
-import flowService from '../services/flowservice'
+import flowService from '@/services/flowservice'
 
 export default {
   [m.DOCUMENT_SYNC] (ctx) {
-    log('document_sync')
-    flowService.documentUpdate(ctx.getters.nodeData, ctx.state.route.params.sessId)
+    flowService.documentUpdate(ctx.getters.nodeData, ctx.rootState.route.params.sessId)
   },
   // Node update full document state somehow
   [m.DOCUMENT_UPDATE] ({commit}, nodeData) {
@@ -52,6 +50,16 @@ export default {
   [m.TRIGGER_REMOVE] (ctx, trigger) {
     ctx.commit(m.TRIGGER_REMOVE, trigger)
     ctx.dispatch(m.DOCUMENT_SYNC)
+  },
+  [m.NOTIFICATION_ADD] (ctx, notification) {
+    ctx.commit(m.NOTIFICATION_ADD, notification)
+    clearTimeout(this.notificationTimeout)
+    this.notificationTimeout = setTimeout(() => {
+      ctx.commit(m.NOTIFICATION_CLEAR)
+    }, 5000)
+  },
+  [m.NOTIFICATION_CLEAR] ({commit}) {
+    commit(m.NOTIFICATION_CLEAR)
   }
 
 }

+ 31 - 0
browser/vue-flow/src/store/flow/default-registry.js

@@ -0,0 +1,31 @@
+export default{
+  'Input': {
+    categories: ['core'],
+    output: {type: 'interface {}'},
+    style: { color: '#686', shape: 'circle' },
+    props: {} // should be sent in the node
+  },
+  'Variable': {
+    categories: ['core'],
+    output: {type: 'interface {}'},
+    style: { color: '#88a', shape: 'circle' },
+    props: {init: ''}
+  },
+  'Const': {
+    categories: ['core'],
+    output: {type: 'interface {}'},
+    style: { color: '#555' }
+    // , props: {value: ''}
+  },
+  'Notify': {
+    categories: ['flow-web'],
+    inputs: [{type: 'interface {}'}, {type: 'string', name: 'msg'}],
+    output: {type: 'interface {}'},
+    style: {color: '#665'}
+  },
+  'Log': {
+    categories: ['flow-web'],
+    output: {type: 'io.Writer'},
+    style: {color: '#665'}
+  }
+}

browser/vue-flow/src/store/getters.js → browser/vue-flow/src/store/flow/getters.js


+ 11 - 0
browser/vue-flow/src/store/flow/index.js

@@ -0,0 +1,11 @@
+import state from './state'
+import mutations from './mutations'
+import actions from './actions'
+import getters from './getters'
+
+export default {
+  state,
+  mutations,
+  actions,
+  getters
+}

+ 2 - 1
browser/vue-flow/src/store/mutation-types.js

@@ -4,7 +4,8 @@ var actions = [
   'ACTIVITY_UPDATE',
   'NODE_RAISE', 'NODE_UPDATE', 'NODE_ADD', 'NODE_REMOVE', 'NODE_INSPECT',
   'LINK_ADD', 'LINK_REMOVE',
-  'TRIGGER_ADD', 'TRIGGER_REMOVE'
+  'TRIGGER_ADD', 'TRIGGER_REMOVE',
+  'NOTIFICATION_ADD', 'NOTIFICATION_CLEAR'
 ]
 
 const obj = {}

+ 6 - 1
browser/vue-flow/src/store/mutations.js

@@ -1,5 +1,4 @@
 import Vue from 'vue'
-
 // import m from './mutation-types'
 import m from './mutation-types'
 
@@ -66,5 +65,11 @@ export default {
     const i = state.nodeData.triggers.findIndex(l => l === trigger)
     if (i === -1) return
     state.nodeData.triggers.splice(i, 1)
+  },
+  [m.NOTIFICATION_ADD] (state, msg) {
+    state.notifications.push(msg)
+  },
+  [m.NOTIFICATION_CLEAR] (state, msg) {
+    state.notifications = []
   }
 }

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

@@ -1,14 +1,14 @@
 export default {
-  // Contain ids of nodes
-  nodeListOrder: [],
   // document
   nodeData: {
     nodes: [],
     links: [],
     triggers: []
   },
+  nodeInspect: {},
+
   registry: {},
   activity: {},
 
-  nodeInspect: {}
+  notifications: []
 }

+ 13 - 8
browser/vue-flow/src/store/index.js

@@ -1,19 +1,24 @@
 import Vuex from 'vuex'
 import Vue from 'vue'
-import actions from './actions'
-import state from './state'
-import getters from './getters'
-import mutations from './mutations'
 
 import plugin from './ws'
 
+import flow from './flow'
+import chat from './chat'
+
 Vue.use(Vuex)
 
 export default new Vuex.Store({
   strict: true,
-  state,
-  getters,
-  actions,
-  mutations,
+  modules: {
+    chat: {
+      namespaced: true,
+      ...chat
+    },
+    flow: {
+      namespaced: true,
+      ...flow
+    }
+  },
   plugins: [plugin]
 })

+ 0 - 7
browser/vue-flow/src/store/log.js

@@ -1,7 +0,0 @@
-const debug = 1
-let log = () => {}
-if (debug) {
-  log = console.log.bind(console.log, '%cSTORE:', 'color:#00a', (Math.random() * 1000).toFixed())
-}
-
-export default log

+ 57 - 6
browser/vue-flow/src/store/ws.js

@@ -1,17 +1,50 @@
-import defRegistry from './default-registry'
-import m from './mutation-types'
+import defRegistry from './flow/default-registry'
+import flowMut from './flow/mutation-types'
+import chatMut from './chat/mutation-types'
 import flowService from '@/services/flowservice'
 
+let flow = {}, chat = {}
+for (let k in flowMut) { flow[k] = 'flow/' + k }
+for (let k in chatMut) { chat[k] = 'chat/' + k }
+
 export default store => {
+  store.dispatch('chat/EVENT_ADD', {type: 'msg', handle: 'system', data: 'hello'})
+
   store.subscribe(mut => {
     // console.log('I changed -- perform the connection somehow', mut)
+    if (mut.type === 'route/ROUTE_CHANGED') {
+      let route = mut.payload.to
+      let ctx = route.params.context
+      let urlPath = [
+        window.location.host,
+        ctx,
+        'conn'
+      ]
+      let targetws = 'ws://' + urlPath.join('/')
+      if (window.location.protocol === 'https:') {
+        targetws = 'wss://' + urlPath.join('/')
+      }
+      flowService.connect(targetws)
+    }
+  })
+
+  // Connected
+  flowService.connected(() => {
+    store.dispatch(flow.NOTIFICATION_ADD, 'Connected')
+    // Make this in a service
+    if (store.state.route.params.sessId === undefined) {
+      flowService.sessionNew()
+      return
+    }
+    flowService.sessionLoad(undefined, store.state.route.params.sessId)
   })
 
   flowService.on('document', (v) => {
-    store.commit(m.DOCUMENT_UPDATE, v.data)
+    console.log('Store:', store)
+    store.commit(flow.DOCUMENT_UPDATE, v.data)
   })
   flowService.on('nodeUpdate', (v) => {
-    store.commit(m.NODE_UPDATE, v.data)
+    store.commit(flow.NODE_UPDATE, v.data)
   })
 
   flowService.on('registry', (v) => {
@@ -28,9 +61,27 @@ export default store => {
         style: e.extra && e.extra.style
       }
     }
-    store.commit(m.REGISTRY_UPDATE, Object.assign({}, defRegistry, res))
+    store.commit(flow.REGISTRY_UPDATE, Object.assign({}, defRegistry, res))
   })
   flowService.on('nodeActivity', (v) => {
-    store.commit(m.ACTIVITY_UPDATE, v.data || {})
+    store.commit(flow.ACTIVITY_UPDATE, v.data || {})
+  })
+  flowService.on('sessionNotify', (v) => {
+    // ACTION
+    store.dispatch(flow.NOTIFICATION_ADD, v.data)
+  })
+  flowService.on('sessionJoin', (v) => {
+    store.dispatch(chat.CHAT_JOIN, {
+      handle: store.state.chat.handle,
+      sessId: store.state.route.params.sessId
+    })
+  })
+
+  /// // CHAT //////
+  flowService.on('chatUserList', (v) => {
+    store.commit(chat.USERLIST_UPDATE, v.data)
+  })
+  flowService.on('chatEvent', (v) => {
+    store.commit(chat.EVENT_ADD, v.data)
   })
 }

+ 81 - 13
go/src/flow/flow.go

@@ -142,17 +142,16 @@ func (f *Flow) Op(name string, params ...interface{}) (Operation, error) {
 	if err != nil {
 		return nil, err
 	}
-	// generate ID
-	for i := 0; i < 10; i++ {
-		id := f.idGen()
-		if _, ok := f.operations.Load(id); ok {
-			continue
-		}
-		f.operations.Store(id, &opEntry{sync.Mutex{}, name, inputs, executor})
-		return opFunc(f, id), nil
+
+	var op *operation
+	err = f.allocID(func(id string) (Data, error) {
+		op = opFunc(f, id)
+		return &opEntry{sync.Mutex{}, name, inputs, executor}, nil
+	})
+	if err != nil {
+		return nil, err
 	}
-	return nil, errors.New("ID exausted")
-	// Initialize opfunc maybe
+	return op, nil
 }
 
 // HasOp verifies if an operation exists
@@ -169,19 +168,40 @@ func (f *Flow) Must(op Operation, err error) Operation {
 	return op
 }
 
+// DefConst define a const by ID
+func (f *Flow) DefConst(id string, value Data) (Operation, error) {
+	f.consts[id] = value
+	executor := func() Data { return f.consts[id] }
+	f.operations.Store(id, &opEntry{sync.Mutex{}, fmt.Sprintf("const<%s>", id), nil, executor})
+	return opFunc(f, id), nil
+}
+
 // Const returns a const operation
 func (f *Flow) Const(value Data) (Operation, error) {
+
+	var op *operation
+
+	err := f.allocID(func(id string) (Data, error) {
+		f.consts[id] = value
+		executor := func() Data { return f.consts[id] }
+		op = opFunc(f, id)
+		return &opEntry{sync.Mutex{}, fmt.Sprintf("const<%s>", id), nil, executor}, nil
+
+	})
+	return op, err
 	// generate ID
-	for i := 0; i < 10; i++ {
+	/*for i := 0; i < 10; i++ {
 		id := f.idGen()
 		if _, ok := f.consts[id]; ok {
 			continue
 		}
-		f.consts[id] = value
 		return opConst(f, id), nil
 	}
+	return nil, errors.New("ID exausted")*/
+
+	//	f.consts[id] = value
+	//	return opFunc(f, id), nil
 
-	return nil, errors.New("ID exausted")
 }
 
 // Var operation
@@ -338,6 +358,44 @@ func (f *Flow) MarshalJSON() ([]byte, error) {
 	return json.Marshal(data)
 }
 
+func (f *Flow) allocID(fn func(id string) (Data, error)) error {
+	f.Lock()
+	defer f.Unlock()
+
+	var id string
+	// generate ID
+	for i := 0; i < 10; i++ {
+		id = f.idGen()
+		if _, ok := f.operations.Load(id); !ok {
+			break
+		}
+	}
+	entry, err := fn(id)
+	if err != nil {
+		return err
+	}
+	f.operations.Store(id, entry)
+	return nil
+
+}
+
+func (f *Flow) addEntry(entry *opEntry) (string, error) {
+	f.Lock()
+	defer f.Unlock()
+
+	// generate ID
+	for i := 0; i < 10; i++ {
+		id := f.idGen()
+		if _, ok := f.operations.Load(id); ok {
+			continue
+		}
+		f.operations.Store(id, entry)
+		return id, nil
+	}
+	return "", errors.New("ID exausted")
+
+}
+
 /////////////////
 // Async data
 /////
@@ -350,6 +408,16 @@ func (f *Flow) getOp(id string) (*opEntry, bool) {
 	return o.(*opEntry), true
 }
 
+// GetOp Return an existing operation or return notfound error
+func (f *Flow) GetOp(id string) Operation {
+	_, ok := f.operations.Load(id)
+	if !ok {
+		return nil
+	}
+	return opFunc(f, id)
+
+}
+
 //////////////////////////////////////////////
 // Experimental event
 ////////////////

+ 60 - 80
go/src/flowserver/flowbuilder.go

@@ -7,9 +7,7 @@ import (
 	"flow/registry"
 	"fmt"
 	"log"
-	"reflect"
 	"strconv"
-	"strings"
 	"time"
 )
 
@@ -43,6 +41,15 @@ type FlowDocument struct {
 	Triggers []Trigger `json:"triggers"`
 }
 
+func (fd *FlowDocument) fetchNodeByID(ID string) *Node {
+	for _, n := range fd.Nodes {
+		if n.ID == ID {
+			return &n
+		}
+	}
+	return nil
+}
+
 func (fd *FlowDocument) fetchTriggerFrom(ID string) []Trigger {
 	ret := []Trigger{}
 	for _, t := range fd.Triggers {
@@ -66,114 +73,86 @@ func (fd *FlowDocument) fetchLinksTo(ID string) []Link {
 	return ret
 }
 
-var ErrLoop = errors.New("Looping through loops is disabled for now")
+var ErrLoop = errors.New("Looping through is disabled for now")
 
 // FlowBuild build the graph based on an starting node
 func FlowBuild(rawData []byte, r *registry.R, startingID string) (*flow.Flow, error) {
 
-	log.Println("Unmarshal document")
 	doc := FlowDocument{[]Node{}, []Link{}, []Trigger{}}
 	err := json.Unmarshal(rawData, &doc)
 	if err != nil {
 		return nil, err
 	}
 
-	log.Println("Create new flow and add a registry")
 	f := flow.New()
 	f.SetRegistry(r)
 
-	log.Println("Temporarly map the nodes with ID")
-	nodeMap := map[string]Node{}
-	for _, n := range doc.Nodes {
-		log.Println("Mapping:", n.ID)
-		nodeMap[n.ID] = n
-	}
-	//inputMap := map[string]flow.Operation{}
-
 	nodeTrack := map[string]bool{}
 
-	log.Println("Recursive node matching")
-	var fetchInputs func(ID string) error
-	fetchInputs = func(ID string) error {
-		log.Println("Check for loops")
+	var build func(ID string) (flow.Operation, error)
+	build = func(ID string) (flow.Operation, error) {
 		if _, ok := nodeTrack[ID]; ok {
-			return ErrLoop //fmt.Errorf("[%v] Looping through nodes is disabled:", ID)
+			return nil, ErrLoop //fmt.Errorf("[%v] Looping through nodes is disabled:", ID)
 		}
 		nodeTrack[ID] = true
 
 		// If flow already has ID just return
-		if f.HasOp(ID) {
-			log.Println("Entry found, continuing")
-			return nil
-		}
-
-		node, ok := nodeMap[ID]
-		if !ok {
-			return fmt.Errorf("node not found [%v]", startingID)
+		if op := f.GetOp(ID); op != nil {
+			log.Println("Return operation")
+			return op, nil
 		}
 
-		entry, err := r.Entry(node.Src)
-		if err != nil {
-			return err
+		node := doc.fetchNodeByID(ID)
+		if node == nil {
+			return nil, fmt.Errorf("node not found [%v]", startingID)
 		}
 
-		inputs := doc.fetchLinksTo(node.ID)
-		param := make([]flow.Data, len(entry.Inputs))
-		// Build ops
-		for _, l := range inputs {
-			from := nodeMap[l.From]
+		var op flow.Operation
 
-			switch from.Src {
-			case "Input":
-				inputID, err := strconv.Atoi(from.Prop["input"])
-				if err != nil {
-					return errors.New("Invalid inputID value, must be a number")
-				}
-				param[l.In] = f.In(inputID) // By id perhaps
-			case "Variable":
-				param[l.In] = f.Var(from.ID, from.Prop["init"])
-			case "Const":
-				// XXX: Automate this in a func
-				raw := from.Label
-				//raw := from.Prop["value"]
-				raw = strings.TrimSpace(raw)
-				if raw[0] == '"' { // its a string
-					log.Println("Unmashal string")
-					var val string
-					json.Unmarshal([]byte(raw), &val)
-					param[l.In] = val
-					log.Println("next Input")
-					continue
-				}
-				newVal := reflect.New(entry.Inputs[l.In])
-				err := json.Unmarshal([]byte(raw), newVal.Interface())
+		switch node.Src {
+		case "Input":
+			inputID, err := strconv.Atoi(node.Prop["input"])
+			if err != nil {
+				return nil, errors.New("Invalid inputID value, must be a number")
+			}
+			op = f.In(inputID) // By id perhaps
+		case "Variable":
+			op = f.Var(node.ID, node.Prop["init"])
+		case "Const":
+			var val flow.Data
+			// XXX: Automate this in a func
+			raw := node.Label
+			err := json.Unmarshal([]byte(raw), &val)
+			if err != nil { // Try to unmarshal as a string?
+				val = string(raw)
+			}
+			op, _ = f.DefConst(node.ID, val)
+		default:
+			// Load entry
+			entry, err := r.Entry(node.Src)
+			if err != nil {
+				return nil, err
+			}
+			inputs := doc.fetchLinksTo(node.ID)
+			param := make([]flow.Data, len(entry.Inputs))
+			for _, l := range inputs {
+				param[l.In], err = build(l.From)
 				if err != nil {
-					// ignore error?
-					log.Println("unmarshalling Error", err)
-					return err
-					//param[l.In] = nil
-					//continue
-				}
-				param[l.In], _ = f.Const(newVal.Elem().Interface())
-			default:
-				param[l.In] = f.Res(from.ID)
-				// Sub process the child
-				if err := fetchInputs(from.ID); err != nil {
-					return err
+					return nil, err
 				}
 			}
+			op, err = f.DefOp(node.ID, node.Src, param...)
+			if err != nil {
+				return nil, err
+			}
 		}
-		f.DefOp(node.ID, node.Src, param...)
 
 		// Process triggers for this node
 		triggers := doc.fetchTriggerFrom(node.ID)
 		for _, t := range triggers {
-			log.Println("Trigger:", t)
-			log.Println("Registering operator", t.To)
-			err := fetchInputs(t.To)
+			_, err := build(t.To)
 			if err != nil {
-				log.Println("Error on trigger input", err)
-				return err
+				return nil, err
 			}
 			// Register the thing here
 			f.Hook(flow.Hook{
@@ -196,18 +175,19 @@ func FlowBuild(rawData []byte, r *registry.R, startingID string) (*flow.Flow, er
 						log.Println("Mismatching trigger, but its a test")
 					}
 
-					log.Println("Matching trigger", t.To)
-					op := f.Res(t.To)
+					op := f.Res(t.To)   // Repeating
 					go op.Process(name) // Background
 				},
 			})
 
 			delete(nodeTrack, node.ID)
 		}
-		return nil
+		return op, nil
 	}
-	err = fetchInputs(startingID)
+
+	_, err = build(startingID)
 	if err != nil {
+		log.Println("Error building")
 		return nil, err
 	}