Ver código fonte

Frontend changes

* Added clone using ctrl + drag will clone selected nodes
* Added drag threshold, only start dragging if pointer goes over a
threshold value
* Moved selection to vuex state
* few changes in css for secondary button
* Added result button to inspector
* Added new activity icon if the node has data
luis 7 anos atrás
pai
commit
fce07f68ad

+ 2 - 0
browser/vue-flow/README.md

@@ -2,6 +2,8 @@
 
 ## Things to do
 
+* Move selection to central state
+
 ### UI
 
 ### Code & Services

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

@@ -12,6 +12,7 @@
   --primary: #c55c00;
   --primary-darker: #a53c00;
   --primary-lighter: #ff8c20;
+  --secondary-inverse: var(--normal);
   --node-label: var(--normal);
   --node-socket: var(--primary);
   --node-socket--withvalue: #44f;
@@ -19,6 +20,8 @@
   --selector-background: rgba(200, 150, 50, 0.1);
   --selector-color: var(--primary);
   --node-selection: var(--primary);
+  --border-color: rgba(150, 150, 150, 0.2);
+  --border-color-lighter: rgba(100, 100, 100, 0.3);
 }
 
 .dark .primary-inverse {
@@ -46,7 +49,7 @@
   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.4);
 }
 
-.dark .flow-node__activity[status="running"] .flow-node__activity-icon > * {
+.dark .flow-node__activity--running > * {
   stroke: yellow;
 }
 

+ 4 - 3
browser/vue-flow/src/assets/default-theme.css

@@ -12,7 +12,7 @@
   --primary-lighter: #6080b0;
   --primary-inverse: #fff;
   --secondary: #666;
-  --secondary-inverse: #fff;
+  --secondary-inverse: #eee;
   --node-label: #fff;
   --node-socket: #444;
   --node-socket--withvalue: #44f;
@@ -21,7 +21,7 @@
   --selector-color: var(--primary);
   --transition-speed: 0.4s;
   --transition-speed-slow: 0.8s;
-  --node-selection: darkblue;
+  --node-selection: rgba(0, 0, 155, 0.5);
   --border-color: rgba(50, 50, 50, 0.3);
   --border-color-lighter: rgba(150, 150, 150, 0.17);
 
@@ -353,7 +353,8 @@ input::placeholder {
 .hx-modal__container {
   background: var(--background-transparent);
   color: var(--normal);
-  height: 70vh;
+  width: 70vw !important;
+  height: 80vh;
   display: flex;
   flex-flow: column;
   overflow: hidden;

+ 12 - 0
browser/vue-flow/src/assets/icons/archive.svg

@@ -0,0 +1,12 @@
+<?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">
+<polyline fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" points="5,41 11,1 53,1 59,41 "/>
+<path fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" d="M21,41c0,6.075,4.925,11,11,11s11-4.925,11-11h16v22
+	H5V41H21z"/>
+<line fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="12" y1="31" x2="52" y2="31"/>
+<line fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="14" y1="21" x2="50" y2="21"/>
+<line fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="16" y1="11" x2="48" y2="11"/>
+</svg>

+ 8 - 0
browser/vue-flow/src/assets/icons/bolt.svg

@@ -0,0 +1,8 @@
+<?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">
+<polygon fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="bevel" stroke-miterlimit="10" points="40,1 17,37 31,37 
+	24,63 50,27 36,27 "/>
+</svg>

+ 15 - 0
browser/vue-flow/src/assets/icons/data.svg

@@ -0,0 +1,15 @@
+<?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">
+<g>
+	<polygon fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" points="23,1 55,1 55,63 9,63 9,15 	"/>
+	<polyline fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" points="9,15 23,15 23,1 	"/>
+	<line fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="32" y1="14" x2="46" y2="14"/>
+	<line fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="18" y1="24" x2="46" y2="24"/>
+	<line fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="18" y1="34" x2="46" y2="34"/>
+	<line fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="18" y1="44" x2="46" y2="44"/>
+	<line fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="18" y1="54" x2="46" y2="54"/>
+</g>
+</svg>

+ 10 - 0
browser/vue-flow/src/assets/icons/message.svg

@@ -0,0 +1,10 @@
+<?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">
+<line fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="10" y1="16" x2="54" y2="16"/>
+<line fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="10" y1="26" x2="54" y2="26"/>
+<line fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="10" y1="36" x2="54" y2="36"/>
+<polygon fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" points="32,47 63,47 63,5 1,5 1,47 18,47 18,59 "/>
+</svg>

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

@@ -11,14 +11,15 @@
   --primary-darker: #0ae;
   --primary-lighter: #6080b0;
   --primary-inverse: #fff;
-  --secondary: #666;
-  --secondary-inverse: #fff;
+  --secondary: #eee;
+  --secondary-inverse: #444;
   --node-label: #fff;
   --node-socket: #444;
   --node-socket--withvalue: #44f;
   --link-hover: #f00;
   --selector-background: rgba(0, 0, 200, 0.1);
   --selector-color: var(--primary);
+  --node-selection: rgba(0, 180, 230, 0.5);
 
   /*--transition-speed: 0.3s;*/
 

+ 22 - 49
browser/vue-flow/src/components/app-flow.vue

@@ -1,10 +1,10 @@
 <template>
   <div class="flow-main" :class="themes[theme%themes.length]">
-    <div class="app-content" :class="{'app-content--blur':helpModal}">
+    <div class="app-content" :class="{'app-content--blur':modalInfo || modalData}">
       <div class="app-header">
         Flow
         <div>
-          <button @click="helpModal=true">?</button>
+          <button @click="modalInfo=true">?</button>
           <button @click="theme++">{{ themes[(theme+1)%themes.length] }}</button>
         </div>
       </div>
@@ -36,6 +36,7 @@
               <transition name="fade">
                 <flow-inspector
                   ref="inspector"
+                  @dataClick="modalData=true"
                   v-show="panel=='inspector'"
                 />
               </transition>
@@ -44,7 +45,7 @@
               ref="flowManager"
               @nodeInspect="nodeInspectStart(...arguments)"
               @nodeProcess="nodeProcess(...arguments)"
-              @nodeDblClick="nodeInspectStart(...arguments,true)"
+              @nodeDoubleClick="nodeInspectStart(...arguments,true)"
               @documentSave="documentSave"
 
               width="100%"
@@ -57,15 +58,20 @@
         <flow-notifications/>
       </div>
     </div>
-    <hx-modal class="app-modal__info" v-if="helpModal" @close="helpModal=false">
+    <!-- create a modal selector here -->
+    <hx-modal class="flow-modal__info" v-if="modalInfo" @close="modalInfo=false">
       <h4 slot="header">INFO</h4>
-      <app-info slot="body"/>
+      <flow-modal-info slot="body"/>
       <template slot="footer">
         <a href="readme" target="_blank">More information</a>
-        <button class="primary-inverse" @click="helpModal=false">OK</button>
+        <button class="primary-inverse" @click="modalInfo=false">OK</button>
       </template>
-
     </hx-modal>
+    <hx-modal class="flow-modal__data" v-if="modalData" @close="modalData=false">
+      <h4 slot="header">Data visualiser</h4>
+      <flow-modal-data slot="body"/>
+    </hx-modal>
+
   </div>
 </template>
 <script>
@@ -75,10 +81,11 @@ import FlowPanzoom from '@/components/flow/panzoom'
 import FlowNotifications from '@/components/flow/notifications'
 import FlowInspector from '@/components/flow/panel-inspector'
 import FlowFuncs from '@/components/flow/panel-funcs'
+import FlowModalData from '@/components/flow/modal-data'
+import FlowModalInfo from '@/components/flow/modal-info'
 import HxSplit from '@/components/shared/hx-split'
 import HxModal from '@/components/shared/hx-modal'
 import AppChat from '@/components/flow/chat'
-import AppInfo from '@/components/flow/modal-info'
 import 'reset-css/reset.css'
 
 import '@/assets/lines-theme.css'
@@ -93,20 +100,23 @@ export default {
     FlowNotifications,
     FlowInspector,
     FlowFuncs,
+    FlowModalData,
+    FlowModalInfo,
     HxSplit,
     HxModal,
-    AppChat,
-    AppInfo
+    AppChat
   },
   data () {
     return {
-      helpModal: false,
+      modalInfo: false,
+      modalData: false,
+
       panel: 'palette',
 
       funcsSize: '250px',
       funcsResizeable: false,
 
-      themes: ['light', 'dark', 'lines'],
+      themes: ['color', 'dark', 'lines'],
       theme: 2
     }
   },
@@ -282,41 +292,4 @@ export default {
   color: var(--normal) !important;
 }
 
-/*
-.flow-modal__info {
-  padding-bottom:20px;
-  display:flex;
-  flex-flow:row;
-}
-
-.flow-modal__info > * {
-  margin-right:10px;
-  flex:1;
-}
-
-.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;
-}
-
-.flow-modal__properties-error .property{
-  color: red;
-}
-
-.flow-modal__button-run {
-  align-self: flex-end;
-  width:100%;
-}
-
-.flow-modal__footer {
-}*/
-
 </style>

+ 74 - 40
browser/vue-flow/src/components/flow/editor.js

@@ -2,27 +2,22 @@ import {mapGetters, mapActions} from 'vuex'
 import FlowNode from './node'
 import FlowLink from './link'
 import FlowTriggerLink from './link-trigger'
-
 import FlowPanZoom from './panzoom'
+import FlowModalData from './modal-data' // NEW 15/02/2018
 import HxContextMenu from '@/components/shared/hx-contextmenu'
 import SvgDefs from './svgdefswrapper'
 import utils from '@/utils/utils'
 
 export default {
   name: 'FlowManager',
-  components: {FlowNode, FlowLink, FlowTriggerLink, FlowPanZoom, HxContextMenu, SvgDefs},
+  components: {FlowNode, FlowLink, FlowTriggerLink, FlowPanZoom, FlowModalData, HxContextMenu, SvgDefs},
   props: {
     'width': {type: String, default: '800px'},
     'height': {type: String, default: '600px'}
   },
   data () {
-    // const cloned = JSON.parse(JSON.stringify(this.value)) // initial?
     return {
       panzoom: { x: 0, y: 0, zoom: 1 },
-
-      // Shared state
-      // nodeData: { nodes: [], links: [], triggers: [] },
-
       dragging: null,
       linking: false,
       triggerLinking: false,
@@ -31,7 +26,6 @@ export default {
       pointerTriggerLink: {active: false, props: {}, src: {}},
 
       selector: null,
-      nodeSelection: {}, // map of true false
 
       stickySockets: false,
       stickyTriggers: false,
@@ -39,7 +33,7 @@ export default {
     }
   },
   computed: {
-    ...mapGetters('flow', ['registry', 'activity', 'nodeData', 'nodeById']),
+    ...mapGetters('flow', ['registry', 'activity', 'nodeData', 'nodeById', 'nodeSelection']),
     outputNode () {
       const n = this.nodeData.nodes.find(n => n.src === 'Output')
 
@@ -157,6 +151,7 @@ export default {
       'NOTIFICATION_ADD',
       'DOCUMENT_SYNC',
       'NODE_RAISE', 'NODE_UPDATE', 'NODE_ADD', 'NODE_REMOVE', 'NODE_INSPECT', 'NODE_PROCESS', 'NODE_TRAIN',
+      'NODE_SELECTION_ADD', 'NODE_SELECTION_CLEAR',
       'LINK_ADD', 'LINK_REMOVE',
       'TRIGGER_ADD', 'TRIGGER_REMOVE' ]),
 
@@ -168,32 +163,23 @@ export default {
       }
 
       let single = null
-      const selectionIds = Object.keys(this.nodeSelection)
-      if (selectionIds.length === 1) { single = this.nodeSelection[selectionIds[0]] }
+      if (Object.keys(this.nodeSelection).length === 1) { single = this.nodeSelection[Object.keys(this.nodeSelection)[0]] }
       switch (ev.key) {
         case 'Enter':
           if (!single) { return }
           this.nodeInspect(single, true)
           break
         case 'Delete':
-          if (!this.nodeSelection) { return }
+          if (Object.keys(this.nodeSelection).length === 0) { return }
           console.log('Removing nodes:', this.nodeSelection)
           this.NODE_REMOVE(this.nodeSelection)
-          // for (let k in this.nodeSelection) {
-          // this.nodeRemove(this.nodeSelection[k])
-          // }
           break
         case 'a':
           if (ev.ctrlKey) {
             ev.preventDefault()
             ev.stopPropagation()
-
-            this.nodeSelection = {}
-            for (let n of this.nodeData.nodes) {
-              this.nodeSelection[n.id] = n
-            }
+            this.NODE_SELECTION_ADD(this.nodeData.nodes)
           }
-
           break
       }
     },
@@ -409,24 +395,63 @@ export default {
       this.nodeInspect(tnode)
 
       // Switch selection
-      if (!this.nodeSelection[tnode.id] && !ev.ctrlKey) this.nodeSelection = {}
-      this.nodeSelection[tnode.id] = tnode
-      // we can handle with nodeId and a search
-
+      if (!this.nodeSelection[tnode.id] && !ev.ctrlKey) {
+        this.NODE_SELECTION_CLEAR()
+      }
+      this.NODE_SELECTION_ADD([tnode])
       this.NODE_RAISE(this.nodeSelection)
 
       let curP = this.transformedPoint(ev.x, ev.y)
-
-      this.dragging = this.nodeSelection
+      let clone = false
+      if (ev.ctrlKey && Object.keys(this.nodeSelection).length > 0) clone = true
       utils.createDrag({
         drag: (ev) => {
-          if (this.nodeSelection === undefined) {
-            console.error('Well something went wrong')
+          /// /////////// IMPORTANT NEW ////////////////
+          // logic: we analyse selection, create new nodes based on same src
+          // with same things, and checkout the inner links, nodes between our nodes
+          // if all ok we link the new nodes aswell
+          // XXX: add a sub function for this, cloneSelection
+
+          if (clone && this.nodeSelection) {
+            clone = false
+            const nodeMap = {}
+            const newNodes = []
+            for (let k in this.nodeSelection) {
+              const n = this.nodeById(k)
+              if (n.src === 'Output') { // Do not clone output
+                continue
+              }
+              const newNode = JSON.parse(JSON.stringify(n))
+              newNode.id = utils.guid()
+              nodeMap[n.id] = newNode.id
+              newNodes.push(newNode)
+            }
+            this.NODE_ADD(newNodes)
+            // Clone links if inside the selection
+            for (let k in this.nodeSelection) {
+              const links = this.nodeData.links.filter(l => l.from === k)
+              if (!links) break
+              for (let l of links) {
+                if (this.nodeSelection[l.to]) { // Link is inside
+                  const link = {
+                    from: nodeMap[k],
+                    to: nodeMap[l.to],
+                    in: l.in
+                  }
+                  this.LINK_ADD(link)
+                }
+              }
+            }
+
+            // Check inner links
+
+            this.NODE_SELECTION_CLEAR()
+            this.NODE_SELECTION_ADD(newNodes)
           }
+          this.dragging = this.nodeSelection
           const dragP = this.transformedPoint(ev.x, ev.y)
           const nodeUpdate = []
           for (let k in this.nodeSelection) {
-            // const n = this.nodeData.nodes.find(n => n.id === k)
             const n = this.nodeById(k)
             // create new nodes
             nodeUpdate.push({
@@ -436,30 +461,32 @@ export default {
             })
           }
           this.NODE_UPDATE(nodeUpdate)
-          // this.sendFlowEvent('nodeUpdate', this.nodeSelection)
           curP = dragP
         },
         drop: (ev) => {
-          // snap?
           this.dragging = null
+          // snap?
           // Snapping
           const dragP = this.transformedPoint(ev.x, ev.y)
           const nodeUpdate = []
           for (let k in this.nodeSelection) {
-            // const n = this.nodeData.nodes.find(n => n.id === k)
             const n = this.nodeById(k)
             // create new nodes
             nodeUpdate.push({
               ...n,
               x: n.x + dragP.x - curP.x,
               y: n.y + dragP.y - curP.y
-              // x: Math.round((n.x + dragP.x - curP.x) / 10) * 10, // snapping
-              // y: Math.round((n.y + dragP.y - curP.y) / 10) * 10 // snapping
+              // snapping
+              // x: Math.round((n.x + dragP.x - curP.x) / 10) * 10,
+              // y: Math.round((n.y + dragP.y - curP.y) / 10) * 10
             })
           }
           // Updating nodes
           this.NODE_UPDATE(nodeUpdate)
           this.DOCUMENT_SYNC()
+        },
+        noDrag: (ev) => {
+          this.dragging = null
         }
       })
     },
@@ -491,7 +518,7 @@ export default {
           newNode.prop[k] = ''
         }
       }
-      this.NODE_ADD(newNode)
+      this.NODE_ADD([newNode])
     },
 
     managerDrop (ev) {
@@ -524,17 +551,24 @@ export default {
           }
         },
         drop: (ev) => {
-          if (!ev.shiftKey) this.nodeSelection = {}
+          if (!ev.shiftKey) this.NODE_SELECTION_CLEAR()
+          const nodesToSelect = []
           for (let n in this.nodeData.nodes) {
             const node = this.nodeData.nodes[n]
             if (node.x > this.selector.x && node.x < (this.selector.x + this.selector.width) &&
               node.y > this.selector.y && node.y < (this.selector.y + this.selector.height)
             ) {
-              this.nodeSelection[node.id] = node
+              nodesToSelect.push(node)
+              // Add to selection
             }
           }
+          this.NODE_SELECTION_ADD(nodesToSelect)
           this.selector = null
-        }})
+        },
+        noDrag: (ev) => {
+          if (!ev.shiftKey) this.NODE_SELECTION_CLEAR()
+        }
+      })
     },
     documentProcess () {
       const n = this.nodeData.nodes.find(n => n.src === 'Output')
@@ -568,7 +602,7 @@ export default {
         src: 'Portal From'
       }
 
-      this.NODE_ADD(portalNode)
+      this.NODE_ADD([portalNode])
     },
     // HELPERS depending on svg ref
     createSVGPoint (x, y) {

+ 10 - 8
browser/vue-flow/src/components/flow/editor.vue

@@ -40,11 +40,11 @@
           v-bind="nodeProps(n)"
           :key="'node' + n.id"
           :id="n.id"
-          :selected="nodeSelection[n.id]?true:false"
+          :selected="!!nodeSelection[n.id]"
           @nodePointerDown.prevent="nodePointerDown($event,i)"
           @socketPointerDown="socketPointerDown(n.id,...arguments)"
           @triggerPointerDown="triggerPointerDown(n.id,...arguments)"
-          @nodeDoubleClick="$emit('nodeDblClick',n)"
+          @nodeDoubleClick="$emit('nodeDoubleClick',n)"
           @nodeRightClick="$refs.menu.open($event,n)"
         />
         <!-- mouse link-->
@@ -76,21 +76,23 @@
       <button v-if="outputNode" @click="documentProcess" class="primary-inverse">RUN</button>
     </div>
     <div class="flow-container__info">
-      x:{{ panzoom.x.toFixed(2) }} y:{{ panzoom.y.toFixed(2) }} scale:{{ panzoom.zoom.toFixed(2) }}
-      nodes: {{ nodeData.nodes.length }}
-      links: {{ nodeData.links.length }}
-      triggers: {{ nodeData.triggers.length }}
+      x:{{ panzoom.x.toFixed(2) }} y:{{ panzoom.y.toFixed(2) }} scale:{{ panzoom.zoom.toFixed(2) }};
+      {{ nodeData.nodes.length }} nodes;
+      {{ nodeData.links.length }} links;
+      {{ nodeData.triggers.length }} triggers;
+      {{ nodeSelection.length }} selected;
     </div>
     <hx-context-menu ref="menu">
       <template slot-scope="d" >
         <div class="flow-node__context-menu">
           <div class="hover" @click="NODE_PROCESS(d.userData.id)">Run</div>
-          <div class="hover" @click="nodeInspect(d.userData,true)">Inspect</div>
           <div class="hover" @click="createPortal(d.userData.id)">Create Portal</div>
           <hr>
+          <div class="hover" @click="NODE_REMOVE([d.userData])">Delete</div>
+          <hr>
           <div class="hover" @click="NODE_TRAIN(d.userData.id)">Train(temporary)</div>
           <hr>
-          <div class="hover" @click="NODE_REMOVE([d.userData])">Delete</div>
+          <div class="hover" @click="nodeInspect(d.userData,true)">Inspect</div>
         </div>
       </template>
     </hx-context-menu>

+ 34 - 0
browser/vue-flow/src/components/flow/modal-data.vue

@@ -0,0 +1,34 @@
+<template>
+  <div slot="body">
+    canvas maybe?
+    <p> {{ nodeInspect }}</p>
+    <hr>
+    <p> {{ nodeActivity }} </p>
+    <hr>
+    Data:
+    <p>{{ data }}</p>
+  </div>
+</template>
+<script>
+import {mapGetters} from 'vuex'
+export default {
+  computed: {
+    ...mapGetters('flow', ['activity', 'nodeInspect']),
+    nodeActivity () {
+      return this.activity && this.activity.nodes && this.activity.nodes[this.nodeInspect.id]
+    },
+    nodeInspect () {
+      return this.$store.state.flow.nodeInspect
+    },
+    data () {
+      // This way we can parse data and show it somehow
+      // Could be a base64etc.etc. url generated by server
+      // or any plotable data
+      return this.nodeActivity && this.nodeActivity.data
+    }
+
+  }
+}
+</script>
+<style>
+</style>

+ 3 - 86
browser/vue-flow/src/components/flow/modal-info.vue

@@ -1,66 +1,7 @@
 <template>
-  <div class="app-info markdown-body" v-html="content">
-
-    <!--<section class="app-info__section app-info--view">
-      <h4>Editor</h4>
-      <ul>
-        <li><b>Collaboration</b>: Using the same url address, others can join the session</li>
-        <li><b>Pan</b>: Drag with Middle Mouse or Ctrl+left mouse button</li>
-        <li><b>Zoom</b>: Mouse wheel up and down to zoom in and out</li>
-        <li><b>Reset</b>: Reset view by pressing on the reset button</li>
-      </ul>
-    </section>
-    <section class="app-info__section app-info--flow">
-      <h4>Flow:</h4>
-      <p>
-        A flow works by requesting the previous nodes the results of its operation, so the starting node will
-        be the node we want the result of, unattached nodes wont be executed
-      </p>
-      <p>
-
-        All nodes are Go functions.
-        here's an example of a Flow app with a node with a single output:
-        <a target="_blank" :href="'http://'+ location.host +'/c1.html'">sample1</a><br>
-        Every function can be registered including from external packages as shown in
-        <a target="_blank" :href="'http://'+ location.host +'/c2.html'">sample2</a><br>
-        Describing functions:
-        <a target="_blank" :href="'http://'+ location.host +'/c3.html'">sample3</a><br>
-
-      </p>
-      <ul>
-        <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>
-        <li><b>Inspect node</b>: Double click on a node to get detailed information</li>
-        <li><b>Move Node</b>: Mouse click and drag</li>
-        <li><b>Links</b>: Press [shift] and Drag from a node/socket to a socket highlighted in green</li>
-        <li><b>Links(alternative)</b>: Toggle socket visualisation in the panel and Drag from a socket to a socket highlighted in green</li>
-        <li><b>Remove Link</b>: Simple click on the link when it turns red</li>
-      </ul>
-    </section>
-    <div class="app-info__section app-info__triggers">
-      <h4>Triggers</h4>
-      <p>
-        Triggers works when a node changes its status it will call the linked node, triggers will have filters
-        such as (finish, error, start), this will be useful to create CI pipelines by triggering different processes
-        (i.e: running a container to send an email informing the status of the build)
-      </p>
-    </div>
-
-    <h4>TODO:</h4>
-    <ul>
-      <li>UX/UI: create undoer</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>
-      <li>UX/UI: drop link in node to link to next compatible available input</li>
-      <li>Collaboration: Better concurrent editing/message passing (testing)</li>
-      <li>FlowPkg: Create training mechanism</li>
-      <li>FlowPkg: matrix pooling function example</li>
-    </ul>
-    <br>
-    <small>&copy; Luis Figueiredo (luisf@hexasoftware.com)</small>
-    -->
-  </div>
+  <div
+    class="app-info markdown-body"
+    v-html="content"/>
 </template>
 <script>
 import md from '@/assets/doc/appinfo.md'
@@ -90,27 +31,3 @@ export default {
   color: var(--primary);
   text-decoration: none;
 }
-
-/*
-.app-info--info {
-  font-size:14px;
-}
-
-.app-info__section h4 {
-  margin-top:20px;
-  margin-bottom:5px;
-  color: var(--primary);
-}
-
-.app-info__section p {
-  font-size:14px;
-  margin:5px 15px 15px 4px;
-  padding:20px 10px;
-  background: var(--background-secondary);
-}
-
-.app-info__section li {
-  padding:4px;
-}*/
-
-</style>

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

@@ -12,11 +12,30 @@
       rx="12"
     />
 
-    <icon-refresh v-if="nodeActivity.status=='running'" v-bind="iconProps" class="flow-node__activity-icon" />
-    <icon-wait v-else-if="nodeActivity.status=='waiting'" v-bind="iconProps" class="flow-node__activity-icon"/>
-    <icon-ok v-else-if="nodeActivity.status=='finish'" v-bind="iconProps"class="flow-node__activity-icon"/>
-    <icon-fail v-else-if="nodeActivity.status=='error'" v-bind="iconProps"class="flow-node__activity-icon"/>
-    <icon-question v-else v-bind="iconProps" class="flow-node__activity-icon" />
+    <icon-refresh
+      v-if="nodeActivity.status=='running'"
+      v-bind="iconProps"
+      class="flow-node__activity-icon flow-node__activity--running" />
+    <icon-wait
+      v-else-if="nodeActivity.status=='waiting'"
+      v-bind="iconProps"
+      class="flow-node__activity-icon flow-node__activity--waiting"/>
+    <icon-data
+      v-else-if="nodeActivity.status=='finish' && nodeActivity.data"
+      v-bind="iconProps"
+      class="flow-node__activity-icon flow-node__activity--finish-data"/>
+    <icon-ok
+      v-else-if="nodeActivity.status=='finish'"
+      v-bind="iconProps"
+      class="flow-node__activity-icon flow-node__activity--finish"/>
+    <icon-fail
+      v-else-if="nodeActivity.status=='error'"
+      v-bind="iconProps"
+      class="flow-node__activity-icon flow-node__activity-error"/>
+    <icon-question
+      v-else
+      v-bind="iconProps"
+      class="flow-node__activity-icon flow-node__activity-unknown" />
 
     <text :class="{active:ellapsed}" class="flow-node__activity-time" x="13" y="4" fill="black">
       {{ ellapsed }}
@@ -27,6 +46,7 @@
 import {mapGetters} from 'vuex'
 import IconWait from '@/assets/icons/wait.svg'
 import IconFail from '@/assets/icons/fail.svg'
+import IconData from '@/assets/icons/data.svg'
 import IconOk from '@/assets/icons/ok.svg'
 import IconQuestion from '@/assets/icons/question.svg'
 import IconRefresh from '@/assets/icons/refresh.svg'
@@ -34,7 +54,7 @@ import utils from '@/utils/utils'
 
 export default {
   name: 'FlowNodeStatus',
-  components: {IconWait, IconFail, IconOk, IconQuestion, IconRefresh},
+  components: {IconWait, IconFail, IconData, IconOk, IconQuestion, IconRefresh},
   props: {
     nodeId: {type: String, default: ''}
   },
@@ -160,24 +180,29 @@ export default {
   stroke: inherits;
 }
 
-.flow-node__activity[status=running] .flow-node__activity-icon  >* {
+.flow-node__activity--running >* {
   -webkit-animation: spin 1s infinite linear;
   -moz-animation: spin 1s infinite linear;
   animation: spin 1s infinite linear;
   stroke: #aa0;
 }
 
-.flow-node__activity[status=error] .flow-node__activity-icon  > * {
+.flow-node__activity--error > * {
   stroke: #f22;
 }
 
-.flow-node__activity[status=waiting] .flow-node__activity-icon  >* {
+.flow-node__activity--waiting > * {
   -webkit-animation: shake 1s infinite linear;
   -moz-animation: shake 1s infinite linear;
   animation: shake 1s infinite linear;
 }
 
-.flow-node__activity[status=finish] .flow-node__activity-icon  >* {
+.flow-node__activity--finish-data > * {
+  stroke: #5c5;
+  transform: scale(0.9) translateY(-3px);
+}
+
+.flow-node__activity--finish > * {
   stroke: #2c2;
 }
 

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

@@ -502,11 +502,11 @@ for hidden
   stroke-dasharray:2,2;
   pointer-events:none;
   transition: all var(--transition-speed);
-  fill: var(--primary-darker);
+  fill: var(--node-selection);
 }
 
 .flow-node--selected .flow-node__selection {
-  opacity:0.7;
+  opacity:0.6;
   animation: flow-node--selected__dash 3s linear infinite;
 }
 

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

@@ -36,6 +36,27 @@
           <div class="property">Connect to input a thing and goes to output another thing</div>
           -->
         </div>
+        <div
+          v-if="nodeActivity && (nodeActivity.error || nodeActivity.data)"
+          class="flow-inspector__area flow-inspector--activity">
+          <div
+            v-if="nodeActivity.data"
+            class="flow-inspector--properties-result">
+            <label>Result</label>
+            <div class="property">
+              {{ result.substr(0,100) }}
+              {{ result.length > 100 ? '...':'' }}
+            </div>
+            <button @click="$emit('dataClick')">Visualize</button>
+          </div>
+          <div
+            v-if="nodeActivity.error"
+            class="flow-inspector--properties-error">
+            <label>Error</label>
+            <div class="property">{{ nodeActivity && nodeActivity.error }}</div>
+          </div>
+        </div>
+
         <!-- STATIC PARAM -->
         <div class="flow-inspector__area  flow-inspector--static">
           <label>label</label>
@@ -66,20 +87,6 @@
           </div>
         </div>
 
-        <div class="flow-inspector__area flow-inspector--activity ">
-          <div
-            v-if="nodeActivity && nodeActivity.data"
-            class="flow-inspector--properties-result">
-            <label>Result</label>
-            <div class="property">{{ nodeActivity && nodeActivity.data }}</div>
-          </div>
-          <div
-            v-if="nodeActivity && nodeActivity.error"
-            class="flow-inspector--properties-error">
-            <label>Error</label>
-            <div class="property">{{ nodeActivity && nodeActivity.error }}</div>
-          </div>
-        </div>
       </div><!-- /container -->
 
       <div class="flow-inspector__area flow-inspector--control">
@@ -89,7 +96,9 @@
       </div>
     </template>
     <template v-else>
-      Select a node
+      <div class="flow-inspector__container">
+        Select a node
+      </div>
     </template>
   </div>
 </template>
@@ -97,11 +106,10 @@
 import {mapGetters, mapActions} from 'vuex'
 import FlowPanzoom from '@/components/flow/panzoom'
 import FlowNode from '@/components/flow/node'
-import HxCollapsible from '@/components/shared/hx-collapsible'
 
 export default {
   name: 'FlowInspector',
-  components: {FlowPanzoom, FlowNode, HxCollapsible},
+  components: {FlowPanzoom, FlowNode},
   data () {
     return {
       nodeInspect: null
@@ -111,6 +119,9 @@ export default {
     ...mapGetters('flow', ['registry', 'activity', 'nodeData']),
     nodeActivity () {
       return this.activity && this.activity.nodes && this.activity.nodes[this.nodeInspect.id]
+    },
+    result () {
+      return this.nodeActivity && this.nodeActivity.data.toString()
     }
   },
   watch: {
@@ -188,7 +199,7 @@ export default {
 .flow-inspector__area{
   flex:0;
   padding: 10px 0;
-  border-bottom: solid 1px rgba(150,150,150,0.3);
+  border-bottom: solid 1px var(--border-color-lighter);
 }
 
 .flow-inspector__area h2{
@@ -225,13 +236,24 @@ export default {
 }
 
 .flow-inspector--activity {
-  flex:1;
 }
 
 .flow-inspector--properties-error {
   color: red;
 }
 
+.flow-inspector--properties-result {
+  display:flex;
+  flex-flow:column;
+}
+
+.flow-inspector--properties-result button {
+  margin-top:10px;
+  background: rgba(140,140,140,0.2);
+  background: var(--secondary);
+  color: var(--secondary-inverse);
+}
+
 .flow-inspector--parameters {
   padding:20px;
 }

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

@@ -66,7 +66,9 @@ export default {
   margin-top: 0;
 }
 
-.hx-modal__header h3 {
+.hx-modal__header h3,
+.hx-modal__header h4
+{
   margin:0;
 }
 
@@ -89,6 +91,8 @@ export default {
   font-weight:bold;
   font-size:1em;
   outline:none;
+  padding:6px 8px;
+  margin-bottom:10px;
 }
 
 /*

+ 10 - 3
browser/vue-flow/src/store/flow/actions.js

@@ -19,9 +19,9 @@ export default {
     flowService.nodeUpdate(nodes)
   },
 
-  [m.NODE_ADD] (ctx, node) {
+  [m.NODE_ADD] (ctx, nodes) {
     // WEBSOCKET
-    ctx.commit(m.NODE_ADD, node)
+    ctx.commit(m.NODE_ADD, nodes)
     ctx.dispatch(m.DOCUMENT_SYNC)
   },
   [m.NODE_REMOVE] (ctx, nodes) {
@@ -35,10 +35,17 @@ export default {
   [m.NODE_PROCESS] (ctx, nodeId) {
     flowService.nodeProcess(nodeId)
   },
-  // XXX: Experimental
+  [m.NODE_SELECTION_CLEAR] (ctx) {
+    ctx.commit(m.NODE_SELECTION_CLEAR)
+  },
+  [m.NODE_SELECTION_ADD] (ctx, nodes) {
+    ctx.commit(m.NODE_SELECTION_ADD, nodes)
+  },
+  // XXX: Experimental, temporary
   [m.NODE_TRAIN] (ctx, nodeId) {
     flowService.nodeTrain(nodeId)
   },
+
   [m.LINK_ADD] (ctx, link) {
     ctx.commit(m.LINK_ADD, link)
     ctx.dispatch(m.DOCUMENT_SYNC)

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

@@ -4,5 +4,12 @@ export default {
     return state.nodeData.nodes.find(n => n.id === id)
   },
   registry: state => state.registry,
-  activity: state => state.activity
+  activity: state => state.activity,
+
+  /* nodeSelection: state => {
+    return Object.keys(state.nodeSelection).map(k => state.nodeSelection[k])
+  }, */
+  nodeSelection: state => state.nodeSelection,
+  nodeInspect: state => state.nodeInspect
+
 }

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

@@ -4,6 +4,7 @@ var actions = [
   'DOCUMENT_UPDATE', 'DOCUMENT_SYNC',
   'ACTIVITY_UPDATE',
   'NODE_RAISE', 'NODE_UPDATE', 'NODE_ADD', 'NODE_REMOVE', 'NODE_INSPECT', 'NODE_PROCESS', 'NODE_TRAIN',
+  'NODE_SELECTION_CLEAR', 'NODE_SELECTION_ADD',
   'LINK_ADD', 'LINK_REMOVE',
   'TRIGGER_ADD', 'TRIGGER_REMOVE',
   'NOTIFICATION_ADD', 'NOTIFICATION_CLEAR'

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

@@ -17,7 +17,8 @@ export default {
   },
   [m.NODE_RAISE] (state, nodes) {
     for (let k in nodes) {
-      let ni = state.nodeData.nodes.findIndex(n => n.id === nodes[k].id)
+      const sn = nodes[k]
+      let ni = state.nodeData.nodes.findIndex(n => n.id === sn.id)
       const node = state.nodeData.nodes[ni]
       state.nodeData.nodes.splice(ni, 1)
       state.nodeData.nodes.push(node) // put in last
@@ -30,12 +31,13 @@ export default {
       const ni = state.nodeData.nodes.findIndex(n => n.id === node.id)
       Vue.set(state.nodeData.nodes, ni, node)
       if (node.id === state.nodeInspect.id) {
+        // Update node inspect
         state.nodeInspect = node
       }
     }
   },
-  [m.NODE_ADD] (state, node) {
-    state.nodeData.nodes.push(node)
+  [m.NODE_ADD] (state, nodes) {
+    state.nodeData.nodes.push(...nodes)
   },
   [m.NODE_REMOVE] (state, nodes) {
     for (let k in nodes) {
@@ -52,6 +54,15 @@ export default {
     const node = state.nodeData.nodes.find(n => n.id === nodeId)
     state.nodeInspect = node
   },
+  [m.NODE_SELECTION_CLEAR] (state) {
+    state.nodeSelection = {}
+  },
+  [m.NODE_SELECTION_ADD] (state, nodes) {
+    for (let k in nodes) {
+      const n = nodes[k]
+      Vue.set(state.nodeSelection, n.id, n)
+    }
+  },
   [m.LINK_ADD] (state, link) {
     state.nodeData.links.push(link)
   },

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

@@ -7,6 +7,7 @@ export default {
     triggers: []
   },
   nodeInspect: {},
+  nodeSelection: [],
 
   registry: {},
   activity: {nodes: []},

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

@@ -28,13 +28,30 @@ module.exports = {
     }
   },
   createDrag (obj) {
+    var x, y
+    // Drag with threshold
+    // we swap the drag function to the obj.drag if threshold achieved
+    // if no drag function we set a dummy function
+    let inDrop = (obj && obj.noDrag) || function () {}
+    let inDrag = (ev) => {
+      if (!x || !y) {
+        x = ev.x
+        y = ev.y
+      }
+      if (Math.abs(x - ev.x) > 10 ||
+        Math.abs(y - ev.y) > 10) {
+        inDrag = (obj && obj.drag) || function () {}
+        inDrop = (obj && obj.drop) || function () {}
+      }
+    }
+
     const drag = (ev) => {
-      obj && obj.drag && obj.drag(ev)
+      inDrag(ev)
     }
     const drop = (ev) => {
       document.removeEventListener('mousemove', drag)
       document.removeEventListener('mouseup', drop)
-      obj && obj.drop && obj.drop(ev)
+      inDrop(ev)
     }
     document.addEventListener('mousemove', drag)
     document.addEventListener('mouseup', drop)

+ 17 - 4
go/src/demos/cmd/xor/main.go

@@ -66,13 +66,22 @@ func main() {
 	x := f.In(0)
 	y := f.In(1)
 
+	// [ 1, 2, 3, 4, 5]
+	// [ 1, 2, 3, 4, 5]
 	wHidden := f.Var("wHidden", f.Op("matNewRand", nInputs, nHidden))
+
+	// [ 1 ]
+	// [ 2 ]
+	// [ 3 ]
+	// [ 4 ]
+	// [ 5 ]
 	wOut := f.Var("wOut", f.Op("matNewRand", nHidden, nOutput))
 
 	// Forward process
 	hiddenLayerInput := f.Op("matMul", x, wHidden)
 	hiddenLayerActivations := f.Op("matSigmoid", hiddenLayerInput)
 	outputLayerInput := f.Op("matMul", hiddenLayerActivations, wOut)
+	// Activations
 	output := f.Op("matSigmoid", outputLayerInput)
 
 	// Back propagation
@@ -80,15 +89,19 @@ func main() {
 	networkError := f.Op("matSub", y, output)
 	slopeOutputLayer := f.Op("matSigmoidPrime", output)
 	dOutput := f.Op("matMulElem", networkError, slopeOutputLayer)
-	wOutAdj := f.Op("matMul", f.Op("matTranspose", hiddenLayerActivations), dOutput)
-	wOutAdj = f.Op("matScale", learningRate, wOutAdj)
+	wOutAdj := f.Op("matScale",
+		learningRate,
+		f.Op("matMul", f.Op("matTranspose", hiddenLayerActivations), dOutput),
+	)
 
 	// hidden weights
 	errorAtHiddenLayer := f.Op("matMul", dOutput, f.Op("matTranspose", wOut))
 	slopeHiddenLayer := f.Op("matSigmoidPrime", hiddenLayerActivations)
 	dHiddenLayer := f.Op("matMulElem", errorAtHiddenLayer, slopeHiddenLayer)
-	wHiddenAdj := f.Op("matMul", f.Op("matTranspose", x), dHiddenLayer)
-	wHiddenAdj = f.Op("matScale", learningRate, wHiddenAdj)
+	wHiddenAdj := f.Op("matScale",
+		learningRate,
+		f.Op("matMul", f.Op("matTranspose", x), dHiddenLayer),
+	)
 
 	// Adjust the parameters
 	setwOut := f.SetVar("wOut", f.Op("matAdd", wOut, wOutAdj))