Browse Source

feat: add wait connections block

Ahmad Kholid 3 years ago
parent
commit
584b9a8a26

+ 70 - 0
src/background/workflow-engine/blocks-handler/handler-wait-connections.js

@@ -0,0 +1,70 @@
+import { getBlockConnection } from '../helper';
+
+async function waitConnections({ data, outputs, inputs, id }, { prevBlock }) {
+  return new Promise((resolve) => {
+    let timeout;
+    let resolved = false;
+
+    const nextBlockId = getBlockConnection({ outputs });
+    const destroyWorker =
+      data.specificFlow && prevBlock.id !== data.flowBlockId;
+
+    const registerConnections = () => {
+      inputs.input_1.connections.forEach(({ node }) => {
+        this.engine.waitConnections[id][node] = {
+          isHere: false,
+          isContinue: false,
+        };
+      });
+    };
+    const checkConnections = () => {
+      if (resolved) return;
+
+      const state = Object.values(this.engine.waitConnections[id]);
+      const isAllHere = state.every((worker) => worker.isHere);
+
+      if (isAllHere) {
+        this.engine.waitConnections[id][prevBlock.id].isContinue = true;
+        const allContinue = state.every((worker) => worker.isContinue);
+
+        if (allContinue) {
+          registerConnections();
+        }
+
+        clearTimeout(timeout);
+
+        resolve({
+          data: '',
+          nextBlockId,
+          destroyWorker,
+        });
+      } else {
+        setTimeout(() => {
+          checkConnections();
+        }, 1000);
+      }
+    };
+
+    if (!this.engine.waitConnections[id]) {
+      this.engine.waitConnections[id] = {};
+
+      registerConnections();
+    }
+
+    this.engine.waitConnections[id][prevBlock.id].isHere = true;
+
+    timeout = setTimeout(() => {
+      resolved = true;
+
+      resolve({
+        data: '',
+        nextBlockId,
+        destroyWorker,
+      });
+    }, data.timeout);
+
+    checkConnections();
+  });
+}
+
+export default waitConnections;

+ 1 - 0
src/background/workflow-engine/engine.js

@@ -18,6 +18,7 @@ class WorkflowEngine {
     this.saveLog = workflow.settings?.saveLog ?? true;
 
     this.workers = new Map();
+    this.waitConnections = {};
 
     this.isDestroyed = false;
     this.isUsingProxy = false;

+ 8 - 8
src/background/workflow-engine/worker.js

@@ -9,7 +9,7 @@ import executeContentScript from './execute-content-script';
 
 class Worker {
   constructor(engine) {
-    this.id = nanoid();
+    this.id = nanoid(5);
     this.engine = engine;
     this.settings = engine.workflow.settings;
 
@@ -79,16 +79,13 @@ class Worker {
       if (index === 0) {
         this.executeBlock(this.engine.blocks[node], prevBlockData);
       } else {
-        const cloneState = cloneDeep({
+        const state = cloneDeep({
           windowId: this.windowId,
+          loopList: this.loopList,
           activeTab: this.activeTab,
+          repeatedTasks: this.repeatedTasks,
           preloadScripts: this.preloadScripts,
         });
-        const state = {
-          ...cloneState,
-          loopList: this.loopList,
-          repeatedTasks: this.repeatedTasks,
-        };
 
         this.engine.addWorker({
           state,
@@ -109,6 +106,7 @@ class Worker {
       return;
     }
 
+    const prevBlock = this.currentBlock;
     this.currentBlock = block;
 
     if (!isRetry) {
@@ -150,6 +148,7 @@ class Worker {
         prevBlockData,
         type: status,
         name: block.name,
+        workerId: this.id,
         description: block.data.description,
         replacedValue: replacedBlock.replacedValue,
         duration: Math.round(Date.now() - startExecuteTime),
@@ -168,6 +167,7 @@ class Worker {
       } else {
         result = await handler.call(this, replacedBlock, {
           refData,
+          prevBlock,
           prevBlockData,
         });
 
@@ -188,7 +188,7 @@ class Worker {
         nodeConnections = result.nextBlockId.connections;
       }
 
-      if (nodeConnections.length > 0) {
+      if (nodeConnections.length > 0 && !result.destroyWorker) {
         setTimeout(() => {
           this.executeNextBlocks(nodeConnections);
         }, blockDelay);

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

@@ -38,6 +38,9 @@
       :key="data.blockId"
       v-model:data="blockData"
       :block-id="data.blockId"
+      v-bind="{
+        connections: data.id === 'wait-connections' ? data.connections : null,
+      }"
     />
     <on-block-error
       v-if="!excludeOnError.includes(data.id)"

+ 1 - 12
src/components/newtab/workflow/WorkflowGlobalData.vue

@@ -1,14 +1,6 @@
 <template>
   <div class="global-data">
-    <a
-      href="https://docs.automa.site/api-reference/reference-data.html"
-      target="_blank"
-      rel="noopener"
-      class="inline-block text-primary"
-    >
-      {{ t('message.useDynamicData') }}
-    </a>
-    <p class="float-right clear-both" title="Characters limit">
+    <p class="text-right" title="Characters limit">
       {{ globalData.length }}/{{ maxLength.toLocaleString() }}
     </p>
     <shared-codemirror
@@ -20,7 +12,6 @@
 </template>
 <script setup>
 import { ref, watch, defineAsyncComponent } from 'vue';
-import { useI18n } from 'vue-i18n';
 import { debounce } from '@/utils/helper';
 
 const SharedCodemirror = defineAsyncComponent(() =>
@@ -35,8 +26,6 @@ const props = defineProps({
 });
 const emit = defineEmits(['update']);
 
-const { t } = useI18n();
-
 const maxLength = 1e4;
 const globalData = ref(`${props.workflow.globalData}`);
 

+ 66 - 0
src/components/newtab/workflow/edit/EditWaitConnections.vue

@@ -0,0 +1,66 @@
+<template>
+  <div class="mb-4">
+    <ui-textarea
+      :model-value="data.description"
+      class="w-full"
+      :placeholder="t('common.description')"
+      @change="updateData({ description: $event })"
+    />
+    <ui-input
+      :model-value="data.timeout"
+      :label="t('workflow.blocks.base.timeout')"
+      placeholder="10000"
+      type="number"
+      class="w-full mt-1"
+      @change="updateData({ timeout: +$event })"
+    />
+    <ui-checkbox
+      :model-value="data.specificFlow"
+      class="mt-4"
+      @change="updateData({ specificFlow: $event })"
+    >
+      {{ t('workflow.blocks.wait-connections.specificFlow') }}
+    </ui-checkbox>
+    <ui-select
+      v-if="data.specificFlow"
+      :model-value="data.flowBlockId"
+      :label="t('workflow.blocks.wait-connections.selectFlow')"
+      class="w-full mt-1"
+      @change="updateData({ flowBlockId: $event })"
+    >
+      <option v-for="item in connections" :key="item.id" :value="item.id">
+        {{ item.name }}
+      </option>
+    </ui-select>
+  </div>
+</template>
+<script setup>
+import { onMounted } from 'vue';
+import { useI18n } from 'vue-i18n';
+
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => ({}),
+  },
+  connections: {
+    type: Array,
+    default: () => [],
+  },
+});
+const emit = defineEmits(['update:data']);
+
+const { t } = useI18n();
+
+function updateData(value) {
+  emit('update:data', { ...props.data, ...value });
+}
+
+onMounted(() => {
+  if (props.data.flowBlockId) return;
+
+  updateData({
+    flowBlockId: props.connections[0]?.id || '',
+  });
+});
+</script>

+ 2 - 0
src/lib/v-remixicon.js

@@ -90,6 +90,7 @@ import {
   riArrowLeftSLine,
   riFullscreenLine,
   riFlashlightLine,
+  riTimerFlashLine,
   riBaseStationLine,
   riInformationLine,
   riArrowUpDownLine,
@@ -200,6 +201,7 @@ export const icons = {
   riArrowLeftSLine,
   riFullscreenLine,
   riFlashlightLine,
+  riTimerFlashLine,
   riBaseStationLine,
   riInformationLine,
   riArrowUpDownLine,

+ 7 - 0
src/locales/en/blocks.json

@@ -12,6 +12,7 @@
       "base": {
         "moveToGroup": "Move block to blocks group",
         "selector": "Element selector",
+        "timeout": "Timeout (milliseconds)",
         "toggle": {
           "enable": "Enable block",
           "disable": "Disable block",
@@ -74,6 +75,12 @@
           }
         }
       },
+      "wait-connections": {
+        "name": "Wait connections",
+        "description": "Wait for all connections before continuing to the next block",
+        "specificFlow": "Only continue a specific flow",
+        "selectFlow": "Select flow"
+      },
       "delete-data": {
         "name": "Delete data",
         "description": "Delete table or variable data",

+ 19 - 0
src/newtab/pages/workflows/[id].vue

@@ -777,6 +777,25 @@ function editBlock(data) {
   state.isEditBlock = true;
   state.showSidebar = true;
   state.blockData = defu(data, tasks[data.id] || {});
+
+  if (data.id === 'wait-connections') {
+    const node = editor.value.getNodeFromId(data.blockId);
+    const connections = node.inputs.input_1.connections.map((input) => {
+      const inputNode = editor.value.getNodeFromId(input.node);
+      const nodeDesc = inputNode.data.description;
+
+      let name = t(`workflow.blocks.${inputNode.name}.name`);
+
+      if (nodeDesc) name += ` (${nodeDesc})`;
+
+      return {
+        name,
+        id: input.node,
+      };
+    });
+
+    state.blockData.connections = connections;
+  }
 }
 function handleEditorDataChanged() {
   state.isDataChanged = true;

+ 20 - 0
src/utils/shared.js

@@ -913,6 +913,26 @@ export const tasks = {
       deleteList: [],
     },
   },
+  'wait-connections': {
+    name: 'Wait connections',
+    description: 'Wait for all connections before continuing to the next block',
+    icon: 'riTimerFlashLine',
+    editComponent: 'EditWaitConnections',
+    component: 'BlockBasic',
+    category: 'general',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: true,
+    maxConnection: 1,
+    disableEdit: true,
+    data: {
+      disableBlock: false,
+      description: '',
+      timeout: 10000,
+      specificFlow: false,
+      flowBlockId: '',
+    },
+  },
 };
 
 export const categories = {