node.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. <template>
  2. <g
  3. class="flow-node"
  4. :class="{
  5. 'flow-node--dragging':dragging && dragging[node.id],
  6. 'flow-node--selected': !!nodeSelection[node.id]
  7. }"
  8. :status="status"
  9. :transform="'translate(' + node.x + ',' + node.y +')'"
  10. @mousedown.stop.prevent="nodePointerDown"
  11. @contextmenu.capture.prevent="nodeRightClick"
  12. @dblclick="nodeDoubleClick"
  13. >
  14. <!-- shape -->
  15. <template>
  16. <g v-if="style.shape == 'portal'" >
  17. <svg
  18. ref="body"
  19. viewBox="0 0 100 100"
  20. class="flow-node__body"
  21. v-bind="bodyProps"
  22. preserveAspectRatio="none"
  23. >
  24. <path d=" M 50 0 L 100 50 L 50 100 L 0 50 Z"/>
  25. </svg>
  26. </g>
  27. <g v-else-if="style.shape == 'circle'">
  28. <circle
  29. ref="body"
  30. class="flow-node__body"
  31. v-bind="bodyProps"
  32. />
  33. </g>
  34. <g v-else>
  35. <rect
  36. ref="body"
  37. class="flow-node__body"
  38. v-bind="bodyProps"
  39. />
  40. </g>
  41. </template>
  42. <!-- selection square -->
  43. <rect
  44. class="flow-node__selection"
  45. :x="bodyProps.x-6"
  46. :y="bodyProps.y-6"
  47. :width="bodyProps.width+12"
  48. :height="bodyProps.height+12"
  49. />
  50. <!-- label -->
  51. <text
  52. ref="label"
  53. class="flow-node__label"
  54. v-bind="labelProps"
  55. text-anchor="middle">
  56. <tspan
  57. :key="'label-wrap' + i"
  58. x="0"
  59. dy="1em"
  60. v-for="(s,i) in labelWrap" >
  61. {{ s }}
  62. </tspan>
  63. </text>
  64. <!-- Sockets -->
  65. <!-- input -->
  66. <g
  67. v-for="(inp,i) in inputs"
  68. :key="'in'+i"
  69. :data-nodeid="node.id"
  70. :data-in="i"
  71. v-bind="inputProps[i]"
  72. @mousedown.stop.prevent="socketPointerDown($event, {in:i})"
  73. class="flow-node__socket flow-node__socket--inputs"
  74. >
  75. <circle r="5" />
  76. <rect
  77. v-bind="inputLabelBGProps(i)"
  78. class="flow-node__socket-detail--background"
  79. />
  80. <text
  81. ref="inputLabel"
  82. text-anchor="end"
  83. :x="-14"
  84. :y="4"
  85. class="flow-node__socket-detail"
  86. >{{ inputLabel[i] }}</text>
  87. </g>
  88. <!-- output -->
  89. <g
  90. v-if="output"
  91. v-bind="outputProps(0)"
  92. :data-nodeid="node.id"
  93. :data-out="0"
  94. :key="'out'+0"
  95. @mousedown.stop.prevent="socketPointerDown($event, {out:0})"
  96. class="flow-node__socket flow-node__socket--outputs"
  97. >
  98. <circle r="5" />
  99. <!--<rect
  100. v-bind="outputLabelBGProps(0)"
  101. class="flow-node__socket-detail--background"
  102. />-->
  103. <text
  104. ref="outputLabel"
  105. class="flow-node__socket-detail"
  106. :x="14"
  107. :y="4">
  108. {{ outputLabel(0) }}
  109. </text>
  110. </g>
  111. <!-- TRIGGER SOCKETS -->
  112. <rect
  113. class="flow-node__trigger flow-node__socket--trigger"
  114. :class="{'flow-node__trigger--match': match.type == 'trigger-out'}"
  115. :data-nodeid="node.id"
  116. data-dir="in"
  117. :x="-5"
  118. :y="-bodyProps.height/2-5"
  119. width="10"
  120. height="10"
  121. @mousedown.stop.prevent="triggerPointerDown($event, 'in')"
  122. />
  123. <rect
  124. class="flow-node__trigger flow-node__socket--trigger"
  125. :class="{'flow-node__trigger--match': match.type == 'trigger-in'}"
  126. :data-nodeid="node.id"
  127. data-dir="out"
  128. :x="-5"
  129. :y="bodyProps.height/2 -5"
  130. width="10"
  131. height="10"
  132. @mousedown.stop.prevent="triggerPointerDown($event, 'out')"
  133. />
  134. <text
  135. class="flow-node__src-detail"
  136. x="0"
  137. :y="-bodyProps.height/2 - 6"
  138. text-anchor="middle">
  139. {{ node.src }}
  140. </text>
  141. <flow-node-activity
  142. @activityPointerDown="activityPointerDown"
  143. v-if="nodeActivity"
  144. :node-id="node.id"
  145. :transform="'translate('+bodyProps.width/2 +','+ -bodyProps.height/2 +')'"/>
  146. </g>
  147. </template>
  148. <script>
  149. import {mapGetters} from 'vuex'
  150. import FlowNodeActivity from './node-activity'
  151. import utils from '@/utils/utils'
  152. import nodeSize from './node-size'
  153. export default {
  154. name: 'FlowNode',
  155. components: {FlowNodeActivity},
  156. props: {
  157. 'node': {type: Object, required: true},
  158. 'match': {type: Object, default: () => {}},
  159. 'pointerLink': {type: Object, default: null},
  160. 'pointerTriggerLink': {type: Object, default: null},
  161. 'dragging': {type: Object, default: null}
  162. // 'activity': {type: Object, default: () => {}},
  163. // 'nodeStyle': {type: Object, default: () => {}}
  164. },
  165. data () {
  166. return {
  167. // maintain reference here?
  168. inputLabelRect: [],
  169. outputLabelRect: [],
  170. labelRect: {x: 0, y: 0, width: 0, height: 0}
  171. // bodyRect: {x: 0, y: 0, width: 0, height: 0}
  172. }
  173. },
  174. computed: {
  175. ...mapGetters('flow', ['nodeCache', 'nodeData', 'registry', 'activity', 'nodeById', 'nodeSelection', 'nodeInputPos', 'nodeOutputPos']),
  176. nodeDim () {
  177. return this.nodeCache[this.node.id].dim
  178. },
  179. inputs () {
  180. return this.registry[this.node.src].inputs || []
  181. },
  182. output () {
  183. return this.registry[this.node.src].output
  184. },
  185. nodeActivity () {
  186. return this.activity && this.activity.nodes && this.activity.nodes[this.node.id]
  187. },
  188. color () {
  189. return this.node.color ||
  190. (this.registry[this.node.src].style && this.registry[this.node.src].style.color)
  191. },
  192. style () {
  193. return this.registry[this.node.src].style || {}
  194. },
  195. status () {
  196. return this.nodeActivity && (this.nodeActivity.status || '')
  197. },
  198. labelWrap () {
  199. let wrapThreshold = 8 // initial wrap threshold
  200. const opt = nodeSize.shapeOpts[this.style.shape] || nodeSize.shapeOpts.default
  201. return utils.textWrap(this.node.label, wrapThreshold, opt.textWrap)
  202. },
  203. labelProps () {
  204. return {
  205. x: 0,
  206. y: 0,
  207. fill: this.textColor,
  208. // transform: `translate(${-this.labelRect.width / 2},${-this.labelRect.height / 2})`
  209. transform: `translate(0,${-this.labelRect.height / 2})`
  210. }
  211. },
  212. bodyProps () {
  213. const nodeDim = this.nodeDim
  214. const rect = {
  215. x: -nodeDim.width / 2,
  216. y: -nodeDim.height / 2,
  217. width: nodeDim.width,
  218. height: nodeDim.height,
  219. stroke: this.color || '#777',
  220. fill: this.color || '#777'
  221. }
  222. if (this.style.shape === 'circle') {
  223. rect.r = Math.max(nodeDim.width / 2, nodeDim.height / 2)
  224. }
  225. return rect
  226. },
  227. inputProps () {
  228. const ret = []
  229. for (var i in this.inputs) {
  230. // console.log('Recalc input props')
  231. // console.log('Rebuild input WHY')
  232. let defaultInput = this.node.defaultInputs[i]
  233. const inp = this.inputs[i]
  234. const match = this.match.type === 'socket-in' && (inp.type === this.match.dtype || this.match.dtype === 'interface {}' || inp.type === 'interface {}')
  235. const {x, y} = this.nodeInputPos(this.node, i)
  236. ret.push({
  237. class: {
  238. 'flow-node__socket--match': match,
  239. 'flow-node__socket--withvalue': !!defaultInput
  240. },
  241. transform: `translate(${x} ${y})`
  242. })
  243. }
  244. return ret
  245. /* return (i) => {
  246. let defaultInput = this.node.defaultInputs[i]
  247. const inp = this.inputs[i]
  248. const match = this.match.type === 'socket-in' && (inp.type === this.match.dtype || this.match.dtype === 'interface {}' || inp.type === 'interface {}')
  249. const {x, y} = this.inputPos(i)
  250. return {
  251. class: {
  252. 'flow-node__socket--match': match,
  253. 'flow-node__socket--withvalue': !!defaultInput
  254. },
  255. transform: `translate(${x} ${y})`
  256. }
  257. } */
  258. },
  259. outputProps () {
  260. return (i) => {
  261. const {x, y} = this.nodeOutputPos(this.node, i)
  262. const outp = this.output
  263. const match = this.match.type === 'socket-out' && (outp.type === this.match.dtype || this.match.dtype === 'interface {}' || outp.type === 'interface {}')
  264. return {
  265. transform: `translate(${x} ${y})`,
  266. class: {
  267. 'flow-node__socket--match': match
  268. }
  269. }
  270. }
  271. },
  272. inputLabel () {
  273. const ret = []
  274. for (var i in this.inputs) {
  275. let input = ''
  276. if (this.inputs[i].name) {
  277. input += this.inputs[i].name + ':'
  278. }
  279. input += this.inputs[i].type
  280. ret.push(input)
  281. }
  282. return ret
  283. },
  284. defInput () {
  285. return (i) => {
  286. }
  287. },
  288. outputLabel () {
  289. return (i) => {
  290. var output = ''
  291. if (this.output.name) {
  292. output += this.output.name + ':'
  293. }
  294. output += this.output.type
  295. return output
  296. }
  297. },
  298. // Backgrounds
  299. inputLabelBGProps () {
  300. return (i) => {
  301. if (!this.$refs.inputLabel) {
  302. this.$nextTick(this.$forceUpdate)
  303. return
  304. // {x: 0, y: 0, width: 0, height: 0}
  305. }
  306. let bbox = this.$refs.inputLabel[i].getBBox()
  307. return {x: bbox.x - 5, y: bbox.y - 2, width: bbox.width + 10, height: bbox.height + 4}
  308. }
  309. },
  310. outputLabelBGProps () {
  311. return (i) => {
  312. if (!this.$refs.outputLabel) {
  313. this.$nextTick(this.$forceUpdate)
  314. return
  315. }
  316. let bbox = this.$refs.outputLabel.getBBox()
  317. return {x: bbox.x - 5, y: bbox.y - 2, width: bbox.width + 10, height: bbox.height + 4}
  318. }
  319. }
  320. },
  321. watch: {
  322. // the only thing now that affects geometry
  323. 'node.label' (val, oldVal) {
  324. if (val === oldVal) { return }
  325. this.$nextTick(() => {
  326. this.labelRect = this.$refs.label.getBBox()
  327. })
  328. }
  329. },
  330. mounted () {
  331. // After render
  332. this.$nextTick(() => { // after mount we reupdate with new values
  333. if (!this.$refs.label) return
  334. this.labelRect = this.$refs.label.getBBox()
  335. this.$forceUpdate()
  336. })
  337. },
  338. udpated () {
  339. console.log('Node update')
  340. },
  341. methods: {
  342. nodePointerDown (ev) {
  343. this.$emit('nodePointerDown', ev)
  344. },
  345. nodeRightClick (ev) {
  346. this.$emit('nodeRightClick', ev)
  347. },
  348. nodeDoubleClick (ev) {
  349. this.$emit('nodeDoubleClick', ev)
  350. },
  351. socketPointerDown (ev, socket) {
  352. this.$emit('socketPointerDown', ev, socket)
  353. },
  354. triggerPointerDown (ev, dir) {
  355. this.$emit('triggerPointerDown', ev, dir)
  356. },
  357. activityPointerDown (ev) {
  358. this.$emit('activityPointerDown', ev)
  359. }
  360. }
  361. }
  362. </script>
  363. <style>
  364. .flow-view:not(.activity) .flow-node:hover,
  365. .flow-node--dragging {
  366. cursor:move;
  367. }
  368. .flow-view:not(.activity) .flow-node {
  369. transition: all var(--transition-speed-fast);
  370. }
  371. .flow-node__body {
  372. opacity:0.9;
  373. transition: all var(--transition-speed-fast);
  374. }
  375. .flow-node[status=running] .flow-node__body{
  376. stroke: yellow !important;
  377. }
  378. .flow-node[status=error] .flow-node__body{
  379. stroke: red !important;
  380. }
  381. .flow-node[status=finished] .flow-node__body{
  382. stroke: green !important;
  383. }
  384. /* opacity with the sockets */
  385. .flow-node__src-detail {
  386. pointer-events:none;
  387. user-select:none;
  388. font-size:15px;
  389. opacity:0;
  390. fill: var(--normal);
  391. transition: all var(--transition-speed-fast);
  392. }
  393. .flow-linking .flow-node__src-detail {
  394. opacity:1;
  395. }
  396. /* sockets */
  397. .flow-node__socket {
  398. pointer-events: none;
  399. stroke-width:1;
  400. opacity:0;
  401. transition: all var(--transition-speed-fast);
  402. }
  403. .flow-view:not(.activity) .flow-node__socket:hover {
  404. stroke-width:10;
  405. cursor:pointer;
  406. }
  407. .flow-node__socket-detail {
  408. opacity:1;
  409. stroke-width:0 !important;
  410. fill: #fff !default;
  411. transition: all var(--transition-speed-fast);
  412. }
  413. .flow-node__socket-detail--background {
  414. stroke-width:0;
  415. transition:all var(--transition-speed-fast);
  416. }
  417. .flow-node__socket--match {
  418. cursor:pointer;
  419. stroke-width:10;
  420. }
  421. .flow-node__socket--withvalue {
  422. fill: var(--node-socket--withvalue) !important;
  423. stroke: var(--node-socket-withvalue) !important;
  424. }
  425. .flow-linking .flow-node__socket {
  426. opacity:1;
  427. pointer-events: inherit;
  428. }
  429. .flow-node__socket--match {
  430. opacity:1;
  431. pointer-events: inherit;
  432. }
  433. /* triggers */
  434. .flow-node__trigger {
  435. pointer-events:none;
  436. opacity:0;
  437. transition: all var(--transition-speed-fast);
  438. }
  439. .flow-triggers .flow-node__trigger {
  440. pointer-events:inherit;
  441. opacity:1;
  442. }
  443. .flow-view:not(.activity) .flow-node__trigger:hover {
  444. stroke-width:10;
  445. cursor:pointer;
  446. }
  447. .flow-node__trigger--match {
  448. stroke-width:10;
  449. opacity:1;
  450. pointer-events: inherit;
  451. }
  452. /*
  453. Override flow-node
  454. for hidden
  455. */
  456. .flow-node__label {
  457. stroke:none;
  458. pointer-events:none;
  459. user-select:none;
  460. fill:#333 !default;
  461. }
  462. .flow-node__selection {
  463. opacity:0;
  464. stroke-width:3;
  465. stroke: var(--node-selection);
  466. stroke-dasharray:2,2;
  467. pointer-events:none;
  468. transition: all var(--transition-speed-fast);
  469. fill: var(--node-selection);
  470. }
  471. .flow-node--selected .flow-node__selection {
  472. opacity:0.6;
  473. animation: flow-node--selected__dash 3s linear infinite;
  474. }
  475. @keyframes flow-node--selected__dash {
  476. from { stroke-dashoffset:100;}
  477. to { stroke-dashoffset: 0; }
  478. }
  479. </style>