WorkflowBuilder.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <template>
  2. <div
  3. id="drawflow"
  4. class="parent-drawflow relative"
  5. @drop="dropHandler"
  6. @dragover.prevent
  7. >
  8. <slot></slot>
  9. <div class="absolute z-10 p-4 bottom-0 left-0">
  10. <button class="p-2 rounded-lg bg-white mr-2" @click="editor.zoom_reset()">
  11. <v-remixicon name="riFullscreenLine" />
  12. </button>
  13. <div class="rounded-lg bg-white inline-block">
  14. <button class="p-2 rounded-lg relative z-10" @click="editor.zoom_out()">
  15. <v-remixicon name="riSubtractLine" />
  16. </button>
  17. <hr class="h-6 border-r inline-block" />
  18. <button class="p-2 rounded-lg" @click="editor.zoom_in()">
  19. <v-remixicon name="riAddLine" />
  20. </button>
  21. </div>
  22. </div>
  23. </div>
  24. </template>
  25. <script>
  26. /* eslint-disable camelcase */
  27. import { onMounted, shallowRef, getCurrentInstance } from 'vue';
  28. import emitter from 'tiny-emitter/instance';
  29. import { tasks } from '@/utils/shared';
  30. import drawflow from '@/lib/drawflow';
  31. export default {
  32. props: {
  33. data: {
  34. type: [Object, String],
  35. default: null,
  36. },
  37. },
  38. emits: ['load', 'deleteBlock'],
  39. setup(props, { emit }) {
  40. const editor = shallowRef(null);
  41. function dropHandler({ dataTransfer, clientX, clientY }) {
  42. const block = JSON.parse(dataTransfer.getData('block') || null);
  43. const isTriggerExists =
  44. block.id === 'trigger' &&
  45. editor.value.getNodesFromName('trigger').length !== 0;
  46. if (!block || isTriggerExists) return;
  47. const xPosition =
  48. clientX *
  49. (editor.value.precanvas.clientWidth /
  50. (editor.value.precanvas.clientWidth * editor.value.zoom)) -
  51. editor.value.precanvas.getBoundingClientRect().x *
  52. (editor.value.precanvas.clientWidth /
  53. (editor.value.precanvas.clientWidth * editor.value.zoom));
  54. const yPosition =
  55. clientY *
  56. (editor.value.precanvas.clientHeight /
  57. (editor.value.precanvas.clientHeight * editor.value.zoom)) -
  58. editor.value.precanvas.getBoundingClientRect().y *
  59. (editor.value.precanvas.clientHeight /
  60. (editor.value.precanvas.clientHeight * editor.value.zoom));
  61. editor.value.addNode(
  62. block.id,
  63. block.inputs,
  64. block.outputs,
  65. xPosition,
  66. yPosition,
  67. block.id,
  68. block.data,
  69. block.component,
  70. 'vue'
  71. );
  72. emitter.emit('editor:data-changed');
  73. }
  74. function isInputAllowed(allowedInputs, input) {
  75. if (typeof allowedInputs === 'boolean') return allowedInputs;
  76. return allowedInputs.some((item) => {
  77. if (item.startsWith('#')) {
  78. return tasks[input].category === item.substr(1);
  79. }
  80. return item === input;
  81. });
  82. }
  83. onMounted(() => {
  84. /* eslint-disable-next-line */
  85. const context = getCurrentInstance().appContext.app._context;
  86. const element = document.querySelector('#drawflow');
  87. editor.value = drawflow(element, { context, options: { reroute: true } });
  88. editor.value.start();
  89. emit('load', editor.value);
  90. if (props.data) {
  91. const data =
  92. typeof props.data === 'string' ? JSON.parse(props.data) : props.data;
  93. editor.value.import(data);
  94. } else {
  95. editor.value.addNode(
  96. 'trigger',
  97. 0,
  98. 1,
  99. 50,
  100. 300,
  101. 'trigger',
  102. { type: 'manual' },
  103. 'BlockBasic',
  104. 'vue'
  105. );
  106. }
  107. editor.value.on('nodeRemoved', (id) => {
  108. emit('deleteBlock', id);
  109. });
  110. editor.value.on(
  111. 'connectionCreated',
  112. ({ output_id, input_id, output_class, input_class }) => {
  113. const { outputs } = editor.value.getNodeFromId(output_id);
  114. const { name: inputName } = editor.value.getNodeFromId(input_id);
  115. const { allowedInputs, maxConnection } = tasks[inputName];
  116. const isAllowed = isInputAllowed(allowedInputs, inputName);
  117. const isMaxConnections =
  118. outputs[output_class]?.connections.length > maxConnection;
  119. if (!isAllowed || isMaxConnections) {
  120. editor.value.removeSingleConnection(
  121. output_id,
  122. input_id,
  123. output_class,
  124. input_class
  125. );
  126. }
  127. emitter.emit('editor:data-changed');
  128. }
  129. );
  130. editor.value.on('connectionRemoved', () => {
  131. emitter.emit('editor:data-changed');
  132. });
  133. });
  134. return {
  135. editor,
  136. dropHandler,
  137. };
  138. },
  139. };
  140. </script>
  141. <style>
  142. #drawflow {
  143. background-image: url('@/assets/images/tile.png');
  144. background-size: 35px;
  145. }
  146. </style>