|
@@ -1,777 +0,0 @@
|
|
-<template>
|
|
|
|
- <div class="flow-container">
|
|
|
|
- <svg
|
|
|
|
- xmlns="http://www.w3.org/2000/svg"
|
|
|
|
- xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
|
|
- ref="svg"
|
|
|
|
- class="flow-view view"
|
|
|
|
- :class="{
|
|
|
|
- 'flow-linking':linking || stickySockets,
|
|
|
|
- 'flow-triggers':triggerLinking || stickyTriggers,
|
|
|
|
-
|
|
|
|
- 'activity':dragging || pointerLink.active ,
|
|
|
|
- 'flow-node--activity':nodeActivity,
|
|
|
|
- 'selecting': !!selector
|
|
|
|
- }"
|
|
|
|
- @dragover.prevent
|
|
|
|
- @drop="managerDrop"
|
|
|
|
- @mousedown="viewPointerDown"
|
|
|
|
- :width="width"
|
|
|
|
- :height="height">
|
|
|
|
- <svg-defs/>
|
|
|
|
-
|
|
|
|
- <flow-pan-zoom
|
|
|
|
- ref="panzoom"
|
|
|
|
- v-model="panzoom">
|
|
|
|
-
|
|
|
|
- <!-- links below nodes -->
|
|
|
|
- <flow-link
|
|
|
|
- v-for="(link,i) in nodeData.links"
|
|
|
|
- :key="'link' + i"
|
|
|
|
- v-bind="linkProps(link)"
|
|
|
|
- @click="linkPointerClick($event,link)"
|
|
|
|
- />
|
|
|
|
- <!-- trigger links -->
|
|
|
|
- <flow-trigger-link
|
|
|
|
- v-for="(trigger,i) in nodeData.triggers"
|
|
|
|
- :key="'trigger'+i"
|
|
|
|
- label="WIP"
|
|
|
|
- v-bind="triggerProps(trigger)"
|
|
|
|
- @click="triggerRemove(trigger)"
|
|
|
|
- />
|
|
|
|
- <!-- nodes -->
|
|
|
|
- <flow-node
|
|
|
|
- ref="nodes"
|
|
|
|
- v-for="(n,i) of nodeData.nodes"
|
|
|
|
- v-bind="nodeProps(n)"
|
|
|
|
- :key="'node' + n.id"
|
|
|
|
- :id="n.id"
|
|
|
|
- :selected="nodeSelection[n.id]?true:false"
|
|
|
|
- @nodePointerDown.prevent="nodePointerDown($event,i)"
|
|
|
|
- @socketPointerDown="socketPointerDown(n.id,...arguments)"
|
|
|
|
- @triggerPointerDown="triggerPointerDown(n.id,...arguments)"
|
|
|
|
- @nodeDoubleClick="$emit('nodeDblClick',n)"
|
|
|
|
- @nodeRightClick="$refs.menu.open($event,n)"
|
|
|
|
-
|
|
|
|
- />
|
|
|
|
- <!-- mouse link-->
|
|
|
|
- <flow-link
|
|
|
|
- :pointer="true"
|
|
|
|
- v-if="pointerLink.active"
|
|
|
|
- v-bind="pointerLink.props"
|
|
|
|
- />
|
|
|
|
- <flow-trigger-link
|
|
|
|
- :pointer="true"
|
|
|
|
- v-if="pointerTriggerLink.active"
|
|
|
|
- v-bind="pointerTriggerLink.props"
|
|
|
|
- />
|
|
|
|
- <rect
|
|
|
|
- class="flow-selector"
|
|
|
|
- :class="{'flow-selector--selecting':(selector)?true:false}"
|
|
|
|
- v-bind="selector"/>
|
|
|
|
- </flow-pan-zoom>
|
|
|
|
- </svg>
|
|
|
|
- <div class="flow-container__control">
|
|
|
|
- <button @click="stickySockets=!stickySockets"> {{ stickySockets? 'Hide':'Show' }} sockets </button>
|
|
|
|
- <button @click="stickyTriggers=!stickyTriggers"> {{ stickyTriggers? 'Hide':'Show' }} triggers </button>
|
|
|
|
- <button @click="nodeActivity=!nodeActivity"> {{ nodeActivity? 'Hide':'Show' }} activity </button>
|
|
|
|
- <button @click="$emit('documentSave', nodeData)"> Save </button> <!-- should disable until confirmation -->
|
|
|
|
- <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) }}
|
|
|
|
- nodes: {{ nodeData.nodes.length }}
|
|
|
|
- links: {{ nodeData.links.length }}
|
|
|
|
- triggers: {{ nodeData.triggers.length }}
|
|
|
|
- </div>
|
|
|
|
- <hx-context-menu ref="menu">
|
|
|
|
- <template slot-scope="d" >
|
|
|
|
- <div class="flow-node__context-menu">
|
|
|
|
- <div class="hover" @click="nodeProcess(d.userData)">Run</div>
|
|
|
|
- <div class="hover" tabindex="0" @click="nodeRemove(d.userData)">Delete</div>
|
|
|
|
- <hr>
|
|
|
|
- <div class="hover" @click="nodeInspect(d.userData,true)">Inspect</div>
|
|
|
|
- </div>
|
|
|
|
- </template>
|
|
|
|
- </hx-context-menu>
|
|
|
|
- </div>
|
|
|
|
-</template>
|
|
|
|
-<script>
|
|
|
|
-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: {
|
|
|
|
- 'activity': {type: Object, default: () => {}},
|
|
|
|
- 'registry': {type: Object, default: () => {}},
|
|
|
|
- '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: {},
|
|
|
|
-
|
|
|
|
- stickySockets: false,
|
|
|
|
- stickyTriggers: false,
|
|
|
|
- nodeActivity: true
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- computed: {
|
|
|
|
- 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.nodeRemove(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.nodeData.nodes.find(n => n.id === link.from)
|
|
|
|
- const nodeTo = this.nodeData.nodes.find(n => n.id === 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.nodeData.nodes.find(n => n.id === trigger.from)
|
|
|
|
- const nodeTo = this.nodeData.nodes.find(n => n.id === 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.$flowService.on('document', (v) => {
|
|
|
|
- if (!v || !v.data || !v.data.nodes) { return }
|
|
|
|
- const nodes = []
|
|
|
|
-
|
|
|
|
- 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(node)
|
|
|
|
- }
|
|
|
|
- if (this.dragging) { // Add our nodes
|
|
|
|
- for (let nid in this.dragging) {
|
|
|
|
- nodes.push(this.dragging[nid])
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- this.nodeData = {
|
|
|
|
- nodes: nodes,
|
|
|
|
- links: v.data.links,
|
|
|
|
- triggers: v.data.triggers
|
|
|
|
- }
|
|
|
|
- this.$nextTick(() => {
|
|
|
|
- this.$forceUpdate()
|
|
|
|
- })
|
|
|
|
- })
|
|
|
|
- this.$flowService.on('nodeUpdate', (v) => {
|
|
|
|
- console.log('Incoming node update')
|
|
|
|
- const nodes = v.data
|
|
|
|
- const nd = this.nodeData
|
|
|
|
- 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(nodes[nid])
|
|
|
|
- continue
|
|
|
|
- }
|
|
|
|
- let lnode = nd.nodes[idx]
|
|
|
|
-
|
|
|
|
- Object.assign(lnode, node)
|
|
|
|
-
|
|
|
|
- // if (!this.dragging || !this.dragging[node.id]) {
|
|
|
|
- // this.$set(nd.nodes, idx, node) // new Node
|
|
|
|
- // }
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
- 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: {
|
|
|
|
- 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 }
|
|
|
|
- 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.nodeData.nodes.find(n => n.id === 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.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.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.linkAdd(link)
|
|
|
|
- }})
|
|
|
|
- },
|
|
|
|
- triggerPointerDown (nodeId, ev, dir) {
|
|
|
|
- if (ev.button !== 0) return
|
|
|
|
-
|
|
|
|
- const nodeRef = this.$refs.nodes.find(n => n.id === nodeId)
|
|
|
|
- const node = this.nodeData.nodes.find(n => n.id === 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.triggerAdd(trigger)
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- nodeInspect (tnode, force) {
|
|
|
|
- this.$emit('nodeInspect', tnode, force)
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- nodePointerDown (ev, i) {
|
|
|
|
- document.activeElement && document.activeElement.blur()
|
|
|
|
- const tnode = this.nodeData.nodes[i]
|
|
|
|
- if (ev.button === 1) {
|
|
|
|
- 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.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
|
|
|
|
-
|
|
|
|
- // 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
|
|
|
|
- }
|
|
|
|
- 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)
|
|
|
|
- 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()
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- // STORE
|
|
|
|
- nodeRemove (node) {
|
|
|
|
- const i = this.nodeData.nodes.indexOf(node)
|
|
|
|
- if (i === -1) return
|
|
|
|
- this.nodeData.links = this.nodeData.links.filter(l => l.from !== node.id && l.to !== node.id)
|
|
|
|
- this.nodeData.triggers = this.nodeData.triggers.filter(l => l.from !== node.id && l.to !== node.id)
|
|
|
|
-
|
|
|
|
- this.nodeData.nodes.splice(i, 1)
|
|
|
|
- this.sendFlowEvent('nodeRemove', node)
|
|
|
|
- this.sendDocumentUpdate()
|
|
|
|
- },
|
|
|
|
- /// ////////////
|
|
|
|
- // 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.nodeData.nodes.push(newNode)
|
|
|
|
- let nu = {}
|
|
|
|
- this.sendFlowEvent('nodeUpdate', (nu[newNode.id] = newNode, nu))
|
|
|
|
- this.sendDocumentUpdate()
|
|
|
|
- },
|
|
|
|
- nodeProcess (node) {
|
|
|
|
- this.sendDocumentUpdate()
|
|
|
|
- console.log('Node process demand')
|
|
|
|
- this.$emit('nodeProcess', node)
|
|
|
|
- },
|
|
|
|
- linkPointerClick (ev, link) {
|
|
|
|
- ev.preventDefault()
|
|
|
|
- this.linkRemove(link)
|
|
|
|
- },
|
|
|
|
- linkAdd (link) {
|
|
|
|
- this.nodeData.links.push(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()
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- triggerAdd (trigger) {
|
|
|
|
- this.nodeData.triggers.push(trigger)
|
|
|
|
- this.sendDocumentUpdate()
|
|
|
|
- },
|
|
|
|
- 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.sendDocumentUpdate()
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- 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 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 () {
|
|
|
|
- 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))
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-</script>
|
|
|
|
-<style>
|
|
|
|
-.flow-container {
|
|
|
|
- display:flex;
|
|
|
|
- flex-flow:row;
|
|
|
|
- position:relative;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.flow-container__control {
|
|
|
|
- position:absolute;
|
|
|
|
- top: 20px;
|
|
|
|
- left: 20px;
|
|
|
|
- display:flex;
|
|
|
|
- justify-content: center;
|
|
|
|
- align-items: center;
|
|
|
|
- color: #fff;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.flow-container__control button {
|
|
|
|
- display:flex;
|
|
|
|
- justify-content: center;
|
|
|
|
- margin:0;
|
|
|
|
- padding:14px;
|
|
|
|
- color:#333;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.flow-container__info {
|
|
|
|
- position:absolute;
|
|
|
|
- bottom:10px;
|
|
|
|
- left:10px;
|
|
|
|
- padding:2px;
|
|
|
|
- font-size:9px;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.flow-view {
|
|
|
|
- border:none;
|
|
|
|
- position:relative;
|
|
|
|
- fill:transparent;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.flow-selector {
|
|
|
|
- pointer-events:none;
|
|
|
|
- opacity:0;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.flow-selector.flow-selector--selecting {
|
|
|
|
- opacity:1;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.flow-node__context-menu {
|
|
|
|
- box-shadow: 0 1px 4px rgba(0,0,0,0.2);
|
|
|
|
- display:flex;
|
|
|
|
- flex-flow: column;
|
|
|
|
- justify-content: start;
|
|
|
|
- align-items: stretch;
|
|
|
|
- width:180px;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.flow-node__context-menu > div {
|
|
|
|
- padding:10px 20px;
|
|
|
|
- cursor: pointer;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.flow-node__context-menu > * {
|
|
|
|
- width:100%;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.flow-node__context-menu hr {
|
|
|
|
- width:90%;
|
|
|
|
- border:none;
|
|
|
|
- border-bottom: solid 1px rgba(150,150,150,0.2);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.flow-node__context-menu button {
|
|
|
|
- text-align:left;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-</style>
|
|
|