node.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <template>
  2. <g
  3. class="flow-node"
  4. :class="{'flow-node--dragging':dragging}"
  5. @mousedown="$emit('nodePointerDown',$event)"
  6. @dblclick="$emit('nodeDoubleClick',$event)"
  7. >
  8. <rect
  9. ref="body"
  10. class="flow-node__body"
  11. :class="{'flow-node__body--dragging':dragging}"
  12. v-bind="bodyProps"
  13. />
  14. <text
  15. ref="label"
  16. class="flow-node__label"
  17. v-bind="labelProps">
  18. <tspan x="0" dy="1em" v-for="s in labelWrap" text-anchor="middle">
  19. {{ s }}
  20. </tspan>
  21. <!--{{ label }}-->
  22. </text>
  23. <!-- input -->
  24. <circle
  25. class="flow-node__socket flow-node__socket--inputs"
  26. v-for="(inp,i) in inputs"
  27. v-bind="inputProps(i)"
  28. :data-nodeid="id"
  29. :data-in="i"
  30. :class="{'flow-node__socket--match': match.in && (inp == match.in || match.in == 'any' || input=='any')}"
  31. :key="'in'+i"
  32. @mousedown.stop.prevent="socketPointerDown($event, {in:i})"
  33. />
  34. <!-- output -->
  35. <circle
  36. v-if="output"
  37. class="flow-node__socket flow-node__socket--outputs"
  38. :data-nodeid="id"
  39. :data-out="0"
  40. :key="'out'+0"
  41. :class="{ 'flow-node__socket--match': match.out && (output == match.out || match.out == 'any' || output == 'any')}"
  42. v-bind="outputProps(0)"
  43. @mousedown.stop.prevent="socketPointerDown($event, {out:0})"
  44. />
  45. <text
  46. class="flow-node__socket-detail"
  47. v-for="(inp,i) in inputs"
  48. text-anchor="end"
  49. :x="inputPos(i).x - 10"
  50. :y="inputPos(i).y+5"
  51. >{{ inp }}</text>
  52. <text
  53. class="flow-node__socket-detail"
  54. :x="outputPos(0).x + 10"
  55. :y="outputPos(0).y+5">
  56. {{ output }}
  57. </text>
  58. </g>
  59. </template>
  60. <script>
  61. export default {
  62. name: 'FlowNode',
  63. props: {
  64. 'id': {type: String, required: true},
  65. 'label': {type: String, default: ''},
  66. 'inputs': {type: Array, default: () => []},
  67. 'output': {type: String, default: ''},
  68. 'match': {type: Object, default: () => {}},
  69. 'dragging': {type: Boolean, default: false},
  70. 'nodeStyle': {type: Object, default: () => {}}
  71. /* 'type': {type: String, default: ''},
  72. 'color': {type: String, default: '#777'},
  73. 'textColor': {type: String, default: '#fff'} */
  74. // 'value': {type: Object, default: () => {}}
  75. },
  76. data () {
  77. return {
  78. // maintain reference here?
  79. labelRect: {x: 0, y: 0, width: 0, height: 0},
  80. bodyRect: {x: 0, y: 0, width: 0, height: 0}
  81. }
  82. },
  83. computed: {
  84. labelWrap () {
  85. var ret = []
  86. const parts = this.label.split(' ', -1)
  87. let wrapThreshold = 10 // initial wrap threshold
  88. parts.forEach(n => {
  89. wrapThreshold = Math.max(n.length + 1, wrapThreshold)
  90. })
  91. ret.push(parts[0])
  92. for (let i = 1; i < parts.length; i++) {
  93. let ri = ret.length - 1 // last
  94. if (ret[ret.length - 1].length + parts[i].length > wrapThreshold) {
  95. ret.push(parts[i])
  96. } else {
  97. // we can add to same
  98. ret[ri] += ' ' + parts[i]
  99. }
  100. }
  101. return ret
  102. },
  103. labelProps () {
  104. return {
  105. x: 0,
  106. y: -0.1 + 'em',
  107. fill: this.textColor,
  108. // transform: `translate(${-this.labelRect.width / 2},${-this.labelRect.height / 2})`
  109. transform: `translate(0,${-this.labelRect.height / 2})`
  110. }
  111. },
  112. bodyProps () {
  113. let width = this.labelRect.width + 30
  114. let height = Math.max(this.labelRect.height + 20, 60)
  115. if (this.nodeStyle.type === 'circle') {
  116. height = Math.max(width, height)
  117. width = height
  118. }
  119. const rect = {
  120. x: -width / 2,
  121. y: -height / 2,
  122. width: width,
  123. height: height,
  124. stroke: this.nodeStyle.color,
  125. fill: this.nodeStyle.color
  126. }
  127. if (this.nodeStyle && this.nodeStyle.type === 'circle') {
  128. rect.rx = width
  129. rect.ry = width
  130. }
  131. return rect
  132. },
  133. inputProps () {
  134. return (i) => {
  135. const {x, y} = this.inputPos(i)
  136. return {
  137. transform: `translate(${x} ${y})`,
  138. r: 5
  139. }
  140. }
  141. },
  142. outputProps () {
  143. return (i) => {
  144. const {x, y} = this.outputPos(i)
  145. return {
  146. transform: `translate(${x} ${y})`,
  147. r: 5
  148. }
  149. }
  150. }
  151. },
  152. watch: {
  153. // the only thing now that affects geometry
  154. 'label' () {
  155. this.$nextTick(() => {
  156. this.labelRect = this.$refs.label.getBBox()
  157. })
  158. }
  159. },
  160. mounted () {
  161. // After render
  162. this.$nextTick(() => { // after mount we reupdate with new values
  163. if (!this.$refs.label) return
  164. this.labelRect = this.$refs.label.getBBox()
  165. this.$forceUpdate()
  166. })
  167. },
  168. methods: {
  169. inputPos (i) {
  170. const ilen = this.inputs.length
  171. if (ilen === 0) return {}
  172. const d = this.bodyProps.height / (ilen * 2)
  173. return {
  174. x: this.bodyProps.x + 7,
  175. y: this.bodyProps.y + d + (i * 2 * d)
  176. }
  177. },
  178. outputPos (i) {
  179. const rect = this.bodyProps
  180. return {
  181. x: rect.x + rect.width - 7,
  182. y: 0
  183. }
  184. },
  185. socketPointerDown (ev, socket) {
  186. this.$emit('socketPointerDown', ev, socket)
  187. }
  188. }
  189. }
  190. </script>
  191. <style>
  192. .flow-view:not(.activity) .flow-node:hover,
  193. .flow-node--dragging {
  194. cursor:move;
  195. }
  196. .flow-node__socket {
  197. pointer-events: none;
  198. stroke-width:1;
  199. opacity:0;
  200. transition: all .3s;
  201. }
  202. .flow-node__body {
  203. transition: all .3s;
  204. }
  205. .flow-view:not(.activity) .flow-node__socket:hover {
  206. stroke-width:10;
  207. cursor:pointer;
  208. }
  209. .flow-node__socket--match {
  210. cursor:pointer;
  211. stroke-width:10;
  212. }
  213. /*
  214. Override flow-node
  215. for hidden
  216. */
  217. .flow-linking .flow-node__socket {
  218. opacity:1;
  219. pointer-events: initial;
  220. }
  221. .flow-linking .flow-node__socket--match,
  222. .flow-node__socket--match {
  223. opacity:1;
  224. pointer-events: initial;
  225. }
  226. .flow-node__label {
  227. stroke:none;
  228. pointer-events:none;
  229. user-select:none;
  230. fill:#333 !default;
  231. }
  232. .flow-node__socket-detail {
  233. pointer-events:none;
  234. opacity:0;
  235. }
  236. .flow-view.flow-detail .flow-node__socket-detail {
  237. opacity:1;
  238. filter: url(#solid) /* doubt */
  239. }
  240. </style>