Browse Source

Selection, improvements on the state flow

luis 7 năm trước cách đây
mục cha
commit
e6d2356d12

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

@@ -10,6 +10,8 @@
   --node-label: var(--normal) !important;
   --node-socket: var(--primary) !important;
   --link-hover: var(--primary) !important;
+  --selector-background: rgba(200, 150, 50, 0.1);
+  --selector-color: var(--primary);
 }
 
 .dark .flow-node__body {

+ 11 - 0
browser/vue-flow/src/assets/default-theme.css

@@ -12,6 +12,8 @@
   --node-label: #fff;
   --node-socket: #444;
   --link-hover: #f00;
+  --selector-background: rgba(0, 0, 200, 0.1);
+  --selector-color: var(--primary);
 }
 
 button {
@@ -105,6 +107,15 @@ input {
   color: var(--normal);
 }
 
+/*
+ * FLOW manager
+ */
+.flow-selector {
+  /* TODO: theme */
+  stroke: var(--selector-color);
+  fill: var(--selector-background);
+}
+
 /*
  * LINKS
  */

+ 149 - 206
browser/vue-flow/src/components/flow/manager.vue

@@ -9,7 +9,7 @@
         'flow-linking':linking || stickySockets,
         'activity':dragging || pointerLink.active ,
         'flow-detail': detailed,
-        'selecting': (selector)?true:false
+        'selecting': !!selector
       }"
       @dragover.prevent
       @drop="managerDrop"
@@ -58,7 +58,9 @@
       <button @click="$emit('funcsPanelToggle')">Panel</button>
       <button @click="stickySockets=!stickySockets"> {{ stickySockets? 'Hide':'Show' }} sockets </button>
       <button @click="detailed=!detailed"> {{ detailed? 'Hide':'Show' }} detail </button>
-      <button v-if="panzoom.x!=0 || panzoom.y!=0 || panzoom.zoom!=1" @click="panzoomReset">Reset view</button>
+      <button v-if="panzoom.x!=0 || panzoom.y!=0 || panzoom.zoom!=1" @click="panzoomReset">
+        Reset view
+      </button>
     </div>
     <div class="flow-container__info">
       x:{{ panzoom.x.toFixed(2) }} y:{{ panzoom.y.toFixed(2) }} scale:{{ panzoom.zoom.toFixed(2) }}
@@ -73,12 +75,12 @@ import FlowNode from './node'
 import FlowLink from './link'
 import FlowPanZoom from './panzoom'
 import SvgDefs from './svgdefwrapper.vue'
+import utils from '@/utils/utils'
 
 export default {
   name: 'FlowManager',
   components: {FlowNode, FlowLink, FlowPanZoom, SvgDefs},
   props: {
-    // 'value': {type: Object, default: () => {}},
     'registry': {type: Object, default: () => {}},
     'width': {type: String, default: '800px'},
     'height': {type: String, default: '600px'}
@@ -88,8 +90,8 @@ export default {
     return {
       panzoom: { x: 0, y: 0, zoom: 1 },
       nodeData: { nodes: [], links: [] },
-      dragging: null,
 
+      dragging: null,
       linking: false,
       stickySockets: false,
       detailed: false,
@@ -117,7 +119,7 @@ export default {
           inputs: nodeClass.inputs,
           output: nodeClass.output,
           match: match,
-          dragging: this.dragging === node,
+          dragging: this.dragging && !!this.dragging[node.id],
           nodeStyle: nodeClass.style
         }
       }
@@ -144,46 +146,28 @@ export default {
       }
     }
   },
-  watch: {
-    value: {
-      handler (val) {
-        const clone = JSON.parse(JSON.stringify(this.value)) // deepClone
-        const nodes = []
-        for (let n of clone.nodes) {
-          if (this.dragging && this.dragging.id === n.id) {
-            continue
-          }
-          nodes.push(n)
-        }
-        if (this.dragging) {
-          nodes.push(this.dragging)
-        }
-        this.nodeData = {
-          nodes: nodes,
-          links: clone.links
-        }
-        this.$nextTick(() => {
-          this.$forceUpdate()
-        })
-      },
-      deep: true
-    }
-  },
   mounted () {
-    this.$parent.$on('sparta', () => {
-      console.log('This is weird')
-    })
     this.$flowService.on('document', (v) => {
-      if (!v.data) { return }
+      console.log('Updating document')
+      if (!v || !v.data || !v.data.nodes) { return }
       const nodes = []
-      for (let n of v.data.nodes) {
-        if (this.dragging && this.dragging.id === n.id) {
+
+      for (let node of v.data.nodes) {
+        if (this.dragging && !!this.dragging[node.id]) {
+          continue
+        }
+        let localNode = this.nodeData.nodes.find(n => n.id === node.id)
+        if (localNode) {
+          Object.assign(localNode, node)
+          nodes.push(localNode)
           continue
         }
-        nodes.push(n)
+        nodes.push(node)
       }
-      if (this.dragging) {
-        nodes.push(this.dragging)
+      if (this.dragging) { // Add our nodes
+        for (let nid in this.dragging) {
+          nodes.push(this.dragging[nid])
+        }
       }
       this.nodeData = {
         nodes: nodes,
@@ -192,22 +176,20 @@ export default {
       this.$nextTick(() => {
         this.$forceUpdate()
       })
-
-      this.nodeData = {
-        nodes: v.data.nodes || [],
-        links: v.data.links || []
-      }
     })
     this.$flowService.on('nodeUpdate', (v) => {
       const nodes = v.data
       const nd = this.nodeData
-      for (let node of nodes) {
+      for (let nid in nodes) {
+        const node = nodes[nid]
         const idx = nd.nodes.findIndex(n => n.id === node.id)
         if (idx === -1) { // new node
-          nd.nodes.push(node)
+          nd.nodes.push(nodes[nid])
           continue
         }
+        // if (!this.dragging || !this.dragging[node.id]) {
         this.$set(nd.nodes, idx, node) // new Node
+        // }
       }
     })
 
@@ -217,7 +199,7 @@ export default {
     document.addEventListener('keydown', this.keyDown)
     document.addEventListener('keyup', this.keyUp)
   },
-  beforeDestroy () {
+  beoreDestroy () {
     document.removeEventListener('keydown', this.keyDown)
     document.removeEventListener('keyup', this.keyUp)
   },
@@ -233,9 +215,7 @@ export default {
       }
     },
     panzoomReset () {
-      this.panzoom.x = 0
-      this.panzoom.y = 0
-      this.panzoom.zoom = 1
+      this.panzoom = {x: 0, y: 0, zoom: 1}
     },
     // XXX: Shrink this function
     // and create some LinkAdd method
@@ -244,7 +224,6 @@ export default {
 
       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)
 
@@ -262,79 +241,67 @@ export default {
       } else {
         this.pointerLink.src = {nodeId: nodeId, type: this.registry[node.src].output, out: 0}
       }
-
-      // What socket is this
-      // Create a temporary link
-      const drag = (ev) => {
-        const p = this.transformedPoint(ev.clientX, ev.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 = (ev) => {
-        document.removeEventListener('mousemove', drag)
-        document.removeEventListener('mouseup', drop)
-        this.pointerLink.active = false
-
-        const targetNodeId = ev.target.getAttribute('data-nodeid')
-        const targetIn = ev.target.getAttribute('data-in')
-        const targetOut = ev.target.getAttribute('data-out')
-
-        // Not a node or same node
-        if (targetNodeId === undefined || targetNodeId === nodeId) {
-          console.error('LINK: target is not a socket or is node')
-          return
-        }
-        let link
-        // target is input
-        if (targetIn && !isInput) {
-          link = {
-            from: nodeId,
-            to: targetNodeId,
-            in: parseInt(targetIn)
+      utils.createDrag({
+        drag: (ev) => {
+          const p = this.transformedPoint(ev.clientX, ev.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
           }
-        } else if (targetOut && isInput) {
-          link = {
-            from: targetNodeId,
-            to: nodeId,
-            in: socket.in
+        },
+        drop: (ev) => {
+          this.pointerLink.active = false
+
+          const targetNodeId = ev.target.getAttribute('data-nodeid')
+          const targetIn = ev.target.getAttribute('data-in')
+          const targetOut = ev.target.getAttribute('data-out')
+
+          // Not a node or same node
+          if (targetNodeId === undefined || targetNodeId === nodeId) {
+            console.error('LINK: target is not a 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)
+
+          const output = this.registry[nodeFrom.src].output
+          const input = this.registry[nodeTo.src].inputs[link.in]
+          // Type checking
+          if (!(output === 'any' || output === input || input === 'any')) {
+            console.error('LINK: Invalid type')
+            return
           }
-        }
-        // 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)
-
-        const output = this.registry[nodeFrom.src].output
-        const input = this.registry[nodeTo.src].inputs[link.in]
-        // Type checking
-        if (!(output === 'any' || output === input || input === 'any')) {
-          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.linkAdd(link)
-      }
-      document.addEventListener('mousemove', drag)
-      document.addEventListener('mouseup', drop)
-    },
-    nodeDoubleClick (ev, i) {
-      this.nodeModalTarget = i
-      this.nodeModal = true
+          // Input already exists, replace
+          const existingInputI = this.nodeData.links.findIndex(l => l.to === link.to && l.in === link.in)
+          if (existingInputI !== -1) {
+            this.nodeData.links.splice(existingInputI, 1)
+          }
+          this.linkAdd(link)
+        }})
     },
 
     nodePointerDown (ev, i) {
@@ -357,66 +324,56 @@ export default {
       if (!this.nodeSelection[tnode.id] && !ev.ctrlKey) this.nodeSelection = {}
       this.nodeSelection[tnode.id] = tnode
       // we can handle with nodeId and a search
-      this.nodeData.nodes.splice(i, 1)
-      this.nodeData.nodes.push(tnode) // put in last
 
-      // transform CTM
-      // delta.x -= tnode.x
-      // delta.y -= tnode.y
-
-      const nodeSel = this.nodeData.nodes.filter(n => {
-        return !!this.nodeSelection[n.id]
-      })
-
-      let curP = this.transformedPoint(ev.x, ev.y)
-      const drag = (ev) => {
-        this.dragging = tnode
-        const dragP = this.transformedPoint(ev.x, ev.y)
-        for (let n of nodeSel) {
-          n.x += dragP.x - curP.x
-          n.y += dragP.y - curP.y
-        }
-        this.sendFlowEvent('nodeUpdate', nodeSel)
-        curP = dragP
-        // Bad possibly
-        // this.$emit('input', this.nodeData)
+      // put the list of nodes in last
+      for (let nk in this.nodeSelection) {
+        let ni = this.nodeData.nodes.findIndex(n => n.id === this.nodeSelection[nk].id)
+        this.nodeData.nodes.splice(ni, 1)
+        this.nodeData.nodes.push(this.nodeSelection[nk]) // put in last
       }
-      const drop = (ev) => {
-        document.removeEventListener('mousemove', drag)
-        document.removeEventListener('mouseup', drop)
-        if (!this.dragging) {
-          if (!ev.ctrlKey) this.nodeSelection = {}
-          this.nodeSelection[tnode.id] = tnode
-        }
-        this.dragging = null
+      let curP = this.transformedPoint(ev.x, ev.y)
 
-        this.sendFlowEvent('nodeUpdate', nodeSel)
-        this.sendDocumentUpdate()
-      }
-      document.addEventListener('mousemove', drag)
-      document.addEventListener('mouseup', drop)
+      this.dragging = this.nodeSelection
+      utils.createDrag({
+        drag: (ev) => {
+          if (this.nodeSelection === undefined) {
+            console.error('Well something went wrong')
+          }
+          const dragP = this.transformedPoint(ev.x, ev.y)
+          for (let n in this.nodeSelection) {
+            this.nodeSelection[n].x += dragP.x - curP.x
+            this.nodeSelection[n].y += dragP.y - curP.y
+          }
+          this.sendFlowEvent('nodeUpdate', this.nodeSelection)
+          curP = dragP
+        },
+        drop: (ev) => {
+          this.dragging = null
+          this.sendDocumentUpdate()
+        }
+      })
     },
     nodeAdd (src, x = 100, y = 100) {
       const newNode = {
-        id: guid(),
+        id: utils.guid(),
         x: x,
         y: y,
         label: src,
         src: src
       }
       this.nodeData.nodes.push(newNode)
-      this.sendFlowEvent('nodeUpdate', [newNode])
+      let nu = {}
+      this.sendFlowEvent('nodeUpdate', (nu[newNode.id] = newNode, nu))
       this.sendDocumentUpdate()
     },
     linkAdd (link) {
       this.nodeData.links.push(link)
-      this.sendflowEvent('linkUpdate', link)
+      this.sendFlowEvent('linkUpdate', link)
       this.sendDocumentUpdate()
     },
     linkRemove (link) {
       const i = this.nodeData.links.findIndex(l => l === link)
       if (i === -1) return
-
       this.nodeData.links.splice(i, 1)
       this.sendFlowEvent('linkRemove', link)
       this.sendDocumentUpdate()
@@ -428,49 +385,44 @@ export default {
         console.error('Registry: Drop src not found in registry')
         return
       }
-
       const pt = this.transformedPoint(ev.x, ev.y)
       this.nodeAdd(reg, pt.x, pt.y)
     },
     viewPointerDown (ev) {
-      console.log('Starting selector')
+      if (ev.button !== 0) return
       ev.preventDefault()
       this.nodeSelection = {}
-      const px = ev.x
-      const py = ev.y
-
-      const p = this.transformedPoint(px, py)
+      const p = this.transformedPoint(ev.x, ev.y)
       this.selector = {x: p.x, y: p.y, width: 0, height: 0}
-      const drag = (ev) => {
-        const p = this.transformedPoint(px, py)
-        const p2 = this.transformedPoint(ev.x, ev.y)
-        const nwidth = p2.x - p.x
-        const nheight = p2.y - p.y
-        this.selector.x = p.x
-        this.selector.y = p.y
-        this.selector.width = nwidth
-        this.selector.height = nheight
-        if (nwidth < 0) {
-          this.selector.x = p2.x
-          this.selector.width = -nwidth
-        }
-        if (nheight < 0) {
-          this.selector.y = p2.y
-          this.selector.height = -nheight
-        }
-      }
-      const drop = (ev) => {
-        this.selector = null
-        document.removeEventListener('mousemove', drag)
-        document.removeEventListener('mouseup', drop)
-      }
-      document.addEventListener('mousemove', drag)
-      document.addEventListener('mouseup', drop)
+      utils.createDrag({
+        drag: (evd) => {
+          // transform again in case we changed zoom/pan
+          const p = this.transformedPoint(ev.x, ev.y)
+          const p2 = this.transformedPoint(evd.x, evd.y)
+          const nwidth = p2.x - p.x
+          const nheight = p2.y - p.y
+          this.selector = {
+            x: nwidth < 0 ? p2.x : p.x,
+            y: nheight < 0 ? p2.y : p.y,
+            width: nwidth < 0 ? -nwidth : nwidth,
+            height: nheight < 0 ? -nheight : nheight
+          }
+        },
+        drop: (ev) => {
+          for (let n of this.nodeData.nodes) {
+            if (n.x > this.selector.x && n.x < (this.selector.x + this.selector.width) &&
+              n.y > this.selector.y && n.y < (this.selector.y + this.selector.height)
+            ) {
+              this.nodeSelection[n.id] = n
+            }
+          }
+          this.selector = null
+        }})
     },
+    // service events
     sendFlowEvent (type, param) {
       this.$flowService[type](param)
     },
-
     sendDocumentUpdate (nodeData) {
       this.$flowService.documentUpdate(this.nodeData, this.$route.params.sessId)
     },
@@ -490,16 +442,7 @@ export default {
     }
   }
 }
-// utils
-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 {
@@ -515,8 +458,9 @@ function guid () {
   display:flex;
   justify-content: center;
   align-items: center;
-  color: #FFF;
+  color: #fff;
 }
+
 .flow-container__control button {
   display:flex;
   justify-content: center;
@@ -524,6 +468,7 @@ function guid () {
   padding:14px;
   color:#333;
 }
+
 .flow-container__info {
   position:absolute;
   bottom:10px;
@@ -531,6 +476,7 @@ function guid () {
   padding:2px;
   font-size:9px;
 }
+
 .flow-view {
   border:none;
   position:relative;
@@ -540,9 +486,6 @@ function guid () {
 .flow-selector {
   pointer-events:none;
   opacity:0;
-  /* TODO: theme */
-  stroke: rgba(30,100,255,1);
-  fill: rgba(30,100,255,0.3);
 }
 
 .flow-selector.flow-selector--selecting {

+ 14 - 9
browser/vue-flow/src/components/flow/node.vue

@@ -87,10 +87,6 @@ export default {
     'selected': {type: Boolean, default: false},
 
     'nodeStyle': {type: Object, default: () => {}}
-    /* 'type': {type: String, default: ''},
-    'color': {type: String, default: '#777'},
-    'textColor': {type: String, default: '#fff'} */
-    // 'value': {type: Object, default: () => {}}
   },
   data () {
     return {
@@ -214,35 +210,41 @@ export default {
 .flow-node--dragging {
   cursor:move;
 }
+
 .flow-node__socket {
   pointer-events: none;
   stroke-width:1;
   opacity:0;
-  transition: all .3s;
+  transition: all 0.3s;
 }
+
 .flow-node__body {
-  transition: all .3s;
+  transition: all 0.3s;
 }
+
 .flow-view:not(.activity) .flow-node__socket:hover {
   stroke-width:10;
   cursor:pointer;
 }
+
 .flow-node__socket--match {
   cursor:pointer;
   stroke-width:10;
 }
+
 /*
 Override flow-node
 for hidden
  */
 .flow-linking .flow-node__socket {
   opacity:1;
-  pointer-events: initial;
+  pointer-events: inherit;
 }
+
 .flow-linking .flow-node__socket--match,
 .flow-node__socket--match {
   opacity:1;
-  pointer-events: initial;
+  pointer-events: inherit;
 }
 
 .flow-node__label {
@@ -251,11 +253,13 @@ for hidden
   user-select:none;
   fill:#333 !default;
 }
+
 .flow-node__socket-detail {
   pointer-events:none;
   opacity:0;
   fill: #fff !default;
 }
+
 .flow-view.flow-detail .flow-node__socket-detail {
   opacity:1;
 }
@@ -264,8 +268,9 @@ for hidden
   opacity:0;
   stroke-width:1;
   pointer-events:none;
-  transition: .3s;
+  transition: 0.3s;
 }
+
 .flow-node--selected .flow-node__selection {
   opacity:1;
 }

+ 1 - 0
browser/vue-flow/src/components/flow/panzoom.vue

@@ -122,6 +122,7 @@ export default {
 .flow-pan-zoom__transformer {
   fill:transparent;
 }
+
 .flow-pan-zoom__grid {
   fill:transparent;
   pointer-events:none;

+ 42 - 29
browser/vue-flow/src/components/main.vue

@@ -10,7 +10,7 @@
         <div class="app-info">
           <h4>HELP</h4>
           <ul>
-            <li><b>Pan</b>: Drag with Mouse button</li>
+            <li><b>Pan</b>: Drag with Middle Mouse button</li>
             <li><b>Zoom</b>: Mouse wheel up and down to zoom in and out</li>
             <li><b>New Node</b>: Create a node by dragging a fn from left panel into area</li>
             <li><b>Remove Node</b>: Middle click in a node to remove a node</li>
@@ -23,7 +23,6 @@
           <h4>TODO:</h4>
           <ul>
             <li>UX/UI: Undo changes</li>
-            <li>UX/UI: Selectable nodes</li>
             <li>UX/UI: Special nodes with display capabilities (images,datatables,...)</li>
             <li>UX/UI: Group nodes into a single box, exposing inputs and outputs</li>
             <li>UX/UI: Implement touch</li>
@@ -55,6 +54,7 @@
           />
 
           <flow-manager
+            ref="flowManager"
             :registry="registry"
             @funcsPanelToggle="funcsActive=!funcsActive"
             @nodeInspect="nodeInspect(...arguments)"
@@ -70,19 +70,21 @@
       <!-- Move this to a different place -->
       <hx-modal class="flow-modal" v-if="nodeInspectTarget" @close="nodeInspectTarget=null">
         <div slot="header">Node inspector:</div>
-        <div slot="body">
+        <div slot="body" class="flow-modal__body">
           <div class="flow-modal__info">
-            <svg class="flow-view preview activity flow-detail flow-linking" width="400" height="200">
-              <flow-node
-                ref="modalPreviewNode"
-                transform="translate(200,100)"
-                :id="nodeInspectTarget.id"
-                :match="{}"
-                :label="nodeInspectTarget.label"
-                :inputs= "registry[nodeInspectTarget.src].inputs"
-                :output= "registry[nodeInspectTarget.src].output"
-                :nodeStyle= "registry[nodeInspectTarget.src].style"
-              />
+            <svg class="flow-view preview activity flow-detail flow-linking" width="300" height="200" viewBox="0 0 300 200">
+              <flow-panzoom>
+                <flow-node
+                  style="pointer-events:none"
+                  ref="modalPreviewNode"
+                  transform="translate(150,100)"
+                  :id="nodeInspectTarget.id"
+                  :match="{}"
+                  :label="nodeInspectTarget.label"
+                  :inputs= "registry[nodeInspectTarget.src].inputs"
+                  :output= "registry[nodeInspectTarget.src].output"
+                  :nodeStyle= "registry[nodeInspectTarget.src].style"
+              /></flow-panzoom>
             </svg>
             <div class="flow-modal__sockets-properties">
               <label>Description</label>
@@ -110,6 +112,7 @@
 import AppChat from '@/components/chat'
 import FlowManager from '@/components/flow/manager'
 import FlowNode from '@/components/flow/node'
+import FlowPanzoom from '@/components/flow/panzoom'
 import FlowPanel from './panel'
 import HxSplit from '@/components/shared/hx-split'
 import HxModal from '@/components/shared/hx-modal'
@@ -120,7 +123,7 @@ import '@/assets/style.css'
 // import nodeData from './nodedata'
 
 export default {
-  components: {FlowManager, FlowPanel, FlowNode, HxSplit, HxModal, AppChat},
+  components: {FlowManager, FlowPanel, FlowNode, FlowPanzoom, HxSplit, HxModal, AppChat},
   data () {
     return {
       registry: {
@@ -153,17 +156,20 @@ export default {
     }
   },
   watch: {
-    /* nodeInspectTarget: {
+    nodeInspectTarget: {
       handler (val, oldVal) {
         if (!val === null && !oldVal) { return }
         if (!val) {
-          this.sendDocumentUpdate()
+          this.$refs.flowManager.sendDocumentUpdate()
+          // this.sendDocumentUpdate()
           return
         }
-        this.sendFlowEvent('nodeUpdate', this.nodeInspectTarget)
+        this.$refs.flowManager.sendFlowEvent('nodeUpdate', [this.nodeInspectTarget])
+        // this.$emit('sparta', 'testing')
+        // this.sendFlowEvent('nodeUpdate', this.nodeInspectTarget)
       },
       deep: true
-    } */
+    }
   },
 
   mounted () {
@@ -206,10 +212,12 @@ export default {
   display:flex;
   flex-direction: column;
 }
+
 .flow-main .app-flow-container {
   position:relative;
   flex:1;
 }
+
 .flow-main .flow-container {
   position:absolute;
   top:0;
@@ -219,12 +227,13 @@ export default {
 }
 
 .flow-main .app-header {
-  padding:0px 14px;
+  padding:0 14px;
   height: 50px;
   display:flex;
   justify-content: space-between;
   align-items: center;
 }
+
 .flow-main  .app-info {
   color: #aaa;
   font-size:10px;
@@ -243,13 +252,15 @@ export default {
   font-size:100px;
   text-shadow: 1px 1px 1px rgba(255,255,255,0.5), -1px -1px 1px rgba(0,0,0,0.05);
 }
+
 .split .splitter{
-  flex-basis:0px;
+  flex-basis:0;
   position:relative;
   background: rgba(208,208,208,0.9);
 }
+
 .split:not(.resizeable) .content:first-child {
-  transition: all .3s;
+  transition: all 0.3s;
 }
 
 .split.resizeable.horizontal .splitter::after {
@@ -259,14 +270,12 @@ export default {
   z-index:100;
   content:" ";
   position:absolute;
-
   top:20%;
   bottom:20%;
-  left:0px;
+  left:0;
   width:10px;
-
   background: rgba(0,0,0,0.4);
-  transition: all .3s;
+  transition: all 0.3s;
 }
 
 .app-horizontal {
@@ -277,10 +286,11 @@ export default {
   flex-flow:row;
   overflow:hidden;
 }
+
 .app-chat {
   position:absolute;
-  top:0px;
-  right:0px;
+  top:0;
+  right:0;
   height:100%;
 }
 
@@ -289,16 +299,19 @@ export default {
   display:flex;
   flex-flow:row;
 }
+
 .flow-modal__info > * {
   margin-right:10px;
 }
 
-.flow-modal__info label {
+.flow-modal__body label {
   font-size:14px;
   display:flex;
   flex-flow:row;
   font-weight:bold;
+  padding-bottom:10px;
 }
+
 .flow-modal__info .property {
   padding-left:20px;
   font-size:12px;

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

@@ -0,0 +1,23 @@
+module.exports = {
+  createDrag (obj) {
+    const drag = (ev) => {
+      obj && obj.drag && obj.drag(ev)
+    }
+    const drop = (ev) => {
+      document.removeEventListener('mousemove', drag)
+      document.removeEventListener('mouseup', drop)
+      obj && obj.drop && obj.drop(ev)
+    }
+    document.addEventListener('mousemove', drag)
+    document.addEventListener('mouseup', drop)
+  },
+  guid () {
+    function s4 () {
+      return Math.floor((1 + Math.random()) * 0x10000)
+        .toString(16)
+        .substring(1)
+    }
+    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+    s4() + '-' + s4() + s4() + s4()
+  }
+}