浏览代码

feat: update conditions block

Ahmad Kholid 3 年之前
父节点
当前提交
502f847dd8

+ 0 - 33
src/background/workflow-engine/blocks-handler/handler-condition.js

@@ -1,33 +0,0 @@
-import { getBlockConnection } from '../helper';
-import compareBlockValue from '@/utils/compare-block-value';
-
-function conditions({ data, outputs }, prevBlockData) {
-  return new Promise((resolve, reject) => {
-    if (data.conditions.length === 0) {
-      reject(new Error('Conditions is empty'));
-      return;
-    }
-
-    let outputIndex = data.conditions.length + 1;
-    let resultData = '';
-    const prevData = Array.isArray(prevBlockData)
-      ? prevBlockData[0]
-      : prevBlockData;
-
-    data.conditions.forEach(({ type, value }, index) => {
-      const result = compareBlockValue(type, prevData, value);
-
-      if (result) {
-        resultData = value;
-        outputIndex = index + 1;
-      }
-    });
-
-    resolve({
-      data: resultData,
-      nextBlockId: getBlockConnection({ outputs }, outputIndex),
-    });
-  });
-}
-
-export default conditions;

+ 46 - 0
src/background/workflow-engine/blocks-handler/handler-conditions.js

@@ -0,0 +1,46 @@
+import { getBlockConnection } from '../helper';
+import { replaceMustache } from '@/utils/helper';
+import { replaceMustacheHandler } from '@/utils/reference-data';
+import compareBlockValue from '@/utils/compare-block-value';
+
+function conditions({ data, outputs }, { prevBlockData, refData }) {
+  return new Promise((resolve, reject) => {
+    if (data.conditions.length === 0) {
+      reject(new Error('conditions-empty'));
+      return;
+    }
+
+    let resultData = '';
+    let isConditionMatch = false;
+    let outputIndex = data.conditions.length + 1;
+    const handleMustache = (match) => replaceMustacheHandler(match, refData);
+    const prevData = Array.isArray(prevBlockData)
+      ? prevBlockData[0]
+      : prevBlockData;
+
+    data.conditions.forEach(({ type, value, compareValue }, index) => {
+      if (isConditionMatch) return;
+
+      const firstValue = replaceMustache(
+        compareValue ?? prevData,
+        handleMustache
+      );
+      const secondValue = replaceMustache(value, handleMustache);
+
+      const isMatch = compareBlockValue(type, firstValue, secondValue);
+
+      if (isMatch) {
+        resultData = value;
+        outputIndex = index + 1;
+        isConditionMatch = true;
+      }
+    });
+
+    resolve({
+      data: resultData,
+      nextBlockId: getBlockConnection({ outputs }, outputIndex),
+    });
+  });
+}
+
+export default conditions;

+ 1 - 8
src/background/workflow-engine/blocks-handler/handler-interaction-block.js

@@ -1,17 +1,10 @@
 import { objectHasKey } from '@/utils/helper';
 import { objectHasKey } from '@/utils/helper';
 import { getBlockConnection } from '../helper';
 import { getBlockConnection } from '../helper';
 
 
-async function interactionHandler(block, prevBlockData) {
+async function interactionHandler(block, { refData }) {
   const nextBlockId = getBlockConnection(block);
   const nextBlockId = getBlockConnection(block);
 
 
   try {
   try {
-    const refData = {
-      prevBlockData,
-      dataColumns: this.data,
-      loopData: this.loopData,
-      globalData: this.globalData,
-      activeTabUrl: this.activeTabUrl,
-    };
     const data = await this._sendMessageToTab(
     const data = await this._sendMessageToTab(
       { ...block, refData },
       { ...block, refData },
       {
       {

+ 1 - 1
src/background/workflow-engine/blocks-handler/handler-loop-breakpoint.js

@@ -1,6 +1,6 @@
 import { getBlockConnection } from '../helper';
 import { getBlockConnection } from '../helper';
 
 
-function loopBreakpoint(block, prevBlockData) {
+function loopBreakpoint(block, { prevBlockData }) {
   const currentLoop = this.loopList[block.data.loopId];
   const currentLoop = this.loopList[block.data.loopId];
 
 
   return new Promise((resolve) => {
   return new Promise((resolve) => {

+ 6 - 5
src/background/workflow-engine/engine.js

@@ -333,18 +333,19 @@ class WorkflowEngine {
         : blockHandler;
         : blockHandler;
 
 
     if (handler) {
     if (handler) {
-      const blockDelay =
-        block.name === 'trigger' ? 0 : this.workflow.settings?.blockDelay || 0;
-      const replacedBlock = referenceData(block, {
+      const refData = {
         prevBlockData,
         prevBlockData,
         data: this.data,
         data: this.data,
         loopData: this.loopData,
         loopData: this.loopData,
         globalData: this.globalData,
         globalData: this.globalData,
         activeTabUrl: this.activeTabUrl,
         activeTabUrl: this.activeTabUrl,
-      });
+      };
+      const replacedBlock = referenceData(block, refData);
+      const blockDelay =
+        block.name === 'trigger' ? 0 : this.workflow.settings?.blockDelay || 0;
 
 
       handler
       handler
-        .call(this, replacedBlock, prevBlockData)
+        .call(this, replacedBlock, { prevBlockData, refData })
         .then((result) => {
         .then((result) => {
           clearTimeout(this.workflowTimeout);
           clearTimeout(this.workflowTimeout);
           this.workflowTimeout = null;
           this.workflowTimeout = null;

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

@@ -3,7 +3,7 @@ export function convertData(data, type) {
 
 
   switch (type) {
   switch (type) {
     case 'integer':
     case 'integer':
-      result = Number.isNaN(data) ? +data?.replace(/\D+/g, '') : data;
+      result = typeof data !== 'number' ? +data?.replace(/\D+/g, '') : data;
       break;
       break;
     case 'boolean':
     case 'boolean':
       result = Boolean(data);
       result = Boolean(data);

+ 61 - 66
src/components/block/BlockConditions.vue

@@ -14,15 +14,11 @@
         class="cursor-pointer mr-2"
         class="cursor-pointer mr-2"
         @click="editor.removeNodeId(`node-${block.id}`)"
         @click="editor.removeNodeId(`node-${block.id}`)"
       />
       />
-      <ui-button
-        :disabled="block.data.conditions && block.data.conditions.length > 4"
-        icon
-        variant="accent"
-        style="height: 37px; width: 37px"
-        @click="addComparison"
-      >
-        <v-remixicon name="riAddLine" class="inline-block" />
-      </ui-button>
+      <v-remixicon
+        name="riPencilLine"
+        class="inline-block cursor-pointer"
+        @click="editBlock"
+      />
     </div>
     </div>
     <div
     <div
       v-if="block.data.conditions && block.data.conditions.length !== 0"
       v-if="block.data.conditions && block.data.conditions.length !== 0"
@@ -35,39 +31,30 @@
       >
       >
         <v-remixicon
         <v-remixicon
           name="riDeleteBin7Line"
           name="riDeleteBin7Line"
-          class="mr-2 invisible group-hover:visible cursor-pointer"
-          @click="deleteComparison(index)"
+          class="mr-2 cursor-pointer group-hover:visible invisible"
+          @click="deleteCondition(index)"
         />
         />
-        <div class="flex items-center transition bg-input rounded-lg">
-          <select
-            v-model="block.data.conditions[index].type"
-            :title="getTitle(index)"
-            class="
-              bg-transparent
-              font-mono
-              z-10
-              p-2
-              text-center
-              transition
-              rounded-l-lg
-              appearance-none
-            "
-          >
-            <option
-              v-for="(name, type) in conditions"
-              :key="type"
-              :value="type"
-            >
-              {{ type }}
-            </option>
-          </select>
-          <div class="bg-gray-300 w-px" style="height: 30px"></div>
-          <input
-            v-model="block.data.conditions[index].value"
-            type="text"
-            placeholder="value"
-            class="p-2 flex-1 transition rounded-r-lg bg-transparent w-36"
-          />
+        <div
+          class="
+            flex
+            items-center
+            flex-1
+            p-2
+            bg-box-transparent
+            rounded-lg
+            overflow-hidden
+            w-44
+          "
+        >
+          <p class="w-5/12 text-overflow text-right">
+            {{ item.compareValue || '_____' }}
+          </p>
+          <p class="w-2/12 text-center mx-1 font-mono">
+            {{ item.type }}
+          </p>
+          <p class="w-5/12 text-overflow">
+            {{ item.value || '_____' }}
+          </p>
         </div>
         </div>
       </div>
       </div>
       <p
       <p
@@ -83,7 +70,7 @@
   </div>
   </div>
 </template>
 </template>
 <script setup>
 <script setup>
-import { watch, toRaw } from 'vue';
+import { watch, toRaw, onBeforeUnmount } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { useI18n } from 'vue-i18n';
 import emitter from 'tiny-emitter/instance';
 import emitter from 'tiny-emitter/instance';
 import { debounce } from '@/utils/helper';
 import { debounce } from '@/utils/helper';
@@ -101,36 +88,36 @@ const { t } = useI18n();
 const componentId = useComponentId('block-conditions');
 const componentId = useComponentId('block-conditions');
 const block = useEditorBlock(`#${componentId}`, props.editor);
 const block = useEditorBlock(`#${componentId}`, props.editor);
 
 
-const conditions = {
-  '==': 'equals',
-  '>': 'gt',
-  '>=': 'gte',
-  '<': 'lt',
-  '<=': 'lte',
-  '()': 'contains',
-};
-
-function getTitle(index) {
-  const type = conditions[block.data.conditions[index]?.type] || 'equals';
-
-  return t(`workflow.blocks.conditions.${type}`);
+function editBlock() {
+  emitter.emit('editor:edit-block', {
+    ...block.details,
+    data: block.data,
+    blockId: block.id,
+  });
 }
 }
-function addComparison() {
-  if (block.data.conditions.length >= 10) return;
+function addConditionEmit({ id }) {
+  if (id !== block.id) return;
 
 
-  block.data.conditions.push({ type: '==', value: '' });
+  const { length } = block.data.conditions;
 
 
-  if (block.data.conditions.length === 1) props.editor.addNodeOutput(block.id);
+  if (length >= 10) return;
+  if (length === 1) props.editor.addNodeOutput(block.id);
 
 
   props.editor.addNodeOutput(block.id);
   props.editor.addNodeOutput(block.id);
 }
 }
-function deleteComparison(index) {
-  block.data.conditions.splice(index, 1);
+function deleteConditionEmit({ index, id }) {
+  if (id !== block.id) return;
 
 
   props.editor.removeNodeOutput(block.id, `output_${index + 1}`);
   props.editor.removeNodeOutput(block.id, `output_${index + 1}`);
+
   if (block.data.conditions.length === 0)
   if (block.data.conditions.length === 0)
     props.editor.removeNodeOutput(block.id, `output_1`);
     props.editor.removeNodeOutput(block.id, `output_1`);
 }
 }
+function deleteCondition(index) {
+  block.data.conditions.splice(index, 1);
+
+  deleteConditionEmit({ index, id: block.id });
+}
 
 
 watch(
 watch(
   () => block.data.conditions,
   () => block.data.conditions,
@@ -141,12 +128,20 @@ watch(
 
 
     props.editor.updateConnectionNodes(`node-${block.id}`);
     props.editor.updateConnectionNodes(`node-${block.id}`);
 
 
-    if (oldValue) {
-      emitter.emit('editor:data-changed', block.id);
-    }
+    if (!oldValue) return;
+
+    emitter.emit('editor:data-changed', block.id);
   }, 250),
   }, 250),
   { deep: true }
   { deep: true }
 );
 );
+
+emitter.on('conditions-block:add', addConditionEmit);
+emitter.on('conditions-block:delete', deleteConditionEmit);
+
+onBeforeUnmount(() => {
+  emitter.off('conditions-block:add', addConditionEmit);
+  emitter.off('conditions-block:delete', deleteConditionEmit);
+});
 </script>
 </script>
 <style>
 <style>
 .drawflow .drawflow-node.conditions .outputs {
 .drawflow .drawflow-node.conditions .outputs {
@@ -154,9 +149,9 @@ watch(
   transform: none !important;
   transform: none !important;
 }
 }
 .drawflow .drawflow-node.conditions .output {
 .drawflow .drawflow-node.conditions .output {
-  margin-bottom: 32px;
+  margin-bottom: 30px;
 }
 }
 .drawflow .drawflow-node.conditions .output:nth-last-child(2) {
 .drawflow .drawflow-node.conditions .output:nth-last-child(2) {
-  margin-bottom: 20px;
+  margin-bottom: 22px;
 }
 }
 </style>
 </style>

+ 133 - 0
src/components/newtab/workflow/edit/EditConditions.vue

@@ -0,0 +1,133 @@
+<template>
+  <div>
+    <ui-button variant="accent" class="mb-4" @click="addCondition">
+      Add condition
+    </ui-button>
+    <ul class="space-y-2">
+      <li
+        v-for="(condition, index) in conditions"
+        :key="index"
+        class="relative rounded-lg bg-input transition-colors group"
+      >
+        <input
+          v-model="condition.compareValue"
+          type="text"
+          placeholder="value"
+          class="py-2 px-4 w-full transition rounded-lg bg-transparent"
+        />
+        <button
+          class="
+            bg-white
+            absolute
+            top-1/2
+            right-4
+            p-2
+            rounded-lg
+            -translate-y-1/2
+            group-hover:right-14
+          "
+          @click="deleteCondition(index)"
+        >
+          <v-remixicon size="20" name="riDeleteBin7Line" />
+        </button>
+        <select
+          v-model="condition.type"
+          :title="getTitle(index)"
+          class="
+            bg-white
+            absolute
+            right-4
+            font-mono
+            z-10
+            p-2
+            top-1/2
+            leading-tight
+            -translate-y-1/2
+            text-center
+            transition
+            rounded-lg
+            appearance-none
+          "
+        >
+          <option
+            v-for="(name, type) in conditionTypes"
+            :key="type"
+            :value="type"
+          >
+            {{ type }}
+          </option>
+        </select>
+        <div
+          class="w-full bg-gray-300 h-px mx-auto"
+          style="max-width: 89%"
+        ></div>
+        <input
+          v-model="condition.value"
+          type="text"
+          placeholder="value"
+          class="py-2 px-4 w-full transition rounded-lg bg-transparent"
+        />
+      </li>
+    </ul>
+  </div>
+</template>
+<script setup>
+import { toRef } from 'vue';
+import { useI18n } from 'vue-i18n';
+import emitter from 'tiny-emitter/instance';
+
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => ({}),
+  },
+  blockId: {
+    type: String,
+    default: '',
+  },
+});
+defineEmits(['update:data']);
+
+const conditionTypes = {
+  '==': 'equals',
+  '!=': 'ne',
+  '>': 'gt',
+  '>=': 'gte',
+  '<': 'lt',
+  '<=': 'lte',
+  '()': 'contains',
+};
+const { t } = useI18n();
+
+const conditions = toRef(props.data, 'conditions');
+
+function getTitle(index) {
+  const type = conditionTypes[conditions.value[index]?.type] || 'equals';
+
+  return t(`workflow.blocks.conditions.${type}`);
+}
+function addCondition() {
+  if (conditions.value.length >= 10) return;
+
+  conditions.value.unshift({
+    compareValue: '',
+    value: '',
+    type: '==',
+  });
+
+  emitter.emit('conditions-block:add', {
+    id: props.blockId,
+  });
+}
+function deleteCondition(index) {
+  conditions.value.splice(index, 1);
+
+  emitter.emit('conditions-block:delete', {
+    index,
+    id: prps.blockId,
+  });
+}
+// function updateData(value) {
+//   emit('update:data', { ...props.data, ...value });
+// }
+</script>

+ 2 - 2
src/content/blocks-handler/handler-attribute-value.js

@@ -20,8 +20,8 @@ function attributeValue(block) {
         if (multiple) result.push(value);
         if (multiple) result.push(value);
         else result = value;
         else result = value;
       },
       },
-      onError() {
-        reject(new Error('element-not-found'));
+      onError(error) {
+        reject(error);
       },
       },
       onSuccess() {
       onSuccess() {
         resolve(result);
         resolve(result);

+ 2 - 2
src/content/blocks-handler/handler-element-scroll.js

@@ -29,8 +29,8 @@ function elementScroll(block) {
           });
           });
         }
         }
       },
       },
-      onError() {
-        reject(new Error('element-not-found'));
+      onError(error) {
+        reject(error);
       },
       },
       onSuccess() {
       onSuccess() {
         window.dispatchEvent(new Event('scroll'));
         window.dispatchEvent(new Event('scroll'));

+ 2 - 2
src/content/blocks-handler/handler-event-click.js

@@ -6,8 +6,8 @@ function eventClick(block) {
       onSelected(element) {
       onSelected(element) {
         element.click();
         element.click();
       },
       },
-      onError() {
-        reject(new Error('element-not-found'));
+      onError(error) {
+        reject(error);
       },
       },
       onSuccess() {
       onSuccess() {
         resolve('');
         resolve('');

+ 15 - 5
src/content/blocks-handler/handler-get-text.js

@@ -3,8 +3,14 @@ import { handleElement } from '../helper';
 function getText(block) {
 function getText(block) {
   return new Promise((resolve, reject) => {
   return new Promise((resolve, reject) => {
     let regex;
     let regex;
-    const { regex: regexData, regexExp, prefixText, suffixText } = block.data;
-    const textResult = [];
+    let textResult = [];
+    const {
+      regex: regexData,
+      regexExp,
+      prefixText,
+      suffixText,
+      multiple,
+    } = block.data;
 
 
     if (regexData) {
     if (regexData) {
       regex = new RegExp(regexData, regexExp.join(''));
       regex = new RegExp(regexData, regexExp.join(''));
@@ -18,10 +24,14 @@ function getText(block) {
 
 
         text = (prefixText || '') + text + (suffixText || '');
         text = (prefixText || '') + text + (suffixText || '');
 
 
-        textResult.push(text);
+        if (multiple) {
+          textResult.push(text);
+        } else {
+          textResult = text;
+        }
       },
       },
-      onError() {
-        reject(new Error('element-not-found'));
+      onError(error) {
+        reject(error);
       },
       },
       onSuccess() {
       onSuccess() {
         resolve(textResult);
         resolve(textResult);

+ 2 - 2
src/content/blocks-handler/handler-switch-to.js

@@ -14,8 +14,8 @@ function switchTo(block) {
       onSuccess() {
       onSuccess() {
         resolve('');
         resolve('');
       },
       },
-      onError() {
-        reject(new Error('element-not-found'));
+      onError(error) {
+        reject(error);
       },
       },
     });
     });
   });
   });

+ 2 - 2
src/content/blocks-handler/handler-trigger-event.js

@@ -12,8 +12,8 @@ function triggerEvent(block) {
       onSuccess() {
       onSuccess() {
         resolve(data.eventName);
         resolve(data.eventName);
       },
       },
-      onError() {
-        reject(new Error('element-not-found'));
+      onError(error) {
+        reject(error);
       },
       },
     });
     });
 
 

+ 7 - 4
src/content/helper.js

@@ -12,7 +12,10 @@ export function handleElement(
   { data, id },
   { data, id },
   { onSelected, onError, onSuccess, returnElement }
   { onSelected, onError, onSuccess, returnElement }
 ) {
 ) {
-  if (!data || !data.selector) return null;
+  if (!data || !data.selector) {
+    if (onError) onError(new Error('selector-empty'));
+    return null;
+  }
 
 
   try {
   try {
     data.blockIdAttr = `block--${id}`;
     data.blockIdAttr = `block--${id}`;
@@ -23,11 +26,11 @@ export function handleElement(
     if (returnElement) return element;
     if (returnElement) return element;
 
 
     if (!element) {
     if (!element) {
-      if (onError) onError();
+      if (onError) onError(new Error('element-not-found'));
 
 
-      return;
+      return null;
     }
     }
-    console.log(element, onSelected);
+
     if (data.multiple && selectorType === 'cssSelector') {
     if (data.multiple && selectorType === 'cssSelector') {
       element.forEach((el) => {
       element.forEach((el) => {
         markElement(el, { id, data });
         markElement(el, { id, data });

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

@@ -230,6 +230,7 @@
         "gte": "Greater than or equal",
         "gte": "Greater than or equal",
         "lt": "Less than",
         "lt": "Less than",
         "lte": "Less than or equal",
         "lte": "Less than or equal",
+        "ne": "Not equals",
         "contains": "Contains"
         "contains": "Contains"
       },
       },
       "element-exists": {
       "element-exists": {

+ 2 - 0
src/locales/en/newtab.json

@@ -84,8 +84,10 @@
       "finish": "Finish"
       "finish": "Finish"
     },
     },
     "messages": {
     "messages": {
+      "conditions-empty": "Conditions is empty",
       "invalid-proxy-host": "Invalid proxy host",
       "invalid-proxy-host": "Invalid proxy host",
       "workflow-disabled": "Workflow is disabled",
       "workflow-disabled": "Workflow is disabled",
+      "selector-empty": "Element selector is empty",
       "empty-workflow": "You must select a workflow first",
       "empty-workflow": "You must select a workflow first",
       "active-tab-removed": "Workflow active tab is removed",
       "active-tab-removed": "Workflow active tab is removed",
       "stop-timeout": "Workflow is stopped because of timeout",
       "stop-timeout": "Workflow is stopped because of timeout",

+ 1 - 0
src/utils/compare-block-value.js

@@ -1,5 +1,6 @@
 const handlers = {
 const handlers = {
   '==': (a, b) => a === b,
   '==': (a, b) => a === b,
+  '!=': (a, b) => a !== b,
   '>': (a, b) => a > b,
   '>': (a, b) => a > b,
   '>=': (a, b) => a >= b,
   '>=': (a, b) => a >= b,
   '<': (a, b) => a < b,
   '<': (a, b) => a < b,

+ 11 - 18
src/utils/reference-data.js

@@ -31,6 +31,16 @@ export function parseKey(key) {
 
 
   return { dataKey: 'data', path: dataPath };
   return { dataKey: 'data', path: dataPath };
 }
 }
+export function replaceMustacheHandler(match, data) {
+  const key = match.slice(2, -2).replace(/\s/g, '');
+
+  if (!key) return '';
+
+  const { dataKey, path } = parseKey(key);
+  const result = objectPath.get(data[dataKey], path) ?? match;
+
+  return isObject(result) ? JSON.stringify(result) : result;
+}
 
 
 export default function (block, data) {
 export default function (block, data) {
   const replaceKeys = [
   const replaceKeys = [
@@ -50,24 +60,7 @@ export default function (block, data) {
 
 
     const newDataValue = replaceMustache(
     const newDataValue = replaceMustache(
       replacedBlock.data[blockDataKey],
       replacedBlock.data[blockDataKey],
-      (match) => {
-        const key = match.slice(2, -2).replace(/\s/g, '');
-
-        if (!key) return '';
-
-        const { dataKey, path } = parseKey(key);
-
-        if (
-          dataKey === 'prevBlockData' &&
-          (!isObject(data.prevBlockData) || !Array.isArray(data.prevBlockData))
-        ) {
-          return data.prevBlockData;
-        }
-
-        const result = objectPath.get(data[dataKey], path) ?? match;
-
-        return isObject(result) ? JSON.stringify(result) : result;
-      }
+      (match) => replaceMustacheHandler(match, data)
     );
     );
 
 
     replacedBlock = objectPath.set(
     replacedBlock = objectPath.set(

+ 1 - 0
src/utils/shared.js

@@ -408,6 +408,7 @@ export const tasks = {
     description: 'Conditional block',
     description: 'Conditional block',
     icon: 'riAB',
     icon: 'riAB',
     component: 'BlockConditions',
     component: 'BlockConditions',
+    editComponent: 'EditConditions',
     category: 'conditions',
     category: 'conditions',
     inputs: 1,
     inputs: 1,
     outputs: 0,
     outputs: 0,