123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- <template>
- <g
- class="flow-node"
- :class="{
- 'flow-node--dragging':dragging && dragging[node.id],
- 'flow-node--selected': !!nodeSelection[node.id]
- }"
- :status="status"
- :transform="'translate(' + node.x + ',' + node.y +')'"
- @mousedown.stop.prevent="nodePointerDown"
- @contextmenu.capture.prevent="nodeRightClick"
- @dblclick="nodeDoubleClick"
- >
- <!-- shape -->
- <template>
- <g v-if="style.shape == 'portal'" >
- <svg
- ref="body"
- viewBox="0 0 100 100"
- class="flow-node__body"
- v-bind="bodyProps"
- preserveAspectRatio="none"
- >
- <path d=" M 50 0 L 100 50 L 50 100 L 0 50 Z"/>
- </svg>
- </g>
- <g v-else-if="style.shape == 'circle'">
- <circle
- ref="body"
- class="flow-node__body"
- v-bind="bodyProps"
- />
- </g>
- <g v-else>
- <rect
- ref="body"
- class="flow-node__body"
- v-bind="bodyProps"
- />
- </g>
- </template>
- <!-- selection square -->
- <rect
- class="flow-node__selection"
- :x="bodyProps.x-6"
- :y="bodyProps.y-6"
- :width="bodyProps.width+12"
- :height="bodyProps.height+12"
- />
- <!-- label -->
- <text
- ref="label"
- class="flow-node__label"
- v-bind="labelProps"
- text-anchor="middle">
- <tspan
- :key="'label-wrap' + i"
- x="0"
- dy="1em"
- v-for="(s,i) in labelWrap" >
- {{ s }}
- </tspan>
- </text>
- <!-- Sockets -->
- <!-- input -->
- <g
- v-for="(inp,i) in inputs"
- :key="'in'+i"
- :data-nodeid="node.id"
- :data-in="i"
- v-bind="inputProps[i]"
- @mousedown.stop.prevent="socketPointerDown($event, {in:i})"
- class="flow-node__socket flow-node__socket--inputs"
- >
- <circle r="5" />
- <rect
- v-bind="inputLabelBGProps(i)"
- class="flow-node__socket-detail--background"
- />
- <text
- ref="inputLabel"
- text-anchor="end"
- :x="-14"
- :y="4"
- class="flow-node__socket-detail"
- >{{ inputLabel[i] }}</text>
- </g>
- <!-- output -->
- <g
- v-if="output"
- v-bind="outputProps(0)"
- :data-nodeid="node.id"
- :data-out="0"
- :key="'out'+0"
- @mousedown.stop.prevent="socketPointerDown($event, {out:0})"
- class="flow-node__socket flow-node__socket--outputs"
- >
- <circle r="5" />
- <!--<rect
- v-bind="outputLabelBGProps(0)"
- class="flow-node__socket-detail--background"
- />-->
- <text
- ref="outputLabel"
- class="flow-node__socket-detail"
- :x="14"
- :y="4">
- {{ outputLabel(0) }}
- </text>
- </g>
- <!-- TRIGGER SOCKETS -->
- <rect
- class="flow-node__trigger flow-node__socket--trigger"
- :class="{'flow-node__trigger--match': match.type == 'trigger-out'}"
- :data-nodeid="node.id"
- data-dir="in"
- :x="-5"
- :y="-bodyProps.height/2-5"
- width="10"
- height="10"
- @mousedown.stop.prevent="triggerPointerDown($event, 'in')"
- />
- <rect
- class="flow-node__trigger flow-node__socket--trigger"
- :class="{'flow-node__trigger--match': match.type == 'trigger-in'}"
- :data-nodeid="node.id"
- data-dir="out"
- :x="-5"
- :y="bodyProps.height/2 -5"
- width="10"
- height="10"
- @mousedown.stop.prevent="triggerPointerDown($event, 'out')"
- />
- <text
- class="flow-node__src-detail"
- x="0"
- :y="-bodyProps.height/2 - 6"
- text-anchor="middle">
- {{ node.src }}
- </text>
- <flow-node-activity
- @activityPointerDown="activityPointerDown"
- v-if="nodeActivity"
- :node-id="node.id"
- :transform="'translate('+bodyProps.width/2 +','+ -bodyProps.height/2 +')'"/>
- </g>
- </template>
- <script>
- import {mapGetters} from 'vuex'
- import FlowNodeActivity from './node-activity'
- import utils from '@/utils/utils'
- import nodeSize from './node-size'
- export default {
- name: 'FlowNode',
- components: {FlowNodeActivity},
- props: {
- 'node': {type: Object, required: true},
- 'match': {type: Object, default: () => {}},
- 'pointerLink': {type: Object, default: null},
- 'pointerTriggerLink': {type: Object, default: null},
- 'dragging': {type: Object, default: null}
- // 'activity': {type: Object, default: () => {}},
- // 'nodeStyle': {type: Object, default: () => {}}
- },
- data () {
- return {
- // maintain reference here?
- inputLabelRect: [],
- outputLabelRect: [],
- labelRect: {x: 0, y: 0, width: 0, height: 0}
- // bodyRect: {x: 0, y: 0, width: 0, height: 0}
- }
- },
- computed: {
- ...mapGetters('flow', ['nodeCache', 'nodeData', 'registry', 'activity', 'nodeById', 'nodeSelection', 'nodeInputPos', 'nodeOutputPos']),
- nodeDim () {
- return this.nodeCache[this.node.id].dim
- },
- inputs () {
- return this.registry[this.node.src].inputs || []
- },
- output () {
- return this.registry[this.node.src].output
- },
- nodeActivity () {
- return this.activity && this.activity.nodes && this.activity.nodes[this.node.id]
- },
- color () {
- return this.node.color ||
- (this.registry[this.node.src].style && this.registry[this.node.src].style.color)
- },
- style () {
- return this.registry[this.node.src].style || {}
- },
- status () {
- return this.nodeActivity && (this.nodeActivity.status || '')
- },
- labelWrap () {
- let wrapThreshold = 8 // initial wrap threshold
- const opt = nodeSize.shapeOpts[this.style.shape] || nodeSize.shapeOpts.default
- return utils.textWrap(this.node.label, wrapThreshold, opt.textWrap)
- },
- labelProps () {
- return {
- x: 0,
- y: 0,
- fill: this.textColor,
- // transform: `translate(${-this.labelRect.width / 2},${-this.labelRect.height / 2})`
- transform: `translate(0,${-this.labelRect.height / 2})`
- }
- },
- bodyProps () {
- const nodeDim = this.nodeDim
- const rect = {
- x: -nodeDim.width / 2,
- y: -nodeDim.height / 2,
- width: nodeDim.width,
- height: nodeDim.height,
- stroke: this.color || '#777',
- fill: this.color || '#777'
- }
- if (this.style.shape === 'circle') {
- rect.r = Math.max(nodeDim.width / 2, nodeDim.height / 2)
- }
- return rect
- },
- inputProps () {
- const ret = []
- for (var i in this.inputs) {
- // console.log('Recalc input props')
- // console.log('Rebuild input WHY')
- let defaultInput = this.node.defaultInputs[i]
- const inp = this.inputs[i]
- const match = this.match.type === 'socket-in' && (inp.type === this.match.dtype || this.match.dtype === 'interface {}' || inp.type === 'interface {}')
- const {x, y} = this.nodeInputPos(this.node, i)
- ret.push({
- class: {
- 'flow-node__socket--match': match,
- 'flow-node__socket--withvalue': !!defaultInput
- },
- transform: `translate(${x} ${y})`
- })
- }
- return ret
- /* return (i) => {
- let defaultInput = this.node.defaultInputs[i]
- const inp = this.inputs[i]
- const match = this.match.type === 'socket-in' && (inp.type === this.match.dtype || this.match.dtype === 'interface {}' || inp.type === 'interface {}')
- const {x, y} = this.inputPos(i)
- return {
- class: {
- 'flow-node__socket--match': match,
- 'flow-node__socket--withvalue': !!defaultInput
- },
- transform: `translate(${x} ${y})`
- }
- } */
- },
- outputProps () {
- return (i) => {
- const {x, y} = this.nodeOutputPos(this.node, i)
- const outp = this.output
- const match = this.match.type === 'socket-out' && (outp.type === this.match.dtype || this.match.dtype === 'interface {}' || outp.type === 'interface {}')
- return {
- transform: `translate(${x} ${y})`,
- class: {
- 'flow-node__socket--match': match
- }
- }
- }
- },
- inputLabel () {
- const ret = []
- for (var i in this.inputs) {
- let input = ''
- if (this.inputs[i].name) {
- input += this.inputs[i].name + ':'
- }
- input += this.inputs[i].type
- ret.push(input)
- }
- return ret
- },
- defInput () {
- return (i) => {
- }
- },
- outputLabel () {
- return (i) => {
- var output = ''
- if (this.output.name) {
- output += this.output.name + ':'
- }
- output += this.output.type
- return output
- }
- },
- // Backgrounds
- inputLabelBGProps () {
- return (i) => {
- if (!this.$refs.inputLabel) {
- this.$nextTick(this.$forceUpdate)
- return
- // {x: 0, y: 0, width: 0, height: 0}
- }
- let bbox = this.$refs.inputLabel[i].getBBox()
- return {x: bbox.x - 5, y: bbox.y - 2, width: bbox.width + 10, height: bbox.height + 4}
- }
- },
- outputLabelBGProps () {
- return (i) => {
- if (!this.$refs.outputLabel) {
- this.$nextTick(this.$forceUpdate)
- return
- }
- let bbox = this.$refs.outputLabel.getBBox()
- return {x: bbox.x - 5, y: bbox.y - 2, width: bbox.width + 10, height: bbox.height + 4}
- }
- }
- },
- watch: {
- // the only thing now that affects geometry
- 'node.label' (val, oldVal) {
- if (val === oldVal) { return }
- this.$nextTick(() => {
- this.labelRect = this.$refs.label.getBBox()
- })
- }
- },
- mounted () {
- // After render
- this.$nextTick(() => { // after mount we reupdate with new values
- if (!this.$refs.label) return
- this.labelRect = this.$refs.label.getBBox()
- this.$forceUpdate()
- })
- },
- udpated () {
- console.log('Node update')
- },
- methods: {
- nodePointerDown (ev) {
- this.$emit('nodePointerDown', ev)
- },
- nodeRightClick (ev) {
- this.$emit('nodeRightClick', ev)
- },
- nodeDoubleClick (ev) {
- this.$emit('nodeDoubleClick', ev)
- },
- socketPointerDown (ev, socket) {
- this.$emit('socketPointerDown', ev, socket)
- },
- triggerPointerDown (ev, dir) {
- this.$emit('triggerPointerDown', ev, dir)
- },
- activityPointerDown (ev) {
- this.$emit('activityPointerDown', ev)
- }
- }
- }
- </script>
- <style>
- .flow-view:not(.activity) .flow-node:hover,
- .flow-node--dragging {
- cursor:move;
- }
- .flow-view:not(.activity) .flow-node {
- transition: all var(--transition-speed-fast);
- }
- .flow-node__body {
- opacity:0.9;
- transition: all var(--transition-speed-fast);
- }
- .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;
- }
- /* opacity with the sockets */
- .flow-node__src-detail {
- pointer-events:none;
- user-select:none;
- font-size:15px;
- opacity:0;
- fill: var(--normal);
- transition: all var(--transition-speed-fast);
- }
- .flow-linking .flow-node__src-detail {
- opacity:1;
- }
- /* sockets */
- .flow-node__socket {
- pointer-events: none;
- stroke-width:1;
- opacity:0;
- transition: all var(--transition-speed-fast);
- }
- .flow-view:not(.activity) .flow-node__socket:hover {
- stroke-width:10;
- cursor:pointer;
- }
- .flow-node__socket-detail {
- opacity:1;
- stroke-width:0 !important;
- fill: #fff !default;
- transition: all var(--transition-speed-fast);
- }
- .flow-node__socket-detail--background {
- stroke-width:0;
- transition:all var(--transition-speed-fast);
- }
- .flow-node__socket--match {
- cursor:pointer;
- stroke-width:10;
- }
- .flow-node__socket--withvalue {
- fill: var(--node-socket--withvalue) !important;
- stroke: var(--node-socket-withvalue) !important;
- }
- .flow-linking .flow-node__socket {
- opacity:1;
- pointer-events: inherit;
- }
- .flow-node__socket--match {
- opacity:1;
- pointer-events: inherit;
- }
- /* triggers */
- .flow-node__trigger {
- pointer-events:none;
- opacity:0;
- transition: all var(--transition-speed-fast);
- }
- .flow-triggers .flow-node__trigger {
- pointer-events:inherit;
- opacity:1;
- }
- .flow-view:not(.activity) .flow-node__trigger:hover {
- stroke-width:10;
- cursor:pointer;
- }
- .flow-node__trigger--match {
- stroke-width:10;
- opacity:1;
- pointer-events: inherit;
- }
- /*
- Override flow-node
- for hidden
- */
- .flow-node__label {
- stroke:none;
- pointer-events:none;
- user-select:none;
- fill:#333 !default;
- }
- .flow-node__selection {
- opacity:0;
- stroke-width:3;
- stroke: var(--node-selection);
- stroke-dasharray:2,2;
- pointer-events:none;
- transition: all var(--transition-speed-fast);
- fill: var(--node-selection);
- }
- .flow-node--selected .flow-node__selection {
- opacity:0.6;
- animation: flow-node--selected__dash 3s linear infinite;
- }
- @keyframes flow-node--selected__dash {
- from { stroke-dashoffset:100;}
- to { stroke-dashoffset: 0; }
- }
- </style>
|