|  | @@ -0,0 +1,542 @@
 | 
	
		
			
				|  |  | +import {mapGetters, mapActions} from 'vuex'
 | 
	
		
			
				|  |  | +import FlowNode from './node'
 | 
	
		
			
				|  |  | +import FlowLink from './link'
 | 
	
		
			
				|  |  | +import FlowTriggerLink from './link-trigger'
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import FlowPanZoom from './panzoom'
 | 
	
		
			
				|  |  | +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},
 | 
	
		
			
				|  |  | +  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,
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      pointerLink: {active: false, props: {}, src: {}},
 | 
	
		
			
				|  |  | +      pointerTriggerLink: {active: false, props: {}, src: {}},
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      selector: null,
 | 
	
		
			
				|  |  | +      nodeSelection: {}, // map of true false
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      stickySockets: false,
 | 
	
		
			
				|  |  | +      stickyTriggers: false,
 | 
	
		
			
				|  |  | +      nodeActivity: true
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  computed: {
 | 
	
		
			
				|  |  | +    ...mapGetters(['registry', 'activity', 'nodeData', 'nodeMap']),
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    nodeProps () {
 | 
	
		
			
				|  |  | +      return (node) => {
 | 
	
		
			
				|  |  | +        let highlight = {}
 | 
	
		
			
				|  |  | +        if (this.pointerLink.active && this.pointerLink.src.nodeId !== node.id) {
 | 
	
		
			
				|  |  | +          if (this.pointerLink.src.in !== undefined) {
 | 
	
		
			
				|  |  | +            highlight = {type: 'socket-out', dtype: this.pointerLink.src.type}
 | 
	
		
			
				|  |  | +          } else {
 | 
	
		
			
				|  |  | +            highlight = {type: 'socket-in', dtype: this.pointerLink.src.type}
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        if (this.pointerTriggerLink.active && this.pointerTriggerLink.src.nodeId !== node.id) {
 | 
	
		
			
				|  |  | +          highlight = {type: (this.pointerTriggerLink.src.dir === 'in') ? 'trigger-in' : 'trigger-out'}
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        const nodeClass = this.registry[node.src]
 | 
	
		
			
				|  |  | +        if (!nodeClass) {
 | 
	
		
			
				|  |  | +          this.NODE_REMOVE([node])
 | 
	
		
			
				|  |  | +          return
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        return {
 | 
	
		
			
				|  |  | +          transform: `translate(${node.x} ${node.y})`,
 | 
	
		
			
				|  |  | +          id: node.id,
 | 
	
		
			
				|  |  | +          label: node.label,
 | 
	
		
			
				|  |  | +          inputs: nodeClass.inputs,
 | 
	
		
			
				|  |  | +          output: nodeClass.output,
 | 
	
		
			
				|  |  | +          // Combine this into one
 | 
	
		
			
				|  |  | +          match: highlight,
 | 
	
		
			
				|  |  | +          dragging: this.dragging && !!this.dragging[node.id],
 | 
	
		
			
				|  |  | +          activity: this.activity[node.id],
 | 
	
		
			
				|  |  | +          nodeStyle: nodeClass.style
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    linkProps () {
 | 
	
		
			
				|  |  | +      return (link) => {
 | 
	
		
			
				|  |  | +        if (!this.$refs.nodes) return
 | 
	
		
			
				|  |  | +        // For size .x .y
 | 
	
		
			
				|  |  | +        const nodeFrom = this.nodeMap(link.from)
 | 
	
		
			
				|  |  | +        const nodeTo = this.nodeMap(link.to)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        const refFrom = this.$refs.nodes.find(n => n.id === link.from)
 | 
	
		
			
				|  |  | +        const refTo = this.$refs.nodes.find(n => n.id === link.to)
 | 
	
		
			
				|  |  | +        if (!refFrom || !refTo) { // delete link
 | 
	
		
			
				|  |  | +          return {}
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        const fromOutput = refFrom.outputPos(0) // only 1 output
 | 
	
		
			
				|  |  | +        const toInput = refTo.inputPos(link.in)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return {
 | 
	
		
			
				|  |  | +          x1: nodeFrom.x + fromOutput.x,
 | 
	
		
			
				|  |  | +          y1: nodeFrom.y + fromOutput.y,
 | 
	
		
			
				|  |  | +          x2: nodeTo.x + toInput.x,
 | 
	
		
			
				|  |  | +          y2: nodeTo.y + toInput.y,
 | 
	
		
			
				|  |  | +          status: this.activity[nodeFrom.id] && this.activity[nodeFrom.id].status
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    triggerProps () {
 | 
	
		
			
				|  |  | +      return (trigger) => {
 | 
	
		
			
				|  |  | +        if (!this.$refs.nodes) return
 | 
	
		
			
				|  |  | +        // For size .x .y
 | 
	
		
			
				|  |  | +        const nodeFrom = this.nodeMap(trigger.from)
 | 
	
		
			
				|  |  | +        const nodeTo = this.nodeMap(trigger.to)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        const refFrom = this.$refs.nodes.find(n => n.id === trigger.from)
 | 
	
		
			
				|  |  | +        const refTo = this.$refs.nodes.find(n => n.id === trigger.to)
 | 
	
		
			
				|  |  | +        if (!refFrom || !refTo) { // delete trigger
 | 
	
		
			
				|  |  | +          return {}
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        const fromOutput = {
 | 
	
		
			
				|  |  | +          x: 0,
 | 
	
		
			
				|  |  | +          y: refFrom.bodyProps.height / 2
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        const toInput = {
 | 
	
		
			
				|  |  | +          x: 0,
 | 
	
		
			
				|  |  | +          y: -refTo.bodyProps.height / 2
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return {
 | 
	
		
			
				|  |  | +          x1: nodeFrom.x + fromOutput.x,
 | 
	
		
			
				|  |  | +          y1: nodeFrom.y + fromOutput.y,
 | 
	
		
			
				|  |  | +          x2: nodeTo.x + toInput.x,
 | 
	
		
			
				|  |  | +          y2: nodeTo.y + toInput.y,
 | 
	
		
			
				|  |  | +          status: this.activity[nodeFrom.id] && this.activity[nodeFrom.id].status
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  mounted () {
 | 
	
		
			
				|  |  | +    this.$nextTick(() => {
 | 
	
		
			
				|  |  | +      this.$forceUpdate()
 | 
	
		
			
				|  |  | +    })
 | 
	
		
			
				|  |  | +    document.addEventListener('keydown', this.keyDown)
 | 
	
		
			
				|  |  | +    document.addEventListener('keyup', this.keyUp)
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  beoreDestroy () {
 | 
	
		
			
				|  |  | +    document.removeEventListener('keydown', this.keyDown)
 | 
	
		
			
				|  |  | +    document.removeEventListener('keyup', this.keyUp)
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  methods: {
 | 
	
		
			
				|  |  | +    ...mapActions([
 | 
	
		
			
				|  |  | +      'DOCUMENT_SYNC',
 | 
	
		
			
				|  |  | +      'NODE_RAISE', 'NODE_UPDATE', 'NODE_ADD', 'NODE_REMOVE', 'NODE_INSPECT',
 | 
	
		
			
				|  |  | +      'LINK_ADD', 'LINK_REMOVE',
 | 
	
		
			
				|  |  | +      'TRIGGER_ADD', 'TRIGGER_REMOVE' ]),
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    keyDown (ev) {
 | 
	
		
			
				|  |  | +      if (document.activeElement && document.activeElement.matches('input,textarea')) { return }
 | 
	
		
			
				|  |  | +      if (ev.shiftKey) {
 | 
	
		
			
				|  |  | +        this.linking = true
 | 
	
		
			
				|  |  | +        this.triggerLinking = true
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      let single = null
 | 
	
		
			
				|  |  | +      const selectionIds = Object.keys(this.nodeSelection)
 | 
	
		
			
				|  |  | +      if (selectionIds.length === 1) { single = this.nodeSelection[selectionIds[0]] }
 | 
	
		
			
				|  |  | +      switch (ev.key) {
 | 
	
		
			
				|  |  | +        case 'Enter':
 | 
	
		
			
				|  |  | +          if (!single) { return }
 | 
	
		
			
				|  |  | +          this.nodeInspect(single)
 | 
	
		
			
				|  |  | +          break
 | 
	
		
			
				|  |  | +        case 'Delete':
 | 
	
		
			
				|  |  | +          if (!this.nodeSelection) { 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
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          break
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    keyUp (ev) {
 | 
	
		
			
				|  |  | +      if (!ev.shiftKey) {
 | 
	
		
			
				|  |  | +        this.linking = false
 | 
	
		
			
				|  |  | +        this.triggerLinking = false
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    panzoomReset () {
 | 
	
		
			
				|  |  | +      this.panzoom = {x: 0, y: 0, zoom: 1}
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    // XXX: Shrink this function
 | 
	
		
			
				|  |  | +    // and create some LinkAdd method
 | 
	
		
			
				|  |  | +    socketPointerDown (nodeId, ev, socket) {
 | 
	
		
			
				|  |  | +      if (ev.button !== 0) return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      const nodeRef = this.$refs.nodes.find(n => n.id === nodeId)
 | 
	
		
			
				|  |  | +      const node = this.nodeMap(nodeId)
 | 
	
		
			
				|  |  | +      const isInput = socket.in !== undefined
 | 
	
		
			
				|  |  | +      const socketPos = isInput ? nodeRef.inputPos(socket.in) : nodeRef.outputPos(socket.out)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      // const p = this.transformedPoint(ev.clientX, ev.clientY)
 | 
	
		
			
				|  |  | +      this.pointerLink.props = {
 | 
	
		
			
				|  |  | +        x1: node.x + socketPos.x,
 | 
	
		
			
				|  |  | +        y1: node.y + socketPos.y,
 | 
	
		
			
				|  |  | +        x2: node.x + socketPos.x,
 | 
	
		
			
				|  |  | +        y2: node.y + socketPos.y
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      this.pointerLink.active = true
 | 
	
		
			
				|  |  | +      if (isInput) {
 | 
	
		
			
				|  |  | +        this.pointerLink.src = {nodeId: nodeId, type: this.registry[node.src].inputs[socket.in].type, in: socket.in}
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        this.pointerLink.src = {nodeId: nodeId, type: this.registry[node.src].output.type, out: 0}
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      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
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        },
 | 
	
		
			
				|  |  | +        drop: (ev) => {
 | 
	
		
			
				|  |  | +          this.pointerLink.active = false
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          // find Parent
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          var curTarget = ev.target
 | 
	
		
			
				|  |  | +          for (; curTarget.hasAttribute !== undefined && curTarget !== document.body; curTarget = curTarget.parentNode) {
 | 
	
		
			
				|  |  | +            if (curTarget.hasAttribute('data-nodeid')) {
 | 
	
		
			
				|  |  | +              break
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          if (!curTarget.hasAttribute || curTarget === document.body) {
 | 
	
		
			
				|  |  | +            console.error('LINK: target is not a socket')
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          const targetNodeId = curTarget.getAttribute('data-nodeid')
 | 
	
		
			
				|  |  | +          const targetIn = curTarget.getAttribute('data-in')
 | 
	
		
			
				|  |  | +          const targetOut = curTarget.getAttribute('data-out')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          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.nodeMap(link.from)
 | 
	
		
			
				|  |  | +          const nodeTo = this.nodeMap(link.to)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          const output = this.registry[nodeFrom.src].output.type
 | 
	
		
			
				|  |  | +          const input = this.registry[nodeTo.src].inputs[link.in].type
 | 
	
		
			
				|  |  | +          // Type checking
 | 
	
		
			
				|  |  | +          if (!(output === 'interface {}' || output === input || input === 'interface {}')) {
 | 
	
		
			
				|  |  | +            console.error('LINK: Invalid type')
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          // 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.LINK_ADD(link)
 | 
	
		
			
				|  |  | +        }})
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    triggerPointerDown (nodeId, ev, dir) {
 | 
	
		
			
				|  |  | +      if (ev.button !== 0) return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      const nodeRef = this.$refs.nodes.find(n => n.id === nodeId)
 | 
	
		
			
				|  |  | +      const node = this.nodeMap(nodeId)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      const isInput = (dir === 'in')
 | 
	
		
			
				|  |  | +      const triggerPos = {
 | 
	
		
			
				|  |  | +        x: 0,
 | 
	
		
			
				|  |  | +        y: nodeRef.bodyProps.height / 2 * (isInput ? -1 : 1)
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      this.pointerTriggerLink.src = {nodeId: node.id, dir: dir}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      this.pointerTriggerLink.props = {
 | 
	
		
			
				|  |  | +        x1: node.x + triggerPos.x,
 | 
	
		
			
				|  |  | +        y1: node.y + triggerPos.y,
 | 
	
		
			
				|  |  | +        x2: node.x + triggerPos.x,
 | 
	
		
			
				|  |  | +        y2: node.y + triggerPos.y
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      this.pointerTriggerLink.active = true
 | 
	
		
			
				|  |  | +      utils.createDrag({
 | 
	
		
			
				|  |  | +        drag: (ev) => {
 | 
	
		
			
				|  |  | +          const p = this.transformedPoint(ev.clientX, ev.clientY)
 | 
	
		
			
				|  |  | +          if (isInput) {
 | 
	
		
			
				|  |  | +            this.pointerTriggerLink.props.x1 = p.x
 | 
	
		
			
				|  |  | +            this.pointerTriggerLink.props.y1 = p.y
 | 
	
		
			
				|  |  | +          } else {
 | 
	
		
			
				|  |  | +            this.pointerTriggerLink.props.x2 = p.x
 | 
	
		
			
				|  |  | +            this.pointerTriggerLink.props.y2 = p.y
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        },
 | 
	
		
			
				|  |  | +        drop: (ev) => {
 | 
	
		
			
				|  |  | +          this.pointerTriggerLink.active = false
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          // find Parent
 | 
	
		
			
				|  |  | +          var curTarget = ev.target
 | 
	
		
			
				|  |  | +          for (; curTarget.hasAttribute !== undefined && curTarget !== document.body; curTarget = curTarget.parentNode) {
 | 
	
		
			
				|  |  | +            if (curTarget.hasAttribute('data-nodeid')) {
 | 
	
		
			
				|  |  | +              break
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          if (!curTarget.hasAttribute || curTarget === document.body) {
 | 
	
		
			
				|  |  | +            console.error('TRIGGER: target is not a socket')
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          const targetNodeId = curTarget.getAttribute('data-nodeid')
 | 
	
		
			
				|  |  | +          const targetDir = curTarget.getAttribute('data-dir')
 | 
	
		
			
				|  |  | +          if (targetNodeId === node.id) {
 | 
	
		
			
				|  |  | +            console.error('TRIGGER: cannot link to self')
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          let trigger
 | 
	
		
			
				|  |  | +          // target is input
 | 
	
		
			
				|  |  | +          if (targetDir === 'in') {
 | 
	
		
			
				|  |  | +            trigger = {
 | 
	
		
			
				|  |  | +              from: nodeId,
 | 
	
		
			
				|  |  | +              to: targetNodeId
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +          } else {
 | 
	
		
			
				|  |  | +            trigger = {
 | 
	
		
			
				|  |  | +              from: targetNodeId,
 | 
	
		
			
				|  |  | +              to: nodeId
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          this.TRIGGER_ADD(trigger)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      })
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    nodeInspect (tnode, force) {
 | 
	
		
			
				|  |  | +      this.$emit('nodeInspect', tnode, force)
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // HERE
 | 
	
		
			
				|  |  | +    nodePointerDown (ev, i) {
 | 
	
		
			
				|  |  | +      document.activeElement && document.activeElement.blur()
 | 
	
		
			
				|  |  | +      const tnode = this.nodeData.nodes[i]
 | 
	
		
			
				|  |  | +      if (ev.button === 1) {
 | 
	
		
			
				|  |  | +        this.NODE_REMOVE([tnode])
 | 
	
		
			
				|  |  | +        // this.nodeRemove(tnode)
 | 
	
		
			
				|  |  | +        // remove related links
 | 
	
		
			
				|  |  | +        return
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      if (ev.button !== 0) return // first button
 | 
	
		
			
				|  |  | +      if (ev.shiftKey) {
 | 
	
		
			
				|  |  | +        this.socketPointerDown(tnode.id, ev, {out: 0})
 | 
	
		
			
				|  |  | +        return
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      // this.NODE_INSPECT(tnode.id)
 | 
	
		
			
				|  |  | +      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
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      this.NODE_RAISE(this.nodeSelection)
 | 
	
		
			
				|  |  | +      // Raise node
 | 
	
		
			
				|  |  | +      // put the list of nodes in last
 | 
	
		
			
				|  |  | +      /* for (let nk in this.nodeSelection) {
 | 
	
		
			
				|  |  | +        // WRONG
 | 
	
		
			
				|  |  | +        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
 | 
	
		
			
				|  |  | +      } */
 | 
	
		
			
				|  |  | +      let curP = this.transformedPoint(ev.x, ev.y)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      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)
 | 
	
		
			
				|  |  | +          const nodeUpdate = []
 | 
	
		
			
				|  |  | +          for (let k in this.nodeSelection) {
 | 
	
		
			
				|  |  | +            // const n = this.nodeData.nodes.find(n => n.id === k)
 | 
	
		
			
				|  |  | +            const n = this.nodeMap(k)
 | 
	
		
			
				|  |  | +            // create new nodes
 | 
	
		
			
				|  |  | +            nodeUpdate.push({
 | 
	
		
			
				|  |  | +              ...n,
 | 
	
		
			
				|  |  | +              x: n.x + dragP.x - curP.x,
 | 
	
		
			
				|  |  | +              y: n.y + dragP.y - curP.y
 | 
	
		
			
				|  |  | +            })
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          this.NODE_UPDATE(nodeUpdate)
 | 
	
		
			
				|  |  | +          // this.sendFlowEvent('nodeUpdate', this.nodeSelection)
 | 
	
		
			
				|  |  | +          curP = dragP
 | 
	
		
			
				|  |  | +        },
 | 
	
		
			
				|  |  | +        drop: (ev) => {
 | 
	
		
			
				|  |  | +          this.dragging = null
 | 
	
		
			
				|  |  | +          this.DOCUMENT_SYNC()
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      })
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    /// ////////////
 | 
	
		
			
				|  |  | +    // NODE CREATOR FUNC
 | 
	
		
			
				|  |  | +    //
 | 
	
		
			
				|  |  | +    // STORE
 | 
	
		
			
				|  |  | +    nodeAdd (src, x = 100, y = 100) {
 | 
	
		
			
				|  |  | +      const newNode = {
 | 
	
		
			
				|  |  | +        id: utils.guid(),
 | 
	
		
			
				|  |  | +        x: x,
 | 
	
		
			
				|  |  | +        y: y,
 | 
	
		
			
				|  |  | +        defaultInputs: {},
 | 
	
		
			
				|  |  | +        label: src,
 | 
	
		
			
				|  |  | +        src: src
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      // Setup Props
 | 
	
		
			
				|  |  | +      if (this.registry[src].props) {
 | 
	
		
			
				|  |  | +        for (let k in this.registry[src].props) {
 | 
	
		
			
				|  |  | +          newNode.prop || (newNode.prop = {})
 | 
	
		
			
				|  |  | +          newNode.prop[k] = ''
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      this.NODE_ADD(newNode)
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    nodeProcess (node) {
 | 
	
		
			
				|  |  | +      // this.DOCUMENT_DYNC()
 | 
	
		
			
				|  |  | +      // console.log('Node process demand')
 | 
	
		
			
				|  |  | +      this.$emit('nodeProcess', node)
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    linkPointerClick (ev, link) {
 | 
	
		
			
				|  |  | +      ev.preventDefault()
 | 
	
		
			
				|  |  | +      this.LINK_REMOVE(link)
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /* triggerRemove (trigger) {
 | 
	
		
			
				|  |  | +      const i = this.nodeData.triggers.findIndex(l => l === trigger)
 | 
	
		
			
				|  |  | +      if (i === -1) return
 | 
	
		
			
				|  |  | +      this.nodeData.triggers.splice(i, 1)
 | 
	
		
			
				|  |  | +      // this.sendFlowEvent('triggerRemove', trigger)
 | 
	
		
			
				|  |  | +      this.DOCUMENT_DYNC()
 | 
	
		
			
				|  |  | +    }, */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    managerDrop (ev) {
 | 
	
		
			
				|  |  | +      ev.preventDefault()
 | 
	
		
			
				|  |  | +      const reg = ev.dataTransfer.getData('text')
 | 
	
		
			
				|  |  | +      if (this.registry[reg] === undefined) {
 | 
	
		
			
				|  |  | +        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) {
 | 
	
		
			
				|  |  | +      if (ev.button !== 0) return
 | 
	
		
			
				|  |  | +      ev.preventDefault()
 | 
	
		
			
				|  |  | +      this.nodeSelection = {}
 | 
	
		
			
				|  |  | +      const p = this.transformedPoint(ev.x, ev.y)
 | 
	
		
			
				|  |  | +      this.selector = {x: p.x, y: p.y, width: 0, height: 0}
 | 
	
		
			
				|  |  | +      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 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
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          this.selector = null
 | 
	
		
			
				|  |  | +        }})
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // service events
 | 
	
		
			
				|  |  | +    /* sendFlowEvent (type, param) {
 | 
	
		
			
				|  |  | +      this.$flowService[type](param)
 | 
	
		
			
				|  |  | +    }, */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    //
 | 
	
		
			
				|  |  | +    // DOCUMENT_DYNC () {
 | 
	
		
			
				|  |  | +    // this.DOCUMENT_SYNC()
 | 
	
		
			
				|  |  | +    // this.$flowService.documentUpdate(this.nodeData, this.$route.params.sessId)
 | 
	
		
			
				|  |  | +    // },
 | 
	
		
			
				|  |  | +    /* 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()
 | 
	
		
			
				|  |  | +      p.x = x; p.y = y
 | 
	
		
			
				|  |  | +      return p
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    transformedPoint (x, y, abs) {
 | 
	
		
			
				|  |  | +      const svgRect = this.$refs.svg.getBoundingClientRect()
 | 
	
		
			
				|  |  | +      if (!abs) {
 | 
	
		
			
				|  |  | +        x -= svgRect.left
 | 
	
		
			
				|  |  | +        y -= svgRect.top
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      return this.$refs.panzoom.transformedPoint(this.createSVGPoint(x, y))
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 |