Prechádzať zdrojové kódy

Added icons when processing nodes

* Drag on ctrl click
* Made flow parallel
luis 7 rokov pred
rodič
commit
5b520aacf2

+ 12 - 5
browser/vue-flow/src/assets/default-theme.css

@@ -134,8 +134,8 @@ input {
 }
 
 .flow-view:not(.activity)
-.flow-link:not(.flow-link--pointer):hover
-.flow-link__visible {
+  .flow-link:not(.flow-link--pointer):hover
+  .flow-link__visible {
   stroke: var(--link-hover);
 
   /*filter: url(#highlight-border);*/
@@ -189,13 +189,20 @@ input {
   font-size: 12px;
   font-weight: 100;
   transition: all 0.3s;
-}
-
-.flow-node__socket-detail {
   fill: var(--normal);
   filter: url(#solid-white);
 }
 
+/*
+ * NODE ACTIVITY
+ */
+.flow-node__activity {
+  fill: var(--background-secondary);
+}
+.flow-node__activity-icon > * {
+  stroke: var(--normal);
+}
+
 /*
  * CHAT
  */

+ 11 - 0
browser/vue-flow/src/assets/icons/question.svg

@@ -0,0 +1,11 @@
+<?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="M53.92,10.081c12.107,12.105,12.107,31.732,0,43.838
+	c-12.106,12.108-31.734,12.108-43.84,0c-12.107-12.105-12.107-31.732,0-43.838C22.186-2.027,41.813-2.027,53.92,10.081z"/>
+<line stroke="#000000" stroke-width="2" stroke-miterlimit="10" x1="30" y1="43" x2="30" y2="47"/>
+<path fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" d="M24,21c0-3,2-5,8-5c5,0,8,3,8,7s-6,7-6,7s-4,2-4,8v1
+	"/>
+</svg>

+ 5 - 2
browser/vue-flow/src/components/chat.vue

@@ -19,7 +19,8 @@
 
       <div class="flow-chat__users">
         <div class="flow-chat__user" v-for="u in userList">
-          <img src="../assets/icons/user.svg"><span>{{ u }}</span>
+          <!--<img class="flow-chat__user-icon" src="../assets/icons/user.svg">-->
+          <icon-user class="flow-chat__user-icon"/> <span>{{ u }}</span>
         </div>
       </div>
 
@@ -29,6 +30,7 @@
 </template>
 
 <script>
+import IconUser from '@/assets/icons/user.svg'
 
 // init
 let storedHandle = localStorage.getItem('handle')
@@ -38,6 +40,7 @@ if (!storedHandle || storedHandle === '') {
 
 // Load handle from storage
 export default {
+  components: {IconUser},
   filters: {
     time (value) {
       const d = new Date(value)
@@ -179,7 +182,7 @@ function pad (n, width, z) {
   align-items: center;
 
 }
-.flow-chat__user img {
+.flow-chat__user-icon{
   height:10px;
   width:auto;
 }

+ 2 - 3
browser/vue-flow/src/components/flow/manager.vue

@@ -59,7 +59,6 @@
       <button @click="stickySockets=!stickySockets"> {{ stickySockets? 'Hide':'Show' }} sockets </button>
       <button @click="detailed=!detailed"> {{ detailed? 'Hide':'Show' }} detail </button>
       <div class="vertical_sep"/>
-      <!--<button @click="sendDocumentRun">Run</button>-->
       <button v-if="panzoom.x!=0 || panzoom.y!=0 || panzoom.zoom!=1" @click="panzoomReset">
         Reset view
       </button>
@@ -437,10 +436,10 @@ export default {
     sendDocumentUpdate (nodeData) {
       this.$flowService.documentUpdate(this.nodeData, this.$route.params.sessId)
     },
-    sendDocumentRun () {
+    /* sendDocumentRun () {
       console.log('Document running')
       this.$flowService.documentRun(this.nodeData, this.$route.params.sessId)
-    },
+    }, */
     // HELPERS depending on svg ref
     createSVGPoint (x, y) {
       const p = this.$refs.svg.createSVGPoint()

+ 13 - 1
browser/vue-flow/src/components/flow/node.vue

@@ -89,6 +89,7 @@
     />
 
     <!-- socket labels -->
+
     <text
       :key="'inp' +i"
       class="flow-node__socket-detail"
@@ -106,6 +107,7 @@
 
     <!-- positioning -->
     <flow-node-status
+      v-if="status!=''"
       :status="status"
       :transform="'translate('+bodyProps.width/2 +','+ -bodyProps.height/2 +')'"/>
   </g>
@@ -150,7 +152,7 @@ export default {
       return this.nodeStyle || {}
     },
     status () {
-      return this.activity && this.activity.status
+      return this.activity && this.activity.status || ''
     },
     labelWrap () {
       let wrapThreshold = 8 // initial wrap threshold
@@ -262,6 +264,16 @@ export default {
 .flow-node__body {
   transition: all 0.3s;
 }
+.flow-node[status=running] .flow-node__body{
+  stroke: yellow !important;
+}
+
+.flow-node[status=error] .flow-node__body{
+  stroke: red !important;
+}
+.flow-node[status=finished] .flow-node__body{
+  stroke: green !important;
+}
 
 .flow-view:not(.activity) .flow-node__socket:hover {
   stroke-width:10;

+ 81 - 29
browser/vue-flow/src/components/flow/nodestatus.vue

@@ -1,56 +1,108 @@
 <template>
   <g class="flow-node__activity"
-     v-if="status!=''">
+     :status="status"
+  >
     <circle
       class="flow-node__activity-background"
       rx="0"
       ry="0"
       r="14"/>
+    >
 
-    <g
-      v-if="status=='running'"
-      transform="rotate(0 0 0)">
-      <animateTransform
-        attributeType="xml"
-        attributeName="transform"
-        type="rotate"
-        from="0"
-        to="360"
-        dur="1s"
-        repeatCount="indefinite" />
-      <object xlink:href="../../assets/icons/refresh.svg"/>
-      <image
-        x="-10"
-        y="-10"
-        width="20"
-        height="20"
-        xlink:href="../../assets/icons/refresh.svg"/>
-    </g>
-    <g v-if="status=='waiting'"/>
+    <icon-refresh v-if="status=='running'" v-bind="iconProps" class="flow-node__activity-icon" />
+    <icon-wait v-else-if="status=='waiting'" v-bind="iconProps" class="flow-node__activity-icon"/>
+    <icon-ok v-else-if="status=='finish'" v-bind="iconProps"class="flow-node__activity-icon"/>
+    <icon-question v-else v-bind="iconProps" class="flow-node__activity-icon" />
 
   </g>
 </template>
 <script>
+import IconWait from '@/assets/icons/wait.svg'
+import IconOk from '@/assets/icons/ok.svg'
+import IconQuestion from '@/assets/icons/question.svg'
+import IconRefresh from '@/assets/icons/refresh.svg'
+
 export default {
   name: 'FlowNodeStatus',
-  props: { status: {type: String, default: ''} }
+  components: {IconWait, IconOk, IconQuestion, IconRefresh},
+  props: { status: {type: String, default: ''} },
+  computed: {
+    iconProps () {
+      return {
+        x: -10,
+        y: -10,
+        viewBox: '-4 -4 72 72',
+        width: 20,
+        height: 20
+      }
+    }
+  }
 }
 </script>
 
 <style>
+.flow-node__activity {
+  opacity:0.8;
+}
+
 .flow-node__activity-background {
-  fill: rgba(255,255,255,0.8);
+  fill: inherits;
+  transition: all .3s;
+}
+.flow-node__activity-icon{
+  width:20px;
+  height:20px;
+}
+.flow-node__activity-icon  >* {
+  transform-origin: 50% 50%;
+  stroke-width: 8px;
+  stroke: inherits;
 }
 
-.flow-node[status=running] .flow-node__body{
-  stroke: yellow !important;
+.flow-node__activity[status=running] .flow-node__activity-icon  >* {
+   -webkit-animation: spin 1s infinite linear;
+   -moz-animation: spin 1s infinite linear;
+   animation: spin 1s infinite linear;
+  stroke: #00C;
+}
+.flow-node__activity[status=waiting] .flow-node__activity-icon  >* {
+   -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  >* {
+  stroke: #2C2;
 }
 
-.flow-node[status=error] .flow-node__body{
-  stroke: red !important;
+/*** ANIMATIONS ***/
+@-moz-keyframes spin {
+    from { -moz-transform: rotate(0deg); }
+    to { -moz-transform: rotate(-360deg); }
+}
+@-webkit-keyframes spin {
+    from { -webkit-transform: rotate(0deg); }
+    to { -webkit-transform: rotate(-360deg); }
 }
-.flow-node[status=finished] .flow-node__body{
-  stroke: green !important;
+@keyframes spin {
+    from {transform:rotate(0deg);}
+    to {transform:rotate(-360deg);}
+  }
+
+@keyframes shake {
+  10%, 90% {
+    transform: rotate(-2deg);
+  }
+
+  20%, 80% {
+    transform: rotate(4deg);
+  }
+
+  30%, 50%, 70% {
+    transform: rotate(-7deg);
+  }
+  40%, 60% {
+    transform: rotate(7deg);
+  }
 }
 
 </style>

+ 12 - 12
browser/vue-flow/src/components/flow/panzoom.vue

@@ -21,6 +21,8 @@
 
 </template>
 <script>
+import utils from '@/utils/utils'
+
 export default {
   name: 'FlowPanZoom',
   props: {
@@ -68,21 +70,19 @@ export default {
     // panStart
     dragStart (ev) {
       document.activeElement && document.activeElement.blur()
-      if (ev.button !== 1) return // first button
+      if (!(ev.button === 1 || (ev.button === 0 && ev.ctrlKey))) return // first button
       if (ev.target !== this.$refs.transformer) return
       ev.stopPropagation()
 
-      const drag = (ev) => {
-        this.moving = true
-        this.update(this.x + ev.movementX, this.y + ev.movementY)
-      }
-      const drop = (ev) => {
-        this.moving = false
-        document.removeEventListener('mousemove', drag)
-        document.removeEventListener('mouseup', drop)
-      }
-      document.addEventListener('mousemove', drag)
-      document.addEventListener('mouseup', drop)
+      utils.createDrag({
+        drag: (ev) => {
+          this.moving = true
+          this.update(this.x + ev.movementX, this.y + ev.movementY)
+        },
+        drop: (ev) => {
+          this.moving = false
+        }
+      })
     },
     wheel (ev) {
       ev.preventDefault()

+ 4 - 2
browser/vue-flow/src/components/main.vue

@@ -10,10 +10,11 @@
         <div class="app-info">
           <h4>HELP</h4>
           <ul>
-            <li><b>Pan</b>: Drag with Middle Mouse button</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>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>
@@ -112,7 +113,7 @@
           >
         </div>
         <div slot="footer">
-          <button class="primary-invert" @click="nodeProcess(nodeInspect)">Run</button>
+          <button class="primary-invert" @click="nodeProcess(nodeInspect);nodeInspect=null">Run</button>
           <button @click="nodeInspect=false">OK</button>
         </div>
       </hx-modal>
@@ -223,6 +224,7 @@ export default {
       this.registry = Object.assign({}, defRegistry, v.data)
     })
     this.$flowService.on('nodeActivity', (v) => {
+      console.log('Received activity')
       this.activity = v.data || {}
     })
 

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

@@ -157,8 +157,8 @@ export default {
 
 .flow-funcs__group.blocks .flow-funcs__src {
   display:block;
-  width: calc(25% - 2px);
-  padding:22px 4px;
+  width: calc(50% - 2px);
+  padding:18px 4px;
   text-overflow: ellipsis;
   margin:1px;
   word-wrap: break-all;

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

@@ -53,17 +53,33 @@ module.exports = {
               'sass-loader?indentedSyntax'
             ]
 
+          },
+          postLoaders: {
+            html: 'babel-loader'
           }
           // other vue-loader options go here
         }
       },
+      {
+        test: /\.svg$/,
+        loader: 'vue-svg-loader', // `vue-svg` for webpack 1.x
+        options: {
+          // optional [svgo](https://github.com/svg/svgo) options
+          svgo: {
+            plugins: [
+              {removeDoctype: true},
+              {removeComments: true}
+            ]
+          }
+        }
+      },
       {
         test: /\.(js|jsx)$/,
         loader: 'babel-loader',
         exclude: /node_modules/
       },
       {
-        test: /\.(pg|jpg|gif|svg)$/,
+        test: /\.(pg|jpg|gif)$/,
         loader: 'file-loader',
         options: {
           name: '[name]-[hash].[ext]'

+ 18 - 18
go/src/flow/operation.go

@@ -9,6 +9,7 @@ import (
 	"errors"
 	"fmt"
 	"reflect"
+	"sync"
 )
 
 // OpCtx operation Context
@@ -110,33 +111,32 @@ func opFunc(f *Flow, id string) *operation {
 		kind: "func",
 		set:  dumbSet,
 		process: func(ctx OpCtx, params ...Data) Data {
-			f.trigger("nodeWait", map[string]Data{
-				"id": id,
-			})
+			f.trigger("nodeWait", map[string]Data{"id": id})
 			op, ok := f.operations[id]
 			if !ok {
 				f.err = fmt.Errorf("invalid operation '%s'", id)
+				f.trigger("nodeError", map[string]Data{"id": id})
 				return nil
 			}
-
+			/////////////////////////////
+			// NEW PARALLEL PROCESSING
+			///////////
 			callParam := make([]reflect.Value, len(op.inputs))
+
+			wg := sync.WaitGroup{}
+			wg.Add(len(op.inputs))
 			for i, in := range op.inputs {
-				fr := in.processWithCtx(ctx, params...)
-				if fr == nil {
-					f.err = errors.New("returning nil")
-					return nil
-				}
-				callParam[i] = reflect.ValueOf(fr)
+				go func(i int, in *operation) {
+					defer wg.Done()
+					fr := in.processWithCtx(ctx, params...)
+					callParam[i] = reflect.ValueOf(fr)
+				}(i, in)
 			}
-			f.trigger("nodeStart", map[string]Data{
-				"id": id,
-			})
-			ret := reflect.ValueOf(op.executor).Call(callParam)[0].Interface()
-			f.trigger("nodeFinish", map[string]Data{
-				"id":     id,
-				"result": ret,
-			})
+			wg.Wait()
 
+			f.trigger("nodeStart", map[string]Data{"id": id})
+			ret := reflect.ValueOf(op.executor).Call(callParam)[0].Interface()
+			f.trigger("nodeFinish", map[string]Data{"id": id, "result": ret})
 			return ret
 		},
 	}

+ 39 - 36
go/src/flowserver/session.go

@@ -122,47 +122,49 @@ func (s *FlowSession) NodeRun(c *websocket.Conn, data []byte) error {
 	if s.flow != nil {
 		return errors.New("node already running")
 	}
-
-	log.Printf("Building flow from '%s'\n", string(s.RawDoc))
-	s.flow, err = FlowBuild(s.RawDoc)
-	if err != nil {
-		return err
-	}
-
 	log.Println("Flow--\n", s.flow)
-	defer func() {
-		s.flow = nil
-	}()
 
-	log.Println("Attaching hooks")
-	//XXX: Transform this function to a typed hook structure
-	s.flow.Handle(func(name string, payload map[string]flow.Data) {
-		switch name {
-		case "nodeWait":
-			s.nodeActivity[payload["id"].(string)] = NodeActivity{Status: "waiting"}
-		case "nodeStart":
-			s.nodeActivity[payload["id"].(string)] = NodeActivity{Status: "running"}
-		case "nodeFinish":
-			s.nodeActivity[payload["id"].(string)] = NodeActivity{Status: "finish", Result: payload["result"]}
+	// Clear activity
+	s.nodeActivity = map[string]NodeActivity{}
+	s.Broadcast(nil, flowmsg.SendMessage{OP: "nodeActivity", Data: s.nodeActivity})
 
+	go func() {
+		log.Printf("Building flow from '%s'\n", string(s.RawDoc))
+		s.flow, err = FlowBuild(s.RawDoc)
+		if err != nil {
+			log.Println("Flow error:", err)
+		}
+		defer func() { // After routing gone
+			s.flow = nil
+		}()
+		log.Println("Attaching hooks")
+		//XXX: Transform this function to a typed hook structure
+		s.flow.Handle(func(name string, payload map[string]flow.Data) {
+			switch name {
+			case "nodeWait":
+				s.nodeActivity[payload["id"].(string)] = NodeActivity{Status: "waiting"}
+			case "nodeStart":
+				s.nodeActivity[payload["id"].(string)] = NodeActivity{Status: "running"}
+			case "nodeFinish":
+				s.nodeActivity[payload["id"].(string)] = NodeActivity{Status: "finish", Result: payload["result"]}
+			}
+			// Add to executing nodes
+			log.Println("Inform every one about", name)
+			s.Broadcast(nil, flowmsg.SendMessage{OP: "nodeActivity", Data: s.nodeActivity})
+		})
+
+		log.Println("Execute node:", ID)
+		op := s.flow.Res(ID)
+		if s.flow.Err() != nil {
+			log.Println("error fetching document", s.flow.Err())
 		}
-		// Add to executing nodes
-		log.Println("Exec doc", name)
-		s.Broadcast(nil, flowmsg.SendMessage{OP: "nodeActivity", Data: s.nodeActivity})
-	})
-
-	log.Println("Fetch result")
-	op := s.flow.Res(ID)
-	if s.flow.Err() != nil {
-		return err
-	}
-
-	res := op.Process()
-	if s.flow.Err() != nil {
-		return err
-	}
 
-	log.Println("Node Result:", res)
+		res := op.Process()
+		if s.flow.Err() != nil {
+			log.Println("error processing node", s.flow.Err())
+		}
+		log.Println("Node Result:", res)
+	}()
 
 	return nil
 }
@@ -171,6 +173,7 @@ func (s *FlowSession) NodeRun(c *websocket.Conn, data []byte) error {
 func (s *FlowSession) Broadcast(c *websocket.Conn, v interface{}) error {
 	s.Lock()
 	defer s.Unlock()
+
 	return s.broadcast(c, v)
 }