Browse Source

feat: duplicate multiple blocks

Ahmad Kholid 3 years ago
parent
commit
9f83baccf9

+ 183 - 118
src/components/newtab/workflow/WorkflowBuilder.vue

@@ -138,6 +138,7 @@ export default {
     let isDragging = false;
     let selectedElements = [];
 
+    const selection = shallowRef(null);
     const editor = shallowRef(null);
     const contextMenu = reactive({
       items: [],
@@ -313,27 +314,87 @@ export default {
     function deleteBlock() {
       editor.value.removeNodeId(contextMenu.data);
     }
-    function duplicateBlock(id) {
-      const { name, pos_x, pos_y, data, html, inputs, outputs } =
-        editor.value.getNodeFromId(id || contextMenu.data.substr(5));
-
-      if (name === 'trigger') return;
-
-      const { outputs: defOutputs, inputs: defInputs } = tasks[name];
-      const blockInputs = Object.keys(inputs).length || defInputs;
-      const blockOutputs = Object.keys(outputs).length || defOutputs;
-
-      editor.value.addNode(
-        name,
-        blockInputs,
-        blockOutputs,
-        pos_x + 50,
-        pos_y + 100,
-        name,
-        data,
-        html,
-        'vue'
-      );
+    function clearSelectedElements() {
+      selection.value.clearSelection();
+      selectedElements.forEach(({ el }) => {
+        el.classList.remove('selected-list');
+      });
+      selectedElements = [];
+      activeNode = null;
+    }
+    function duplicateBlock(nodeId) {
+      const nodes = new Map();
+      const addNode = (id) => {
+        const node = editor.value.getNodeFromId(id);
+
+        if (node.name === 'trigger') return;
+
+        nodes.set(node.id, node);
+      };
+
+      if (nodeId) addNode(nodeId);
+      else if (activeNode) addNode(activeNode.id);
+
+      selectedElements.forEach((node) => {
+        if (activeNode?.id === node.id || nodeId === node.id) return;
+
+        addNode(node.id);
+      });
+
+      const nodesOutputs = [];
+
+      clearSelectedElements();
+
+      nodes.forEach((node) => {
+        const { outputs, inputs } = tasks[node.name];
+
+        const inputsLen = Object.keys(node.inputs).length;
+        const outputsLen = Object.keys(node.outputs).length;
+
+        const blockInputs = inputsLen || inputs;
+        const blockOutputs = outputsLen || outputs;
+
+        const newNodeId = editor.value.addNode(
+          node.name,
+          blockInputs,
+          blockOutputs,
+          node.pos_x + 25,
+          node.pos_y + 70,
+          node.name,
+          node.data,
+          node.html,
+          'vue'
+        );
+
+        nodes.set(node.id, { ...nodes.get(node.id), newId: newNodeId });
+
+        const nodeElement = document.querySelector(`#node-${newNodeId}`);
+        nodeElement.classList.add('selected-list');
+        selectedElements.push({
+          id: newNodeId,
+          el: nodeElement,
+          posY: parseInt(nodeElement.style.top, 10),
+          posX: parseInt(nodeElement.style.left, 10),
+        });
+
+        if (outputsLen > 0) {
+          nodesOutputs.push({ id: newNodeId, outputs: node.outputs });
+        }
+      });
+
+      if (nodesOutputs.length < 1) return;
+
+      nodesOutputs.forEach(({ id, outputs }) => {
+        Object.keys(outputs).forEach((key) => {
+          outputs[key].connections.forEach((connection) => {
+            const node = nodes.get(connection.node);
+
+            if (!node) return;
+
+            editor.value.addConnection(id, node.newId, key, 'input_1');
+          });
+        });
+      });
     }
     function checkWorkflowData() {
       if (!editor.value) return;
@@ -361,7 +422,7 @@ export default {
       localStorage.setItem('editor-states', JSON.stringify(editorStates));
     }
     function initSelectArea() {
-      const selection = new SelectionArea({
+      selection.value = new SelectionArea({
         container: '#drawflow',
         startareas: ['#drawflow'],
         boundaries: ['#drawflow'],
@@ -373,7 +434,7 @@ export default {
         },
       });
 
-      selection.on('beforestart', ({ event }) => {
+      selection.value.on('beforestart', ({ event }) => {
         if (!event.ctrlKey) return false;
 
         editor.value.editor_mode = 'fixed';
@@ -381,10 +442,10 @@ export default {
 
         return true;
       });
-      selection.on('move', () => {
+      selection.value.on('move', () => {
         hasDragged = true;
       });
-      selection.on('stop', (event) => {
+      selection.value.on('stop', (event) => {
         event.store.selected.forEach((el) => {
           const isExists = selectedElements.some((item) =>
             item.el.isEqualNode(el)
@@ -407,120 +468,115 @@ export default {
         }, 500);
       });
     }
-    function clearSelectedElements() {
-      selectedElements.forEach(({ el }) => {
-        el.classList.remove('selected-list');
-      });
-      selectedElements = [];
-      activeNode = null;
-    }
-
-    useShortcut('editor:duplicate-block', () => {
-      const selectedElement = document.querySelector('.drawflow-node.selected');
-
-      if (!selectedElement) return;
-
-      duplicateBlock(selectedElement.id.substr(5));
-    });
-
-    watch(() => props.isShared, checkWorkflowData);
-
-    onMounted(() => {
-      const context = getCurrentInstance().appContext.app._context;
-      const element = document.querySelector('#drawflow');
-
-      element.addEventListener('mousedown', ({ target }) => {
-        const nodeEl = target.closest('.drawflow-node');
-        if (!nodeEl) return;
-
-        if (nodeEl.classList.contains('selected-list')) {
-          activeNode = {
-            el: nodeEl,
-            id: nodeEl.id.slice(5),
-            posY: parseInt(nodeEl.style.top, 10),
-            posX: parseInt(nodeEl.style.left, 10),
-          };
-        }
-
-        isDragging = true;
-      });
-      element.addEventListener('mouseup', ({ target }) => {
-        editor.value.editor_mode = 'edit';
+    function onMouseup({ target }) {
+      editor.value.editor_mode = 'edit';
 
-        const isNodeEl = target.closest('.drawflow-node');
-        if (!isNodeEl) return;
+      const isNodeEl = target.closest('.drawflow-node');
+      if (!isNodeEl) return;
 
-        const getPosition = (el) => {
-          return {
-            posY: parseInt(el.style.top, 10),
-            posX: parseInt(el.style.left, 10),
-          };
+      const getPosition = (el) => {
+        return {
+          posY: parseInt(el.style.top, 10),
+          posX: parseInt(el.style.left, 10),
         };
+      };
 
-        selectedElements.forEach(({ el }, index) => {
-          Object.assign(selectedElements[index], getPosition(el));
-        });
+      selectedElements.forEach(({ el }, index) => {
+        Object.assign(selectedElements[index], getPosition(el));
+      });
 
-        if (activeNode) Object.assign(activeNode, getPosition(activeNode.el));
+      if (activeNode) Object.assign(activeNode, getPosition(activeNode.el));
 
-        isDragging = false;
-      });
-      element.addEventListener('click', ({ ctrlKey, target }) => {
-        const nodeEl = target.closest('.drawflow-node');
-        if (!nodeEl) {
-          if (!hasDragged) clearSelectedElements();
-          return;
-        }
+      isDragging = false;
+    }
+    function onMousedown({ target }) {
+      const nodeEl = target.closest('.drawflow-node');
+      if (!nodeEl) return;
 
-        const nodeProperties = {
+      if (nodeEl.classList.contains('selected-list')) {
+        activeNode = {
           el: nodeEl,
           id: nodeEl.id.slice(5),
           posY: parseInt(nodeEl.style.top, 10),
           posX: parseInt(nodeEl.style.left, 10),
         };
+      }
 
-        if (!ctrlKey && !hasDragged) {
-          clearSelectedElements();
+      isDragging = true;
+    }
+    function onClick({ ctrlKey, target }) {
+      const nodeEl = target.closest('.drawflow-node');
+      if (!nodeEl) {
+        if (!hasDragged) clearSelectedElements();
+        return;
+      }
 
-          activeNode = nodeProperties;
-          nodeEl.classList.add('selected-list');
-          selectedElements = [nodeProperties];
-          hasDragged = false;
+      const nodeProperties = {
+        el: nodeEl,
+        id: nodeEl.id.slice(5),
+        posY: parseInt(nodeEl.style.top, 10),
+        posX: parseInt(nodeEl.style.left, 10),
+      };
 
-          return;
-        }
+      if (!ctrlKey && !hasDragged) {
+        clearSelectedElements();
+
+        activeNode = nodeProperties;
+        nodeEl.classList.add('selected-list');
+        selectedElements = [nodeProperties];
         hasDragged = false;
 
-        if (!ctrlKey) return;
+        return;
+      }
+      hasDragged = false;
 
-        const nodeIndex = selectedElements.findIndex(({ el }) =>
-          nodeEl.isEqualNode(el)
-        );
-        if (nodeIndex !== -1) {
-          setTimeout(() => {
-            nodeEl.classList.remove('selected-list', 'selected');
-          }, 400);
-          selectedElements.splice(nodeIndex, 1);
-        } else {
-          nodeEl.classList.add('selected-list');
-          selectedElements.push(nodeProperties);
-        }
-      });
-      element.addEventListener('keyup', ({ key, target }) => {
-        const isAnInput =
-          ['INPUT', 'TEXTAREA'].includes(target.tagName) &&
-          target.isContentEditable;
+      if (!ctrlKey) return;
 
-        if (key !== 'Delete' || isAnInput) return;
+      const nodeIndex = selectedElements.findIndex(({ el }) =>
+        nodeEl.isEqualNode(el)
+      );
+      if (nodeIndex !== -1) {
+        setTimeout(() => {
+          nodeEl.classList.remove('selected-list', 'selected');
+        }, 400);
+        selectedElements.splice(nodeIndex, 1);
+      } else {
+        nodeEl.classList.add('selected-list');
+        selectedElements.push(nodeProperties);
+      }
+    }
+    function onKeyup({ key, target }) {
+      const isAnInput =
+        ['INPUT', 'TEXTAREA'].includes(target.tagName) &&
+        target.isContentEditable;
 
-        selectedElements.forEach(({ id }) => {
-          editor.value.removeNodeId(`node-${id}`);
-        });
+      if (key !== 'Delete' || isAnInput) return;
 
-        selectedElements = [];
-        activeNode = null;
+      selectedElements.forEach(({ id }) => {
+        editor.value.removeNodeId(`node-${id}`);
       });
 
+      selectedElements = [];
+      activeNode = null;
+    }
+
+    useShortcut('editor:duplicate-block', () => {
+      if (!activeNode && selectedElements.length <= 0) return;
+
+      duplicateBlock();
+    });
+
+    watch(() => props.isShared, checkWorkflowData);
+
+    onMounted(() => {
+      const context = getCurrentInstance().appContext.app._context;
+      const element = document.querySelector('#drawflow');
+
+      element.addEventListener('mousedown', onMousedown);
+      element.addEventListener('mouseup', onMouseup);
+      element.addEventListener('click', onClick);
+      element.addEventListener('keyup', onKeyup);
+
       editor.value = drawflow(element, {
         context,
         options: {
@@ -685,7 +741,16 @@ export default {
         refreshConnection();
       }, 500);
     });
-    onBeforeUnmount(saveEditorState);
+    onBeforeUnmount(() => {
+      const element = document.querySelector('#drawflow');
+
+      element.removeEventListener('mousedown', onMousedown);
+      element.removeEventListener('mouseup', onMouseup);
+      element.removeEventListener('click', onClick);
+      element.removeEventListener('keyup', onKeyup);
+
+      saveEditorState();
+    });
 
     return {
       t,
@@ -695,7 +760,7 @@ export default {
       handleDragOver,
       contextMenuHandler: {
         deleteBlock,
-        duplicateBlock: () => duplicateBlock(),
+        duplicateBlock: () => duplicateBlock(contextMenu.data.substr(5)),
       },
     };
   },

+ 3 - 1
src/components/newtab/workflow/WorkflowEditBlock.vue

@@ -128,7 +128,9 @@ export default {
       if (!tasks[name].autocomplete) return;
 
       tasks[name].autocomplete.forEach((key) => {
-        if (!data[key]) return;
+        const variableNotAssigned =
+          key === 'variableName' && !data.assignVariable;
+        if (!data[key] || variableNotAssigned) return;
 
         autocompleteData.value[id].add(`${dataKeywords[key]}@${data[key]}`);
       });

+ 1 - 2
src/newtab/pages/workflows/[id].vue

@@ -880,8 +880,7 @@ onMounted(() => {
     return;
   }
 
-  state.drawflow =
-    workflow.value.drawflow || '{drawflow: {Home: { data: {} }}}';
+  state.drawflow = workflow.value.drawflow;
   state.showSidebar =
     JSON.parse(localStorage.getItem('workflow:sidebar')) ?? true;